You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

709 lines
18 KiB

#include "quakedef.h"
#ifdef USE_FMOD
#include "fmod.h"
#include "fmod_errors.h"
extern qboolean sound_started; // in snd_dma.c
extern sfx_t *known_sfx;
extern int num_sfx;
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;
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];}
#define sound_nominal_clip_dist 1000.0
typedef struct soundslot_s
{
qboolean zone;
FMOD_CHANNEL *channel;
float dist_mult;
} soundslot_t;
typedef struct entsounds_s
{
soundslot_t slots[8];
} 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];
static int numSfxThisFrame;
void S_Startup(void)
{
FMOD_RESULT result;
FMOD_SPEAKERMODE speakermode;
unsigned int version;
int driver, numchannels;
char name[1024];
// Create FMOD System if it doesn't exist already
if (!fmod_system)
{
result = FMOD_System_Create(&fmod_system);
if (result != FMOD_OK)
{
Con_Printf("Failed to create FMOD System: %s\n", FMOD_ErrorString(result));
return;
}
result = FMOD_System_SetSoftwareChannels(fmod_system, MAX_DYNAMIC_CHANNELS);
if (result != FMOD_OK)
{
Con_Printf("Failed to set number of FMOD software channels: %s\n", FMOD_ErrorString(result));
return;
}
result = FMOD_System_Init(fmod_system, MAX_CHANNELS, FMOD_INIT_VOL0_BECOMES_VIRTUAL, NULL);
if (result != FMOD_OK)
{
Con_Printf("Failed to initialize FMOD System: %s\n", FMOD_ErrorString(result));
return;
}
fmod_ownership = true;
}
result = FMOD_System_GetVersion(fmod_system, &version);
if (result != FMOD_OK)
{
Con_Printf("Failed to retrieve FMOD version: %s\n", FMOD_ErrorString(result));
return;
}
if (version < FMOD_VERSION)
{
Con_Printf("Incorrect FMOD library version, expected: 0x%x, found: 0x%x", FMOD_VERSION, version);
return;
}
result = FMOD_System_GetDriver(fmod_system, &driver);
if (result != FMOD_OK)
{
Con_Printf("Failed to retrieve selected FMOD driver: %s\n", FMOD_ErrorString(result));
return;
}
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));
return;
}
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), fmod_samplerate, numchannels);
result = FMOD_System_CreateChannelGroup(fmod_system, "SFX", &sfx_channelGroup);
if (result != FMOD_OK)
{
Con_Printf("Failed to create FMOD SFX channel group: %s\n", FMOD_ErrorString(result));
return;
}
// Set up custom distance attenuation system
FMOD_ChannelGroup_Set3DMinMaxDistance(sfx_channelGroup, 0.0f, sound_nominal_clip_dist);
FMOD_System_Set3DRolloffCallback(fmod_system, &SND_FMOD_Attenuation);
memset(entsounds, 0, sizeof(entsounds));
memset(sfxThisFrame, 0, sizeof(sfxThisFrame));
numSfxThisFrame = 0;
sound_started = true;
}
void S_Shutdown(void)
{
sfx_t *sfx;
int i;
Con_DPrintf("[FMOD] Shutdown\n");
S_StopAllSounds(false);
// Release all sounds that were loaded and attached to sfx_t's
for (i = 0; i < num_sfx; i++)
{
sfx = &known_sfx[i];
if (sfx->sound)
{
FMOD_Sound_Release(sfx->sound);
sfx->sound = NULL;
}
}
if (sfx_channelGroup)
{
FMOD_ChannelGroup_Release(sfx_channelGroup);
sfx_channelGroup = NULL;
}
// If we created the FMOD System (and consequently own it), destroy it here
if (fmod_ownership)
{
FMOD_System_Close(fmod_system);
fmod_system = NULL;
fmod_ownership = false;
}
}
static unsigned int SND_GetDelay(const sfx_t *sfx, float maxseconds)
{
unsigned int delay;
delay = maxseconds * 1000;
if (delay > sfx->length)
delay = sfx->length;
if (delay > 0)
delay = rand() % delay;
return delay * fmod_samplerate / 1000;
}
/*
====================
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)
{
FMOD_RESULT result;
soundslot_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;
}
/*
=================
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;
}
if (userdata->zone)
{
Z_Free(userdata);
}
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);
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;
unsigned long long dspclock;
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_ChannelGroup_GetDSPClock(sfx_channelGroup, &dspclock, NULL);
FMOD_Channel_SetFadePointRamp(slot->channel, dspclock + 64, 0.0f);
FMOD_Channel_SetMode(slot->channel, FMOD_LOOP_OFF);
slot->channel = NULL;
}
slot->zone = false;
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_RESULT result;
soundslot_t *slot;
unsigned long long dspclock;
if (!fmod_system || !sfx)
return;
if (nosound.value)
return;
S_LoadSound(sfx);
if (!sfx->sound)
return;
// Choose a slot to play the sound on, and stop any conflicting sound on the same entchannel
// Do this before playing the new sound, so that any previous sound will be stopped in time
slot = SND_PickSoundSlot(entnum, entchannel);
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;
}
SND_FMOD_SetChannelAttributes(channel, sfx, origin, fvol);
// Set up callback data for rolloff and cleanup
slot->channel = channel;
slot->dist_mult = attenuation / sound_nominal_clip_dist;
FMOD_Channel_SetUserData(channel, slot);
FMOD_Channel_SetCallback(channel, &SND_FMOD_Callback);
// 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)
{
FMOD_Channel_Set3DLevel(channel, 0.0f);
FMOD_Channel_SetPriority(channel, 64); // Ensure local sounds always get priority over other entities
}
for (i = 0; i < numSfxThisFrame; i++)
{
// if an identical sound has also been started this frame, offset the pos
// a bit to keep it from just making the first one louder
if (sfxThisFrame[i] == sfx)
{
FMOD_ChannelGroup_GetDSPClock(sfx_channelGroup, &dspclock, NULL);
FMOD_Channel_SetDelay(channel, dspclock + SND_GetDelay(sfx, 0.1f), 0, 0);
break;
}
}
if (numSfxThisFrame < 16)
sfxThisFrame[numSfxThisFrame++] = sfx;
FMOD_Channel_SetPaused(channel, 0);
}
/*
=============
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_RESULT result;
soundslot_t *slot;
unsigned long long dspclock;
if (!fmod_system || !sfx)
return;
S_LoadSound(sfx);
if (!sfx->sound)
return;
if (sfx->loopstart < 0)
{
Con_Printf("Sound %s not looped\n", sfx->name);
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;
}
SND_FMOD_SetChannelAttributes(channel, sfx, origin, vol / 255);
// Set up attenuation info for use by the rolloff callback
slot = SND_PickSoundSlot(-1, -1);
slot->channel = channel;
slot->dist_mult = (attenuation / 64) / sound_nominal_clip_dist;
FMOD_Channel_SetUserData(channel, slot);
FMOD_Channel_SetCallback(channel, &SND_FMOD_Callback);
// 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
FMOD_ChannelGroup_GetDSPClock(sfx_channelGroup, &dspclock, NULL);
FMOD_Channel_SetDelay(channel, dspclock + SND_GetDelay(sfx, 0.2f), 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)
{
soundslot_t *slot;
unsigned long long dspclock;
if (!fmod_system)
return;
if (entnum < 0 || entnum >= MAX_CHANNELS || entchannel < 0 || entchannel > 7)
return;
slot = &entsounds[entnum].slots[entchannel];
if (slot->channel)
{
// Instead of immediately stopping the sound, we fade it down to 0 volume over several samples and let it play out
// This prevents an annoying popping noise, which is especially noticeable on rapid-fire weapons
FMOD_ChannelGroup_GetDSPClock(sfx_channelGroup, &dspclock, NULL);
FMOD_Channel_SetFadePointRamp(slot->channel, dspclock + 64, 0.0f);
FMOD_Channel_SetMode(slot->channel, FMOD_LOOP_OFF);
slot->channel = NULL;
}
}
void S_StopAllSounds(qboolean clear)
{
if (!fmod_system)
return;
// Stopping all sounds also ensures that any associated zone memory is freed
FMOD_ChannelGroup_Stop(sfx_channelGroup);
if (clear) // We're abusing the clear flag to also mean "keep ambients alive"
{
S_ClearBuffer();
// Ambient sounds need to be restarted, as they are always "playing"
SND_StartAmbientSounds();
}
}
void S_ClearBuffer(void)
{
// 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)
{
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;
}
VectorCopy(origin, listener_origin);
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);
S_UpdateAmbientSounds();
FMOD_System_Update(fmod_system);
// Reset sounds played for the next frame
memset(sfxThisFrame, 0, sizeof(sfxThisFrame));
numSfxThisFrame = 0;
}
void S_ExtraUpdate(void)
{
if (!fmod_system)
return;
FMOD_System_Update(fmod_system);
}
static void S_SetMasterMute(FMOD_BOOL mute)
{
FMOD_CHANNELGROUP *master;
if (!fmod_system)
return;
FMOD_System_GetMasterChannelGroup(fmod_system, &master);
FMOD_ChannelGroup_SetMute(master, mute);
}
void S_BlockSound(void)
{
S_SetMasterMute(1);
}
void S_UnblockSound(void)
{
S_SetMasterMute(0);
}
sfxcache_t *S_LoadSound(sfx_t *s)
{
char namebuffer[256];
byte *data;
wavinfo_t info;
FMOD_CREATESOUNDEXINFO exinfo;
FMOD_RESULT result;
#if _DEBUG
FMOD_SOUND_TYPE type;
FMOD_SOUND_FORMAT format;
int channels, bits;
#endif
if (!fmod_system)
return NULL;
// Check if it's already loaded
if (s->sound)
return NULL;
q_strlcpy(namebuffer, "sound/", sizeof(namebuffer));
q_strlcat(namebuffer, s->name, sizeof(namebuffer));
data = COM_LoadHunkFile(namebuffer, NULL);
if (!data)
{
Con_Printf("Couldn't load %s\n", namebuffer);
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 = com_filesize;
result = FMOD_System_CreateSound(fmod_system, (const char*)data, FMOD_3D | FMOD_OPENMEMORY, &exinfo, &s->sound);
if (result != FMOD_OK)
{
Con_Printf("Failed to create FMOD sound: %s\n", FMOD_ErrorString(result));
return NULL;
}
// 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);
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
}
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)
{
switch (speakermode)
{
case FMOD_SPEAKERMODE_MONO:
return "Mono";
case FMOD_SPEAKERMODE_STEREO:
return "Stereo";
case FMOD_SPEAKERMODE_QUAD:
return "4.0 Quad";
case FMOD_SPEAKERMODE_SURROUND:
return "5.0 Surround";
case FMOD_SPEAKERMODE_5POINT1:
return "5.1 Surround";
case FMOD_SPEAKERMODE_7POINT1:
return "7.1 Surround";
case FMOD_SPEAKERMODE_7POINT1POINT4:
return "7.1.4 Surround";
default:
return "Unknown";
}
}
#endif // USE_FMOD