ref: 9943fcfd70b75bf5182dd3a0b7b7a8b7ce5cd2e0
dir: /src/i_sdlmusic.c/
// // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // DESCRIPTION: // System interface for music. // #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include "SDL.h" #include "SDL_mixer.h" #include "i_winmusic.h" #include "config.h" #include "doomtype.h" #include "memio.h" #include "mus2mid.h" #include "deh_str.h" #include "gusconf.h" #include "i_sound.h" #include "i_system.h" #include "i_swap.h" #include "m_argv.h" #include "m_config.h" #include "m_misc.h" #include "sha1.h" #include "w_wad.h" #include "z_zone.h" char *fluidsynth_sf_path = ""; char *timidity_cfg_path = ""; static char *temp_timidity_cfg = NULL; // If the temp_timidity_cfg config variable is set, generate a "wrapper" // config file for Timidity to point to the actual config file. This // is needed to inject a "dir" command so that the patches are read // relative to the actual config file. static boolean WriteWrapperTimidityConfig(char *write_path) { char *path; FILE *fstream; if (!strcmp(timidity_cfg_path, "")) { return false; } fstream = M_fopen(write_path, "w"); if (fstream == NULL) { return false; } path = M_DirName(timidity_cfg_path); fprintf(fstream, "dir %s\n", path); free(path); fprintf(fstream, "source %s\n", timidity_cfg_path); fclose(fstream); return true; } // putenv requires a non-const string whose lifetime is the whole program // so can't use a string directly, have to do this silliness static char sdl_mixer_disable_fluidsynth[] = "SDL_MIXER_DISABLE_FLUIDSYNTH=1"; void I_InitTimidityConfig(void) { char *env_string; boolean success; temp_timidity_cfg = M_TempFile("timidity.cfg"); if (snd_musicdevice == SNDDEVICE_GUS) { success = GUS_WriteConfig(temp_timidity_cfg); } else { success = WriteWrapperTimidityConfig(temp_timidity_cfg); } // Set the TIMIDITY_CFG environment variable to point to the temporary // config file. if (success) { env_string = M_StringJoin("TIMIDITY_CFG=", temp_timidity_cfg, NULL); putenv(env_string); // env_string deliberately not freed; see putenv manpage // If we're explicitly configured to use Timidity (either through // timidity_cfg_path or GUS mode), then disable Fluidsynth, because // SDL_mixer considers Fluidsynth a higher priority than Timidity and // therefore can end up circumventing Timidity entirely. putenv(sdl_mixer_disable_fluidsynth); } else { free(temp_timidity_cfg); temp_timidity_cfg = NULL; } } #define MAXMIDLENGTH (96 * 1024) static boolean music_initialized = false; // If this is true, this module initialized SDL sound and has the // responsibility to shut it down static boolean sdl_was_initialized = false; static boolean win_midi_stream_opened = false; static boolean musicpaused = false; static int current_music_volume; // Remove the temporary config file generated by I_InitTimidityConfig(). static void RemoveTimidityConfig(void) { if (temp_timidity_cfg != NULL) { M_remove(temp_timidity_cfg); free(temp_timidity_cfg); } } // Shutdown music static void I_SDL_ShutdownMusic(void) { if (music_initialized) { #if defined(_WIN32) if (win_midi_stream_opened) { I_WIN_ShutdownMusic(); win_midi_stream_opened = false; } #endif Mix_HaltMusic(); music_initialized = false; if (sdl_was_initialized) { Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); sdl_was_initialized = false; } } } static boolean SDLIsInitialized(void) { int freq, channels; Uint16 format; return Mix_QuerySpec(&freq, &format, &channels) != 0; } // Initialize music subsystem static boolean I_SDL_InitMusic(void) { boolean fluidsynth_sf_is_set = false; // If SDL_mixer is not initialized, we have to initialize it // and have the responsibility to shut it down later on. if (SDLIsInitialized()) { music_initialized = true; } else { if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to set up sound.\n"); } else if (Mix_OpenAudioDevice(snd_samplerate, AUDIO_S16SYS, 2, 1024, NULL, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) < 0) { fprintf(stderr, "Error initializing SDL_mixer: %s\n", Mix_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); } else { SDL_PauseAudio(0); sdl_was_initialized = true; music_initialized = true; } } // When using FluidSynth, proceed to set the soundfont path via // Mix_SetSoundFonts if necessary. We need to do this before calling // Mix_Init() in order for FluidSynth to be registered as a valid decoder // in the Mix_GetMusicDecoder() list. if ((strlen(fluidsynth_sf_path) > 0) && (strlen(timidity_cfg_path) == 0)) { if (M_FileExists(fluidsynth_sf_path)) { Mix_SetSoundFonts(fluidsynth_sf_path); } else { fprintf(stderr, "I_SDL_InitMusic: Can't find FluidSynth soundfont.\n"); } } // Initialize SDL_Mixer for MIDI music playback Mix_Init(MIX_INIT_MID); // Once initialization is complete, the temporary Timidity config // file can be removed. RemoveTimidityConfig(); // If a soundfont has been set (either here on in the environment), // confirm that FluidSynth is actually available before trying to use it. if ((Mix_GetSoundFonts() != NULL) && (strlen(timidity_cfg_path) == 0)) { int total; total = Mix_GetNumMusicDecoders(); // If FluidSynth is present and has a valid soundfont, it will be in // the list of available music decoders. for (int i = 0; i < total; ++i) { if (!strcmp(Mix_GetMusicDecoder(i), "FLUIDSYNTH")) { fluidsynth_sf_is_set = true; break; } } if (fluidsynth_sf_is_set) { printf("I_SDL_InitMusic: Using FluidSynth.\n"); } else { fprintf(stderr, "I_SDL_InitMusic: FluidSynth unavailable.\n"); } } // If snd_musiccmd is set, we need to call Mix_SetMusicCMD to // configure an external music playback program. if (strlen(snd_musiccmd) > 0) { Mix_SetMusicCMD(snd_musiccmd); } #if defined(_WIN32) // Don't enable it for GUS or Fluidsynth, since they handle their own volume // just fine. if (snd_musicdevice != SNDDEVICE_GUS && !fluidsynth_sf_is_set) { win_midi_stream_opened = I_WIN_InitMusic(); } #endif return music_initialized; } // // SDL_mixer's native MIDI music playing does not pause properly. // As a workaround, set the volume to 0 when paused. // static void UpdateMusicVolume(void) { int vol; if (musicpaused) { vol = 0; } else { vol = (current_music_volume * MIX_MAX_VOLUME) / 127; } #if defined(_WIN32) I_WIN_SetMusicVolume(vol); #endif Mix_VolumeMusic(vol); } // Set music volume (0 - 127) static void I_SDL_SetMusicVolume(int volume) { // Internal state variable. current_music_volume = volume; UpdateMusicVolume(); } // Start playing a mid static void I_SDL_PlaySong(void *handle, boolean looping) { int loops; if (!music_initialized) { return; } if (handle == NULL && !win_midi_stream_opened) { return; } if (looping) { loops = -1; } else { loops = 1; } #if defined(_WIN32) if (win_midi_stream_opened) { I_WIN_PlaySong(looping); } else #endif { Mix_PlayMusic((Mix_Music *) handle, loops); } } static void I_SDL_PauseSong(void) { if (!music_initialized) { return; } #if defined(_WIN32) if (win_midi_stream_opened) { I_WIN_PauseSong(); } else #endif { musicpaused = true; UpdateMusicVolume(); } } static void I_SDL_ResumeSong(void) { if (!music_initialized) { return; } #if defined(_WIN32) if (win_midi_stream_opened) { I_WIN_ResumeSong(); } else #endif { musicpaused = false; UpdateMusicVolume(); } } static void I_SDL_StopSong(void) { if (!music_initialized) { return; } #if defined(_WIN32) if (win_midi_stream_opened) { I_WIN_StopSong(); } else #endif { Mix_HaltMusic(); } } static void I_SDL_UnRegisterSong(void *handle) { Mix_Music *music = (Mix_Music *) handle; if (!music_initialized) { return; } #if defined(_WIN32) if (win_midi_stream_opened) { I_WIN_UnRegisterSong(); } else #endif { if (handle != NULL) { Mix_FreeMusic(music); } } } // Determine whether memory block is a .mid file static boolean IsMid(byte *mem, int len) { return len > 4 && !memcmp(mem, "MThd", 4); } static boolean ConvertMus(byte *musdata, int len, const char *filename) { MEMFILE *instream; MEMFILE *outstream; void *outbuf; size_t outbuf_len; int result; instream = mem_fopen_read(musdata, len); outstream = mem_fopen_write(); result = mus2mid(instream, outstream); if (result == 0) { mem_get_buf(outstream, &outbuf, &outbuf_len); M_WriteFile(filename, outbuf, outbuf_len); } mem_fclose(instream); mem_fclose(outstream); return result; } static void *I_SDL_RegisterSong(void *data, int len) { char *filename; Mix_Music *music; if (!music_initialized) { return NULL; } // MUS files begin with "MUS" // Reject anything which doesnt have this signature filename = M_TempFile("doom.mid"); if (IsMid(data, len) && len < MAXMIDLENGTH) { M_WriteFile(filename, data, len); } else { // Assume a MUS file and try to convert ConvertMus(data, len, filename); } // Load the MIDI. In an ideal world we'd be using Mix_LoadMUS_RW() // by now, but Mix_SetMusicCMD() only works with Mix_LoadMUS(), so // we have to generate a temporary file. #if defined(_WIN32) // If we do not have an external music command defined, play // music with the Windows native MIDI. if (win_midi_stream_opened) { if (I_WIN_RegisterSong(filename)) { music = (void *) 1; } else { music = NULL; fprintf(stderr, "Error loading midi: Failed to register song.\n"); } } else #endif { music = Mix_LoadMUS(filename); if (music == NULL) { // Failed to load fprintf(stderr, "Error loading midi: %s\n", Mix_GetError()); } // Remove the temporary MIDI file; however, when using an external // MIDI program we can't delete the file. Otherwise, the program // won't find the file to play. This means we leave a mess on // disk :( if (strlen(snd_musiccmd) == 0) { M_remove(filename); } } free(filename); return music; } // Is the song playing? static boolean I_SDL_MusicIsPlaying(void) { if (!music_initialized) { return false; } return Mix_PlayingMusic(); } static snddevice_t music_sdl_devices[] = { SNDDEVICE_PAS, SNDDEVICE_GUS, SNDDEVICE_WAVEBLASTER, SNDDEVICE_SOUNDCANVAS, SNDDEVICE_GENMIDI, SNDDEVICE_AWE32, }; music_module_t music_sdl_module = { music_sdl_devices, arrlen(music_sdl_devices), I_SDL_InitMusic, I_SDL_ShutdownMusic, I_SDL_SetMusicVolume, I_SDL_PauseSong, I_SDL_ResumeSong, I_SDL_RegisterSong, I_SDL_UnRegisterSong, I_SDL_PlaySong, I_SDL_StopSong, I_SDL_MusicIsPlaying, NULL, // Poll };