Kart-Public/src/s_sound.c
James R 22177753d2 Apply volume to sounds with origin too
Previously sounds with an origin would always start at max volume. This is
because the distance calculation adjusts the volume, and that volume needs to
be updated every tic as distance changes. Storing the original volume works.
2020-09-08 22:40:42 -07:00

2439 lines
57 KiB
C

// 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 <sys/msg.h>
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, FixedSqrt((splitscreen+1)<<FRACBITS))>>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, FixedSqrt((splitscreen+1)<<FRACBITS))>>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, FixedSqrt((splitscreen+1)<<FRACBITS))>>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 <name/num> [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();
}
}