// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2023 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 #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_skins.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 "fastcmp.h" #include "m_misc.h" // for tunes command #include "m_cond.h" // for conditionsets #include "lua_hook.h" // MusicChange hook #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 static void GameMIDIMusic_OnChange(void); static void GameSounds_OnChange(void); static void GameDigiMusic_OnChange(void); static void MusicPref_OnChange(void); #ifdef HAVE_OPENMPT static void ModFilter_OnChange(void); #endif static lumpnum_t S_GetMusicLumpNum(const char *mname); static boolean S_CheckQueue(void); #if defined (_WINDOWS) && !defined (SURROUND) //&& defined (_X86_) #define SURROUND #endif #ifdef _WINDOWS consvar_t cv_samplerate = CVAR_INIT ("samplerate", "44100", 0, CV_Unsigned, NULL); //Alam: For easy hacking? #else consvar_t cv_samplerate = CVAR_INIT ("samplerate", "22050", 0, CV_Unsigned, NULL); //Alam: For easy hacking? #endif // stereo reverse consvar_t stereoreverse = CVAR_INIT ("stereoreverse", "Off", CV_SAVE, CV_OnOff, NULL); // if true, all sounds are loaded at game startup static consvar_t precachesound = CVAR_INIT ("precachesound", "Off", CV_SAVE, CV_OnOff, NULL); // actual general (maximum) sound & music volume, saved into the config consvar_t cv_soundvolume = CVAR_INIT ("soundvolume", "16", CV_SAVE, soundvolume_cons_t, NULL); consvar_t cv_digmusicvolume = CVAR_INIT ("digmusicvolume", "16", CV_SAVE, soundvolume_cons_t, NULL); consvar_t cv_midimusicvolume = CVAR_INIT ("midimusicvolume", "16", CV_SAVE, soundvolume_cons_t, NULL); static void Captioning_OnChange(void) { S_ResetCaptions(); if (cv_closedcaptioning.value) S_StartSound(NULL, sfx_menu1); } consvar_t cv_closedcaptioning = CVAR_INIT ("closedcaptioning", "Off", CV_SAVE|CV_CALL, CV_OnOff, Captioning_OnChange); // number of channels available consvar_t cv_numChannels = CVAR_INIT ("snd_channels", "32", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum); static consvar_t surround = CVAR_INIT ("surround", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_resetmusic = CVAR_INIT ("resetmusic", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_resetmusicbyheader = CVAR_INIT ("resetmusicbyheader", "Yes", CV_SAVE, CV_YesNo, NULL); static CV_PossibleValue_t cons_1upsound_t[] = { {0, "Jingle"}, {1, "Sound"}, {0, NULL} }; consvar_t cv_1upsound = CVAR_INIT ("1upsound", "Jingle", CV_SAVE, cons_1upsound_t, NULL); // Sound system toggles, saved into the config consvar_t cv_gamedigimusic = CVAR_INIT ("digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameDigiMusic_OnChange); consvar_t cv_gamemidimusic = CVAR_INIT ("midimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameMIDIMusic_OnChange); consvar_t cv_gamesounds = CVAR_INIT ("sounds", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameSounds_OnChange); // Music preference static CV_PossibleValue_t cons_musicpref_t[] = { {0, "Digital"}, {1, "MIDI"}, {0, NULL} }; consvar_t cv_musicpref = CVAR_INIT ("musicpref", "Digital", CV_SAVE|CV_CALL|CV_NOINIT, cons_musicpref_t, MusicPref_OnChange); // Window focus sound sytem toggles consvar_t cv_playmusicifunfocused = CVAR_INIT ("playmusicifunfocused", "No", CV_SAVE, CV_YesNo, NULL); consvar_t cv_playsoundsifunfocused = CVAR_INIT ("playsoundsifunfocused", "No", CV_SAVE, CV_YesNo, NULL); #ifdef HAVE_OPENMPT openmpt_module *openmpt_mhandle = NULL; static CV_PossibleValue_t interpolationfilter_cons_t[] = {{0, "Default"}, {1, "None"}, {2, "Linear"}, {4, "Cubic"}, {8, "Windowed sinc"}, {0, NULL}}; consvar_t cv_modfilter = CVAR_INIT ("modfilter", "0", CV_SAVE|CV_CALL, interpolationfilter_cons_t, ModFilter_OnChange); #endif #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 // the set of channels available static channel_t *channels = NULL; static INT32 numofchannels = 0; caption_t closedcaptions[NUMCAPTIONS]; void S_ResetCaptions(void) { UINT8 i; for (i = 0; i < NUMCAPTIONS; i++) { closedcaptions[i].c = NULL; closedcaptions[i].s = NULL; closedcaptions[i].t = 0; closedcaptions[i].b = 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); CV_RegisterVar(&surround); CV_RegisterVar(&cv_samplerate); CV_RegisterVar(&cv_resetmusic); CV_RegisterVar(&cv_resetmusicbyheader); CV_RegisterVar(&cv_1upsound); CV_RegisterVar(&cv_playsoundsifunfocused); CV_RegisterVar(&cv_playmusicifunfocused); CV_RegisterVar(&cv_gamesounds); CV_RegisterVar(&cv_gamedigimusic); CV_RegisterVar(&cv_gamemidimusic); CV_RegisterVar(&cv_musicpref); #ifdef HAVE_OPENMPT CV_RegisterVar(&cv_modfilter); #endif #ifdef HAVE_MIXERX CV_RegisterVar(&cv_midiplayer); CV_RegisterVar(&cv_midisoundfontpath); CV_RegisterVar(&cv_miditimiditypath); #endif COM_AddCommand("tunes", Command_Tunes_f, COM_LUA); COM_AddCommand("restartaudio", Command_RestartAudio_f, COM_LUA); } 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; S_ResetCaptions(); } // 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"); } // // Sound Status // boolean S_SoundDisabled(void) { return ( sound_disabled || ( window_notinfocus && ! cv_playsoundsifunfocused.value ) ); } // 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); S_ResetCaptions(); } 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_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan) { UINT8 i, set, moveup, start; boolean same = false; sfxinfo_t *sfx; if (!cv_closedcaptioning.value) // no captions at all return; // check for bogus sound # // I_Assert(sfx_id >= 0); -- allowing sfx_None; this shouldn't be allowed directly if S_StartCaption is ever exposed to Lua by itself I_Assert(sfx_id < NUMSFX); sfx = &S_sfx[sfx_id]; if (sfx->caption[0] == '/') // no caption for this one return; start = ((closedcaptions[0].s && (closedcaptions[0].s-S_sfx == sfx_None)) ? 1 : 0); if (sfx_id) { for (i = start; i < (set = NUMCAPTIONS-1); i++) { same = ((sfx == closedcaptions[i].s) || (closedcaptions[i].s && fastcmp(sfx->caption, closedcaptions[i].s->caption))); if (same) { set = i; break; } } } else { set = 0; same = (closedcaptions[0].s == sfx); } moveup = 255; if (!same) { for (i = start; i < set; i++) { if (!(closedcaptions[i].c || closedcaptions[i].s) || (sfx->priority >= closedcaptions[i].s->priority)) { set = i; if (closedcaptions[i].s && (sfx->priority >= closedcaptions[i].s->priority)) moveup = i; break; } } for (i = NUMCAPTIONS-1; i > set; i--) { if (sfx == closedcaptions[i].s) { closedcaptions[i].c = NULL; closedcaptions[i].s = NULL; closedcaptions[i].t = 0; closedcaptions[i].b = 0; } } } if (moveup != 255) { for (i = moveup; i < NUMCAPTIONS-1; i++) { if (!(closedcaptions[i].c || closedcaptions[i].s)) break; } for (; i > set; i--) { closedcaptions[i].c = closedcaptions[i-1].c; closedcaptions[i].s = closedcaptions[i-1].s; closedcaptions[i].t = closedcaptions[i-1].t; closedcaptions[i].b = closedcaptions[i-1].b; } } closedcaptions[set].c = ((cnum == -1) ? NULL : &channels[cnum]); closedcaptions[set].s = sfx; closedcaptions[set].t = lifespan; closedcaptions[set].b = 3; // bob } void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) { const INT32 initial_volume = volume; INT32 sep, pitch, priority, cnum; const sfxenum_t actual_id = sfx_id; sfxinfo_t *sfx; const mobj_t *origin = (const mobj_t *)origin_p; listener_t listener = {0,0,0,0}; listener_t listener2 = {0,0,0,0}; mobj_t *listenmobj = players[displayplayer].mo; mobj_t *listenmobj2 = NULL; if (S_SoundDisabled() || !sound_started) return; // Don't want a sound? Okay then... if (sfx_id == sfx_None) return; if (players[displayplayer].awayviewtics) listenmobj = players[displayplayer].awayviewmobj; if (splitscreen) { listenmobj2 = players[secondarydisplayplayer].mo; if (players[secondarydisplayplayer].awayviewtics) listenmobj2 = players[secondarydisplayplayer].awayviewmobj; } #ifdef HW3SOUND if (hws_mode != HWS_DEFAULT_MODE) { HW3S_StartSound(origin, sfx_id); return; }; #endif if (camera.chase && !players[displayplayer].awayviewtics) { listener.x = camera.x; listener.y = camera.y; listener.z = camera.z; listener.angle = camera.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 (camera2.chase && !players[secondarydisplayplayer].awayviewtics) { listener2.x = camera2.x; listener2.y = camera2.y; listener2.z = camera2.z; listener2.angle = camera2.angle; } else { listener2.x = listenmobj2->x; listener2.y = listenmobj2->y; listener2.z = listenmobj2->z; listener2.angle = listenmobj2->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 && 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; #ifdef SURROUND // Avoid channel reverse if surround if (stereoreverse.value && sep != SURROUND_SEP) sep = (~sep) & 255; #else if (stereoreverse.value) sep = (~sep) & 255; #endif // Handle closed caption input. S_StartCaption(actual_id, cnum, MAXCAPTIONTICS); // 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: // 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; #ifdef SURROUND // Avoid channel reverse if surround if (stereoreverse.value && sep != SURROUND_SEP) sep = (~sep) & 255; #else if (stereoreverse.value) sep = (~sep) & 255; #endif // Handle closed caption input. S_StartCaption(actual_id, cnum, MAXCAPTIONTICS); // 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 (S_SoundDisabled()) 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: case sfx_wepfir: sfx_id = sfx_mario7; break; case sfx_pop: sfx_id = sfx_mario5; break; case sfx_jump: sfx_id = sfx_mario6; break; case sfx_shield: case sfx_wirlsg: case sfx_forcsg: case sfx_elemsg: case sfx_armasg: case sfx_attrsg: case sfx_s3k3e: case sfx_s3k3f: case sfx_s3k41: 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; static INT32 actualmidimusicvolume; void S_UpdateSounds(void) { INT32 audible, cnum, volume, sep, pitch; channel_t *c; listener_t listener; listener_t listener2; mobj_t *listenmobj = players[displayplayer].mo; mobj_t *listenmobj2 = NULL; memset(&listener, 0, sizeof(listener_t)); memset(&listener2, 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); if (actualmidimusicvolume != cv_midimusicvolume.value) S_SetMIDIMusicVolume (cv_midimusicvolume.value); // 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 goto notinlevel; } if (dedicated || sound_disabled) return; if (players[displayplayer].awayviewtics) listenmobj = players[displayplayer].awayviewmobj; if (splitscreen) { listenmobj2 = players[secondarydisplayplayer].mo; if (players[secondarydisplayplayer].awayviewtics) listenmobj2 = players[secondarydisplayplayer].awayviewmobj; } if (camera.chase && !players[displayplayer].awayviewtics) { listener.x = camera.x; listener.y = camera.y; listener.z = camera.z; listener.angle = camera.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(); goto notinlevel; } #endif if (listenmobj2) { if (camera2.chase && !players[secondarydisplayplayer].awayviewtics) { listener2.x = camera2.x; listener2.y = camera2.y; listener2.z = camera2.z; listener2.angle = camera2.angle; } else { listener2.x = listenmobj2->x; listener2.y = listenmobj2->y; listener2.z = listenmobj2->z; listener2.angle = listenmobj2->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; // check non-local sounds for distance clipping // or modify their params if (c->origin && ((c->origin != players[consoleplayer].mo) || (splitscreen && c->origin != players[secondarydisplayplayer].mo))) { // Whomever is closer gets the sound, but only in splitscreen. if (listenmobj && listenmobj2 && splitscreen) { const mobj_t *soundmobj = c->origin; fixed_t dist1, dist2; dist1 = P_AproxDistance(listener.x-soundmobj->x, listener.y-soundmobj->y); dist2 = P_AproxDistance(listener2.x-soundmobj->x, listener2.y-soundmobj->y); if (dist1 <= dist2) { // Player 1 gets the sound audible = S_AdjustSoundParams(listenmobj, c->origin, &volume, &sep, &pitch, c->sfxinfo); } else { // Player 2 gets the sound audible = S_AdjustSoundParams(listenmobj2, 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); } } } notinlevel: I_UpdateSound(); } void S_UpdateClosedCaptions(void) { UINT8 i; boolean gamestopped = (paused || P_AutoPause()); for (i = 0; i < NUMCAPTIONS; i++) // update captions { if (!closedcaptions[i].s) continue; if (i == 0 && (closedcaptions[0].s-S_sfx == sfx_None) && gamestopped) continue; if (!(--closedcaptions[i].t)) { closedcaptions[i].c = NULL; closedcaptions[i].s = NULL; } else if (closedcaptions[i].c && !I_SoundIsPlaying(closedcaptions[i].c->handle)) { closedcaptions[i].c = NULL; if (closedcaptions[i].t > CAPTIONFADETICS) closedcaptions[i].t = CAPTIONFADETICS; } } } 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) { size_t i; for (i = 1; i < NUMSFX; i++) I_FreeSfx(S_sfx + i); } static void S_StopChannel(INT32 cnum) { channel_t *c = &channels[cnum]; if (c->sfxinfo) { // stop the sound playing if (I_SoundIsPlaying(c->handle)) I_StopSound(c->handle); // degrade usefulness of sound data c->sfxinfo->usefulness--; c->sfxinfo = 0; } c->origin = NULL; } // // 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 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; (void)pitch; if (!listener) return false; if (listener == players[displayplayer].mo && camera.chase) { listensource.x = camera.x; listensource.y = camera.y; listensource.z = camera.z; listensource.angle = camera.angle; } else if (splitscreen && listener == players[secondarydisplayplayer].mo && camera2.chase) { listensource.x = camera2.x; listensource.y = camera2.y; listensource.z = camera2.z; listensource.angle = camera2.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); #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; } 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 /// ------------------------ 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; static tic_t pause_starttic; /// ------------------------ /// Music Definitions /// ------------------------ enum { MUSICDEF_220, MUSICDEF_221, }; musicdef_t soundtestsfx = { "_STSFX", // prevents exactly one valid track name from being used on the sound test "Sound Effects", "", "SEGA, VAdaPEGA, other sources", 1, // show on soundtest page 1 0, // with no conditions 0, 0, 0, false, NULL }; musicdef_t *musicdefstart = &soundtestsfx; // // 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 } static void MusicDefStrcpy (char *p, const char *s, size_t n, int version) { strlcpy(p, s, n); if (version == MUSICDEF_220) { while (( p = strchr(p, '_') )) *p++ = ' '; // turn _ into spaces. } } static boolean ReadMusicDefFields (UINT16 wadnum, int line, boolean fields, char *stoken, musicdef_t **defp, int *versionp) { musicdef_t *def; int version; char *value; char *textline; int i; if (!stricmp(stoken, "lump")) { value = strtok(NULL, " "); if (!value) { CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' is missing name. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); return false; } else { musicdef_t *prev = NULL; 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); def->bpm = TICRATE<<(FRACBITS-1); // FixedDiv((60*TICRATE)<next = def; //CONS_Printf("S_LoadMusicDefs: Added song '%s'\n", def->name); } (*defp) = def; } } else if (!stricmp(stoken, "version")) { if (fields)/* is this not the first field? */ { CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' must come first. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); return false; } else { value = strtok(NULL, " "); if (!value) { CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' is missing version. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); return false; } else { if (strcasecmp(value, "2.2.0")) (*versionp) = MUSICDEF_221; } } } else { version = (*versionp); if (version == MUSICDEF_220) value = strtok(NULL, " ="); else { value = strtok(NULL, ""); if (value) { // Find the equals sign. value = strchr(value, '='); } } if (!value) { CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' is missing value. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); return false; } else { def = (*defp); if (!def) { CONS_Alert(CONS_ERROR, "MUSICDEF: No music definition before field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); return false; } if (version != MUSICDEF_220) { // Skip the equals sign. value++; // Now skip funny whitespace. value += strspn(value, "\t "); } textline = value; i = atoi(value); /* based ignored lumps */ if (!stricmp(stoken, "usage")) { #if 0 // Ignore for now STRBUFCPY(def->usage, textline); #endif } else if (!stricmp(stoken, "source")) { #if 0 // Ignore for now STRBUFCPY(def->source, textline); #endif } else if (!stricmp(stoken, "title")) { MusicDefStrcpy(def->title, textline, sizeof def->title, version); } else if (!stricmp(stoken, "alttitle")) { MusicDefStrcpy(def->alttitle, textline, sizeof def->alttitle, version); } else if (!stricmp(stoken, "authors")) { MusicDefStrcpy(def->authors, textline, sizeof def->authors, version); } else if (!stricmp(stoken, "soundtestpage")) { def->soundtestpage = (UINT8)i; } else if (!stricmp(stoken, "soundtestcond")) { // Convert to map number if (textline[0] >= 'A' && textline[0] <= 'Z' && textline[2] == '\0') i = M_MapNumber(textline[0], textline[1]); def->soundtestcond = (INT16)i; } else if (!stricmp(stoken, "stoppingtime")) { double stoppingtime = atof(textline)*TICRATE; def->stoppingtics = (tic_t)stoppingtime; } else if (!stricmp(stoken, "bpm")) { double bpm = atof(textline); fixed_t bpmf = FLOAT_TO_FIXED(bpm); if (bpmf > 0) def->bpm = FixedDiv((60*TICRATE)<loop_ms = atoi(textline); } else { CONS_Alert(CONS_WARNING, "MUSICDEF: Invalid field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line); } } } return true; } void S_LoadMusicDefs(UINT16 wadnum) { UINT16 lumpnum; char *lump; char *musdeftext; size_t size; char *lf; char *stoken; size_t nlf = 0xFFFFFFFF; size_t ncr; musicdef_t *def = NULL; int version = MUSICDEF_220; int line = 1; // for better error msgs boolean fields = false; lumpnum = W_CheckForMusicDefInPwad(wadnum); if (lumpnum == INT16_MAX) return; lump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE); size = W_LumpLengthPwad(wadnum, lumpnum); // Null-terminated MUSICDEF lump. musdeftext = malloc(size+1); if (!musdeftext) I_Error("S_LoadMusicDefs: No more free memory for the parser\n"); M_Memcpy(musdeftext, lump, size); musdeftext[size] = '\0'; // Find music def stoken = musdeftext; for (;;) { lf = strpbrk(stoken, "\r\n"); if (lf) { if (*lf == '\n') nlf = 1; else nlf = 0; *lf++ = '\0';/* now we can delimit to here */ } stoken = strtok(stoken, " "); if (stoken) { if (! ReadMusicDefFields(wadnum, line, fields, stoken, &def, &version)) break; fields = true; } if (lf) { do { line += nlf; ncr = strspn(lf, "\r");/* skip CR */ lf += ncr; nlf = strspn(lf, "\n"); lf += nlf; } while (nlf || ncr) ; stoken = lf;/* now the next nonempty line */ } else break;/* EOF */ } free(musdeftext); } // // S_InitMusicDefs // // Simply load music defs in all wads. // void S_InitMusicDefs(void) { UINT16 i; for (i = 0; i < numwadfiles; i++) S_LoadMusicDefs(i); } musicdef_t **soundtestdefs = NULL; INT32 numsoundtestdefs = 0; UINT8 soundtestpage = 1; // // S_PrepareSoundTest // // Prepare sound test. What am I, your butler? // boolean S_PrepareSoundTest(void) { gamedata_t *data = clientGamedata; musicdef_t *def; INT32 pos = numsoundtestdefs = 0; for (def = musicdefstart; def; def = def->next) { if (!(def->soundtestpage & soundtestpage)) continue; def->allowed = false; numsoundtestdefs++; } if (!numsoundtestdefs) return false; if (soundtestdefs) Z_Free(soundtestdefs); if (!(soundtestdefs = Z_Malloc(numsoundtestdefs*sizeof(musicdef_t *), PU_STATIC, NULL))) I_Error("S_PrepareSoundTest(): could not allocate soundtestdefs."); for (def = musicdefstart; def /*&& i < numsoundtestdefs*/; def = def->next) { if (!(def->soundtestpage & soundtestpage)) continue; soundtestdefs[pos++] = def; if (def->soundtestcond > 0 && !(data->mapvisited[def->soundtestcond-1] & MV_BEATEN)) continue; if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond, data)) continue; def->allowed = true; } return true; } /// ------------------------ /// Music Status /// ------------------------ boolean S_DigMusicDisabled(void) { return digital_disabled; } boolean S_MIDIMusicDisabled(void) { return midi_disabled; } boolean S_MusicDisabled(void) { return (midi_disabled && digital_disabled); } boolean S_MusicPlaying(void) { return I_SongPlaying(); } boolean S_MusicPaused(void) { return I_SongPaused(); } boolean S_MusicNotInFocus(void) { return ( ( window_notinfocus && ! cv_playmusicifunfocused.value ) ); } musictype_t S_MusicType(void) { return I_SongType(); } const char *S_MusicName(void) { return music_name; } 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 Stacking (Jingles) /// In this section: mazmazz doesn't know how to do dynamic arrays or struct pointers! /// ------------------------ char music_stack_nextmusname[7]; boolean music_stack_noposition = false; UINT32 music_stack_fadeout = 0; UINT32 music_stack_fadein = 0; static musicstack_t *music_stacks = NULL; static musicstack_t *last_music_stack = NULL; void S_SetStackAdjustmentStart(void) { if (!pause_starttic) pause_starttic = gametic; } void S_AdjustMusicStackTics(void) { if (pause_starttic) { musicstack_t *mst; for (mst = music_stacks; mst; mst = mst->next) mst->tic += gametic - pause_starttic; pause_starttic = 0; } } static void S_ResetMusicStack(void) { musicstack_t *mst, *mst_next; for (mst = music_stacks; mst; mst = mst_next) { mst_next = mst->next; Z_Free(mst); } music_stacks = last_music_stack = NULL; } static void S_RemoveMusicStackEntry(musicstack_t *entry) { musicstack_t *mst; for (mst = music_stacks; mst; mst = mst->next) { if (mst == entry) { // Remove ourselves from the chain and link // prev and next together if (mst->prev) mst->prev->next = mst->next; else music_stacks = mst->next; if (mst->next) mst->next->prev = mst->prev; else last_music_stack = mst->prev; break; } } Z_Free(entry); } static void S_RemoveMusicStackEntryByStatus(UINT16 status) { musicstack_t *mst, *mst_next; if (!status) return; for (mst = music_stacks; mst; mst = mst_next) { mst_next = mst->next; if (mst->status == status) S_RemoveMusicStackEntry(mst); } } static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status) { musicstack_t *mst, *new_mst; // if the first entry is empty, force master onto it if (!music_stacks) { music_stacks = Z_Calloc(sizeof (*mst), PU_MUSIC, NULL); strncpy(music_stacks->musname, (status == JT_MASTER ? mname : (S_CheckQueue() ? queue_name : mapmusname)), sizeof(music_stacks->musname)-1); music_stacks->musflags = (status == JT_MASTER ? mflags : (S_CheckQueue() ? queue_flags : mapmusflags)); music_stacks->looping = (status == JT_MASTER ? looping : (S_CheckQueue() ? queue_looping : true)); music_stacks->position = (status == JT_MASTER ? position : (S_CheckQueue() ? queue_position : S_GetMusicPosition())); music_stacks->tic = gametic; music_stacks->status = JT_MASTER; music_stacks->mlumpnum = S_GetMusicLumpNum(music_stacks->musname); music_stacks->noposition = S_CheckQueue(); if (status == JT_MASTER) return; // we just added the user's entry here } // look for an empty slot to park ourselves for (mst = music_stacks; mst->next; mst = mst->next); // create our new entry new_mst = Z_Calloc(sizeof (*new_mst), PU_MUSIC, NULL); strncpy(new_mst->musname, mname, 7); new_mst->musname[6] = 0; new_mst->musflags = mflags; new_mst->looping = looping; new_mst->position = position; new_mst->tic = gametic; new_mst->status = status; new_mst->mlumpnum = S_GetMusicLumpNum(new_mst->musname); new_mst->noposition = false; mst->next = new_mst; new_mst->prev = mst; new_mst->next = NULL; last_music_stack = new_mst; } static musicstack_t *S_GetMusicStackEntry(UINT16 status, boolean fromfirst, INT16 startindex) { musicstack_t *mst, *start_mst = NULL, *mst_next; // if the first entry is empty, force master onto it // fixes a memory corruption bug if (!music_stacks && status != JT_MASTER) S_AddMusicStackEntry(mapmusname, mapmusflags, true, S_GetMusicPosition(), JT_MASTER); if (startindex >= 0) { INT16 i = 0; for (mst = music_stacks; mst && i <= startindex; mst = mst->next, i++) start_mst = mst; } else start_mst = (fromfirst ? music_stacks : last_music_stack); for (mst = start_mst; mst; mst = mst_next) { mst_next = (fromfirst ? mst->next : mst->prev); if (!status || mst->status == status) { if (P_EvaluateMusicStatus(mst->status, mst->musname)) { if (!S_MusicExists(mst->musname, !midi_disabled, !digital_disabled)) // paranoia S_RemoveMusicStackEntry(mst); // then continue else return mst; } else S_RemoveMusicStackEntry(mst); // then continue } } return NULL; } void S_RetainMusic(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status) { musicstack_t *mst; if (!status) // we use this as a null indicator, don't push { CONS_Alert(CONS_ERROR, "Music stack entry must have a nonzero status.\n"); return; } else if (status == JT_MASTER) // enforce only one JT_MASTER { for (mst = music_stacks; mst; mst = mst->next) { if (mst->status == JT_MASTER) { CONS_Alert(CONS_ERROR, "Music stack can only have one JT_MASTER entry.\n"); return; } } } else // remove any existing status S_RemoveMusicStackEntryByStatus(status); S_AddMusicStackEntry(mname, mflags, looping, position, status); } boolean S_RecallMusic(UINT16 status, boolean fromfirst) { UINT32 newpos = 0; boolean mapmuschanged = false; musicstack_t *result; musicstack_t *entry = Z_Calloc(sizeof (*result), PU_MUSIC, NULL); boolean currentmidi = (I_SongType() == MU_MID || I_SongType() == MU_MID_EX); boolean midipref = cv_musicpref.value; if (status) result = S_GetMusicStackEntry(status, fromfirst, -1); else result = S_GetMusicStackEntry(JT_NONE, false, -1); if (result && !S_MusicExists(result->musname, !midi_disabled, !digital_disabled)) { Z_Free(entry); return false; // music doesn't exist, so don't do anything } // make a copy of result, since we make modifications to our copy if (result) { *entry = *result; memcpy(entry->musname, result->musname, sizeof(entry->musname)); } // no result, just grab mapmusname if (!result || !entry->musname[0] || ((status == JT_MASTER || (music_stacks ? !music_stacks->status : false)) && !entry->status)) { strncpy(entry->musname, mapmusname, 7); entry->musflags = mapmusflags; entry->looping = true; entry->position = mapmusposition; entry->tic = gametic; entry->status = JT_MASTER; entry->mlumpnum = S_GetMusicLumpNum(entry->musname); entry->noposition = false; // don't set this until we do the mapmuschanged check, below. Else, this breaks some resumes. } if (entry->status == JT_MASTER) { mapmuschanged = strnicmp(entry->musname, mapmusname, 7); if (mapmuschanged) { strncpy(entry->musname, mapmusname, 7); entry->musflags = mapmusflags; entry->looping = true; entry->position = mapmusposition; entry->tic = gametic; entry->status = JT_MASTER; entry->mlumpnum = S_GetMusicLumpNum(entry->musname); entry->noposition = true; } S_ResetMusicStack(); } else if (!entry->status) { Z_Free(entry); return false; } if (strncmp(entry->musname, S_MusicName(), 7) || // don't restart music if we're already playing it (midipref != currentmidi && S_PrefAvailable(midipref, entry->musname))) // but do if the user's preference has changed { if (music_stack_fadeout) S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, music_stack_fadeout, 0); else { S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, 0, music_stack_fadein); if (!entry->noposition && !music_stack_noposition) // HACK: Global boolean to toggle position resuming, e.g., de-superize { UINT32 poslapse = 0; // To prevent the game from jumping past the end of the music, we need // to check if we can get the song's length. Otherwise, if the lapsed resume time goes // over a LOOPPOINT, mixer_sound.c will be unable to calculate the new resume position. if (S_GetMusicLength()) poslapse = (UINT32)((float)(gametic - entry->tic)/(float)TICRATE*(float)MUSICRATE); newpos = entry->position + poslapse; } // If the newly recalled music lumpnum does not match the lumpnum that we stored in stack, // then discard the new position. That way, we will not recall an invalid position // when the music is replaced or digital/MIDI is toggled. if (newpos > 0 && S_MusicPlaying() && S_GetMusicLumpNum(entry->musname) == entry->mlumpnum) S_SetMusicPosition(newpos); else { S_StopFadingMusic(); S_SetInternalMusicVolume(100); } } music_stack_noposition = false; music_stack_fadeout = 0; music_stack_fadein = JINGLEPOSTFADE; } Z_Free(entry); return true; } /// ------------------------ /// Music Playback /// ------------------------ static lumpnum_t S_GetMusicLumpNum(const char *mname) { boolean midipref = cv_musicpref.value; if (S_PrefAvailable(midipref, mname)) return W_GetNumForName(va(midipref ? "d_%s":"o_%s", mname)); else if (S_PrefAvailable(!midipref, mname)) return W_GetNumForName(va(midipref ? "o_%s":"d_%s", mname)); else return LUMPERROR; } static boolean S_LoadMusic(const char *mname) { lumpnum_t mlumpnum; void *mdata; if (S_MusicDisabled()) return false; mlumpnum = S_GetMusicLumpNum(mname); if (mlumpnum == LUMPERROR) { CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded: lump not found!\n", mname); return false; } // load & register it mdata = W_CacheLumpNum(mlumpnum, PU_MUSIC); if (I_LoadSong(mdata, W_LumpLength(mlumpnum))) { strncpy(music_name, mname, 7); music_name[6] = 0; music_data = mdata; return true; } else { CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded: engine failure!\n", mname); 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) { musicdef_t *def; if (S_MusicDisabled()) return false; if ((!fadeinms && !I_PlaySong(looping)) || (fadeinms && !I_FadeInPlaySong(fadeinms, looping))) { CONS_Alert(CONS_ERROR, "Music %.6s could not be played: engine failure!\n", music_name); S_UnloadMusic(); return false; } /* set loop point from MUSICDEF */ for (def = musicdefstart; def; def = def->next) { if (strcasecmp(def->name, music_name) == 0) { if (def->loop_ms) S_SetMusicLoopPoint(def->loop_ms); break; } } S_InitMusicVolume(); // switch between digi and sequence volume if (S_MusicNotInFocus()) S_PauseAudio(); 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 boolean S_CheckQueue(void) { return queue_name[0]; } 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]; struct MusicChange hook_param = { newmusic, &mflags, &looping, &position, &prefadems, &fadeinms }; boolean currentmidi = (I_SongType() == MU_MID || I_SongType() == MU_MID_EX); boolean midipref = cv_musicpref.value; if (S_MusicDisabled()) return; strncpy(newmusic, mmusic, sizeof(newmusic)-1); newmusic[6] = 0; if (LUA_HookMusicChange(music_name, &hook_param)) return; // No Music (empty string) if (newmusic[0] == 0) { if (prefadems) I_FadeSong(0, prefadems, &S_StopMusic); else S_StopMusic(); return; } if (prefadems) // queue music change for after fade // allow even if the music is the same // && S_MusicPlaying() // Let the delay happen even if we're not playing music { 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) || (midipref != currentmidi && S_PrefAvailable(midipref, newmusic))) { CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic); S_StopMusic(); if (!S_LoadMusic(newmusic)) return; music_flags = mflags; music_looping = looping; if (!S_PlayMusic(looping, fadeinms)) 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()) return; if (I_SongPaused()) I_ResumeSong(); S_SpeedMusic(1.0f); I_StopSong(); S_UnloadMusic(); // for now, stopping also means you unload the song if (cv_closedcaptioning.value) { if (closedcaptions[0].s-S_sfx == sfx_None) { if (gamestate != wipegamestate) { closedcaptions[0].c = NULL; closedcaptions[0].s = NULL; closedcaptions[0].t = 0; closedcaptions[0].b = 0; } else closedcaptions[0].t = CAPTIONFADETICS; } } } // // Stop and resume music, during game PAUSE. // void S_PauseAudio(void) { if (I_SongPlaying() && !I_SongPaused()) I_PauseSong(); S_SetStackAdjustmentStart(); } void S_ResumeAudio(void) { if (S_MusicNotInFocus()) return; if (I_SongPlaying() && I_SongPaused()) I_ResumeSong(); S_AdjustMusicStackTics(); } void S_SetMusicVolume(INT32 digvolume, INT32 seqvolume) { if (digvolume < 0) digvolume = cv_digmusicvolume.value; if (seqvolume < 0) seqvolume = cv_midimusicvolume.value; 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 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 switch(I_SongType()) { case MU_MID: case MU_MID_EX: //case MU_MOD: //case MU_GME: I_SetMusicVolume(seqvolume&31); break; 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_StartEx(boolean reset) { 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 (RESETMUSIC || reset) S_StopMusic(); S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); S_ResetMusicStack(); music_stack_noposition = false; music_stack_fadeout = 0; music_stack_fadein = JINGLEPOSTFADE; } static void Command_Tunes_f(void) { const char *tunearg; UINT16 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); 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; } if (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; strncpy(mapmusname, tunearg, 7); mapmusname[6] = 0; if (argc > 4) position = (UINT32)atoi(COM_Argv(4)); 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) { 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); S_SetMusicVolume(cv_digmusicvolume.value, cv_midimusicvolume.value); if (Playing()) // Gotta make sure the player is in a level P_RestoreMusic(&players[consoleplayer]); } void GameSounds_OnChange(void) { if (M_CheckParm("-nosound") || M_CheckParm("-noaudio")) return; if (sound_disabled) { sound_disabled = false; I_StartupSound(); // will return early if initialised S_InitSfxChannels(cv_soundvolume.value); S_StartSound(NULL, sfx_strpst); } else { sound_disabled = true; S_StopSounds(); } } 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_StartupSound(); // will return early if initialised I_InitMusic(); if (Playing()) P_RestoreMusic(&players[consoleplayer]); else if ((!cv_musicpref.value || midi_disabled) && S_DigExists("_clear")) S_ChangeMusicInternal("_clear", false); } else { digital_disabled = true; if (S_MusicType() != MU_MID && S_MusicType() != MU_MID_EX) { S_StopMusic(); if (!midi_disabled) { if (Playing()) P_RestoreMusic(&players[consoleplayer]); else S_ChangeMusicInternal("_clear", false); } } } } 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_StartupSound(); // will return early if initialised I_InitMusic(); if (Playing()) P_RestoreMusic(&players[consoleplayer]); else if ((cv_musicpref.value || digital_disabled) && S_MIDIExists("_clear")) S_ChangeMusicInternal("_clear", false); } else { midi_disabled = true; if (S_MusicType() == MU_MID || S_MusicType() == MU_MID_EX) { S_StopMusic(); if (!digital_disabled) { if (Playing()) P_RestoreMusic(&players[consoleplayer]); else S_ChangeMusicInternal("_clear", false); } } } } void MusicPref_OnChange(void) { if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio") || M_CheckParm("-nomidimusic") || M_CheckParm("-nodigmusic")) return; if (Playing()) P_RestoreMusic(&players[consoleplayer]); else if (S_PrefAvailable(cv_musicpref.value, "_clear")) S_ChangeMusicInternal("_clear", false); } #ifdef HAVE_OPENMPT void ModFilter_OnChange(void) { if (openmpt_mhandle) openmpt_module_set_render_param(openmpt_mhandle, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, cv_modfilter.value); } #endif