Browse Source

WIP FMOD sound system:

- Update listener position and SFX volume
- Implemented static looping sounds, with an attenuation algorithm mimicking the one from Quake
- First bits of entity sound playback, which needs some additional thought first
console
Nico de Poel 5 years ago
parent
commit
fa4183ac30
  1. 3
      engine/Quake/q_sound.h
  2. 216
      engine/Quake/snd_fmod.c

3
engine/Quake/q_sound.h

@ -26,7 +26,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define __QUAKE_SOUND__
#if USE_FMOD
#include "fmod.h"
typedef struct FMOD_SOUND FMOD_SOUND;
#endif
/* !!! if this is changed, it must be changed in asm_i386.h too !!! */
@ -42,6 +42,7 @@ typedef struct sfx_s
cache_user_t cache;
#if USE_FMOD
FMOD_SOUND *sound;
unsigned int samples;
#endif
} sfx_t;

216
engine/Quake/snd_fmod.c

@ -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, &systemrate, &speakermode, &numchannels);
result = FMOD_System_GetDriverInfo(fmod_system, driver, name, sizeof(name), NULL, &fmod_samplerate, &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), systemrate, numchannels);
(version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff, name, FMOD_SpeakerModeString(speakermode), fmod_samplerate, 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 3D
// 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 3D
// 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 samples, 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

Loading…
Cancel
Save