From 9806b057c77866160cc8d6ef80cd3cc0cb085f49 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Tue, 13 Apr 2021 22:38:09 +0200 Subject: [PATCH] Implemented ambient sounds, did some refactoring and added explanations about the different types of sounds --- engine/Quake/snd_fmod.c | 165 ++++++++++++++++++++++++++++++++++------ 1 file changed, 142 insertions(+), 23 deletions(-) diff --git a/engine/Quake/snd_fmod.c b/engine/Quake/snd_fmod.c index 70ad1fd..74f3a2c 100644 --- a/engine/Quake/snd_fmod.c +++ b/engine/Quake/snd_fmod.c @@ -15,8 +15,11 @@ static float old_volume = -1.0f; static FMOD_CHANNELGROUP *sfx_channelGroup = NULL; +vec3_t listener_origin; + static const char *FMOD_SpeakerModeString(FMOD_SPEAKERMODE speakermode); static float SND_FMOD_Attenuation(FMOD_CHANNELCONTROL *channelControl, float distance); +static void SND_StartAmbientSounds(); // Copy and convert coordinate system #define FMOD_VectorCopy(a, b) {(b).x=(a)[0];(b).y=(a)[2];(b).z=(a)[1];} @@ -35,6 +38,7 @@ typedef struct entsounds_s } entsounds_t; static entsounds_t entsounds[MAX_CHANNELS]; +static FMOD_CHANNEL *ambients[NUM_AMBIENTS]; // Keep track of all the sounds started each frame static sfx_t *sfxThisFrame[16]; @@ -130,7 +134,7 @@ void S_Shutdown(void) Con_DPrintf("[FMOD] Shutdown\n"); - S_StopAllSounds(true); + S_StopAllSounds(false); // Release all sounds that were loaded and attached to sfx_t's for (i = 0; i < num_sfx; i++) @@ -228,6 +232,26 @@ static FMOD_RESULT SND_FMOD_Callback(FMOD_CHANNELCONTROL *channelcontrol, FMOD_C return FMOD_OK; } +static void SND_FMOD_SetChannelAttributes(FMOD_CHANNEL *channel, sfx_t *sfx, vec3_t origin, float vol) +{ + FMOD_VECTOR position; + + FMOD_VectorCopy(origin, position); + FMOD_Channel_Set3DAttributes(channel, &position, NULL); + FMOD_Channel_SetVolume(channel, vol); + FMOD_Channel_SetVolumeRamp(channel, 1); // This *might* help somewhat with slight audio pops (esp. noticable on the Nailgun) + + if (sfx->loopstart >= 0) + { + FMOD_Channel_SetMode(channel, FMOD_LOOP_NORMAL); + FMOD_Channel_SetLoopPoints(channel, sfx->loopstart, FMOD_TIMEUNIT_MS, sfx->loopend, FMOD_TIMEUNIT_MS); + } + else + { + FMOD_Channel_SetMode(channel, FMOD_LOOP_OFF); + } +} + static soundslot_t *SND_PickSoundSlot(int entnum, int entchannel) { soundslot_t *slot; @@ -255,11 +279,107 @@ static soundslot_t *SND_PickSoundSlot(int entnum, int entchannel) return slot; } +/* +============== +Ambient sounds + +These are sounds that are always present and always playing. They are unaffected by distance or orientation. +Instead they are modulated in volume based on whether the player is inside a world volume that has ambient sound levels set. +============== +*/ +static FMOD_CHANNEL *SND_StartAmbientSound(const char *samplename) +{ + sfx_t *sfx; + FMOD_CHANNEL *channel; + FMOD_RESULT result; + + sfx = S_PrecacheSound(samplename); + if (!sfx) + return NULL; + + S_LoadSound(sfx); + if (!sfx->sound) + return NULL; + + result = FMOD_System_PlaySound(fmod_system, sfx->sound, sfx_channelGroup, 1, &channel); + if (result != FMOD_OK) + { + Con_Printf("Failed to play ambient FMOD sound: %s\n", FMOD_ErrorString(result)); + return NULL; + } + + SND_FMOD_SetChannelAttributes(channel, sfx, vec3_origin, 0.0f); + FMOD_Channel_Set3DLevel(channel, 0.0f); + FMOD_Channel_SetPaused(channel, 0); + + return channel; +} + +static void SND_StartAmbientSounds() +{ + memset(ambients, 0, sizeof(ambients)); + ambients[AMBIENT_WATER] = SND_StartAmbientSound("ambience/water1.wav"); + ambients[AMBIENT_SKY] = SND_StartAmbientSound("ambience/wind2.wav"); +} + +static void S_UpdateAmbientSounds() +{ + FMOD_CHANNEL *channel; + mleaf_t *leaf; + float vol, channel_vol; + + if (cl.worldmodel && cl.worldmodel->nodes) + leaf = Mod_PointInLeaf(listener_origin, cl.worldmodel); + else + leaf = NULL; + + for (int i = 0; i < NUM_AMBIENTS; i++) + { + channel = ambients[i]; + if (!channel) + continue; + + if (!leaf || !ambient_level.value) + { + FMOD_Channel_SetVolume(channel, 0.0f); + continue; + } + + vol = (ambient_level.value * (float)leaf->ambient_sound_level[i]) / 255; + if (vol < 0.03f) + vol = 0.0f; + + // don't adjust volume too fast + FMOD_Channel_GetVolume(channel, &channel_vol); + if (channel_vol < vol) + { + channel_vol += host_frametime * (ambient_fade.value / 255); + if (channel_vol > vol) + channel_vol = vol; + } + else if (channel_vol > vol) + { + channel_vol -= host_frametime * (ambient_fade.value / 255); + if (channel_vol < vol) + channel_vol = vol; + } + + FMOD_Channel_SetVolume(channel, channel_vol); + } +} + +/* +============= +Entity sounds + +These are your typical 3D sounds generated by entities in the world, including the player. +Each entity has a number of preset voice channels, each of which can only play one sound at a time. +============= +*/ 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 { int i; FMOD_CHANNEL *channel; - FMOD_VECTOR position; FMOD_RESULT result; soundslot_t *userdata; unsigned long long dspclock; @@ -285,26 +405,14 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f return; } + SND_FMOD_SetChannelAttributes(channel, sfx, origin, fvol); + // 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_Channel_Set3DAttributes(channel, &position, NULL); - FMOD_Channel_SetVolume(channel, fvol); - - if (sfx->loopstart >= 0) - { - FMOD_Channel_SetMode(channel, FMOD_LOOP_NORMAL); - FMOD_Channel_SetLoopPoints(channel, sfx->loopstart, FMOD_TIMEUNIT_MS, sfx->loopend, FMOD_TIMEUNIT_MS); - } - else - { - FMOD_Channel_SetMode(channel, FMOD_LOOP_OFF); - } - // Anything coming from the view entity will always be full volume, and entchannel -1 is used for local sounds (e.g. menu sounds) if (entchannel < 0 || entnum == cl.viewentity) { @@ -330,12 +438,17 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f FMOD_Channel_SetPaused(channel, 0); } -// TODO: we're still missing Automatic Ambient sounds for water, lava, slime etc (see S_UpdateAmbientSounds) +/* +============= +Static sounds +These are sounds that have a fixed position in the world and loop continuously. +They typically start playing immediately on level load. +============= +*/ 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; soundslot_t *userdata; unsigned long long dspclock; @@ -354,11 +467,8 @@ void S_StaticSound(sfx_t *sfx, vec3_t origin, float vol, float attenuation) // N return; } - FMOD_VectorCopy(origin, position); - FMOD_Channel_Set3DAttributes(channel, &position, NULL); - FMOD_Channel_SetMode(channel, FMOD_LOOP_NORMAL); - FMOD_Channel_SetVolume(channel, vol / 255); - + SND_FMOD_SetChannelAttributes(channel, sfx, origin, vol / 255); + // Set up attenuation info for use by the rolloff callback userdata = SND_PickSoundSlot(-1, -1); userdata->channel = channel; @@ -402,7 +512,12 @@ void S_StopAllSounds(qboolean clear) FMOD_ChannelGroup_Stop(sfx_channelGroup); if (clear) + { S_ClearBuffer(); + + // Ambient sounds need to be restarted, as they are always "playing" + SND_StartAmbientSounds(); + } } void S_ClearBuffer(void) @@ -426,6 +541,8 @@ void S_Update(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) old_volume = sfxvolume.value; } + VectorCopy(origin, listener_origin); + FMOD_VECTOR fmod_pos, fmod_forward, fmod_up; FMOD_VectorCopy(origin, fmod_pos); FMOD_VectorCopy(forward, fmod_forward); @@ -436,6 +553,8 @@ void S_Update(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) FMOD_ChannelGroup_SetVolume(sfx_channelGroup, sfxvolume.value); + S_UpdateAmbientSounds(); + FMOD_System_Update(fmod_system); // Reset sounds played for the next frame