@ -20,15 +20,19 @@ static float SND_FMOD_Attenuation(FMOD_CHANNELCONTROL *channelControl, float dis
# define FMOD_VectorCopy(a, b) {(b).x=(a)[0];(b).y=(a)[2];(b).z=(a)[1];}
# define FMOD_VectorCopy(a, b) {(b).x=(a)[0];(b).y=(a)[2];(b).z=(a)[1];}
# define sound_nominal_clip_dist 1000.0
# define sound_nominal_clip_dist 1000.0
typedef struct entchannel _s
typedef struct soundslot _s
{
{
int entnum ;
int entchannel ;
float dist_mult ;
qboolean zone ;
FMOD_CHANNEL * channel ;
FMOD_CHANNEL * channel ;
} entchannel_t ;
float dist_mult ;
} soundslot_t ;
typedef struct entsounds_s
{
soundslot_t slots [ 8 ] ;
} entsounds_t ;
static entchannel_t entchannels [ MAX_CHANNELS ] ;
static entsounds_t entsound s [ MAX_CHANNELS ] ;
void S_Startup ( void )
void S_Startup ( void )
{
{
@ -87,7 +91,7 @@ void S_Startup(void)
FMOD_ChannelGroup_Set3DMinMaxDistance ( sfx_channelGroup , 0.0f , sound_nominal_clip_dist ) ;
FMOD_ChannelGroup_Set3DMinMaxDistance ( sfx_channelGroup , 0.0f , sound_nominal_clip_dist ) ;
FMOD_System_Set3DRolloffCallback ( fmod_system , & SND_FMOD_Attenuation ) ;
FMOD_System_Set3DRolloffCallback ( fmod_system , & SND_FMOD_Attenuation ) ;
memset ( entchannel s , 0 , sizeof ( entchannel s ) ) ;
memset ( entsound s , 0 , sizeof ( entsound s ) ) ;
sound_started = true ;
sound_started = true ;
}
}
@ -107,11 +111,11 @@ void S_Shutdown(void)
}
}
}
}
static unsigned int SND_GetDelay ( const sfx_t * sfx )
static unsigned int SND_GetDelay ( const sfx_t * sfx , float maxseconds )
{
{
unsigned int delay ;
unsigned int delay ;
delay = 0.1 * fmod_samplerate ;
delay = maxseconds * fmod_samplerate ;
if ( delay > sfx - > samples )
if ( delay > sfx - > samples )
delay = sfx - > samples ;
delay = sfx - > samples ;
if ( delay > 0 )
if ( delay > 0 )
@ -120,10 +124,18 @@ static unsigned int SND_GetDelay(const sfx_t *sfx)
return delay ;
return delay ;
}
}
/*
= = = = = = = = = = = = = = = = = = = =
SND_FMOD_Attenuation
Custom rolloff callback that mimics Quake ' s attenuation algorithm ,
whereby sounds can have varying degrees of distance rolloff .
= = = = = = = = = = = = = = = = = = = =
*/
static float SND_FMOD_Attenuation ( FMOD_CHANNELCONTROL * channelcontrol , float distance )
static float SND_FMOD_Attenuation ( FMOD_CHANNELCONTROL * channelcontrol , float distance )
{
{
FMOD_RESULT result ;
FMOD_RESULT result ;
entchannel_t * userdata ;
soundslot _t * userdata ;
float scale ;
float scale ;
result = FMOD_Channel_GetUserData ( ( FMOD_CHANNEL * ) channelcontrol , & userdata ) ;
result = FMOD_Channel_GetUserData ( ( FMOD_CHANNEL * ) channelcontrol , & userdata ) ;
@ -140,66 +152,78 @@ static float SND_FMOD_Attenuation(FMOD_CHANNELCONTROL *channelcontrol, float dis
return scale ;
return scale ;
}
}
/ / static entchannel_t * SND_PickChannel ( int entnum , int entchannel )
/ / {
/ / int ch_idx ;
/ / entchannel_t * channel ;
/ / FMOD_BOOL isplaying ;
/ /
/ / / / Check for replacement sound , or find the best one to replace
/ / for ( ch_idx = MAX_AMBIENTS ; ch_idx < MAX_CHANNELS ; ch_idx + + )
/ / {
/ / channel = & entchannels [ ch_idx ] ;
/ /
/ / if ( entchannel ! = 0 / / channel 0 never overrides
/ / & & channel - > entnum = = entnum
/ / & & ( channel - > entchannel = = entchannel | | entchannel = = - 1 ) )
/ / {
/ / / / always override sound from same entity
/ / break ;
/ / }
/ /
/ / / / don ' t let monster sounds override player sounds
/ / if ( channel - > entnum = = cl . viewentity & & entnum ! = cl . viewentity & & channel - > sfx )
/ / continue ;
/ /
/ / if ( snd_channels [ ch_idx ] . end - paintedtime < life_left )
/ / {
/ / life_left = snd_channels [ ch_idx ] . end - paintedtime ;
/ / first_to_die = ch_idx ;
/ / }
/ / }
/ /
/ / if ( ! channel )
/ / return NULL ;
/ /
/ / if ( channel - > sfx & & channel - > channel )
/ / {
/ / FMOD_Channel_IsPlaying ( channel - > channel , & isplaying ) ;
/ / if ( isplaying )
/ / {
/ / FMOD_Channel_Stop ( channel - > channel ) ;
/ / }
/ / channel - > channel = NULL ;
/ / channel - > sfx = NULL ;
/ / }
/ /
/ / channel - > entnum = entnum ;
/ / channel - > entchannel = entchannel ;
/ / return channel ;
/ / }
/*
= = = = = = = = = = = = = = = = =
SND_FMOD_Callback
Channel control callback that ensures a channel ' s associated userdata
is properly cleared when the channel stops playing .
= = = = = = = = = = = = = = = = =
*/
static FMOD_RESULT SND_FMOD_Callback ( FMOD_CHANNELCONTROL * channelcontrol , FMOD_CHANNELCONTROL_TYPE controltype , FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype , void * commanddata1 , void * commanddata2 )
{
FMOD_RESULT result ;
soundslot_t * userdata ;
/ / We ' re only interested in notifications for when a channel is done playing a sound
if ( controltype ! = FMOD_CHANNELCONTROL_CHANNEL | | callbacktype ! = FMOD_CHANNELCONTROL_CALLBACK_END )
return FMOD_OK ;
result = FMOD_Channel_GetUserData ( ( FMOD_CHANNEL * ) channelcontrol , & userdata ) ;
if ( result ! = FMOD_OK | | ! userdata )
{
return FMOD_OK ;
}
/ / Clear the channel to signify that it ' s free
userdata - > channel = NULL ;
if ( userdata - > zone )
{
Z_Free ( userdata ) ;
}
return FMOD_OK ;
}
static soundslot_t * SND_PickSoundSlot ( int entnum , int entchannel )
{
soundslot_t * slot ;
if ( entnum < 0 | | entnum > = MAX_CHANNELS | | entchannel = = 0 | | entchannel > 7 )
{
/ / Just play on any free channel
slot = ( soundslot_t * ) Z_Malloc ( sizeof ( soundslot_t ) ) ;
slot - > zone = true ;
return slot ;
}
/ / Local sound , use the first slot and override anything already playing
if ( entchannel < 0 )
entchannel = 0 ;
slot = & entsounds [ entnum ] . slots [ entchannel ] ;
if ( slot - > channel )
{
/ / Stop any sound already playing on this slot
FMOD_Channel_Stop ( slot - > channel ) ;
}
slot - > zone = false ;
return slot ;
}
void S_StartSound ( int entnum , int entchannel , sfx_t * sfx , vec3_t origin , float fvol , float attenuation ) / / Note : volume and attenuation are properly normalized here
void S_StartSound ( int entnum , int entchannel , sfx_t * sfx , vec3_t origin , float fvol , float attenuation ) / / Note : volume and attenuation are properly normalized here
{
{
FMOD_CHANNEL * channel ;
FMOD_CHANNEL * channel ;
FMOD_VECTOR position ;
FMOD_VECTOR position ;
FMOD_RESULT result ;
FMOD_RESULT result ;
soundslot_t * userdata ;
if ( ! fmod_system | | ! sfx )
if ( ! fmod_system | | ! sfx )
return ;
return ;
/ / TODO : check nosound cvar
/ / TODO : check nosound cvar
/ / return ; / / HACK play no entity sfx for now
S_LoadSound ( sfx ) ;
S_LoadSound ( sfx ) ;
if ( ! sfx - > sound )
if ( ! sfx - > sound )
@ -207,8 +231,11 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f
/ / Find channel group for entity number ( or create ) = > note entnum can also be some random hash value
/ / Find channel group for entity number ( or create ) = > note entnum can also be some random hash value
/ / ChannelControl : : setMode = > set to 3 D
/ / ChannelControl : : setMode = > set to 3 D
/ / Can use channel group per entity to store entity - specific userdata , with maybe 16 fixed channel slots
/ / Can use channel group per entity to store entity - specific userdata , with maybe 8 - 16 fixed channel slots
/ / Note : entnum < 0 is used by temporary entities , means there ' s no tie to any entity info at all : just play on any free channel
userdata = SND_PickSoundSlot ( entnum , entchannel ) ;
/ / Pre - define channels for each entity ( could be an array of ints , probably array of entchannel_t structs )
/ / Pre - define channels for each entity ( could be an array of ints , probably array of entchannel_t structs )
/ / For entchannel 0 , dynamically select a free channel ( or just play without doing anything , let FMOD handle it )
/ / For entchannel 0 , dynamically select a free channel ( or just play without doing anything , let FMOD handle it )
/ / If channel at index entchannel > 0 is already playing : stop
/ / If channel at index entchannel > 0 is already playing : stop
@ -223,7 +250,12 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f
return ;
return ;
}
}
/ / ChannelControl : : set3DAttributes
/ / Set up callback data for rolloff and cleanup
userdata - > channel = channel ;
userdata - > dist_mult = attenuation / sound_nominal_clip_dist ;
FMOD_Channel_SetUserData ( channel , userdata ) ;
FMOD_Channel_SetCallback ( channel , & SND_FMOD_Callback ) ;
FMOD_VectorCopy ( origin , position ) ;
FMOD_VectorCopy ( origin , position ) ;
FMOD_Channel_Set3DAttributes ( channel , & position , NULL ) ;
FMOD_Channel_Set3DAttributes ( channel , & position , NULL ) ;
FMOD_Channel_SetMode ( channel , FMOD_LOOP_OFF ) ; / / TODO : some entity sounds do need to loop , e . g . moving elevators . How is this done ?
FMOD_Channel_SetMode ( channel , FMOD_LOOP_OFF ) ; / / TODO : some entity sounds do need to loop , e . g . moving elevators . How is this done ?
@ -232,30 +264,19 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f
/ / Anything coming from the view entity will always be full volume , and entchannel - 1 is used for local sounds ( e . g . menu sounds )
/ / Anything coming from the view entity will always be full volume , and entchannel - 1 is used for local sounds ( e . g . menu sounds )
FMOD_Channel_Set3DLevel ( channel , entchannel < 0 | | entnum = = cl . viewentity ? 0.0f : 1.0f ) ;
FMOD_Channel_Set3DLevel ( channel , entchannel < 0 | | entnum = = cl . viewentity ? 0.0f : 1.0f ) ;
/ / TODO : attenuation ( this is just a quick hack to test )
entchannel_t * userdata = ( entchannel_t * ) malloc ( sizeof ( entchannel_t ) ) ; / / TODO : we REALLY need to allocate this properly , this is now a memory leak ! !
userdata - > entnum = entnum ;
userdata - > entchannel = entchannel ;
userdata - > channel = channel ;
userdata - > dist_mult = attenuation / sound_nominal_clip_dist ;
FMOD_Channel_SetUserData ( channel , userdata ) ;
/ / TODO : use ChannelControl Callback to detect when the sound ends , then clear it from the entchannel_t list
/ / Use ChannelControl : : setDelay and ChannelControl : : getDSPClock to add a delay to move sounds out of phase if necessary
/ / Use ChannelControl : : setDelay and ChannelControl : : getDSPClock to add a delay to move sounds out of phase if necessary
/ / ChannelControl : : setPaused = false
FMOD_Channel_SetPaused ( channel , 0 ) ;
FMOD_Channel_SetPaused ( channel , 0 ) ;
/ / Store channel handle at position entchannel
}
}
/ / TODO : we ' re still missing Automatic Ambient sounds for water , lava , slime etc ( see S_UpdateAmbientSounds )
/ / TODO : we ' re still missing Automatic Ambient sounds for water , lava , slime etc ( see S_UpdateAmbientSounds )
void S_StaticSound ( sfx_t * sfx , vec3_t origin , float vol , float attenuation ) / / Note : volume and attenuation are in 0 - 255 range here
void S_StaticSound ( sfx_t * sfx , vec3_t origin , float vol , float attenuation ) / / Note : volume and attenuation are in 0 - 255 range here
{
{
FMOD_CHANNEL * channel ;
FMOD_CHANNEL * channel ;
FMOD_VECTOR position ;
FMOD_VECTOR position ;
FMOD_RESULT result ;
FMOD_RESULT result ;
soundslot_t * userdata ;
unsigned long long dspclock ;
unsigned long long dspclock ;
if ( ! fmod_system | | ! sfx )
if ( ! fmod_system | | ! sfx )
@ -278,16 +299,16 @@ void S_StaticSound(sfx_t *sfx, vec3_t origin, float vol, float attenuation) // N
FMOD_Channel_SetVolume ( channel , vol / 255 ) ;
FMOD_Channel_SetVolume ( channel , vol / 255 ) ;
/ / Set up attenuation info for use by the rolloff callback
/ / Set up attenuation info for use by the rolloff callback
entchannel_t * userdata = ( entchannel_t * ) malloc ( sizeof ( entchannel_t ) ) ; / / This is rather convenient . Can we use Z_Malloc here perhaps ?
userdata - > entnum = - 1 ;
userdata = SND_PickSoundSlot ( - 1 , - 1 ) ;
userdata - > channel = channel ;
userdata - > channel = channel ;
userdata - > dist_mult = ( attenuation / 64 ) / sound_nominal_clip_dist ;
userdata - > dist_mult = ( attenuation / 64 ) / sound_nominal_clip_dist ;
FMOD_Channel_SetUserData ( channel , userdata ) ;
FMOD_Channel_SetUserData ( channel , userdata ) ;
FMOD_Channel_SetCallback ( channel , & SND_FMOD_Callback ) ;
/ / Add a random delay so that similar sounds don ' t phase together
/ / Add a random delay so that similar sounds don ' t phase together
/ / Note : this isn ' t really authentic to the original Quake sound system , but it does improve the sense of directionality
/ / Note : this isn ' t really authentic to the original Quake sound system , but it does improve the sense of directionality
FMOD_ChannelGroup_GetDSPClock ( sfx_channelGroup , & dspclock , NULL ) ;
FMOD_ChannelGroup_GetDSPClock ( sfx_channelGroup , & dspclock , NULL ) ;
FMOD_Channel_SetDelay ( channel , dspclock + SND_GetDelay ( sfx ) , 0 , 0 ) ;
FMOD_Channel_SetDelay ( channel , dspclock + SND_GetDelay ( sfx , 0.2f ) , 0 , 0 ) ;
FMOD_Channel_SetPaused ( channel , 0 ) ;
FMOD_Channel_SetPaused ( channel , 0 ) ;
@ -296,11 +317,28 @@ void S_StaticSound(sfx_t *sfx, vec3_t origin, float vol, float attenuation) // N
void S_StopSound ( int entnum , int entchannel )
void S_StopSound ( int entnum , int entchannel )
{
{
soundslot_t * slot ;
if ( ! fmod_system )
return ;
if ( entnum < 0 | | entnum > = MAX_CHANNELS | | entchannel < 0 | | entchannel > 7 )
return ;
slot = & entsounds [ entnum ] . slots [ entchannel ] ;
if ( slot - > channel )
{
FMOD_Channel_Stop ( slot - > channel ) ;
}
}
}
void S_StopAllSounds ( qboolean clear )
void S_StopAllSounds ( qboolean clear )
{
{
if ( ! fmod_system )
return ;
/ / TODO Use this to remove per - entity channel group and clear all userdata ? = > should already be handled by Stop ( but double check ! )
FMOD_ChannelGroup_Stop ( sfx_channelGroup ) ;
FMOD_ChannelGroup_Stop ( sfx_channelGroup ) ;
if ( clear )
if ( clear )
@ -309,7 +347,8 @@ void S_StopAllSounds(qboolean clear)
void S_ClearBuffer ( void )
void S_ClearBuffer ( void )
{
{
/ / TODO Use this to remove per - entity channel group and clear all userdata ?
/ / This is meant to prevent the same sound buffer playing over and over while the game is stalled
/ / I don ' t think that ' s really an issue with FMOD
}
}
void S_Update ( vec3_t origin , vec3_t forward , vec3_t right , vec3_t up )
void S_Update ( vec3_t origin , vec3_t forward , vec3_t right , vec3_t up )
@ -405,8 +444,6 @@ sfxcache_t *S_LoadSound(sfx_t *s)
exinfo . cbsize = sizeof ( FMOD_CREATESOUNDEXINFO ) ;
exinfo . cbsize = sizeof ( FMOD_CREATESOUNDEXINFO ) ;
exinfo . length = len ;
exinfo . length = len ;
/ / System : : createSound with FMOD_3D ( sfx_t will need a pointer to FMOD_Sound )
/ / Might need to set rate and width ? FMOD can probably figure this out by itself based on the WAV file contents
result = FMOD_System_CreateSound ( fmod_system , ( const char * ) data , FMOD_3D | FMOD_OPENMEMORY , & exinfo , & s - > sound ) ;
result = FMOD_System_CreateSound ( fmod_system , ( const char * ) data , FMOD_3D | FMOD_OPENMEMORY , & exinfo , & s - > sound ) ;
if ( result ! = FMOD_OK )
if ( result ! = FMOD_OK )
{
{
@ -427,7 +464,8 @@ sfxcache_t *S_LoadSound(sfx_t *s)
void S_TouchSound ( const char * sample )
void S_TouchSound ( const char * sample )
{
{
/ / Move the sound data up into the CPU cache
/ / Not really necessary here
}
}
static const char * FMOD_SpeakerModeString ( FMOD_SPEAKERMODE speakermode )
static const char * FMOD_SpeakerModeString ( FMOD_SPEAKERMODE speakermode )