ref: 8c723c09234bbfdda47b12e65ca8dfbd58e18673
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;
}
}
#ifndef DISABLE_SDL2MIXER
#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
};
#endif // DISABLE_SDL2MIXER