// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file s_sound.c /// \brief System-independent sound and music routines #ifdef MUSSERV #include struct musmsg { long msg_type; char msg_text[12]; }; extern INT32 msg_id; #endif #include "doomdef.h" #include "doomstat.h" #include "command.h" #include "g_game.h" #include "m_argv.h" #include "r_main.h" // R_PointToAngle2() used to calc stereo sep. #include "r_things.h" // for skins #include "i_system.h" #include "i_sound.h" #include "s_sound.h" #include "w_wad.h" #include "z_zone.h" #include "d_main.h" #include "r_sky.h" // skyflatnum #include "p_local.h" // camera info #include "m_misc.h" // for tunes command #ifdef HAVE_BLUA #include "lua_hook.h" // MusicChange hook #endif #ifdef HW3SOUND // 3D Sound Interface #include "hardware/hw3sound.h" #else static INT32 S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, INT32 *vol, INT32 *sep, INT32 *pitch, sfxinfo_t *sfxinfo); #endif CV_PossibleValue_t soundvolume_cons_t[] = {{0, "MIN"}, {31, "MAX"}, {0, NULL}}; static void SetChannelsNum(void); static void Command_Tunes_f(void); static void Command_RestartAudio_f(void); // Sound system toggles #ifndef NO_MIDI static void GameMIDIMusic_OnChange(void); #endif static void GameSounds_OnChange(void); static void GameDigiMusic_OnChange(void); static void PlayMusicIfUnfocused_OnChange(void); static void PlaySoundIfUnfocused_OnChange(void); // commands for music and sound servers #ifdef MUSSERV consvar_t musserver_cmd = {"musserver_cmd", "musserver", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t musserver_arg = {"musserver_arg", "-t 20 -f -u 0 -i music.dta", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; #endif #ifdef SNDSERV consvar_t sndserver_cmd = {"sndserver_cmd", "llsndserv", CV_SAVE, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t sndserver_arg = {"sndserver_arg", "-quiet", CV_SAVE, NULL, 0, NULL, NULL, 0, 0, NULL}; #endif #if defined (_WINDOWS) && !defined (SURROUND) //&& defined (_X86_) #define SURROUND #endif #if defined (_WIN32_WCE) || defined (DC) || defined(GP2X) consvar_t cv_samplerate = {"samplerate", "11025", 0, CV_Unsigned, NULL, 11025, NULL, NULL, 0, 0, NULL}; //Alam: For easy hacking? #elif defined(_PSP) || defined(_WINDOWS) consvar_t cv_samplerate = {"samplerate", "44100", 0, CV_Unsigned, NULL, 44100, NULL, NULL, 0, 0, NULL}; //Alam: For easy hacking? #elif defined(_WII) consvar_t cv_samplerate = {"samplerate", "32000", 0, CV_Unsigned, NULL, 32000, NULL, NULL, 0, 0, NULL}; //Alam: For easy hacking? #else consvar_t cv_samplerate = {"samplerate", "22050", 0, CV_Unsigned, NULL, 22050, NULL, NULL, 0, 0, NULL}; //Alam: For easy hacking? #endif // stereo reverse consvar_t stereoreverse = {"stereoreverse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // if true, all sounds are loaded at game startup static consvar_t precachesound = {"precachesound", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // actual general (maximum) sound & music volume, saved into the config consvar_t cv_soundvolume = {"soundvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_digmusicvolume = {"digmusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; #ifndef NO_MIDI consvar_t cv_midimusicvolume = {"midimusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; #endif // number of channels available #if defined (_WIN32_WCE) || defined (DC) || defined (PSP) || defined(GP2X) consvar_t cv_numChannels = {"snd_channels", "8", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum, 0, NULL, NULL, 0, 0, NULL}; #else consvar_t cv_numChannels = {"snd_channels", "64", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum, 0, NULL, NULL, 0, 0, NULL}; #endif consvar_t surround = {"surround", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; //consvar_t cv_resetmusic = {"resetmusic", "No", CV_SAVE|CV_NOSHOWHELP, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; // Sound system toggles, saved into the config consvar_t cv_gamedigimusic = {"digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameDigiMusic_OnChange, 0, NULL, NULL, 0, 0, NULL}; #ifndef NO_MIDI consvar_t cv_gamemidimusic = {"midimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameMIDIMusic_OnChange, 0, NULL, NULL, 0, 0, NULL}; #endif consvar_t cv_gamesounds = {"sounds", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameSounds_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_playmusicifunfocused = {"playmusicifunfocused", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, PlayMusicIfUnfocused_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_playsoundifunfocused = {"playsoundsifunfocused", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, PlaySoundIfUnfocused_OnChange, 0, NULL, NULL, 0, 0, NULL}; #define S_MAX_VOLUME 127 // when to clip out sounds // Does not fit the large outdoor areas. // added 2-2-98 in 8 bit volume control (before (1200*0x10000)) #define S_CLIPPING_DIST (1536*0x10000) // Distance to origin when sounds should be maxed out. // This should relate to movement clipping resolution // (see BLOCKMAP handling). // Originally: (200*0x10000). // added 2-2-98 in 8 bit volume control (before (160*0x10000)) #define S_CLOSE_DIST (160*0x10000) // added 2-2-98 in 8 bit volume control (before remove the +4) #define S_ATTENUATOR ((S_CLIPPING_DIST-S_CLOSE_DIST)>>(FRACBITS+4)) // Adjustable by menu. #define NORM_VOLUME snd_MaxVolume #define NORM_PITCH 128 #define NORM_PRIORITY 64 #define NORM_SEP 128 #define S_PITCH_PERTURB 1 #define S_STEREO_SWING (96*0x10000) #ifdef SURROUND #define SURROUND_SEP -128 #endif // percent attenuation from front to back #define S_IFRACVOL 30 typedef struct { // sound information (if null, channel avail.) sfxinfo_t *sfxinfo; // origin of sound const void *origin; // initial volume of sound, which is applied after distance and direction INT32 volume; // handle of the sound being played INT32 handle; } channel_t; // the set of channels available static channel_t *channels = NULL; static INT32 numofchannels = 0; // // Internals. // static void S_StopChannel(INT32 cnum); // // S_getChannel // // If none available, return -1. Otherwise channel #. // static INT32 S_getChannel(const void *origin, sfxinfo_t *sfxinfo) { // channel number to use INT32 cnum; channel_t *c; // Find an open channel for (cnum = 0; cnum < numofchannels; cnum++) { if (!channels[cnum].sfxinfo) break; // Now checks if same sound is being played, rather // than just one sound per mobj else if (sfxinfo == channels[cnum].sfxinfo && (sfxinfo->pitch & SF_NOMULTIPLESOUND)) { return -1; break; } else if (sfxinfo == channels[cnum].sfxinfo && sfxinfo->singularity == true) { S_StopChannel(cnum); break; } else if (origin && channels[cnum].origin == origin && channels[cnum].sfxinfo == sfxinfo) { if (sfxinfo->pitch & SF_NOINTERRUPT) return -1; else S_StopChannel(cnum); break; } else if (origin && channels[cnum].origin == origin && channels[cnum].sfxinfo->name != sfxinfo->name && (channels[cnum].sfxinfo->pitch & SF_TOTALLYSINGLE) && (sfxinfo->pitch & SF_TOTALLYSINGLE)) { S_StopChannel(cnum); break; } } // None available if (cnum == numofchannels) { // Look for lower priority for (cnum = 0; cnum < numofchannels; cnum++) if (channels[cnum].sfxinfo->priority <= sfxinfo->priority) break; if (cnum == numofchannels) { // No lower priority. Sorry, Charlie. return -1; } else { // Otherwise, kick out lower priority. S_StopChannel(cnum); } } c = &channels[cnum]; // channel is decided to be cnum. c->sfxinfo = sfxinfo; c->origin = origin; return cnum; } void S_RegisterSoundStuff(void) { if (dedicated) { sound_disabled = true; return; } CV_RegisterVar(&stereoreverse); CV_RegisterVar(&precachesound); #ifdef SNDSERV CV_RegisterVar(&sndserver_cmd); CV_RegisterVar(&sndserver_arg); #endif #ifdef MUSSERV CV_RegisterVar(&musserver_cmd); CV_RegisterVar(&musserver_arg); #endif CV_RegisterVar(&surround); CV_RegisterVar(&cv_samplerate); //CV_RegisterVar(&cv_resetmusic); CV_RegisterVar(&cv_gamesounds); CV_RegisterVar(&cv_gamedigimusic); #ifndef NO_MIDI CV_RegisterVar(&cv_gamemidimusic); #endif CV_RegisterVar(&cv_playmusicifunfocused); CV_RegisterVar(&cv_playsoundifunfocused); COM_AddCommand("tunes", Command_Tunes_f); COM_AddCommand("restartaudio", Command_RestartAudio_f); #if defined (macintosh) && !defined (HAVE_SDL) // mp3 playlist stuff { INT32 i; for (i = 0; i < PLAYLIST_LENGTH; i++) { user_songs[i].name = malloc(7); if (!user_songs[i].name) I_Error("No more free memory for mp3 playlist"); sprintf(user_songs[i].name, "song%d%d",i/10,i%10); user_songs[i].defaultvalue = malloc(sizeof (char)); if (user_songs[i].defaultvalue) I_Error("No more free memory for blank mp3 playerlist"); *user_songs[i].defaultvalue = 0; user_songs[i].flags = CV_SAVE; user_songs[i].PossibleValue = NULL; CV_RegisterVar(&user_songs[i]); } CV_RegisterVar(&play_mode); } #endif } static void SetChannelsNum(void) { INT32 i; // Allocating the internal channels for mixing // (the maximum number of sounds rendered // simultaneously) within zone memory. if (channels) S_StopSounds(); Z_Free(channels); channels = NULL; if (cv_numChannels.value == 999999999) //Alam_GBC: OH MY ROD!(ROD rimmiced with GOD!) CV_StealthSet(&cv_numChannels,cv_numChannels.defaultvalue); #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_SetSourcesNum(); return; } #endif if (cv_numChannels.value) channels = (channel_t *)Z_Malloc(cv_numChannels.value * sizeof (channel_t), PU_STATIC, NULL); numofchannels = cv_numChannels.value; // Free all channels for use for (i = 0; i < numofchannels; i++) channels[i].sfxinfo = 0; } // Retrieve the lump number of sfx // lumpnum_t S_GetSfxLumpNum(sfxinfo_t *sfx) { char namebuf[9]; lumpnum_t sfxlump; sprintf(namebuf, "ds%s", sfx->name); sfxlump = W_CheckNumForName(namebuf); if (sfxlump != LUMPERROR) return sfxlump; strlcpy(namebuf, sfx->name, sizeof namebuf); sfxlump = W_CheckNumForName(namebuf); if (sfxlump != LUMPERROR) return sfxlump; return W_GetNumForName("dsthok"); } // Stop all sounds, load level info, THEN start sounds. void S_StopSounds(void) { INT32 cnum; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_StopSounds(); return; } #endif // kill all playing sounds at start of level for (cnum = 0; cnum < numofchannels; cnum++) if (channels[cnum].sfxinfo) S_StopChannel(cnum); } void S_StopSoundByID(void *origin, sfxenum_t sfx_id) { INT32 cnum; // Sounds without origin can have multiple sources, they shouldn't // be stopped by new sounds. if (!origin) return; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_StopSoundByID(origin, sfx_id); return; } #endif for (cnum = 0; cnum < numofchannels; cnum++) { if (channels[cnum].sfxinfo == &S_sfx[sfx_id] && channels[cnum].origin == origin) { S_StopChannel(cnum); break; } } } void S_StopSoundByNum(sfxenum_t sfxnum) { INT32 cnum; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_StopSoundByNum(sfxnum); return; } #endif for (cnum = 0; cnum < numofchannels; cnum++) { if (channels[cnum].sfxinfo == &S_sfx[sfxnum]) { S_StopChannel(cnum); break; } } } void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) { const INT32 initial_volume = volume; INT32 sep, pitch, priority, cnum; sfxinfo_t *sfx; const boolean reverse = (stereoreverse.value ^ encoremode); const mobj_t *origin = (const mobj_t *)origin_p; listener_t listener = {0,0,0,0}; listener_t listener2 = {0,0,0,0}; listener_t listener3 = {0,0,0,0}; listener_t listener4 = {0,0,0,0}; mobj_t *listenmobj = democam.soundmobj ? : players[displayplayers[0]].mo; mobj_t *listenmobj2 = NULL; mobj_t *listenmobj3 = NULL; mobj_t *listenmobj4 = NULL; if (sound_disabled || !sound_started) return; // Don't want a sound? Okay then... if (sfx_id == sfx_None) return; if (players[displayplayers[0]].awayviewtics) listenmobj = players[displayplayers[0]].awayviewmobj; if (splitscreen) { listenmobj2 = players[displayplayers[1]].mo; if (players[displayplayers[1]].awayviewtics) listenmobj2 = players[displayplayers[1]].awayviewmobj; if (splitscreen > 1) { listenmobj3 = players[displayplayers[2]].mo; if (players[displayplayers[2]].awayviewtics) listenmobj3 = players[displayplayers[2]].awayviewmobj; if (splitscreen > 2) { listenmobj4 = players[displayplayers[3]].mo; if (players[displayplayers[3]].awayviewtics) listenmobj4 = players[displayplayers[3]].awayviewmobj; } } } #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_StartSound(origin, sfx_id); return; }; #endif if (camera[0].chase && !players[displayplayers[0]].awayviewtics) { listener.x = camera[0].x; listener.y = camera[0].y; listener.z = camera[0].z; listener.angle = camera[0].angle; } else if (listenmobj) { listener.x = listenmobj->x; listener.y = listenmobj->y; listener.z = listenmobj->z; listener.angle = listenmobj->angle; } else if (origin) return; if (listenmobj2) { if (camera[1].chase && !players[displayplayers[1]].awayviewtics) { listener2.x = camera[1].x; listener2.y = camera[1].y; listener2.z = camera[1].z; listener2.angle = camera[1].angle; } else { listener2.x = listenmobj2->x; listener2.y = listenmobj2->y; listener2.z = listenmobj2->z; listener2.angle = listenmobj2->angle; } } if (listenmobj3) { if (camera[2].chase && !players[displayplayers[2]].awayviewtics) { listener3.x = camera[2].x; listener3.y = camera[2].y; listener3.z = camera[2].z; listener3.angle = camera[2].angle; } else { listener3.x = listenmobj3->x; listener3.y = listenmobj3->y; listener3.z = listenmobj3->z; listener3.angle = listenmobj3->angle; } } if (listenmobj4) { if (camera[3].chase && !players[displayplayers[3]].awayviewtics) { listener4.x = camera[3].x; listener4.y = camera[3].y; listener4.z = camera[3].z; listener4.angle = camera[3].angle; } else { listener4.x = listenmobj4->x; listener4.y = listenmobj4->y; listener4.z = listenmobj4->z; listener4.angle = listenmobj4->angle; } } // check for bogus sound # I_Assert(sfx_id >= 1); I_Assert(sfx_id < NUMSFX); sfx = &S_sfx[sfx_id]; if (sfx->skinsound != -1 && origin && origin->skin) { // redirect player sound to the sound in the skin table sfx_id = ((skin_t *)origin->skin)->soundsid[sfx->skinsound]; sfx = &S_sfx[sfx_id]; } // Initialize sound parameters pitch = NORM_PITCH; priority = NORM_PRIORITY; if (splitscreen && origin) volume = FixedDiv(volume<>FRACBITS; if (splitscreen && listenmobj2) // Copy the sound for the split player { // Check to see if it is audible, and if not, modify the params if (origin && origin != listenmobj2) { INT32 rc; rc = S_AdjustSoundParams(listenmobj2, origin, &volume, &sep, &pitch, sfx); if (!rc) goto dontplay; // Maybe the other player can hear it... if (origin->x == listener2.x && origin->y == listener2.y) sep = NORM_SEP; } else if (!origin) // Do not play origin-less sounds for the second player. // The first player will be able to hear it just fine, // we really don't want it playing twice. goto dontplay; else sep = NORM_SEP; // try to find a channel cnum = S_getChannel(origin, sfx); if (cnum < 0) return; // If there's no free channels, it's not gonna be free for player 1, either. // This is supposed to handle the loading/caching. // For some odd reason, the caching is done nearly // each time the sound is needed? // cache data if necessary // NOTE: set sfx->data NULL sfx->lump -1 to force a reload if (!sfx->data) sfx->data = I_GetSfx(sfx); // increase the usefulness if (sfx->usefulness++ < 0) sfx->usefulness = -1; // Avoid channel reverse if surround if (reverse #ifdef SURROUND && sep != SURROUND_SEP #endif ) sep = (~sep) & 255; // Assigns the handle to one of the channels in the // mix/output buffer. channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum); } dontplay: if (splitscreen > 1 && listenmobj3) // Copy the sound for the third player { // Check to see if it is audible, and if not, modify the params if (origin && origin != listenmobj3) { INT32 rc; rc = S_AdjustSoundParams(listenmobj3, origin, &volume, &sep, &pitch, sfx); if (!rc) goto dontplay3; // Maybe the other player can hear it... if (origin->x == listener3.x && origin->y == listener3.y) sep = NORM_SEP; } else if (!origin) // Do not play origin-less sounds for the second player. // The first player will be able to hear it just fine, // we really don't want it playing twice. goto dontplay3; else sep = NORM_SEP; // try to find a channel cnum = S_getChannel(origin, sfx); if (cnum < 0) return; // If there's no free channels, it's not gonna be free for player 1, either. // This is supposed to handle the loading/caching. // For some odd reason, the caching is done nearly // each time the sound is needed? // cache data if necessary // NOTE: set sfx->data NULL sfx->lump -1 to force a reload if (!sfx->data) sfx->data = I_GetSfx(sfx); // increase the usefulness if (sfx->usefulness++ < 0) sfx->usefulness = -1; // Avoid channel reverse if surround if (reverse #ifdef SURROUND && sep != SURROUND_SEP #endif ) sep = (~sep) & 255; // Assigns the handle to one of the channels in the // mix/output buffer. channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum); } dontplay3: if (splitscreen > 2 && listenmobj4) // Copy the sound for the split player { // Check to see if it is audible, and if not, modify the params if (origin && origin != listenmobj4) { INT32 rc; rc = S_AdjustSoundParams(listenmobj4, origin, &volume, &sep, &pitch, sfx); if (!rc) goto dontplay4; // Maybe the other player can hear it... if (origin->x == listener4.x && origin->y == listener4.y) sep = NORM_SEP; } else if (!origin) // Do not play origin-less sounds for the second player. // The first player will be able to hear it just fine, // we really don't want it playing twice. goto dontplay4; else sep = NORM_SEP; // try to find a channel cnum = S_getChannel(origin, sfx); if (cnum < 0) return; // If there's no free channels, it's not gonna be free for player 1, either. // This is supposed to handle the loading/caching. // For some odd reason, the caching is done nearly // each time the sound is needed? // cache data if necessary // NOTE: set sfx->data NULL sfx->lump -1 to force a reload if (!sfx->data) sfx->data = I_GetSfx(sfx); // increase the usefulness if (sfx->usefulness++ < 0) sfx->usefulness = -1; // Avoid channel reverse if surround if (reverse #ifdef SURROUND && sep != SURROUND_SEP #endif ) sep = (~sep) & 255; // Assigns the handle to one of the channels in the // mix/output buffer. channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum); } dontplay4: // Check to see if it is audible, and if not, modify the params if (origin && origin != listenmobj) { INT32 rc; rc = S_AdjustSoundParams(listenmobj, origin, &volume, &sep, &pitch, sfx); if (!rc) return; if (origin->x == listener.x && origin->y == listener.y) sep = NORM_SEP; } else sep = NORM_SEP; // try to find a channel cnum = S_getChannel(origin, sfx); if (cnum < 0) return; // This is supposed to handle the loading/caching. // For some odd reason, the caching is done nearly // each time the sound is needed? // cache data if necessary // NOTE: set sfx->data NULL sfx->lump -1 to force a reload if (!sfx->data) sfx->data = I_GetSfx(sfx); // increase the usefulness if (sfx->usefulness++ < 0) sfx->usefulness = -1; // Avoid channel reverse if surround if (reverse #ifdef SURROUND && sep != SURROUND_SEP #endif ) sep = (~sep) & 255; // Assigns the handle to one of the channels in the // mix/output buffer. channels[cnum].volume = initial_volume; channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum); } void S_StartSound(const void *origin, sfxenum_t sfx_id) { if (sound_disabled) return; if (mariomode) // Sounds change in Mario mode! { switch (sfx_id) { // case sfx_altow1: // case sfx_altow2: // case sfx_altow3: // case sfx_altow4: // sfx_id = sfx_mario8; // break; case sfx_thok: sfx_id = sfx_mario7; break; case sfx_pop: sfx_id = sfx_mario5; break; case sfx_jump: sfx_id = sfx_mario6; break; case sfx_shield: sfx_id = sfx_mario3; break; case sfx_itemup: sfx_id = sfx_mario4; break; // case sfx_tink: // sfx_id = sfx_mario1; // break; // case sfx_cgot: // sfx_id = sfx_mario9; // break; // case sfx_lose: // sfx_id = sfx_mario2; // break; default: break; } } if (maptol & TOL_XMAS) // Some sounds change for xmas { switch (sfx_id) { case sfx_ideya: case sfx_nbmper: case sfx_ncitem: case sfx_ngdone: ++sfx_id; default: break; } } // the volume is handled 8 bits #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) HW3S_StartSound(origin, sfx_id); else #endif S_StartSoundAtVolume(origin, sfx_id, 255); } void S_StopSound(void *origin) { INT32 cnum; // Sounds without origin can have multiple sources, they shouldn't // be stopped by new sounds. if (!origin) return; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_StopSound(origin); return; } #endif for (cnum = 0; cnum < numofchannels; cnum++) { if (channels[cnum].sfxinfo && channels[cnum].origin == origin) { S_StopChannel(cnum); break; } } } // // Updates music & sounds // static INT32 actualsfxvolume; // check for change through console static INT32 actualdigmusicvolume; #ifndef NO_MIDI static INT32 actualmidimusicvolume; #endif void S_UpdateSounds(void) { INT32 audible, cnum, volume, sep, pitch; channel_t *c; listener_t listener; listener_t listener2; listener_t listener3; listener_t listener4; mobj_t *listenmobj = democam.soundmobj ? : players[displayplayers[0]].mo; mobj_t *listenmobj2 = NULL; mobj_t *listenmobj3 = NULL; mobj_t *listenmobj4 = NULL; memset(&listener, 0, sizeof(listener_t)); memset(&listener2, 0, sizeof(listener_t)); memset(&listener3, 0, sizeof(listener_t)); memset(&listener4, 0, sizeof(listener_t)); // Update sound/music volumes, if changed manually at console if (actualsfxvolume != cv_soundvolume.value) S_SetSfxVolume (cv_soundvolume.value); if (actualdigmusicvolume != cv_digmusicvolume.value) S_SetDigMusicVolume (cv_digmusicvolume.value); #ifndef NO_MIDI if (actualmidimusicvolume != cv_midimusicvolume.value) S_SetMIDIMusicVolume (cv_midimusicvolume.value); #endif // We're done now, if we're not in a level. if (gamestate != GS_LEVEL) { #ifndef NOMUMBLE // Stop Mumble cutting out. I'm sick of it. I_UpdateMumble(NULL, listener); #endif // Stop cutting FMOD out. WE'RE sick of it. I_UpdateSound(); return; } if (dedicated || sound_disabled) return; if (players[displayplayers[0]].awayviewtics) listenmobj = players[displayplayers[0]].awayviewmobj; if (splitscreen) { listenmobj2 = players[displayplayers[1]].mo; if (players[displayplayers[1]].awayviewtics) listenmobj2 = players[displayplayers[1]].awayviewmobj; if (splitscreen > 1) { listenmobj3 = players[displayplayers[2]].mo; if (players[displayplayers[2]].awayviewtics) listenmobj3 = players[displayplayers[2]].awayviewmobj; if (splitscreen > 2) { listenmobj4 = players[displayplayers[3]].mo; if (players[displayplayers[3]].awayviewtics) listenmobj4 = players[displayplayers[3]].awayviewmobj; } } } if (camera[0].chase && !players[displayplayers[0]].awayviewtics) { listener.x = camera[0].x; listener.y = camera[0].y; listener.z = camera[0].z; listener.angle = camera[0].angle; } else if (listenmobj) { listener.x = listenmobj->x; listener.y = listenmobj->y; listener.z = listenmobj->z; listener.angle = listenmobj->angle; } #ifndef NOMUMBLE I_UpdateMumble(players[consoleplayer].mo, listener); #endif #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_UpdateSources(); I_UpdateSound(); return; } #endif if (listenmobj2) { if (camera[1].chase && !players[displayplayers[1]].awayviewtics) { listener2.x = camera[1].x; listener2.y = camera[1].y; listener2.z = camera[1].z; listener2.angle = camera[1].angle; } else { listener2.x = listenmobj2->x; listener2.y = listenmobj2->y; listener2.z = listenmobj2->z; listener2.angle = listenmobj2->angle; } } if (listenmobj3) { if (camera[2].chase && !players[displayplayers[2]].awayviewtics) { listener3.x = camera[2].x; listener3.y = camera[2].y; listener3.z = camera[2].z; listener3.angle = camera[2].angle; } else { listener3.x = listenmobj3->x; listener3.y = listenmobj3->y; listener3.z = listenmobj3->z; listener3.angle = listenmobj3->angle; } } if (listenmobj4) { if (camera[3].chase && !players[displayplayers[3]].awayviewtics) { listener4.x = camera[3].x; listener4.y = camera[3].y; listener4.z = camera[3].z; listener4.angle = camera[3].angle; } else { listener4.x = listenmobj4->x; listener4.y = listenmobj4->y; listener4.z = listenmobj4->z; listener4.angle = listenmobj4->angle; } } for (cnum = 0; cnum < numofchannels; cnum++) { c = &channels[cnum]; if (c->sfxinfo) { if (I_SoundIsPlaying(c->handle)) { // initialize parameters volume = c->volume; // 8 bits internal volume precision pitch = NORM_PITCH; sep = NORM_SEP; if (splitscreen && c->origin) volume = FixedDiv(volume<>FRACBITS; // check non-local sounds for distance clipping // or modify their params if (c->origin && ((c->origin != players[consoleplayer].mo) || (splitscreen && c->origin != players[displayplayers[1]].mo) || (splitscreen > 1 && c->origin != players[displayplayers[2]].mo) || (splitscreen > 2 && c->origin != players[displayplayers[3]].mo))) { // Whomever is closer gets the sound, but only in splitscreen. if (splitscreen) { const mobj_t *soundmobj = c->origin; fixed_t recdist = -1; INT32 i, p = -1; for (i = 0; i <= splitscreen; i++) { fixed_t thisdist = -1; if (i == 0 && listenmobj) thisdist = P_AproxDistance(listener.x-soundmobj->x, listener.y-soundmobj->y); else if (i == 1 && listenmobj2) thisdist = P_AproxDistance(listener2.x-soundmobj->x, listener2.y-soundmobj->y); else if (i == 2 && listenmobj3) thisdist = P_AproxDistance(listener3.x-soundmobj->x, listener3.y-soundmobj->y); else if (i == 3 && listenmobj4) thisdist = P_AproxDistance(listener4.x-soundmobj->x, listener4.y-soundmobj->y); else continue; if (recdist == -1 || (thisdist != -1 && thisdist < recdist)) { recdist = thisdist; p = i; } } if (p != -1) { if (p == 1) { // Player 2 gets the sound audible = S_AdjustSoundParams(listenmobj2, c->origin, &volume, &sep, &pitch, c->sfxinfo); } else if (p == 2) { // Player 3 gets the sound audible = S_AdjustSoundParams(listenmobj3, c->origin, &volume, &sep, &pitch, c->sfxinfo); } else if (p == 3) { // Player 4 gets the sound audible = S_AdjustSoundParams(listenmobj4, c->origin, &volume, &sep, &pitch, c->sfxinfo); } else { // Player 1 gets the sound audible = S_AdjustSoundParams(listenmobj, c->origin, &volume, &sep, &pitch, c->sfxinfo); } if (audible) I_UpdateSoundParams(c->handle, volume, sep, pitch); else S_StopChannel(cnum); } } else if (listenmobj && !splitscreen) { // In the case of a single player, he or she always should get updated sound. audible = S_AdjustSoundParams(listenmobj, c->origin, &volume, &sep, &pitch, c->sfxinfo); if (audible) I_UpdateSoundParams(c->handle, volume, sep, pitch); else S_StopChannel(cnum); } } } else { // if channel is allocated but sound has stopped, free it S_StopChannel(cnum); } } } I_UpdateSound(); } void S_SetSfxVolume(INT32 volume) { if (volume < 0 || volume > 31) CONS_Alert(CONS_WARNING, "sfxvolume should be between 0-31\n"); CV_SetValue(&cv_soundvolume, volume&0x1F); actualsfxvolume = cv_soundvolume.value; // check for change of var #ifdef HW3SOUND hws_mode == HWS_DEFAULT_MODE ? I_SetSfxVolume(volume&0x1F) : HW3S_SetSfxVolume(volume&0x1F); #else // now hardware volume I_SetSfxVolume(volume&0x1F); #endif } void S_ClearSfx(void) { #ifndef DJGPPDOS size_t i; for (i = 1; i < NUMSFX; i++) I_FreeSfx(S_sfx + i); #endif } static void S_StopChannel(INT32 cnum) { INT32 i; channel_t *c = &channels[cnum]; if (c->sfxinfo) { // stop the sound playing if (I_SoundIsPlaying(c->handle)) I_StopSound(c->handle); // check to see // if other channels are playing the sound for (i = 0; i < numofchannels; i++) if (cnum != i && c->sfxinfo == channels[i].sfxinfo) break; // degrade usefulness of sound data c->sfxinfo->usefulness--; c->sfxinfo = 0; } } // // S_CalculateSoundDistance // // Calculates the distance between two points for a sound. // Clips the distance to prevent overflow. // fixed_t S_CalculateSoundDistance(fixed_t sx1, fixed_t sy1, fixed_t sz1, fixed_t sx2, fixed_t sy2, fixed_t sz2) { fixed_t approx_dist, adx, ady; // calculate the distance to sound origin and clip it if necessary adx = abs((sx1>>FRACBITS) - (sx2>>FRACBITS)); ady = abs((sy1>>FRACBITS) - (sy2>>FRACBITS)); // From _GG1_ p.428. Approx. euclidian distance fast. // Take Z into account adx = adx + ady - ((adx < ady ? adx : ady)>>1); ady = abs((sz1>>FRACBITS) - (sz2>>FRACBITS)); approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1); if (approx_dist >= FRACUNIT/2) approx_dist = FRACUNIT/2-1; approx_dist <<= FRACBITS; return FixedDiv(approx_dist, mapobjectscale); // approx_dist } // // Changes volume, stereo-separation, and pitch variables // from the norm of a sound effect to be played. // If the sound is not audible, returns a 0. // Otherwise, modifies parameters and returns 1. // INT32 S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, INT32 *vol, INT32 *sep, INT32 *pitch, sfxinfo_t *sfxinfo) { fixed_t approx_dist; angle_t angle; listener_t listensource; const boolean reverse = (stereoreverse.value ^ encoremode); (void)pitch; if (!listener) return false; if (listener == players[displayplayers[0]].mo && camera[0].chase) { listensource.x = camera[0].x; listensource.y = camera[0].y; listensource.z = camera[0].z; listensource.angle = camera[0].angle; } else if (splitscreen && listener == players[displayplayers[1]].mo && camera[1].chase) { listensource.x = camera[1].x; listensource.y = camera[1].y; listensource.z = camera[1].z; listensource.angle = camera[1].angle; } else if (splitscreen > 1 && listener == players[displayplayers[2]].mo && camera[2].chase) { listensource.x = camera[2].x; listensource.y = camera[2].y; listensource.z = camera[2].z; listensource.angle = camera[2].angle; } else if (splitscreen > 2 && listener == players[displayplayers[3]].mo && camera[3].chase) { listensource.x = camera[3].x; listensource.y = camera[3].y; listensource.z = camera[3].z; listensource.angle = camera[3].angle; } else { listensource.x = listener->x; listensource.y = listener->y; listensource.z = listener->z; listensource.angle = listener->angle; } if (sfxinfo->pitch & SF_OUTSIDESOUND) // Rain special case { fixed_t x, y, yl, yh, xl, xh, newdist; if (R_PointInSubsector(listensource.x, listensource.y)->sector->ceilingpic == skyflatnum) approx_dist = 0; else { // Essentially check in a 1024 unit radius of the player for an outdoor area. yl = listensource.y - 1024*FRACUNIT; yh = listensource.y + 1024*FRACUNIT; xl = listensource.x - 1024*FRACUNIT; xh = listensource.x + 1024*FRACUNIT; approx_dist = 1024*FRACUNIT; for (y = yl; y <= yh; y += FRACUNIT*64) for (x = xl; x <= xh; x += FRACUNIT*64) { if (R_PointInSubsector(x, y)->sector->ceilingpic == skyflatnum) { // Found the outdoors! newdist = S_CalculateSoundDistance(listensource.x, listensource.y, 0, x, y, 0); if (newdist < approx_dist) { approx_dist = newdist; } } } } } else { approx_dist = S_CalculateSoundDistance(listensource.x, listensource.y, listensource.z, source->x, source->y, source->z); } // Ring loss, deaths, etc, should all be heard louder. if (sfxinfo->pitch & SF_X8AWAYSOUND) approx_dist = FixedDiv(approx_dist,8*FRACUNIT); // Combine 8XAWAYSOUND with 4XAWAYSOUND and get.... 32XAWAYSOUND? if (sfxinfo->pitch & SF_X4AWAYSOUND) approx_dist = FixedDiv(approx_dist,4*FRACUNIT); if (sfxinfo->pitch & SF_X2AWAYSOUND) approx_dist = FixedDiv(approx_dist,2*FRACUNIT); if (approx_dist > S_CLIPPING_DIST) return 0; // angle of source to listener angle = R_PointToAngle2(listensource.x, listensource.y, source->x, source->y); if (angle > listensource.angle) angle = angle - listensource.angle; else angle = angle + InvAngle(listensource.angle); if (reverse) angle = InvAngle(angle); #ifdef SURROUND // Produce a surround sound for angle from 105 till 255 if (surround.value == 1 && (angle > ANG105 && angle < ANG255 )) *sep = SURROUND_SEP; else #endif { angle >>= ANGLETOFINESHIFT; // stereo separation *sep = 128 - (FixedMul(S_STEREO_SWING, FINESINE(angle))>>FRACBITS); } // volume calculation /* not sure if it should be > (no =), but this matches the old behavior */ if (approx_dist >= S_CLOSE_DIST) { // distance effect INT32 n = (15 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS)); *vol = FixedMul(*vol * FRACUNIT / 255, n) / S_ATTENUATOR; } if (splitscreen) *vol = FixedDiv((*vol)<>FRACBITS; return (*vol > 0); } // Searches through the channels and checks if a sound is playing // on the given origin. INT32 S_OriginPlaying(void *origin) { INT32 cnum; if (!origin) return false; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) return HW3S_OriginPlaying(origin); #endif for (cnum = 0; cnum < numofchannels; cnum++) if (channels[cnum].origin == origin) return 1; return 0; } // Searches through the channels and checks if a given id // is playing anywhere. INT32 S_IdPlaying(sfxenum_t id) { INT32 cnum; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) return HW3S_IdPlaying(id); #endif for (cnum = 0; cnum < numofchannels; cnum++) if ((size_t)(channels[cnum].sfxinfo - S_sfx) == (size_t)id) return 1; return 0; } // Searches through the channels and checks for // origin x playing sound id y. INT32 S_SoundPlaying(void *origin, sfxenum_t id) { INT32 cnum; if (!origin) return 0; #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) return HW3S_SoundPlaying(origin, id); #endif for (cnum = 0; cnum < numofchannels; cnum++) { if (channels[cnum].origin == origin && (size_t)(channels[cnum].sfxinfo - S_sfx) == (size_t)id) return 1; } return 0; } // // S_StartSoundName // Starts a sound using the given name. #define MAXNEWSOUNDS 10 static sfxenum_t newsounds[MAXNEWSOUNDS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; void S_StartSoundName(void *mo, const char *soundname) { INT32 i, soundnum = 0; // Search existing sounds... for (i = sfx_None + 1; i < NUMSFX; i++) { if (!S_sfx[i].name) continue; if (!stricmp(S_sfx[i].name, soundname)) { soundnum = i; break; } } if (!soundnum) { for (i = 0; i < MAXNEWSOUNDS; i++) { if (newsounds[i] == 0) break; if (!S_IdPlaying(newsounds[i])) { S_RemoveSoundFx(newsounds[i]); break; } } if (i == MAXNEWSOUNDS) { CONS_Debug(DBG_GAMELOGIC, "Cannot load another extra sound!\n"); return; } soundnum = S_AddSoundFx(soundname, false, 0, false); newsounds[i] = soundnum; } S_StartSound(mo, soundnum); } // // Initializes sound stuff, including volume // Sets channels, SFX volume, // allocates channel buffer, sets S_sfx lookup. // void S_InitSfxChannels(INT32 sfxVolume) { INT32 i; if (dedicated) return; S_SetSfxVolume(sfxVolume); SetChannelsNum(); // Note that sounds have not been cached (yet). for (i = 1; i < NUMSFX; i++) { S_sfx[i].usefulness = -1; // for I_GetSfx() S_sfx[i].lumpnum = LUMPERROR; } // precache sounds if requested by cmdline, or precachesound var true if (!sound_disabled && (M_CheckParm("-precachesound") || precachesound.value)) { // Initialize external data (all sounds) at start, keep static. CONS_Printf(M_GetText("Loading sounds... ")); for (i = 1; i < NUMSFX; i++) if (S_sfx[i].name) S_sfx[i].data = I_GetSfx(&S_sfx[i]); CONS_Printf(M_GetText(" pre-cached all sound data\n")); } } /// ------------------------ /// Music /// ------------------------ #ifdef MUSICSLOT_COMPATIBILITY const char *compat_special_music_slots[16] = { "titles", // 1036 title screen "read_m", // 1037 intro "lclear", // 1038 level clear "invinc", // 1039 invincibility "shoes", // 1040 super sneakers "minvnc", // 1041 Mario invincibility "drown", // 1042 drowning "gmover", // 1043 game over "xtlife", // 1044 extra life "contsc", // 1045 continue screen "supers", // 1046 Super Sonic "chrsel", // 1047 character select "credit", // 1048 credits "racent", // 1049 Race Results "stjr", // 1050 Sonic Team Jr. Presents "" }; #endif static char music_name[7]; // up to 6-character name static void *music_data; static UINT16 music_flags; static boolean music_looping; static char queue_name[7]; static UINT16 queue_flags; static boolean queue_looping; static UINT32 queue_position; static UINT32 queue_fadeinms; /// ------------------------ /// Music Definitions /// ------------------------ musicdef_t *musicdefstart = NULL; // First music definition struct cursongcredit cursongcredit; // Currently displayed song credit info // // search for music definition in wad // static UINT16 W_CheckForMusicDefInPwad(UINT16 wadid) { UINT16 i; lumpinfo_t *lump_p; lump_p = wadfiles[wadid]->lumpinfo; for (i = 0; i < wadfiles[wadid]->numlumps; i++, lump_p++) if (memcmp(lump_p->name, "MUSICDEF", 8) == 0) return i; return INT16_MAX; // not found } void S_LoadMusicDefs(UINT16 wadnum) { UINT16 lump; char *buf; char *buf2; char *stoken; char *value; size_t size; musicdef_t *def, *prev; UINT16 line = 1; // for better error msgs lump = W_CheckForMusicDefInPwad(wadnum); if (lump == INT16_MAX) return; buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE); size = W_LumpLengthPwad(wadnum, lump); // for strtok buf2 = malloc(size+1); if (!buf2) I_Error("S_LoadMusicDefs: No more free memory\n"); M_Memcpy(buf2,buf,size); buf2[size] = '\0'; def = prev = NULL; stoken = strtok (buf2, "\r\n "); // Find music def while (stoken) { /*if ((stoken[0] == '/' && stoken[1] == '/') || (stoken[0] == '#')) // skip comments { stoken = strtok(NULL, "\r\n"); // skip end of line if (def) stoken = strtok(NULL, "\r\n= "); else stoken = strtok(NULL, "\r\n "); line++; } else*/ if (!stricmp(stoken, "lump")) { value = strtok(NULL, "\r\n "); if (!value) { CONS_Alert(CONS_WARNING, "MUSICDEF: Lump '%s' is missing name. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); stoken = strtok(NULL, "\r\n"); // skip end of line goto skip_lump; } // No existing musicdefs if (!musicdefstart) { musicdefstart = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL); STRBUFCPY(musicdefstart->name, value); strlwr(musicdefstart->name); def = musicdefstart; //CONS_Printf("S_LoadMusicDefs: Initialized musicdef w/ song '%s'\n", def->name); } else { def = musicdefstart; // Search if this is a replacement //CONS_Printf("S_LoadMusicDefs: Searching for song replacement...\n"); while (def) { if (!stricmp(def->name, value)) { //CONS_Printf("S_LoadMusicDefs: Found song replacement '%s'\n", def->name); break; } prev = def; def = def->next; } // Nothing found, add to the end. if (!def) { def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL); STRBUFCPY(def->name, value); strlwr(def->name); if (prev != NULL) prev->next = def; //CONS_Printf("S_LoadMusicDefs: Added song '%s'\n", def->name); } } skip_lump: stoken = strtok(NULL, "\r\n "); line++; } else { value = strtok(NULL, "\r\n= "); if (!value) { CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' is missing value. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); stoken = strtok(NULL, "\r\n"); // skip end of line goto skip_field; } if (!def) { CONS_Alert(CONS_ERROR, "MUSICDEF: No music definition before field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); free(buf2); return; } if (!stricmp(stoken, "usage")) { #if 0 // Ignore for now STRBUFCPY(def->usage, value); for (value = def->usage; *value; value++) if (*value == '_') *value = ' '; // turn _ into spaces. //CONS_Printf("S_LoadMusicDefs: Set usage to '%s'\n", def->usage); #endif } else if (!stricmp(stoken, "source")) { STRBUFCPY(def->source, value); for (value = def->source; *value; value++) if (*value == '_') *value = ' '; // turn _ into spaces. //CONS_Printf("S_LoadMusicDefs: Set source to '%s'\n", def->source); } else { CONS_Alert(CONS_WARNING, "MUSICDEF: Invalid field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); } skip_field: stoken = strtok(NULL, "\r\n= "); line++; } } free(buf2); return; } // // S_InitMusicDefs // // Simply load music defs in all wads. // void S_InitMusicDefs(void) { UINT16 i; for (i = 0; i < numwadfiles; i++) S_LoadMusicDefs(i); } // // S_ShowMusicCredit // // Display current song's credit on screen // void S_ShowMusicCredit(void) { musicdef_t *def = musicdefstart; if (!cv_songcredits.value || demo.rewinding) return; if (!def) // No definitions return; while (def) { if (!stricmp(def->name, music_name)) { cursongcredit.def = def; cursongcredit.anim = 5*TICRATE; cursongcredit.x = 0; cursongcredit.trans = NUMTRANSMAPS; return; } else def = def->next; } } /// ------------------------ /// Music Status /// ------------------------ boolean S_DigMusicDisabled(void) { return digital_disabled; } boolean S_MIDIMusicDisabled(void) { return midi_disabled; // SRB2Kart: defined as "true" w/ NO_MIDI } boolean S_MusicDisabled(void) { return (midi_disabled && digital_disabled); } boolean S_MusicPlaying(void) { return I_SongPlaying(); } boolean S_MusicPaused(void) { return I_SongPaused(); } musictype_t S_MusicType(void) { return I_SongType(); } const char *S_MusicName(void) { return music_name; } boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping) { if (!I_SongPlaying()) return false; strncpy(mname, music_name, 7); mname[6] = 0; *mflags = music_flags; *looping = music_looping; return (boolean)mname[0]; } boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi) { return ( (checkDigi ? W_CheckNumForName(va("O_%s", mname)) != LUMPERROR : false) || (checkMIDI ? W_CheckNumForName(va("D_%s", mname)) != LUMPERROR : false) ); } /// ------------------------ /// Music Effects /// ------------------------ boolean S_SpeedMusic(float speed) { return I_SetSongSpeed(speed); } /// ------------------------ /// Music Seeking /// ------------------------ UINT32 S_GetMusicLength(void) { return I_GetSongLength(); } boolean S_SetMusicLoopPoint(UINT32 looppoint) { return I_SetSongLoopPoint(looppoint); } UINT32 S_GetMusicLoopPoint(void) { return I_GetSongLoopPoint(); } boolean S_SetMusicPosition(UINT32 position) { return I_SetSongPosition(position); } UINT32 S_GetMusicPosition(void) { return I_GetSongPosition(); } /// ------------------------ /// Music Playback /// ------------------------ static boolean S_LoadMusic(const char *mname) { lumpnum_t mlumpnum; void *mdata; if (S_MusicDisabled()) return false; if (!S_DigMusicDisabled() && S_DigExists(mname)) mlumpnum = W_GetNumForName(va("o_%s", mname)); else if (!S_MIDIMusicDisabled() && S_MIDIExists(mname)) mlumpnum = W_GetNumForName(va("d_%s", mname)); else if (S_DigMusicDisabled() && S_DigExists(mname)) { CONS_Alert(CONS_NOTICE, "Digital music is disabled!\n"); return false; } else if (S_MIDIMusicDisabled() && S_MIDIExists(mname)) { #ifdef NO_MIDI CONS_Alert(CONS_ERROR, "A MIDI music lump %.6s was found,\nbut SRB2Kart does not support MIDI output.\nWe apologise for the inconvenience.\n", mname); #else CONS_Alert(CONS_NOTICE, "MIDI music is disabled!\n"); #endif return false; } else { CONS_Alert(CONS_ERROR, M_GetText("Music lump %.6s not found!\n"), mname); return false; } // load & register it mdata = W_CacheLumpNum(mlumpnum, PU_MUSIC); #ifdef MUSSERV if (msg_id != -1) { struct musmsg msg_buffer; msg_buffer.msg_type = 6; memset(msg_buffer.msg_text, 0, sizeof (msg_buffer.msg_text)); sprintf(msg_buffer.msg_text, "d_%s", mname); msgsnd(msg_id, (struct msgbuf*)&msg_buffer, sizeof (msg_buffer.msg_text), IPC_NOWAIT); } #endif if (I_LoadSong(mdata, W_LumpLength(mlumpnum))) { strncpy(music_name, mname, 7); music_name[6] = 0; music_data = mdata; return true; } else return false; } static void S_UnloadMusic(void) { I_UnloadSong(); #ifndef HAVE_SDL //SDL uses RWOPS Z_ChangeTag(music_data, PU_CACHE); #endif music_data = NULL; music_name[0] = 0; music_flags = 0; music_looping = false; } static boolean S_PlayMusic(boolean looping, UINT32 fadeinms) { if (S_MusicDisabled()) return false; if ((!fadeinms && !I_PlaySong(looping)) || (fadeinms && !I_FadeInPlaySong(fadeinms, looping))) { S_UnloadMusic(); return false; } S_InitMusicVolume(); // switch between digi and sequence volume if (window_notinfocus && !cv_playmusicifunfocused.value) I_PauseSong(); return true; } static void S_QueueMusic(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 fadeinms) { strncpy(queue_name, mmusic, 7); queue_flags = mflags; queue_looping = looping; queue_position = position; queue_fadeinms = fadeinms; } static void S_ClearQueue(void) { queue_name[0] = queue_flags = queue_looping = queue_position = queue_fadeinms = 0; } static void S_ChangeMusicToQueue(void) { S_ChangeMusicEx(queue_name, queue_flags, queue_looping, queue_position, 0, queue_fadeinms); S_ClearQueue(); } void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms) { char newmusic[7]; #if defined (DC) || defined (_WIN32_WCE) || defined (PSP) || defined(GP2X) S_ClearSfx(); #endif if (S_MusicDisabled() || demo.rewinding // Don't mess with music while rewinding! || demo.title) // SRB2Kart: Demos don't interrupt title screen music return; strncpy(newmusic, mmusic, 7); #ifdef HAVE_BLUA if(LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms)) return; #endif newmusic[6] = 0; // No Music (empty string) if (newmusic[0] == 0) { if (prefadems) I_FadeSong(0, prefadems, &S_StopMusic); else S_StopMusic(); return; } if (prefadems && S_MusicPlaying()) // queue music change for after fade // allow even if the music is the same { CONS_Debug(DBG_DETAILED, "Now fading out song %s\n", music_name); S_QueueMusic(newmusic, mflags, looping, position, fadeinms); I_FadeSong(0, prefadems, S_ChangeMusicToQueue); return; } else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET)) { CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic); S_StopMusic(); if (!S_LoadMusic(newmusic)) { CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded!\n", newmusic); return; } music_flags = mflags; music_looping = looping; if (!S_PlayMusic(looping, fadeinms)) { CONS_Alert(CONS_ERROR, "Music %.6s could not be played!\n", newmusic); return; } if (position) I_SetSongPosition(position); I_SetSongTrack(mflags & MUSIC_TRACKMASK); } else if (fadeinms) // let fades happen with same music { I_SetSongPosition(position); I_FadeSong(100, fadeinms, NULL); } else // reset volume to 100 with same music { I_StopFadingSong(); I_FadeSong(100, 500, NULL); } } void S_StopMusic(void) { if (!I_SongPlaying() || demo.rewinding // Don't mess with music while rewinding! || demo.title) // SRB2Kart: Demos don't interrupt title screen music return; if (I_SongPaused()) I_ResumeSong(); S_SpeedMusic(1.0f); I_StopSong(); S_UnloadMusic(); // for now, stopping also means you unload the song } // // Stop and resume music, during game PAUSE. // void S_PauseAudio(void) { if (I_SongPlaying() && !I_SongPaused()) I_PauseSong(); // pause cd music #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) I_PauseCD(); #else I_StopCD(); #endif } void S_ResumeAudio(void) { if (I_SongPlaying() && I_SongPaused()) I_ResumeSong(); // resume cd music I_ResumeCD(); } void S_DisableSound(void) { if (sound_started && !sound_disabled) { sound_disabled = true; S_StopSounds(); } } void S_EnableSound(void) { if (sound_started && sound_disabled) { sound_disabled = false; S_InitSfxChannels(cv_soundvolume.value); } } void S_SetMusicVolume(INT32 digvolume, INT32 seqvolume) { if (digvolume < 0) digvolume = cv_digmusicvolume.value; #ifdef NO_MIDI (void)seqvolume; #else if (seqvolume < 0) seqvolume = cv_midimusicvolume.value; #endif if (digvolume < 0 || digvolume > 31) CONS_Alert(CONS_WARNING, "digmusicvolume should be between 0-31\n"); CV_SetValue(&cv_digmusicvolume, digvolume&31); actualdigmusicvolume = cv_digmusicvolume.value; //check for change of var #ifndef NO_MIDI if (seqvolume < 0 || seqvolume > 31) CONS_Alert(CONS_WARNING, "midimusicvolume should be between 0-31\n"); CV_SetValue(&cv_midimusicvolume, seqvolume&31); actualmidimusicvolume = cv_midimusicvolume.value; //check for change of var #endif #ifdef DJGPPDOS digvolume = 31; #ifndef NO_MIDI seqvolume = 31; #endif #endif switch(I_SongType()) { #ifndef NO_MIDI case MU_MID: //case MU_MOD: //case MU_GME: I_SetMusicVolume(seqvolume&31); break; #endif default: I_SetMusicVolume(digvolume&31); break; } } /// ------------------------ /// Music Fading /// ------------------------ void S_SetInternalMusicVolume(INT32 volume) { I_SetInternalMusicVolume(min(max(volume, 0), 100)); } void S_StopFadingMusic(void) { I_StopFadingSong(); } boolean S_FadeMusicFromVolume(UINT8 target_volume, INT16 source_volume, UINT32 ms) { if (source_volume < 0) return I_FadeSong(target_volume, ms, NULL); else return I_FadeSongFromVolume(target_volume, source_volume, ms, NULL); } boolean S_FadeOutStopMusic(UINT32 ms) { return I_FadeSong(0, ms, &S_StopMusic); } /// ------------------------ /// Init & Others /// ------------------------ // // Per level startup code. // Kills playing sounds at start of level, // determines music if any, changes music. // void S_Start(void) { if (mapmusflags & MUSIC_RELOADRESET) { strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7); mapmusname[6] = 0; mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK); mapmusposition = mapheaderinfo[gamemap-1]->muspos; } //if (cv_resetmusic.value) // Starting ambience should always be restarted S_StopMusic(); if (leveltime < (starttime + (TICRATE/2))) // SRB2Kart S_ChangeMusicEx((encoremode ? "estart" : "kstart"), 0, false, mapmusposition, 0, 0); else S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); } static void Command_Tunes_f(void) { const char *tunearg; UINT16 tunenum, track = 0; UINT32 position = 0; const size_t argc = COM_Argc(); if (argc < 2) //tunes slot ... { CONS_Printf("tunes [track] [speed] [position] / <-show> / <-default> / <-none>:\n"); CONS_Printf(M_GetText("Play an arbitrary music lump. If a map number is used, 'MAP##M' is played.\n")); CONS_Printf(M_GetText("If the format supports multiple songs, you can specify which one to play.\n\n")); CONS_Printf(M_GetText("* With \"-show\", shows the currently playing tune and track.\n")); CONS_Printf(M_GetText("* With \"-default\", returns to the default music for the map.\n")); CONS_Printf(M_GetText("* With \"-none\", any music playing will be stopped.\n")); return; } tunearg = COM_Argv(1); tunenum = (UINT16)atoi(tunearg); track = 0; if (!strcasecmp(tunearg, "-show")) { CONS_Printf(M_GetText("The current tune is: %s [track %d]\n"), mapmusname, (mapmusflags & MUSIC_TRACKMASK)); return; } if (!strcasecmp(tunearg, "-none")) { S_StopMusic(); return; } else if (!strcasecmp(tunearg, "-default")) { tunearg = mapheaderinfo[gamemap-1]->musname; track = mapheaderinfo[gamemap-1]->mustrack; } else if (!tunearg[2] && toupper(tunearg[0]) >= 'A' && toupper(tunearg[0]) <= 'Z') tunenum = (UINT16)M_MapNumber(tunearg[0], tunearg[1]); if (tunenum && tunenum >= 1036) { CONS_Alert(CONS_NOTICE, M_GetText("Valid music slots are 1 to 1035.\n")); return; } if (!tunenum && strlen(tunearg) > 6) // This is automatic -- just show the error just in case CONS_Alert(CONS_NOTICE, M_GetText("Music name too long - truncated to six characters.\n")); if (argc > 2) track = (UINT16)atoi(COM_Argv(2))-1; if (tunenum) snprintf(mapmusname, 7, "%sM", G_BuildMapName(tunenum)); else strncpy(mapmusname, tunearg, 7); if (argc > 4) position = (UINT32)atoi(COM_Argv(4)); mapmusname[6] = 0; mapmusflags = (track & MUSIC_TRACKMASK); mapmusposition = position; S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); if (argc > 3) { float speed = (float)atof(COM_Argv(3)); if (speed > 0.0f) S_SpeedMusic(speed); } } static void Command_RestartAudio_f(void) { if (dedicated) // No point in doing anything if game is a dedicated server. return; S_StopMusic(); S_StopSounds(); I_ShutdownMusic(); I_ShutdownSound(); I_StartupSound(); I_InitMusic(); // These must be called or no sound and music until manually set. I_SetSfxVolume(cv_soundvolume.value); #ifdef NO_MIDI S_SetMusicVolume(cv_digmusicvolume.value, -1); #else S_SetMusicVolume(cv_digmusicvolume.value, cv_midimusicvolume.value); #endif S_StartSound(NULL, sfx_strpst); if (Playing()) // Gotta make sure the player is in a level P_RestoreMusic(&players[consoleplayer]); else S_ChangeMusicInternal("titles", looptitle); } void GameSounds_OnChange(void) { if (M_CheckParm("-nosound") || M_CheckParm("-noaudio")) return; if (sound_disabled) { if (!( cv_playsoundifunfocused.value && window_notinfocus )) S_EnableSound(); } else S_DisableSound(); } void GameDigiMusic_OnChange(void) { if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio")) return; else if (M_CheckParm("-nodigmusic")) return; if (digital_disabled) { digital_disabled = false; I_InitMusic(); S_StopMusic(); if (Playing()) P_RestoreMusic(&players[consoleplayer]); else S_ChangeMusicInternal("titles", looptitle); } else { digital_disabled = true; if (S_MusicType() != MU_MID) { if (midi_disabled) S_StopMusic(); else { char mmusic[7]; UINT16 mflags; boolean looping; if (S_MusicInfo(mmusic, &mflags, &looping) && S_MIDIExists(mmusic)) { S_StopMusic(); S_ChangeMusic(mmusic, mflags, looping); } else S_StopMusic(); } } } } #ifndef NO_MIDI void GameMIDIMusic_OnChange(void) { if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio")) return; else if (M_CheckParm("-nomidimusic")) return; if (midi_disabled) { midi_disabled = false; I_InitMusic(); if (Playing()) P_RestoreMusic(&players[consoleplayer]); else S_ChangeMusicInternal("titles", looptitle); } else { midi_disabled = true; if (S_MusicType() == MU_MID) { if (digital_disabled) S_StopMusic(); else { char mmusic[7]; UINT16 mflags; boolean looping; if (S_MusicInfo(mmusic, &mflags, &looping) && S_DigExists(mmusic)) { S_StopMusic(); S_ChangeMusic(mmusic, mflags, looping); } else S_StopMusic(); } } } } #endif static void PlayMusicIfUnfocused_OnChange(void) { if (window_notinfocus) { if (cv_playmusicifunfocused.value) I_PauseSong(); else I_ResumeSong(); } } static void PlaySoundIfUnfocused_OnChange(void) { if (!cv_gamesounds.value) return; if (window_notinfocus) { if (cv_playsoundifunfocused.value) S_DisableSound(); else S_EnableSound(); } }