FAudioGMS/src/FAudioGMS.c

2101 lines
61 KiB
C

/* FAudioGMS - Game Maker FAudio bindings in C
*
* Copyright (c) 2021 Evan Hemsley
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* Evan "cosmonaut" Hemsley <evan@moonside.games>
*
*/
#include "SDL.h"
#define DISABLE_XNASONG
#include "FAudioGMS.h"
#include "F3DAudio.h"
#include "FAPOBase.h"
#include "FAudioFX.h"
#include "FAudio.h"
#define DR_WAV_IMPLEMENTATION
#define DRWAV_MALLOC(sz) SDL_malloc((sz))
#define DRWAV_REALLOC(p, sz) SDL_realloc((p), (sz))
#define DRWAV_FREE(p) SDL_free((p))
#define DRWAV_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz))
#define DRWAV_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz))
#include "../lib/dr_wav.h"
/* stb vorbis defines. this is kind of a mess but oh well */
#include "../lib/FAudio/src/FAudio_internal.h"
#define malloc FAudio_malloc
#define realloc FAudio_realloc
#define free FAudio_free
#ifdef memset /* Thanks, Apple! */
#undef memset
#endif
#define memset SDL_memset
#ifdef memcpy /* Thanks, Apple! */
#undef memcpy
#endif
#define memcpy SDL_memcpy
#define memcmp FAudio_memcmp
#define pow FAudio_pow
#define log(x) FAudio_log(x)
#define sin(x) FAudio_sin(x)
#define cos(x) FAudio_cos(x)
#define floor FAudio_floor
#define abs(x) FAudio_abs(x)
#define ldexp(v, e) FAudio_ldexp((v), (e))
#define exp(x) FAudio_exp(x)
#define qsort FAudio_qsort
#ifdef assert
#undef assert
#endif
#define assert FAudio_assert
#define FILE FAudioIOStream
#ifdef SEEK_SET
#undef SEEK_SET
#endif
#ifdef SEEK_END
#undef SEEK_END
#endif
#ifdef EOF
#undef EOF
#endif
#define SEEK_SET FAUDIO_SEEK_SET
#define SEEK_END FAUDIO_SEEK_END
#define EOF FAUDIO_EOF
#define fopen(path, mode) FAudio_fopen(path)
#define fopen_s(io, path, mode) (!(*io = FAudio_fopen(path)))
#define fclose(io) FAudio_close(io)
#define fread(dst, size, count, io) io->read(io->data, dst, size, count)
#define fseek(io, offset, whence) io->seek(io->data, offset, whence)
#define ftell(io) io->seek(io->data, 0, FAUDIO_SEEK_CUR)
#define STB_VORBIS_NO_PUSHDATA_API 1
#define STB_VORBIS_NO_INTEGER_CONVERSION 1
#include "../lib/FAudio/src/stb_vorbis.h"
#include <stdio.h>
static inline void Log(char *string)
{
printf("%s\n", string);
fflush(stdout);
}
typedef struct IdStack
{
uint32_t *array;
uint32_t count;
uint32_t capacity;
} IdStack;
static inline void IdStack_Init(IdStack *stack)
{
stack->array = NULL;
stack->capacity = 0;
stack->count = 0;
}
static inline void IdStack_Push(IdStack *stack, uint32_t id)
{
if (stack->count >= stack->capacity)
{
stack->array = SDL_realloc(stack->array, (stack->capacity + 1) * sizeof(uint32_t));
stack->capacity += 1;
}
stack->array[stack->count] = id;
stack->count += 1;
}
static inline uint32_t IdStack_Pop(IdStack *stack)
{
stack->count -= 1;
return stack->array[stack->count];
}
typedef enum FAudioGMS_SoundState
{
SoundState_Playing,
SoundState_Paused,
SoundState_Stopped
} FAudioGMS_SoundState;
typedef struct FAudioGMS_Voice
{
FAudioVoice *handle;
FAudioVoiceSends sends;
uint8_t effectChainAttached;
FAudioSubmixVoice *effectVoice;
FAudioVoiceSends effectSends;
float effectGain;
} FAudioGMS_Voice;
typedef struct FAudioGMS_StaticSound
{
uint32_t id;
FAudioBuffer buffer;
uint32_t channels;
uint32_t samplesPerSecond;
uint32_t lengthInSeconds;
} FAudioGMS_StaticSound;
typedef struct FAudioGMS_StreamingSound
{
stb_vorbis *fileHandle;
stb_vorbis_info info;
float *streamBuffer;
uint32_t streamBufferSize;
uint32_t mostRecentBufferOffset; /* used for calculating track position */
uint8_t isFinalBuffer; /* used to detect end of playback */
} FAudioGMS_StreamingSound;
typedef struct FAudioGMS_SoundInstance FAudioGMS_SoundInstance;
struct FAudioGMS_SoundInstance
{
uint32_t id;
FAudioGMS_Voice voice;
FAudioWaveFormatEx format;
uint8_t loop; /* bool */
FAudioGMS_SoundState soundState;
F3DAUDIO_DSP_SETTINGS dspSettings;
float pan;
float pitch;
float volume;
float lowPassFilter;
float highPassFilter;
float bandPassFilter;
uint8_t adjustingVolumeOverTime;
float targetVolume;
float volumeDelta;
uint8_t isStatic;
uint8_t destroyOnFinish;
uint8_t is3D;
F3DAUDIO_EMITTER *emitter; /* must not be NULL if is3D */
float stereoAzimuth[2];
uint8_t isGlobalPaused;
uint32_t playBegin;
uint32_t playLength;
FAudioGMS_SoundInstance *queuedSoundInstance;
union
{
FAudioGMS_StaticSound *staticSound; /* static sounds are loaded separately, so they do
not belong to the instance */
FAudioGMS_StreamingSound streamingSound;
} soundData;
};
typedef enum FAudioGMS_EffectType
{
EffectType_Reverb
} FAudioGMS_EffectType;
typedef union FAudioGMS_EffectParameters
{
FAudioFXReverbParameters reverbParameters;
} FAudioGMS_EffectParameters;
typedef struct FAudioGMS_EffectChain
{
uint32_t id;
uint32_t effectCount;
FAudioGMS_EffectType *effectTypes; /* length equal to effectCount */
union FAudioGMS_EffectParameters *effectParameters; /* length equal to effectCount */
} FAudioGMS_EffectChain;
static const float SPEED_OF_SOUND = 343.5f;
static const float DOPPLER_SCALE = 1.0f;
typedef struct FAudioGMS_Device
{
FAudio *handle;
F3DAUDIO_HANDLE handle3D;
FAudioDeviceDetails deviceDetails;
FAudioMasteringVoice *masteringVoice;
FAudioGMS_Voice fauxMasteringVoice;
F3DAUDIO_LISTENER listener;
float spatialDistanceScale;
FAudioVoiceCallback voiceCallbacks;
FAudioGMS_StaticSound **staticSounds;
uint32_t staticSoundCount;
IdStack staticSoundIndexStack;
FAudioGMS_SoundInstance **soundInstances;
uint32_t soundInstanceCount;
IdStack soundInstanceIndexStack;
FAudioGMS_EffectChain **effectChains;
uint32_t effectChainCount;
IdStack effectChainIndexStack;
double timestep;
} FAudioGMS_Device;
static FAudioGMS_Device *device = NULL;
/* Game Maker doesn't let us control execution order on clean up so we have this
* stupid macro to help us not crash on exit */
#define RETURN_ON_NULL_DEVICE(x) \
if (device == NULL) \
{ \
return x; \
}
static inline FAudioGMS_StaticSound *FAudioGMS_INTERNAL_LookupStaticSound(uint32_t id)
{
if (id >= 0 && id < device->staticSoundCount)
{
return device->staticSounds[id];
}
else
{
Log("Invalid StaticSound ID!");
return NULL;
}
}
static inline FAudioGMS_SoundInstance *FAudioGMS_INTERNAL_LookupSoundInstance(uint32_t id)
{
if (id >= 0 && id < device->soundInstanceCount)
{
return device->soundInstances[id];
}
else
{
Log("Invalid SoundInstance ID!");
return NULL;
}
}
static inline FAudioGMS_EffectChain *FAudioGMS_INTERNAL_LookupEffectChain(uint32_t id)
{
if (id >= 0 && id < device->effectChainCount)
{
return device->effectChains[id];
}
else
{
Log("Invalid EffectChain ID!");
return NULL;
}
}
/* Forward declarations to avoid some annoying BS */
static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance *instance);
static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance *instance);
static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *instance);
static void FAudioGMS_INTERNAL_OnBufferEndCallback(
FAudioVoiceCallback *callback,
FAudioGMS_SoundInstance *instance)
{
if (instance->isStatic)
{
FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
}
else
{
if (instance->soundData.streamingSound.isFinalBuffer)
{
FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
}
else
{
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance);
}
}
if (instance->soundState == SoundState_Stopped)
{
if (instance->queuedSoundInstance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_Play(instance->queuedSoundInstance);
instance->queuedSoundInstance = NULL;
}
}
}
void FAudioGMS_Init(double spatialDistanceScale, double timestep)
{
device = SDL_malloc(sizeof(FAudioGMS_Device));
uint32_t result = FAudioCreate(&device->handle, 0, FAUDIO_DEFAULT_PROCESSOR);
if (result != 0)
{
Log("Failed to create device! Bailing!");
SDL_free(device);
device = NULL;
return;
}
/* Find a suitable device */
uint32_t deviceCount;
FAudio_GetDeviceCount(device->handle, &deviceCount);
if (deviceCount == 0)
{
Log("No audio devices found! Bailing!");
FAudio_Release(device->handle);
SDL_free(device);
device = NULL;
return;
}
FAudioDeviceDetails deviceDetails;
uint32_t i = 0;
for (i = 0; i < deviceCount; i += 1)
{
FAudio_GetDeviceDetails(device->handle, i, &deviceDetails);
if ((deviceDetails.Role & FAudioDefaultGameDevice) == FAudioDefaultGameDevice)
{
device->deviceDetails = deviceDetails;
break;
}
}
if (i == deviceCount)
{
i = 0; /* whatever we'll just use the first one i guess */
FAudio_GetDeviceDetails(device->handle, i, &deviceDetails);
device->deviceDetails = deviceDetails;
}
if (FAudio_CreateMasteringVoice(
device->handle,
&device->masteringVoice,
FAUDIO_DEFAULT_CHANNELS,
FAUDIO_DEFAULT_SAMPLERATE,
0,
i,
NULL) != 0)
{
Log("No mastering voice found! Bailing!");
FAudio_Release(device->handle);
SDL_free(device);
device = NULL;
return;
}
device->fauxMasteringVoice.sends.SendCount = 1;
device->fauxMasteringVoice.sends.pSends = SDL_malloc(sizeof(FAudioSendDescriptor));
device->fauxMasteringVoice.sends.pSends[0].Flags = 0;
device->fauxMasteringVoice.sends.pSends[0].pOutputVoice = device->masteringVoice;
if (FAudio_CreateSubmixVoice(
device->handle,
&device->fauxMasteringVoice.handle,
device->deviceDetails.OutputFormat.Format.nChannels,
device->deviceDetails.OutputFormat.Format.nSamplesPerSec,
0,
0,
&device->fauxMasteringVoice.sends,
NULL) != 0)
{
Log("Failed to create faux mastering voice! Bailing!");
FAudioVoice_DestroyVoice(device->masteringVoice);
FAudio_Release(device->handle);
SDL_free(device);
device = NULL;
return;
}
device->fauxMasteringVoice.effectChainAttached = 0;
device->fauxMasteringVoice.effectGain = 0;
device->fauxMasteringVoice.effectVoice = NULL;
device->fauxMasteringVoice.effectSends.SendCount = 0;
device->fauxMasteringVoice.effectSends.pSends = NULL;
device->spatialDistanceScale = spatialDistanceScale;
F3DAudioInitialize(
device->deviceDetails.OutputFormat.dwChannelMask,
SPEED_OF_SOUND,
device->handle3D);
device->listener.OrientFront.x = 0;
device->listener.OrientFront.y = 0;
device->listener.OrientFront.z = 1;
device->listener.OrientTop.x = 0;
device->listener.OrientTop.y = 1;
device->listener.OrientTop.z = 0;
device->listener.Position.x = 0;
device->listener.Position.y = 0;
device->listener.Position.z = 0;
device->listener.Velocity.x = 0;
device->listener.Velocity.y = 0;
device->listener.Velocity.z = 0;
device->listener.pCone = NULL;
device->voiceCallbacks.OnBufferEnd = FAudioGMS_INTERNAL_OnBufferEndCallback;
device->voiceCallbacks.OnBufferStart = NULL;
device->voiceCallbacks.OnLoopEnd = NULL;
device->voiceCallbacks.OnStreamEnd = NULL;
device->voiceCallbacks.OnVoiceError = NULL;
device->voiceCallbacks.OnVoiceProcessingPassEnd = NULL;
device->voiceCallbacks.OnVoiceProcessingPassStart = NULL;
device->staticSounds = NULL;
device->staticSoundCount = 0;
IdStack_Init(&device->staticSoundIndexStack);
device->soundInstances = NULL;
device->soundInstanceCount = 0;
IdStack_Init(&device->soundInstanceIndexStack);
device->effectChains = NULL;
device->effectChainCount = 0;
IdStack_Init(&device->effectChainIndexStack);
device->timestep = timestep;
Log("FAudio initialized successfully!");
printf("Device: %ls\n", device->deviceDetails.DisplayName);
fflush(stdout);
}
/* Taken from
* https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
*/
static void SetPanMatrixCoefficients(FAudioGMS_SoundInstance *instance)
{
/* Two major things to notice:
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
* 2. Stereo panning is WAY more complicated than you think.
* The main thing is that hard panning does NOT eliminate an
* entire channel; the two channels are blended on each side.
* -flibit
*/
float *outputMatrix = (float *)instance->dspSettings.pMatrixCoefficients;
if (instance->dspSettings.SrcChannelCount == 1)
{
if (instance->dspSettings.DstChannelCount == 1)
{
outputMatrix[0] = 1.0f;
}
else
{
outputMatrix[0] = (instance->pan > 0.0f) ? (1.0f - instance->pan) : 1.0f;
outputMatrix[1] = (instance->pan < 0.0f) ? (1.0f + instance->pan) : 1.0f;
}
}
else
{
if (instance->dspSettings.DstChannelCount == 1)
{
outputMatrix[0] = 1.0f;
outputMatrix[1] = 1.0f;
}
else
{
if (instance->pan <= 0.0f)
{
// Left speaker blends left/right channels
outputMatrix[0] = 0.5f * instance->pan + 1.0f;
outputMatrix[1] = 0.5f * -instance->pan;
// Right speaker gets less of the right channel
outputMatrix[2] = 0.0f;
outputMatrix[3] = instance->pan + 1.0f;
}
else
{
// Left speaker gets less of the left channel
outputMatrix[0] = -instance->pan + 1.0f;
outputMatrix[1] = 0.0f;
// Right speaker blends right/left channels
outputMatrix[2] = 0.5f * instance->pan;
outputMatrix[3] = 0.5f * -instance->pan + 1.0f;
}
}
}
}
double FAudioGMS_StaticSound_LoadWAV(char *filePath)
{
RETURN_ON_NULL_DEVICE(-1.0)
drwav_uint64 frameCount;
FAudioGMS_StaticSound *sound = SDL_malloc(sizeof(FAudioGMS_StaticSound));
float *pSampleData = drwav_open_file_and_read_pcm_frames_f32(
filePath,
&sound->channels,
&sound->samplesPerSecond,
&frameCount,
NULL);
if (pSampleData == NULL)
{
Log("Error opening WAV file: ");
Log(filePath);
SDL_free(sound);
return -1;
}
sound->buffer.AudioBytes = (uint32_t)(frameCount * sound->channels * sizeof(float));
sound->buffer.Flags = 0;
sound->buffer.LoopBegin = 0;
sound->buffer.LoopCount = 0;
sound->buffer.LoopLength = 0;
sound->buffer.PlayBegin = 0;
sound->buffer.PlayLength = frameCount;
sound->buffer.pAudioData = (uint8_t *)pSampleData;
sound->buffer.pContext = NULL;
sound->lengthInSeconds = frameCount / sound->samplesPerSecond;
if (device->staticSoundIndexStack.count > 0)
{
sound->id = IdStack_Pop(&device->staticSoundIndexStack);
}
else
{
sound->id = device->staticSoundCount;
device->staticSounds = SDL_realloc(
device->staticSounds,
(device->staticSoundCount + 1) * sizeof(FAudioGMS_StaticSound *));
device->staticSoundCount += 1;
}
device->staticSounds[sound->id] = sound;
return (double)sound->id;
}
static void FAudioGMS_INTERNAL_SoundInstance_SetPan(FAudioGMS_SoundInstance *instance, float pan)
{
pan = SDL_max(-1.0, SDL_min(1.0, pan));
instance->pan = pan;
SetPanMatrixCoefficients(instance);
FAudioVoice_SetOutputMatrix(
instance->voice.handle,
device->fauxMasteringVoice.handle,
instance->dspSettings.SrcChannelCount,
instance->dspSettings.DstChannelCount,
instance->dspSettings.pMatrixCoefficients,
0);
}
static void FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(FAudioGMS_SoundInstance *instance)
{
float doppler = 1.0f;
if (!instance->is3D || DOPPLER_SCALE == 0.0f)
{
doppler = 1.0f;
}
else
{
doppler = instance->dspSettings.DopplerFactor * DOPPLER_SCALE;
}
FAudioSourceVoice_SetFrequencyRatio(
instance->voice.handle,
SDL_powf(2.0f, instance->pitch - 1) *
doppler, /* FAudio expects pitch range to be -1.0 to 1.0 while GM
uses 0.0 to 2.0 so
we adjust here */
0);
}
static void FAudioGMS_INTERNAL_SoundInstance_SetPitch(
FAudioGMS_SoundInstance *instance,
float pitch)
{
pitch = SDL_max(0.0, SDL_min(2.0, pitch));
instance->pitch = pitch;
FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(instance);
}
static void FAudioGMS_INTERNAL_SoundInstance_SetVolume(
FAudioGMS_SoundInstance *instance,
float volume)
{
instance->volume = volume;
FAudioVoice_SetVolume(instance->voice.handle, volume, 0);
}
static FAudioGMS_SoundInstance *FAudioGMS_INTERNAL_SoundInstance_Init(
uint32_t channelCount,
uint32_t samplesPerSecond,
uint8_t isStatic)
{
FAudioGMS_SoundInstance *instance = SDL_malloc(sizeof(FAudioGMS_SoundInstance));
instance->voice.handle = NULL;
instance->isStatic = isStatic;
instance->loop = 0;
instance->destroyOnFinish = 0;
instance->isGlobalPaused = 0;
instance->format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT;
instance->format.wBitsPerSample = 32;
instance->format.nChannels = channelCount;
instance->format.nBlockAlign = (uint16_t)(4 * channelCount);
instance->format.nSamplesPerSec = samplesPerSecond;
instance->format.nAvgBytesPerSec = instance->format.nBlockAlign * samplesPerSecond;
instance->voice.sends.SendCount = 1;
instance->voice.sends.pSends = SDL_malloc(sizeof(FAudioSendDescriptor));
instance->voice.sends.pSends[0].Flags = 0;
instance->voice.sends.pSends[0].pOutputVoice = device->fauxMasteringVoice.handle;
FAudio_CreateSourceVoice(
device->handle,
&instance->voice.handle,
&instance->format,
FAUDIO_VOICE_USEFILTER,
FAUDIO_DEFAULT_FREQ_RATIO,
isStatic ? NULL : &device->voiceCallbacks,
&instance->voice.sends,
NULL);
if (instance->voice.handle == NULL)
{
Log("SoundInstance failed to initialize!");
SDL_free(instance);
return NULL;
}
instance->dspSettings.DopplerFactor = 1.0f;
instance->dspSettings.SrcChannelCount = channelCount;
instance->dspSettings.DstChannelCount = device->deviceDetails.OutputFormat.Format.nChannels;
uint32_t memsize =
4 * instance->dspSettings.SrcChannelCount * instance->dspSettings.DstChannelCount;
instance->dspSettings.pMatrixCoefficients = SDL_malloc(memsize);
SDL_memset(instance->dspSettings.pMatrixCoefficients, 0, memsize);
instance->voice.effectChainAttached = 0;
instance->voice.effectVoice = NULL;
instance->voice.effectGain = 0;
instance->lowPassFilter = 0.0f;
instance->highPassFilter = 0.0f;
instance->bandPassFilter = 0.0f;
FAudioGMS_INTERNAL_SoundInstance_SetPan(instance, 0);
FAudioGMS_INTERNAL_SoundInstance_SetPitch(instance, 1);
FAudioGMS_INTERNAL_SoundInstance_SetVolume(instance, 1);
instance->soundState = SoundState_Stopped;
instance->adjustingVolumeOverTime = 0;
instance->volumeDelta = 0;
instance->targetVolume = 1;
instance->is3D = 0;
instance->emitter = NULL;
instance->stereoAzimuth[0] = 0.0f;
instance->stereoAzimuth[1] = 0.0f;
instance->playBegin = 0;
instance->playLength = 0;
instance->queuedSoundInstance = NULL;
if (device->soundInstanceIndexStack.count > 0)
{
instance->id = IdStack_Pop(&device->soundInstanceIndexStack);
}
else
{
instance->id = device->soundInstanceCount;
device->soundInstances = SDL_realloc(
device->soundInstances,
(device->soundInstanceCount + 1) * sizeof(FAudioGMS_SoundInstance *));
device->soundInstanceCount += 1;
}
device->soundInstances[instance->id] = instance;
return instance;
}
static FAudioGMS_SoundInstance *FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(
FAudioGMS_StaticSound *staticSound)
{
FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_SoundInstance_Init(
staticSound->channels,
staticSound->samplesPerSecond,
1);
instance->isStatic = 1;
instance->soundData.staticSound = staticSound;
return instance;
}
double FAudioGMS_StaticSound_CreateSoundInstance(double staticSoundID)
{
RETURN_ON_NULL_DEVICE(-1.0)
FAudioGMS_StaticSound *staticSound =
FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
if (staticSound != NULL)
{
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound);
return instance->id;
}
else
{
Log("Invalid static sound ID!");
return -1.0;
}
}
static void FAudioGMS_INTERNAL_SoundInstance_SetLowPassFilter(
FAudioGMS_SoundInstance *instance,
float lowPassFilter,
float Q)
{
FAudioFilterParameters p;
p.Type = FAudioLowPassFilter;
p.Frequency = SDL_max(0.0, SDL_min(1.0, lowPassFilter));
p.OneOverQ = 1.0f / Q;
FAudioVoice_SetFilterParameters(instance->voice.handle, &p, 0);
instance->lowPassFilter = lowPassFilter;
}
void FAudioGMS_SoundInstance_SetLowPassFilter(
double soundInstanceID,
double lowPassFilter,
double Q)
{
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_SetLowPassFilter(instance, lowPassFilter, Q);
}
}
static void FAudioGMS_INTERNAL_SoundInstance_SetHighPassFilter(
FAudioGMS_SoundInstance *instance,
float highPassFilter,
float Q)
{
FAudioFilterParameters p;
p.Type = FAudioHighPassFilter;
p.Frequency = SDL_max(0.0, SDL_min(1.0, highPassFilter));
p.OneOverQ = 1.0f / Q;
FAudioVoice_SetFilterParameters(instance->voice.handle, &p, 0);
instance->highPassFilter = highPassFilter;
}
void FAudioGMS_SoundInstance_SetHighPassFilter(
double soundInstanceID,
double highPassFilter,
double Q)
{
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_SetHighPassFilter(instance, highPassFilter, Q);
}
}
static void FAudioGMS_INTERNAL_SoundInstance_SetBandPassFilter(
FAudioGMS_SoundInstance *instance,
float bandPassFilter,
float Q)
{
FAudioFilterParameters p;
p.Type = FAudioBandPassFilter;
p.Frequency = SDL_max(0.0, SDL_min(1.0, bandPassFilter));
p.OneOverQ = 1.0f / Q;
FAudioVoice_SetFilterParameters(instance->voice.handle, &p, 0);
instance->bandPassFilter = bandPassFilter;
}
void FAudioGMS_SoundInstance_SetBandPassFilter(
double soundInstanceID,
double bandPassFilter,
double Q)
{
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_SetBandPassFilter(instance, bandPassFilter, Q);
}
}
void FAudioGMS_SoundInstance_QueueSoundInstance(double soundInstanceID, double queueSoundInstanceID)
{
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
FAudioGMS_SoundInstance *queueInstance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)queueSoundInstanceID);
if (instance != NULL && queueInstance != NULL)
{
instance->queuedSoundInstance = queueInstance;
}
else
{
if (instance == NULL)
{
Log("QueueSoundInstace: Invalid instance ID!");
}
else
{
Log("QueueSoundInstance: Invalid queue sound instance ID!");
}
}
}
static void FAudioGMS_INTERNAL_Apply3D(FAudioGMS_SoundInstance *instance)
{
F3DAUDIO_EMITTER *emitter = instance->emitter;
if (emitter == NULL)
{
Log("Sound instance does not have 3D data! Did you forget to "
"initialize?");
return;
}
F3DAudioCalculate(
device->handle3D,
&device->listener,
emitter,
F3DAUDIO_CALCULATE_MATRIX | F3DAUDIO_CALCULATE_DOPPLER,
&instance->dspSettings);
FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(instance);
FAudioVoice_SetOutputMatrix(
instance->voice.handle,
device->fauxMasteringVoice.handle,
instance->dspSettings.SrcChannelCount,
instance->dspSettings.DstChannelCount,
instance->dspSettings.pMatrixCoefficients,
0);
}
static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance *instance)
{
uint32_t defaultRequestedSampleCount = instance->format.nSamplesPerSec / 4;
uint32_t requestedSampleCount = defaultRequestedSampleCount;
if (instance->playLength != 0)
{
uint32_t distanceToEndPoint =
(instance->playBegin + instance->playLength) -
stb_vorbis_get_sample_offset(instance->soundData.streamingSound.fileHandle);
requestedSampleCount = SDL_min(requestedSampleCount, distanceToEndPoint);
}
uint32_t requiredStagingBufferSize =
requestedSampleCount * instance->format.nChannels * sizeof(float);
if (instance->soundData.streamingSound.streamBufferSize < requiredStagingBufferSize)
{
instance->soundData.streamingSound.streamBuffer =
SDL_realloc(instance->soundData.streamingSound.streamBuffer, requiredStagingBufferSize);
instance->soundData.streamingSound.streamBufferSize = requiredStagingBufferSize;
}
instance->soundData.streamingSound.mostRecentBufferOffset =
stb_vorbis_get_sample_offset(instance->soundData.streamingSound.fileHandle);
/* NOTE: this function returns samples per channel, not total samples */
uint32_t sampleCount = stb_vorbis_get_samples_float_interleaved(
instance->soundData.streamingSound.fileHandle,
instance->format.nChannels,
instance->soundData.streamingSound.streamBuffer,
requestedSampleCount * instance->format.nChannels);
FAudioBuffer buffer;
buffer.AudioBytes = sampleCount * instance->format.nChannels * sizeof(float);
buffer.pAudioData = (uint8_t *)instance->soundData.streamingSound.streamBuffer;
buffer.PlayBegin = 0;
buffer.PlayLength = sampleCount;
buffer.Flags = 0;
buffer.LoopBegin = 0;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pContext = instance; /* context for OnBufferEnd callback */
FAudioSourceVoice_SubmitSourceBuffer(instance->voice.handle, &buffer, NULL);
instance->soundData.streamingSound.isFinalBuffer = 0;
if (sampleCount < defaultRequestedSampleCount)
{
if (instance->loop)
{
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin);
}
else
{
instance->soundData.streamingSound.isFinalBuffer = 1;
}
}
}
double FAudioGMS_StreamingSound_LoadOGG(char *filePath)
{
RETURN_ON_NULL_DEVICE(-1.0)
int error = 0;
stb_vorbis *fileHandle = stb_vorbis_open_filename(filePath, &error, NULL);
if (error != 0)
{
Log("Error opening OGG file!");
Log(filePath);
return -1;
}
stb_vorbis_info info = stb_vorbis_get_info(fileHandle);
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_SoundInstance_Init(info.channels, info.sample_rate, 0);
instance->soundData.streamingSound.fileHandle = fileHandle;
instance->soundData.streamingSound.info = info;
instance->soundData.streamingSound.streamBuffer = NULL;
instance->soundData.streamingSound.streamBufferSize = 0;
instance->soundData.streamingSound.mostRecentBufferOffset = 0;
instance->soundData.streamingSound.isFinalBuffer = 0;
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance);
return instance->id;
}
static void FAudioGMS_INTERNAL_SoundInstance_AddEmitter(
FAudioGMS_SoundInstance *instance,
float x,
float y,
float z)
{
instance->is3D = 1;
instance->emitter = SDL_malloc(sizeof(F3DAUDIO_EMITTER));
/* defaults based on XNA behavior */
instance->emitter->pCone = NULL;
instance->emitter->ChannelRadius = 1.0f;
instance->emitter->pChannelAzimuths = instance->stereoAzimuth;
instance->emitter->pVolumeCurve = NULL;
instance->emitter->pLFECurve = NULL;
instance->emitter->pLPFDirectCurve = NULL;
instance->emitter->pLPFReverbCurve = NULL;
instance->emitter->pReverbCurve = NULL;
instance->emitter->InnerRadius = 0;
instance->emitter->InnerRadiusAngle = 0;
instance->emitter->ChannelCount = instance->dspSettings.SrcChannelCount;
instance->emitter->CurveDistanceScaler = device->spatialDistanceScale;
instance->emitter->DopplerScaler = 1.0f;
instance->emitter->Position.x = x;
instance->emitter->Position.y = y;
instance->emitter->Position.z = z;
instance->emitter->Velocity.x = 0;
instance->emitter->Velocity.y = 0;
instance->emitter->Velocity.z = 0;
instance->emitter->OrientTop.x = 0;
instance->emitter->OrientTop.y = 1;
instance->emitter->OrientTop.z = 0;
instance->emitter->OrientFront.x = 0;
instance->emitter->OrientFront.y = 0;
instance->emitter->OrientFront.z = -1;
FAudioGMS_INTERNAL_Apply3D(instance);
}
static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance *instance)
{
if (instance->soundState == SoundState_Playing)
{
return;
}
if (instance->isStatic)
{
instance->soundData.staticSound->buffer.pContext = instance;
if (instance->loop)
{
instance->soundData.staticSound->buffer.LoopCount = FAUDIO_LOOP_INFINITE;
instance->soundData.staticSound->buffer.LoopBegin = instance->playBegin;
instance->soundData.staticSound->buffer.LoopLength = instance->playLength;
}
else
{
instance->soundData.staticSound->buffer.LoopCount = 0;
instance->soundData.staticSound->buffer.LoopBegin = 0;
instance->soundData.staticSound->buffer.LoopLength = 0;
}
if (instance->soundState == SoundState_Paused)
{
FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle);
instance->soundData.staticSound->buffer.PlayBegin =
instance->voice.handle->src.curBufferOffset;
instance->soundData.staticSound->buffer.PlayLength = instance->playLength;
}
else
{
instance->soundData.staticSound->buffer.PlayBegin = instance->playBegin;
instance->soundData.staticSound->buffer.PlayLength = instance->playLength;
}
FAudioSourceVoice_SubmitSourceBuffer(
instance->voice.handle,
&instance->soundData.staticSound->buffer,
NULL);
}
FAudioSourceVoice_Start(instance->voice.handle, 0, 0);
instance->soundState = SoundState_Playing;
}
void FAudioGMS_SoundInstance_Play(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
}
static void FAudioGMS_INTERNAL_SoundInstance_Pause(FAudioGMS_SoundInstance *instance)
{
if (instance != NULL)
{
if (instance->soundState == SoundState_Playing)
{
FAudioSourceVoice_Stop(
instance->voice.handle,
0,
0); /* this actually just pauses lol */
instance->soundState = SoundState_Paused;
}
}
else
{
Log("SoundInstance_Pause: Invalid sound instance ID! Did you destroy "
"this instance?");
}
}
void FAudioGMS_SoundInstance_Pause(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
FAudioGMS_INTERNAL_SoundInstance_Pause(instance);
}
static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *instance)
{
if (instance != NULL)
{
instance->soundState =
SoundState_Stopped; /* set this before so flush
buffers
doesn't trigger buffer add callback */
FAudioSourceVoice_Stop(instance->voice.handle, 0, 0);
FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle);
if (!instance->isStatic)
{
stb_vorbis_seek(
instance->soundData.streamingSound.fileHandle,
instance->playBegin); /* back to the start */
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(
instance); /* preload so we dont stutter on play */
}
}
else
{
Log("SoundInstance_Stop: Invalid sound instance ID! Did you destroy "
"this instance?");
}
}
void FAudioGMS_SoundInstance_Stop(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
}
void FAudioGMS_SoundInstance_SetLoop(double soundInstanceID, double loop)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
instance->loop = (uint8_t)loop;
if (instance->isStatic && instance->soundState == SoundState_Playing)
{
/* We need to pause and play so that static buffers get resubmitted
*/
FAudioGMS_INTERNAL_SoundInstance_Pause(instance);
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
}
}
void FAudioGMS_SoundInstance_SetPan(double soundInstanceID, double pan)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL && !instance->is3D)
{
FAudioGMS_INTERNAL_SoundInstance_SetPan(instance, pan);
}
}
void FAudioGMS_SoundInstance_SetPitch(double soundInstanceID, double pitch)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_SetPitch(instance, pitch);
}
}
void FAudioGMS_SoundInstance_SetVolume(double soundInstanceID, double volume)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
instance->adjustingVolumeOverTime = 0; /* override volume adjustment over time */
FAudioGMS_INTERNAL_SoundInstance_SetVolume(instance, volume);
}
}
void FAudioGMS_SoundInstance_Set3DPosition(double soundInstanceID, double x, double y, double z)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
if (instance->is3D)
{
instance->emitter->Position.x = x;
instance->emitter->Position.y = y;
instance->emitter->Position.z = z;
FAudioGMS_INTERNAL_Apply3D(instance);
}
else
{
FAudioGMS_INTERNAL_SoundInstance_AddEmitter(instance, x, y, z);
}
}
}
void FAudioGMS_SoundInstance_Set3DVelocity(
double soundInstanceID,
double xVelocity,
double yVelocity,
double zVelocity)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
if (!instance->is3D)
{
Log("Not a 3D sound!");
return;
}
instance->emitter->Velocity.x = xVelocity;
instance->emitter->Velocity.y = yVelocity;
instance->emitter->Velocity.z = zVelocity;
}
}
/* FIXME: this will die horribly if position is greater than total length */
void FAudioGMS_SoundInstance_SetTrackPositionInSeconds(
double soundInstanceID,
double trackPositionInSeconds)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
uint32_t sampleFrame =
instance->soundData.staticSound->samplesPerSecond * trackPositionInSeconds;
FAudioGMS_SoundState currentState = instance->soundState;
if (currentState == SoundState_Playing)
{
FAudioSourceVoice_Stop(instance->voice.handle, 0, 0);
FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle);
}
if (instance->isStatic)
{
instance->soundData.staticSound->buffer.PlayBegin = sampleFrame;
}
else
{
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, sampleFrame);
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance);
}
if (currentState == SoundState_Playing)
{
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
}
}
static uint32_t FAudioGMS_INTERNAL_SoundInstance_GetTrackPositionInSampleFrames(
FAudioGMS_SoundInstance *instance)
{
if (instance != NULL)
{
if (instance->isStatic)
{
return instance->voice.handle->src.curBufferOffset / sizeof(float);
}
else
{
return instance->soundData.streamingSound.mostRecentBufferOffset +
instance->voice.handle->src.curBufferOffset;
}
}
Log("Invalid sound instance!");
return 0;
}
void FAudioGMS_SoundInstance_SetPlayRegion(
double soundInstanceID,
double startInMilliseconds,
double endInMilliseconds)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
uint32_t playBeginSampleFrame =
instance->format.nSamplesPerSec * (startInMilliseconds / 1000);
uint32_t playEndSampleFrame = instance->format.nSamplesPerSec * (endInMilliseconds / 1000);
uint32_t playLength = playEndSampleFrame - playBeginSampleFrame;
if (playLength <= 0)
{
Log("Play end is less than or equal to play start! Bailing!");
return;
}
instance->playBegin = playBeginSampleFrame;
instance->playLength = playLength;
uint32_t currentFrame =
FAudioGMS_INTERNAL_SoundInstance_GetTrackPositionInSampleFrames(instance);
if (currentFrame < instance->playBegin ||
(currentFrame > instance->playBegin + instance->playLength))
{
/* we are outside the play region */
if (instance->isStatic && instance->soundState == SoundState_Playing)
{
FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
else if (!instance->isStatic)
{
FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle);
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin);
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance);
}
}
else
{
/* we are inside the play region */
if (instance->isStatic && instance->soundState != SoundState_Stopped)
{
FAudioGMS_INTERNAL_SoundInstance_Pause(instance);
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
}
}
}
void FAudioGMS_SoundInstance_SetVolumeOverTime(
double soundInstanceID,
double volume,
double milliseconds)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
instance->adjustingVolumeOverTime = 1;
instance->targetVolume = volume;
instance->volumeDelta =
(volume - instance->volume) / ((milliseconds / 1000) / device->timestep);
}
}
double FAudioGMS_SoundInstance_GetPitch(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE(-1.0)
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
return instance->pitch;
}
else
{
Log("Invalid sound instance!");
return -1;
}
}
double FAudioGMS_SoundInstance_GetVolume(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE(-1.0)
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
return instance->volume;
}
else
{
Log("Invalid sound instance!");
return -1;
}
}
double FAudioGMS_SoundInstance_GetTrackLengthInSeconds(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE(-1.0)
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
if (instance->isStatic)
{
return instance->soundData.staticSound->lengthInSeconds;
}
else
{
return stb_vorbis_stream_length_in_seconds(
instance->soundData.streamingSound.fileHandle);
}
}
else
{
Log("Invalid sound instance!");
return -1;
}
}
double FAudioGMS_SoundInstance_GetTrackPositionInSeconds(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE(-1.0)
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
return (double)FAudioGMS_INTERNAL_SoundInstance_GetTrackPositionInSampleFrames(instance) /
instance->format.nSamplesPerSec;
}
else
{
Log("Invalid sound instance!");
return -1.0;
}
}
void FAudioGMS_SetListenerPosition(double x, double y, double z)
{
RETURN_ON_NULL_DEVICE()
device->listener.Position.x = x;
device->listener.Position.y = y;
device->listener.Position.z = z;
}
void FAudioGMS_SetListenerVelocity(double xVelocity, double yVelocity, double zVelocity)
{
RETURN_ON_NULL_DEVICE()
device->listener.Velocity.x = xVelocity;
device->listener.Velocity.y = yVelocity;
device->listener.Velocity.z = zVelocity;
}
static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance)
{
if (instance != NULL)
{
device->soundInstances[instance->id] = NULL;
IdStack_Push(&device->soundInstanceIndexStack, instance->id);
FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
if (instance->voice.effectChainAttached)
{
FAudioVoice_DestroyVoice(instance->voice.effectVoice);
SDL_free(instance->voice.effectSends.pSends);
}
SDL_free(instance->voice.sends.pSends);
FAudioVoice_DestroyVoice(instance->voice.handle);
if (!instance->isStatic)
{
SDL_free(instance->soundData.streamingSound.streamBuffer);
stb_vorbis_close(instance->soundData.streamingSound.fileHandle);
}
if (instance->is3D)
{
SDL_free(instance->emitter);
}
SDL_free(instance->dspSettings.pMatrixCoefficients);
SDL_free(instance);
}
}
void FAudioGMS_SoundInstance_Destroy(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_Destroy(instance);
}
}
void FAudioGMS_SoundInstance_DestroyWhenFinished(double soundInstanceID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
instance->destroyOnFinish = 1;
}
}
/* NOTE: this will die horribly if a sound is playing */
static void FAudioGMS_INTERNAL_StaticSound_Destroy(FAudioGMS_StaticSound *sound)
{
if (sound != NULL)
{
device->staticSounds[sound->id] = NULL;
IdStack_Push(&device->staticSoundIndexStack, sound->id);
SDL_free((void *)sound->buffer.pAudioData);
SDL_free(sound);
}
}
void FAudioGMS_StaticSound_Destroy(double staticSoundID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_StaticSound *sound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
FAudioGMS_INTERNAL_StaticSound_Destroy(sound);
}
double FAudioGMS_EffectChain_Create()
{
RETURN_ON_NULL_DEVICE(-1.0)
FAudioGMS_EffectChain *effectChain = SDL_malloc(sizeof(FAudioGMS_EffectChain));
effectChain->effectCount = 0;
effectChain->effectTypes = NULL;
effectChain->effectParameters = NULL;
if (device->effectChainIndexStack.count > 0)
{
effectChain->id = IdStack_Pop(&device->effectChainIndexStack);
}
else
{
effectChain->id = device->effectChainCount;
device->effectChains = SDL_realloc(
device->effectChains,
(device->effectChainCount + 1) * sizeof(FAudioGMS_EffectChain *));
device->effectChainCount += 1;
}
device->effectChains[effectChain->id] = effectChain;
return effectChain->id;
}
static void FAudioGMS_INTERNAL_EffectChain_AddReverb(
FAudioGMS_EffectChain *effectChain,
FAudioFXReverbParameters *reverbParameters)
{
effectChain->effectCount += 1;
effectChain->effectTypes = SDL_realloc(
effectChain->effectTypes,
effectChain->effectCount * sizeof(FAudioGMS_EffectType));
effectChain->effectTypes[effectChain->effectCount - 1] = EffectType_Reverb;
effectChain->effectParameters = SDL_realloc(
effectChain->effectParameters,
effectChain->effectCount * sizeof(FAudioGMS_EffectParameters));
SDL_memcpy(
&effectChain->effectParameters[effectChain->effectCount - 1],
reverbParameters,
sizeof(FAudioFXReverbParameters));
}
void FAudioGMS_EffectChain_AddDefaultReverb(double effectChainID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_EffectChain *effectChain =
FAudioGMS_INTERNAL_LookupEffectChain((uint32_t)effectChainID);
if (effectChain != NULL)
{
FAudioFXReverbParameters reverbParams;
reverbParams.WetDryMix = 100.0f;
reverbParams.ReflectionsDelay = 7;
reverbParams.ReverbDelay = 11;
reverbParams.RearDelay = FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
reverbParams.PositionLeft = FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams.PositionRight = FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams.PositionMatrixLeft = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams.PositionMatrixRight = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams.EarlyDiffusion = 15;
reverbParams.LateDiffusion = 15;
reverbParams.LowEQGain = 8;
reverbParams.LowEQCutoff = 4;
reverbParams.HighEQGain = 8;
reverbParams.HighEQCutoff = 6;
reverbParams.RoomFilterFreq = 5000.0f;
reverbParams.RoomFilterMain = -10.0f;
reverbParams.RoomFilterHF = -1.0f;
reverbParams.ReflectionsGain = -26.0200005f;
reverbParams.ReverbGain = 10.0f;
reverbParams.DecayTime = 1.49000001f;
reverbParams.Density = 100.0f;
reverbParams.RoomSize = FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
FAudioGMS_INTERNAL_EffectChain_AddReverb(effectChain, &reverbParams);
}
}
/* no room filter params because game maker only supports 16 arguments lmao */
void FAudioGMS_EffectChain_AddReverb(
double effectChainID,
double wetDryMix,
double reflectionsDelay,
double reverbDelay,
double earlyDiffusion,
double lateDiffusion,
double lowEQGain,
double lowEQCutoff,
double highEQGain,
double highEQCutoff,
double reflectionsGain,
double reverbGain,
double decayTime,
double density,
double roomSize)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_EffectChain *effectChain =
FAudioGMS_INTERNAL_LookupEffectChain((uint32_t)effectChainID);
if (effectChain != NULL)
{
FAudioFXReverbParameters reverbParams;
reverbParams.WetDryMix = wetDryMix;
reverbParams.ReflectionsDelay = reflectionsDelay;
reverbParams.ReverbDelay = reverbDelay;
reverbParams.RearDelay = FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
reverbParams.PositionLeft = FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams.PositionRight = FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams.PositionMatrixLeft = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams.PositionMatrixRight = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams.EarlyDiffusion = earlyDiffusion;
reverbParams.LateDiffusion = lateDiffusion;
reverbParams.LowEQGain = lowEQGain;
reverbParams.LowEQCutoff = lowEQCutoff;
reverbParams.HighEQGain = highEQGain;
reverbParams.HighEQCutoff = highEQCutoff;
reverbParams.RoomFilterFreq = 5000.0f;
reverbParams.RoomFilterMain = -10.0f;
reverbParams.RoomFilterHF = -1.0f;
reverbParams.ReflectionsGain = reflectionsGain;
reverbParams.ReverbGain = reverbGain;
reverbParams.DecayTime = decayTime;
reverbParams.Density = density;
reverbParams.RoomSize = roomSize;
FAudioGMS_INTERNAL_EffectChain_AddReverb(effectChain, &reverbParams);
}
}
static void FAudioGMS_INTERNAL_Voice_SetEffectGain(FAudioGMS_Voice *voice, float effectGain)
{
if (voice->effectChainAttached)
{
float *outputMatrix = SDL_stack_alloc(float, voice->effectSends.SendCount);
outputMatrix[0] = effectGain;
if (voice->effectSends.SendCount == 2)
{
outputMatrix[1] = effectGain;
}
FAudioVoice_SetOutputMatrix(
voice->handle,
voice->effectVoice,
voice->effectSends.SendCount,
1,
outputMatrix,
0);
voice->effectGain = effectGain;
SDL_stack_free(outputMatrix);
}
}
void FAudioGMS_SoundInstance_SetEffectGain(double soundInstanceID, double effectGain)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
FAudioGMS_INTERNAL_Voice_SetEffectGain(&instance->voice, effectGain);
}
}
static FAudioEffectChain *FAudioGMS_INTERNAL_CreateFAudioEffectChain(
FAudioGMS_EffectChain *effectChain)
{
FAudioEffectChain *fAudioEffectChain = SDL_malloc(sizeof(FAudioEffectChain));
uint32_t i;
fAudioEffectChain->EffectCount = effectChain->effectCount;
fAudioEffectChain->pEffectDescriptors =
SDL_malloc(fAudioEffectChain->EffectCount * sizeof(FAudioEffectDescriptor));
FAPO *reverb;
for (i = 0; i < effectChain->effectCount; i += 1)
{
switch (effectChain->effectTypes[i])
{
case EffectType_Reverb:
FAudioCreateReverb(&reverb, 0);
FAudioEffectDescriptor *reverbDescriptor = &fAudioEffectChain->pEffectDescriptors[i];
reverbDescriptor->InitialState = 1;
reverbDescriptor->OutputChannels =
device->deviceDetails.OutputFormat.Format.nChannels == 6 ? 6 : 1;
reverbDescriptor->pEffect = reverb;
break;
default:
Log("Unknown effect type! Something is very wrong!");
return NULL;
}
}
return fAudioEffectChain;
}
static void FAudioGMS_INTERNAL_SetEffectChain(
FAudioGMS_Voice *voice,
FAudioGMS_EffectChain *effectChain,
float effectGain)
{
uint32_t i;
if (voice->effectChainAttached)
{
/* This frees the effect chain on the voice */
FAudioVoice_SetEffectChain(voice->effectVoice, NULL);
SDL_free(voice->effectSends.pSends);
}
else
{
voice->sends.SendCount = 2;
voice->sends.pSends = SDL_realloc(voice->sends.pSends, 2 * sizeof(FAudioSendDescriptor));
}
FAudioEffectChain *fAudioEffectChain = FAudioGMS_INTERNAL_CreateFAudioEffectChain(effectChain);
voice->effectSends.SendCount = 1;
voice->effectSends.pSends = SDL_malloc(sizeof(FAudioSendDescriptor));
voice->effectSends.pSends[0].Flags = 0;
if (voice == &device->fauxMasteringVoice)
{
voice->effectSends.pSends[0].pOutputVoice = device->masteringVoice;
}
else
{
voice->effectSends.pSends[0].pOutputVoice = device->fauxMasteringVoice.handle;
}
FAudio_CreateSubmixVoice(
device->handle,
&voice->effectVoice,
1, /* FIXME: is this correct? */
device->deviceDetails.OutputFormat.Format.nSamplesPerSec,
0,
0,
&voice->effectSends,
fAudioEffectChain);
/* Copy the effect params */
for (i = 0; i < effectChain->effectCount; i += 1)
{
uint32_t parametersSize;
void *parameters;
switch (effectChain->effectTypes[i])
{
case EffectType_Reverb:
parametersSize = sizeof(FAudioFXReverbParameters);
parameters = &effectChain->effectParameters[i].reverbParameters;
break;
default:
Log("Unknown effect type! Something is very wrong!");
}
FAudioVoice_SetEffectParameters(voice->effectVoice, i, parameters, parametersSize, 0);
}
/* Set the instance voice to go through both faux mastering and effect voice
* for wet/dry */
voice->sends.pSends[0].Flags = 0;
if (voice == &device->fauxMasteringVoice)
{
voice->sends.pSends[0].pOutputVoice = device->masteringVoice;
}
else
{
voice->sends.pSends[0].pOutputVoice = device->fauxMasteringVoice.handle;
}
voice->sends.pSends[1].Flags = 0;
voice->sends.pSends[1].pOutputVoice = voice->effectVoice;
voice->effectChainAttached = 1;
FAudioVoice_SetOutputVoices(voice->handle, &voice->sends);
FAudioGMS_INTERNAL_Voice_SetEffectGain(voice, effectGain);
/* All the effect parameters are copied to the voice so we free here */
for (i = 0; i < effectChain->effectCount; i += 1)
{
FAPOBase_Release((FAPOBase *)fAudioEffectChain->pEffectDescriptors[i].pEffect);
}
SDL_free(fAudioEffectChain->pEffectDescriptors);
SDL_free(fAudioEffectChain);
}
void FAudioGMS_SoundInstance_SetEffectChain(
double soundInstanceID,
double effectChainID,
double effectGain)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_SoundInstance *instance =
FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
FAudioGMS_EffectChain *effectChain =
FAudioGMS_INTERNAL_LookupEffectChain((uint32_t)effectChainID);
if (instance != NULL && effectChain != NULL)
{
FAudioGMS_INTERNAL_SetEffectChain(&instance->voice, effectChain, effectGain);
}
}
void FAudioGMS_SetMasteringEffectChain(double effectChainID, double effectGain)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_EffectChain *effectChain =
FAudioGMS_INTERNAL_LookupEffectChain((uint32_t)effectChainID);
if (effectChain != NULL)
{
FAudioGMS_INTERNAL_SetEffectChain(&device->fauxMasteringVoice, effectChain, effectGain);
}
}
void FAudioGMS_SetMasteringEffectGain(double effectGain)
{
RETURN_ON_NULL_DEVICE()
if (device->fauxMasteringVoice.effectChainAttached)
{
FAudioGMS_INTERNAL_Voice_SetEffectGain(&device->fauxMasteringVoice, effectGain);
}
}
static void FAudioGMS_INTERNAL_EffectChain_Destroy(FAudioGMS_EffectChain *effectChain)
{
if (effectChain != NULL)
{
device->effectChains[effectChain->id] = NULL;
IdStack_Push(&device->effectChainIndexStack, effectChain->id);
SDL_free(effectChain->effectParameters);
SDL_free(effectChain->effectTypes);
SDL_free(effectChain);
}
}
void FAudioGMS_EffectChain_Destroy(double effectChainID)
{
RETURN_ON_NULL_DEVICE()
FAudioGMS_EffectChain *effectChain =
FAudioGMS_INTERNAL_LookupEffectChain((uint32_t)effectChainID);
if (effectChain != NULL)
{
FAudioGMS_INTERNAL_EffectChain_Destroy(effectChain);
}
}
void FAudioGMS_Update()
{
RETURN_ON_NULL_DEVICE()
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_SoundInstance *instance = device->soundInstances[i];
if (instance != NULL)
{
if (instance->is3D)
{
FAudioGMS_INTERNAL_Apply3D(instance);
}
if (instance->adjustingVolumeOverTime)
{
float volume = instance->volume + instance->volumeDelta;
if (instance->volumeDelta > 0)
{
if (volume >= instance->targetVolume)
{
volume = instance->targetVolume;
instance->adjustingVolumeOverTime = 0;
}
}
else if (instance->volumeDelta < 0)
{
if (volume <= instance->targetVolume)
{
volume = instance->targetVolume;
instance->adjustingVolumeOverTime = 0;
}
}
else
{
instance->adjustingVolumeOverTime = 0;
}
FAudioGMS_INTERNAL_SoundInstance_SetVolume(instance, volume);
}
if (instance->destroyOnFinish)
{
FAudioVoiceState state;
FAudioSourceVoice_GetState(
instance->voice.handle,
&state,
FAUDIO_VOICE_NOSAMPLESPLAYED);
if (state.BuffersQueued == 0)
{
FAudioGMS_INTERNAL_SoundInstance_Destroy(instance);
}
}
}
}
}
void FAudioGMS_PauseAll()
{
RETURN_ON_NULL_DEVICE()
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_SoundInstance *instance = device->soundInstances[i];
if (instance != NULL)
{
instance->isGlobalPaused = 1;
FAudioGMS_INTERNAL_SoundInstance_Pause(instance);
}
}
}
void FAudioGMS_ResumeAll()
{
RETURN_ON_NULL_DEVICE()
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_SoundInstance *instance = device->soundInstances[i];
if (instance != NULL && instance->isGlobalPaused)
{
instance->isGlobalPaused = 0;
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
}
}
void FAudioGMS_StopAll()
{
RETURN_ON_NULL_DEVICE()
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_INTERNAL_SoundInstance_Stop(device->soundInstances[i]);
}
}
void FAudioGMS_Destroy()
{
RETURN_ON_NULL_DEVICE()
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[i]);
}
for (i = 0; i < device->effectChainCount; i += 1)
{
FAudioGMS_INTERNAL_EffectChain_Destroy(device->effectChains[i]);
}
for (i = 0; i < device->staticSoundCount; i += 1)
{
FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[i]);
}
FAudioVoice_DestroyVoice(device->fauxMasteringVoice.handle);
SDL_free(device->fauxMasteringVoice.sends.pSends);
FAudio_Release(device->handle);
SDL_free(device);
device = NULL;
Log("FAudio cleaned up successfully!");
}