@ -8,17 +8,35 @@ extern qboolean sound_started; // in snd_dma.c
FMOD_SYSTEM * fmod_system = NULL ;
static qboolean fmod_ownership = false ;
static int fmod_samplerate ;
static float old_volume = - 1.0f ;
static FMOD_CHANNELGROUP * sfx_channelGroup = NULL ;
static const char * FMOD_SpeakerModeString ( FMOD_SPEAKERMODE speakermode ) ;
static float SND_FMOD_Attenuation ( FMOD_CHANNELCONTROL * channelControl , float distance ) ;
/ / Copy and convert coordinate system
# define FMOD_VectorCopy(a, b) {(b).x=(a)[0];(b).y=(a)[2];(b).z=(a)[1];}
# define sound_nominal_clip_dist 1000.0
typedef struct entchannel_s
{
sfx_t * sfx ;
int entnum ;
int entchannel ;
float dist_mult ;
FMOD_CHANNEL * channel ;
} entchannel_t ;
static entchannel_t entchannels [ MAX_CHANNELS ] ;
void S_Startup ( void )
{
FMOD_RESULT result ;
FMOD_SPEAKERMODE speakermode ;
unsigned int version ;
int driver , systemrate , numchannels ;
int driver , numchannels ;
char name [ 1024 ] ;
/ / Create FMOD System if it doesn ' t exist already
@ -48,7 +66,7 @@ void S_Startup(void)
return ;
}
result = FMOD_System_GetDriverInfo ( fmod_system , driver , name , sizeof ( name ) , NULL , & system rate, & speakermode , & numchannels ) ;
result = FMOD_System_GetDriverInfo ( fmod_system , driver , name , sizeof ( name ) , NULL , & fmod_sample rate, & speakermode , & numchannels ) ;
if ( result ! = FMOD_OK )
{
Con_Printf ( " Failed to retrieve FMOD driver info: %s \n " , FMOD_ErrorString ( result ) ) ;
@ -56,7 +74,7 @@ void S_Startup(void)
}
Con_Printf ( " FMOD version %01x.%02x.%02x, driver '%s', %s speaker mode, %d Hz, %d channels \n " ,
( version > > 16 ) & 0xff , ( version > > 8 ) & 0xff , version & 0xff , name , FMOD_SpeakerModeString ( speakermode ) , system rate, numchannels ) ;
( version > > 16 ) & 0xff , ( version > > 8 ) & 0xff , version & 0xff , name , FMOD_SpeakerModeString ( speakermode ) , fmod_sample rate, numchannels ) ;
result = FMOD_System_CreateChannelGroup ( fmod_system , " SFX " , & sfx_channelGroup ) ;
if ( result ! = FMOD_OK )
@ -67,6 +85,10 @@ void S_Startup(void)
/ / Could use System : : set3DRolloffCallback to set up a ( attenuation / sound_nominal_clip_dist ) distance multiplier ( would need to use ChannelControl : : setUserData to hold ref to attn value )
/ / Note : sound_nominal_clip_dist could be dynamic to allow a small sound ' bubble ' for local multiplayer
FMOD_ChannelGroup_Set3DMinMaxDistance ( sfx_channelGroup , 0.0f , sound_nominal_clip_dist ) ;
FMOD_System_Set3DRolloffCallback ( fmod_system , & SND_FMOD_Attenuation ) ;
memset ( entchannels , 0 , sizeof ( entchannels ) ) ;
sound_started = true ;
}
@ -86,29 +108,183 @@ void S_Shutdown(void)
}
}
static unsigned int SND_GetDelay ( const sfx_t * sfx )
{
unsigned int delay ;
delay = 0.1 * fmod_samplerate ;
if ( delay > sfx - > samples )
delay = sfx - > samples ;
if ( delay > 0 )
delay = rand ( ) % delay ;
return delay ;
}
static float SND_FMOD_Attenuation ( FMOD_CHANNELCONTROL * channelcontrol , float distance )
{
FMOD_RESULT result ;
entchannel_t * userdata ;
float scale ;
result = FMOD_Channel_GetUserData ( ( FMOD_CHANNEL * ) channelcontrol , & userdata ) ;
if ( result ! = FMOD_OK | | ! userdata )
{
/ / Unknown type of channel or maybe it ' s a channel group , either way just return full volume in this case
return 1.0f ;
}
scale = 1.0f - ( distance * userdata - > dist_mult ) ;
if ( scale < 0.0f )
scale = 0.0f ;
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 ;
/ / }
void S_StartSound ( int entnum , int entchannel , sfx_t * sfx , vec3_t origin , float fvol , float attenuation )
{
FMOD_CHANNEL * channel ;
FMOD_VECTOR position ;
FMOD_RESULT result ;
if ( ! fmod_system | | ! sfx )
return ;
/ / TODO : check nosound cvar
return ; / / HACK play no entity sfx for now
S_LoadSound ( sfx ) ;
if ( ! sfx - > sound )
return ;
/ / Find channel group for entity number ( or create ) = > note entnum can also be some random hash value
/ / ChannelControl : : setMode = > set to 3 D
/ / 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 )
/ / If channel at index entchannel > 0 is already playing : stop
/ / Set userdata pointer to entchannel_t so we can check if the FMOD channel still belongs to this entity & entchannel , before checking if it ' s already playing
/ / Special case entchannel - 1 = > just play locally on listener , no 3 D
/ / System : : playSound with paused = true
result = FMOD_System_PlaySound ( fmod_system , sfx - > sound , sfx_channelGroup , 1 , & channel ) ;
if ( result ! = FMOD_OK )
{
Con_Printf ( " Failed to play FMOD sound: %s \n " , FMOD_ErrorString ( result ) ) ;
return ;
}
/ / ChannelControl : : set3DAttributes
FMOD_VectorCopy ( origin , position ) ;
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_SetVolume ( channel , fvol ) ;
FMOD_Channel_Set3DLevel ( channel , entchannel < 0 ? 0.0f : 1.0f ) ; / / entchannel - 1 is used for local sounds
/ / TODO : attenuation
/ / 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
/ / ChannelControl : : setPaused = false
FMOD_Channel_SetPaused ( channel , 0 ) ;
/ / Store channel handle at position entchannel
}
void S_StaticSound ( sfx_t * sfx , vec3_t origin , float vol , float attenuation )
/ / 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
{
FMOD_CHANNEL * channel ;
FMOD_VECTOR position ;
FMOD_RESULT result ;
unsigned long long dspclock ;
/ / Similar to above , but without the per - entity channel group song and dance
/ / Check if sound is looped ( should be ) and FMOD_Channel_SetMode to looped
/ / Set Channel : : setLoopPoints if the sfxcache specifies something non - standard
if ( ! fmod_system | | ! sfx )
return ;
S_LoadSound ( sfx ) ;
if ( ! sfx - > sound )
return ;
result = FMOD_System_PlaySound ( fmod_system , sfx - > sound , sfx_channelGroup , 1 , & channel ) ;
if ( result ! = FMOD_OK )
{
Con_Printf ( " Failed to play static FMOD sound: %s \n " , FMOD_ErrorString ( result ) ) ;
return ;
}
FMOD_VectorCopy ( origin , position ) ;
FMOD_Channel_Set3DAttributes ( channel , & position , NULL ) ;
FMOD_Channel_SetMode ( channel , FMOD_LOOP_NORMAL ) ;
FMOD_Channel_SetVolume ( channel , vol / 255 ) ;
/ / TODO : attenuation ( which is pre - multiplied by 64 )
entchannel_t * userdata = ( entchannel_t * ) malloc ( sizeof ( entchannel_t ) ) ; / / This is rather convenient . Can we use Z_Malloc here perhaps ?
userdata - > sfx = sfx ;
userdata - > channel = channel ;
userdata - > dist_mult = ( attenuation / 64 ) / sound_nominal_clip_dist ;
FMOD_Channel_SetUserData ( channel , userdata ) ;
/ / Add a random delay so that similar sounds don ' t phase together ( note : this isn ' t really authentic to original Quake , but it improves the sense of directionality )
FMOD_ChannelGroup_GetDSPClock ( sfx_channelGroup , & dspclock , NULL ) ;
FMOD_Channel_SetDelay ( channel , dspclock + SND_GetDelay ( sfx ) , 0 , 0 ) ;
FMOD_Channel_SetPaused ( channel , 0 ) ;
/ / Note : we can forget about the channel we just created , because all SFX channels will be stopped and released on level change through S_StopAllSounds
}
void S_StopSound ( int entnum , int entchannel )
@ -118,7 +294,10 @@ void S_StopSound(int entnum, int entchannel)
void S_StopAllSounds ( qboolean clear )
{
FMOD_ChannelGroup_Stop ( sfx_channelGroup ) ;
if ( clear )
S_ClearBuffer ( ) ;
}
void S_ClearBuffer ( void )
@ -128,7 +307,28 @@ void S_ClearBuffer(void)
void S_Update ( vec3_t origin , vec3_t forward , vec3_t right , vec3_t up )
{
/ / System : : set3DListenerAttributes
if ( ! fmod_system )
return ;
if ( old_volume ! = sfxvolume . value )
{
if ( sfxvolume . value < 0 )
Cvar_SetQuick ( & sfxvolume , " 0 " ) ;
else if ( sfxvolume . value > 1 )
Cvar_SetQuick ( & sfxvolume , " 1 " ) ;
old_volume = sfxvolume . value ;
}
FMOD_VECTOR fmod_pos , fmod_forward , fmod_up ;
FMOD_VectorCopy ( origin , fmod_pos ) ;
FMOD_VectorCopy ( forward , fmod_forward ) ;
FMOD_VectorCopy ( up , fmod_up ) ;
/ / TODO : set listener number based on player ID ( for split - screen )
FMOD_System_Set3DListenerAttributes ( fmod_system , 0 , & fmod_pos , NULL , & fmod_forward , & fmod_up ) ;
FMOD_ChannelGroup_SetVolume ( sfx_channelGroup , sfxvolume . value ) ;
FMOD_System_Update ( fmod_system ) ;
}
@ -170,7 +370,6 @@ sfxcache_t *S_LoadSound(sfx_t *s)
FMOD_SOUND_TYPE type ;
FMOD_SOUND_FORMAT format ;
int channels , bits , loopcount ;
unsigned int length ;
# endif
if ( ! fmod_system )
@ -208,11 +407,12 @@ sfxcache_t *S_LoadSound(sfx_t *s)
return NULL ;
}
FMOD_Sound_GetLength ( s - > sound , & s - > samples , FMOD_TIMEUNIT_PCM ) ;
# if _DEBUG
FMOD_Sound_GetFormat ( s - > sound , & type , & format , & channels , & bits ) ;
FMOD_Sound_GetLength ( s - > sound , & length , FMOD_TIMEUNIT_MS ) ;
FMOD_Sound_GetLoopCount ( s - > sound , & loopcount ) ;
Con_DPrintf ( " [FMOD] Loaded sound '%s': type %d, format %d, %d channel(s), %d bits, %d ms, loopcount = %d \n " , s - > name , type , format , channels , bits , length , loopcount ) ;
Con_DPrintf ( " [FMOD] Loaded sound '%s': type %d, format %d, %d channel(s), %d bits, %d sa mple s, loopcount = %d \n " , s - > name , type , format , channels , bits , s - > samples , loopcount ) ;
# endif
return NULL ; / / Return value is unused ; FMOD has its own internal cache , we never need to use Quake ' s sfxcache_t