From e928833f5ec31b01ab2c1e7d2f8f5f35ff5f6f6c Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Tue, 13 Apr 2021 18:06:46 +0200 Subject: [PATCH] Implemented proper looping of sounds based on cue information from the WAV file header. Also modified Delay function so that it is unaffected by differences in sample rate between the input file and FMOD's mixer. --- engine/Quake/q_sound.h | 4 +++- engine/Quake/snd_fmod.c | 52 ++++++++++++++++++++++++++++------------- engine/Quake/snd_mem.c | 3 +-- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/engine/Quake/q_sound.h b/engine/Quake/q_sound.h index ca7a385..f9f5018 100644 --- a/engine/Quake/q_sound.h +++ b/engine/Quake/q_sound.h @@ -42,7 +42,9 @@ typedef struct sfx_s cache_user_t cache; #if USE_FMOD FMOD_SOUND *sound; - unsigned int samples; + unsigned int length; // milliseconds + int loopstart; + int loopend; #endif } sfx_t; diff --git a/engine/Quake/snd_fmod.c b/engine/Quake/snd_fmod.c index 64b2267..36bd089 100644 --- a/engine/Quake/snd_fmod.c +++ b/engine/Quake/snd_fmod.c @@ -152,13 +152,13 @@ static unsigned int SND_GetDelay(const sfx_t *sfx, float maxseconds) { unsigned int delay; - delay = maxseconds * fmod_samplerate; - if (delay > sfx->samples) - delay = sfx->samples; + delay = maxseconds * 1000; + if (delay > sfx->length) + delay = sfx->length; if (delay > 0) delay = rand() % delay; - return delay; + return delay * fmod_samplerate / 1000; } /* @@ -285,8 +285,17 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f 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); + + 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) @@ -442,13 +451,13 @@ sfxcache_t *S_LoadSound(sfx_t *s) { char namebuffer[256]; byte *data; - int len, h; + wavinfo_t info; FMOD_CREATESOUNDEXINFO exinfo; FMOD_RESULT result; #if _DEBUG FMOD_SOUND_TYPE type; FMOD_SOUND_FORMAT format; - int channels, bits, loopcount; + int channels, bits; #endif if (!fmod_system) @@ -461,11 +470,6 @@ sfxcache_t *S_LoadSound(sfx_t *s) q_strlcpy(namebuffer, "sound/", sizeof(namebuffer)); q_strlcat(namebuffer, s->name, sizeof(namebuffer)); - // We need to briefly open the file to figure out its length, which FMOD needs to know - len = COM_OpenFile(namebuffer, &h, NULL); - if (h >= 0) - COM_CloseFile(h); - data = COM_LoadHunkFile(namebuffer, NULL); if (!data) { @@ -473,9 +477,16 @@ sfxcache_t *S_LoadSound(sfx_t *s) return NULL; } + info = GetWavinfo(s->name, data, com_filesize); + if (!info.channels) + { + Con_Printf("Invalid WAV file: %s\n", namebuffer); + return NULL; + } + memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO)); exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO); - exinfo.length = len; + exinfo.length = com_filesize; result = FMOD_System_CreateSound(fmod_system, (const char*)data, FMOD_3D | FMOD_OPENMEMORY, &exinfo, &s->sound); if (result != FMOD_OK) @@ -484,12 +495,21 @@ sfxcache_t *S_LoadSound(sfx_t *s) return NULL; } - FMOD_Sound_GetLength(s->sound, &s->samples, FMOD_TIMEUNIT_PCM); + // Collect data required for looping and delay + if (info.loopstart >= 0) + { + s->loopstart = info.loopstart * 1000 / info.rate; + s->loopend = info.samples * 1000 / info.rate; + } + else + { + s->loopstart = s->loopend = -1; + } + FMOD_Sound_GetLength(s->sound, &s->length, FMOD_TIMEUNIT_MS); #if _DEBUG FMOD_Sound_GetFormat(s->sound, &type, &format, &channels, &bits); - FMOD_Sound_GetLoopCount(s->sound, &loopcount); - Con_DPrintf("[FMOD] Loaded sound '%s': type %d, format %d, %d channel(s), %d bits, %d samples, loopcount = %d\n", s->name, type, format, channels, bits, s->samples, loopcount); + Con_DPrintf("[FMOD] Loaded sound '%s': type %d, format %d, %d channel(s), %d bits, %d ms, %d samples, loopstart = %d\n", s->name, type, format, channels, bits, s->length, info.samples, s->loopstart); #endif return NULL; // Return value is unused; FMOD has its own internal cache, we never need to use Quake's sfxcache_t diff --git a/engine/Quake/snd_mem.c b/engine/Quake/snd_mem.c index e46c76c..e444d21 100644 --- a/engine/Quake/snd_mem.c +++ b/engine/Quake/snd_mem.c @@ -162,7 +162,7 @@ sfxcache_t *S_LoadSound (sfx_t *s) return sc; } - +#endif // USE_FMOD /* =============================================================================== @@ -352,4 +352,3 @@ wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength) return info; } -#endif // USE_FMOD