forked from MoonsideGames/FAudioGMS
1650 lines
50 KiB
C
1650 lines
50 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 "FAPOBase.h"
|
|
#include "FAudioFX.h"
|
|
#include "F3DAudio.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_StaticSound
|
|
{
|
|
uint32_t id;
|
|
FAudioBuffer buffer;
|
|
uint32_t channels;
|
|
uint32_t samplesPerSecond;
|
|
uint32_t loopStart;
|
|
uint32_t loopLength;
|
|
uint32_t lengthInSeconds;
|
|
} FAudioGMS_StaticSound;
|
|
|
|
typedef struct FAudioGMS_StreamingSound
|
|
{
|
|
stb_vorbis* fileHandle;
|
|
stb_vorbis_info info;
|
|
} FAudioGMS_StreamingSound;
|
|
|
|
typedef struct FAudioGMS_SoundInstance
|
|
{
|
|
uint32_t id;
|
|
FAudioSourceVoice *handle;
|
|
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 effectChainAttached;
|
|
FAudioSubmixVoice* effectVoice;
|
|
FAudioVoiceSends effectSends;
|
|
float effectGain;
|
|
|
|
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];
|
|
|
|
union
|
|
{
|
|
FAudioGMS_StaticSound *staticSound; /* static sounds are loaded separately, so they do not belong to the instance */
|
|
FAudioGMS_StreamingSound streamingSound;
|
|
} soundData;
|
|
} FAudioGMS_SoundInstance;
|
|
|
|
typedef enum FAudioGMS_EffectType
|
|
{
|
|
EffectType_Reverb
|
|
} FAudioGMS_EffectType;
|
|
|
|
typedef union FAudioGMS_EffectParameters
|
|
{
|
|
FAudioFXReverbParameters reverbParameters;
|
|
} FAudioGMS_EffectParameters;
|
|
|
|
typedef struct FAudioGMS_EffectChain
|
|
{
|
|
uint32_t id;
|
|
FAudioEffectChain fAudioEffectChain;
|
|
FAudioGMS_EffectType *effectTypes; /* size equal to effect chain size */
|
|
union FAudioGMS_EffectParameters *effectParameters; /* size equal to effect chain size */
|
|
} FAudioGMS_EffectChain;
|
|
|
|
static const float SPEED_OF_SOUND = 343.5f;
|
|
static const float DOPPLER_SCALE = 1.0f;
|
|
|
|
#define STREAMING_BUFFER_SIZE 1024 * 16 * sizeof(float) /* FIXME: what should this value be? */
|
|
|
|
typedef struct FAudioGMS_Device
|
|
{
|
|
FAudio* handle;
|
|
F3DAUDIO_HANDLE handle3D;
|
|
|
|
FAudioDeviceDetails deviceDetails;
|
|
FAudioMasteringVoice *masteringVoice;
|
|
|
|
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;
|
|
|
|
float streamStagingBuffer[STREAMING_BUFFER_SIZE];
|
|
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 declare this to avoid annoying BS */
|
|
static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance* instance);
|
|
|
|
static void FAudioGMS_INTERNAL_StreamingBufferEndCallback(FAudioVoiceCallback* callback, FAudioGMS_SoundInstance *instance)
|
|
{
|
|
if (instance->soundState == SoundState_Playing)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance);
|
|
}
|
|
}
|
|
|
|
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->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_StreamingBufferEndCallback;
|
|
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 = FAUDIO_END_OF_STREAM;
|
|
sound->buffer.LoopBegin = 0;
|
|
sound->buffer.LoopCount = 0;
|
|
sound->buffer.LoopLength = 0;
|
|
sound->buffer.pAudioData = (uint8_t*) pSampleData;
|
|
sound->buffer.pContext = NULL;
|
|
sound->buffer.PlayBegin = 0;
|
|
sound->buffer.PlayLength = 0;
|
|
|
|
sound->loopStart = 0;
|
|
sound->loopLength = 0;
|
|
|
|
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_SetLowPassFilter(FAudioGMS_SoundInstance* instance, float lowPassFilter)
|
|
{
|
|
FAudioFilterParameters p;
|
|
p.Type = FAudioLowPassFilter;
|
|
p.Frequency = lowPassFilter;
|
|
p.OneOverQ = 1.0f;
|
|
|
|
FAudioVoice_SetFilterParameters(instance->handle, &p, 0);
|
|
|
|
instance->lowPassFilter = lowPassFilter;
|
|
}
|
|
|
|
void FAudioGMS_SoundInstance_SetLowPassFilter(double soundInstanceID, double lowPassFilter)
|
|
{
|
|
FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
|
|
|
|
if (instance != NULL)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_SetLowPassFilter(instance, lowPassFilter);
|
|
}
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_SoundInstance_SetHighPassFilter(FAudioGMS_SoundInstance* instance, float highPassFilter)
|
|
{
|
|
FAudioFilterParameters p;
|
|
p.Type = FAudioHighPassFilter;
|
|
p.Frequency = highPassFilter;
|
|
p.OneOverQ = 1.0f;
|
|
|
|
FAudioVoice_SetFilterParameters(instance->handle, &p, 0);
|
|
|
|
instance->highPassFilter = highPassFilter;
|
|
}
|
|
|
|
void FAudioGMS_SoundInstance_SetHighPassFilter(double soundInstanceID, double highPassFilter)
|
|
{
|
|
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
|
|
|
|
if (instance != NULL)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_SetHighPassFilter(instance, highPassFilter);
|
|
}
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_SoundInstance_SetBandPassFilter(FAudioGMS_SoundInstance* instance, float bandPassFilter)
|
|
{
|
|
FAudioFilterParameters p;
|
|
p.Type = FAudioBandPassFilter;
|
|
p.Frequency = bandPassFilter;
|
|
p.OneOverQ = 1.0f;
|
|
|
|
FAudioVoice_SetFilterParameters(instance->handle, &p, 0);
|
|
|
|
instance->bandPassFilter = bandPassFilter;
|
|
}
|
|
|
|
void FAudioGMS_SoundInstance_SetBandPassFilter(double soundInstanceID, double bandPassFilter)
|
|
{
|
|
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
|
|
|
|
if (instance != NULL)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_SetBandPassFilter(instance, bandPassFilter);
|
|
}
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_SoundInstance_SetPan(FAudioGMS_SoundInstance* instance, float pan)
|
|
{
|
|
instance->pan = pan;
|
|
SetPanMatrixCoefficients(instance);
|
|
|
|
FAudioVoice_SetOutputMatrix(
|
|
instance->handle,
|
|
device->masteringVoice,
|
|
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->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->handle, volume, 0);
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_SoundInstance_SetProperties(FAudioGMS_SoundInstance* instance, double pan, double pitch, double volume)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_SetPan(instance, pan);
|
|
FAudioGMS_INTERNAL_SoundInstance_SetPitch(instance, pitch);
|
|
FAudioGMS_INTERNAL_SoundInstance_SetVolume(instance, volume);
|
|
}
|
|
|
|
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->handle,
|
|
device->masteringVoice,
|
|
instance->dspSettings.SrcChannelCount,
|
|
instance->dspSettings.DstChannelCount,
|
|
instance->dspSettings.pMatrixCoefficients,
|
|
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->handle = NULL;
|
|
|
|
instance->isStatic = isStatic;
|
|
instance->loop = 0;
|
|
instance->destroyOnFinish = 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;
|
|
|
|
FAudio_CreateSourceVoice(
|
|
device->handle,
|
|
&instance->handle,
|
|
&instance->format,
|
|
FAUDIO_VOICE_USEFILTER,
|
|
FAUDIO_DEFAULT_FREQ_RATIO,
|
|
isStatic ? NULL : &device->voiceCallbacks,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (instance->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->effectChainAttached = 0;
|
|
instance->effectVoice = NULL;
|
|
instance->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;
|
|
|
|
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;
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance* instance)
|
|
{
|
|
/* NOTE: this function returns samples per channel, not total samples */
|
|
uint32_t samples = stb_vorbis_get_samples_float_interleaved(
|
|
instance->soundData.streamingSound.fileHandle,
|
|
instance->format.nChannels,
|
|
device->streamStagingBuffer,
|
|
STREAMING_BUFFER_SIZE
|
|
);
|
|
|
|
uint32_t sampleCount = samples * instance->format.nChannels;
|
|
uint32_t bufferLength = sampleCount * sizeof(float);
|
|
|
|
uint8_t* nextBuffer = SDL_malloc(bufferLength);
|
|
SDL_memcpy(nextBuffer, device->streamStagingBuffer, bufferLength);
|
|
|
|
FAudioBuffer buffer;
|
|
buffer.AudioBytes = bufferLength;
|
|
buffer.pAudioData = nextBuffer;
|
|
buffer.PlayLength =
|
|
bufferLength /
|
|
instance->format.nChannels /
|
|
(instance->format.wBitsPerSample / 8);
|
|
buffer.PlayBegin = 0;
|
|
|
|
buffer.Flags = 0;
|
|
buffer.LoopBegin = 0;
|
|
buffer.LoopCount = 0;
|
|
buffer.LoopLength = 0;
|
|
buffer.pContext = instance; /* context for OnBufferEnd callback */
|
|
|
|
FAudioSourceVoice_SubmitSourceBuffer(instance->handle, &buffer, NULL);
|
|
|
|
/* We have reached the end of the file! */
|
|
if (sampleCount < STREAMING_BUFFER_SIZE)
|
|
{
|
|
if (instance->loop)
|
|
{
|
|
stb_vorbis_seek_start(instance->soundData.streamingSound.fileHandle);
|
|
}
|
|
else
|
|
{
|
|
instance->soundState = SoundState_Stopped;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance);
|
|
|
|
return instance->id;
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_StaticSound_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)
|
|
{
|
|
if (instance->loop)
|
|
{
|
|
instance->soundData.staticSound->buffer.LoopCount = FAUDIO_LOOP_INFINITE;
|
|
instance->soundData.staticSound->buffer.LoopBegin = instance->soundData.staticSound->loopStart;
|
|
instance->soundData.staticSound->buffer.LoopLength = instance->soundData.staticSound->loopLength;
|
|
}
|
|
else
|
|
{
|
|
instance->soundData.staticSound->buffer.LoopCount = 0;
|
|
instance->soundData.staticSound->buffer.LoopBegin = 0;
|
|
instance->soundData.staticSound->buffer.LoopLength = 0;
|
|
}
|
|
|
|
FAudioSourceVoice_SubmitSourceBuffer(instance->handle, &instance->soundData.staticSound->buffer, NULL);
|
|
}
|
|
|
|
FAudioSourceVoice_Start(instance->handle, 0, 0);
|
|
instance->soundState = SoundState_Playing;
|
|
}
|
|
|
|
void FAudioGMS_StaticSound_PlayOneOff(double staticSoundID, double pan, double pitch, double volume)
|
|
{
|
|
RETURN_ON_NULL_DEVICE()
|
|
FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
|
|
|
|
if (staticSound != NULL)
|
|
{
|
|
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound);
|
|
instance->destroyOnFinish = 1;
|
|
FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, pan, pitch, volume);
|
|
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
|
|
}
|
|
else
|
|
{
|
|
Log("StaticSound_PlayOneOff: Invalid static sound ID! Did you destroy this sound?");
|
|
}
|
|
}
|
|
|
|
void FAudioGMS_StaticSound_PlayOneOffSpatial(double staticSoundID, double x, double y, double z, double pitch, double volume)
|
|
{
|
|
RETURN_ON_NULL_DEVICE()
|
|
FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
|
|
|
|
if (staticSound != NULL)
|
|
{
|
|
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound);
|
|
instance->destroyOnFinish = 1;
|
|
FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, 0, pitch, volume);
|
|
FAudioGMS_INTERNAL_StaticSound_AddEmitter(instance, x, y, z);
|
|
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
|
|
}
|
|
else
|
|
{
|
|
Log("StaticSound_PlayOneOffSpatial: Invalid static sound ID! Did you destroy this sound?");
|
|
}
|
|
}
|
|
|
|
double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double loop)
|
|
{
|
|
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);
|
|
instance->loop = (uint8_t)loop;
|
|
FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, pan, pitch, volume);
|
|
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
|
|
return (double)instance->id;
|
|
}
|
|
else
|
|
{
|
|
Log("StaticSound_Play: Invalid static sound ID! Did you destroy this sound?");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
double FAudioGMS_StaticSound_PlaySpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double loop)
|
|
{
|
|
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);
|
|
instance->loop = (uint8_t)loop;
|
|
FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, 0, pitch, volume);
|
|
FAudioGMS_INTERNAL_StaticSound_AddEmitter(instance, x, y, z);
|
|
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
|
|
return (double)instance->id;
|
|
}
|
|
else
|
|
{
|
|
Log("StaticSound_PlaySpatial: Invalid static sound ID! Did you destroy this sound?");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void FAudioGMS_SoundInstance_Play(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;
|
|
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
|
|
}
|
|
}
|
|
|
|
void FAudioGMS_SoundInstance_Pause(double soundInstanceID)
|
|
{
|
|
RETURN_ON_NULL_DEVICE()
|
|
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
|
|
|
|
if (instance != NULL)
|
|
{
|
|
if (instance->soundState == SoundState_Playing)
|
|
{
|
|
FAudioSourceVoice_Stop(instance->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?");
|
|
}
|
|
}
|
|
|
|
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->handle, 0, 0);
|
|
FAudioSourceVoice_FlushSourceBuffers(instance->handle);
|
|
|
|
if (!instance->isStatic)
|
|
{
|
|
stb_vorbis_seek_start(instance->soundData.streamingSound.fileHandle); /* 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_SetPan(double soundInstanceID, double pan)
|
|
{
|
|
RETURN_ON_NULL_DEVICE()
|
|
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
|
|
|
|
if (instance != NULL)
|
|
{
|
|
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);
|
|
|
|
instance->adjustingVolumeOverTime = 0; /* override volume adjustment over time */
|
|
if (instance != NULL)
|
|
{
|
|
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)
|
|
{
|
|
Log("Not a 3D sound!");
|
|
return;
|
|
}
|
|
|
|
instance->emitter->Velocity.x = x - instance->emitter->Position.x;
|
|
instance->emitter->Velocity.y = y - instance->emitter->Position.y;
|
|
instance->emitter->Velocity.z = z - instance->emitter->Position.z;
|
|
|
|
instance->emitter->Position.x = x;
|
|
instance->emitter->Position.y = y;
|
|
instance->emitter->Position.z = z;
|
|
}
|
|
}
|
|
|
|
void FAudioGMS_SoundInstance_SetTrackPosition(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)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
|
|
}
|
|
|
|
if (instance->isStatic)
|
|
{
|
|
instance->soundData.staticSound->buffer.PlayBegin = instance->soundData.staticSound->samplesPerSecond * trackPositionInSeconds;
|
|
}
|
|
else
|
|
{
|
|
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, sampleFrame);
|
|
}
|
|
|
|
if (currentState == SoundState_Playing)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
void FAudioGMS_SetListenerPosition(double x, double y, double z)
|
|
{
|
|
RETURN_ON_NULL_DEVICE()
|
|
device->listener.Velocity.x = x - device->listener.Position.x;
|
|
device->listener.Velocity.y = y - device->listener.Position.y;
|
|
device->listener.Velocity.z = z - device->listener.Position.z;
|
|
|
|
device->listener.Position.x = x;
|
|
device->listener.Position.y = y;
|
|
device->listener.Position.z = z;
|
|
}
|
|
|
|
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);
|
|
FAudioVoice_DestroyVoice(instance->handle);
|
|
if (!instance->isStatic)
|
|
{
|
|
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->fAudioEffectChain.EffectCount = 0;
|
|
effectChain->fAudioEffectChain.pEffectDescriptors = NULL;
|
|
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)
|
|
{
|
|
FAPO* reverb;
|
|
FAudioCreateReverb(&reverb, 0);
|
|
|
|
effectChain->fAudioEffectChain.EffectCount += 1;
|
|
effectChain->fAudioEffectChain.pEffectDescriptors = SDL_realloc(effectChain->fAudioEffectChain.pEffectDescriptors, effectChain->fAudioEffectChain.EffectCount * sizeof(FAudioEffectDescriptor));
|
|
|
|
FAudioEffectDescriptor* reverbDescriptor = &effectChain->fAudioEffectChain.pEffectDescriptors[effectChain->fAudioEffectChain.EffectCount - 1];
|
|
|
|
reverbDescriptor->InitialState = 1;
|
|
reverbDescriptor->OutputChannels = device->deviceDetails.OutputFormat.Format.nChannels == 6 ? 6 : 1;
|
|
reverbDescriptor->pEffect = reverb;
|
|
|
|
effectChain->effectTypes = SDL_realloc(effectChain->effectTypes, effectChain->fAudioEffectChain.EffectCount * sizeof(FAudioGMS_EffectType));
|
|
effectChain->effectTypes[effectChain->fAudioEffectChain.EffectCount - 1] = EffectType_Reverb;
|
|
|
|
effectChain->effectParameters = SDL_realloc(effectChain->effectParameters, effectChain->fAudioEffectChain.EffectCount * sizeof(FAudioGMS_EffectParameters));
|
|
SDL_memcpy(&effectChain->effectParameters[effectChain->fAudioEffectChain.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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
uint32_t i;
|
|
|
|
if (instance != NULL && effectChain != NULL)
|
|
{
|
|
if (!instance->effectChainAttached)
|
|
{
|
|
FAudio_CreateSubmixVoice(
|
|
device->handle,
|
|
&instance->effectVoice,
|
|
1, /* FIXME: is this correct? */
|
|
device->deviceDetails.OutputFormat.Format.nSamplesPerSec,
|
|
0,
|
|
0,
|
|
NULL,
|
|
&effectChain->fAudioEffectChain);
|
|
|
|
instance->effectSends.SendCount = 2;
|
|
instance->effectSends.pSends = SDL_malloc(2 * sizeof(FAudioSendDescriptor));
|
|
|
|
instance->effectSends.pSends[0].Flags = 0;
|
|
instance->effectSends.pSends[0].pOutputVoice = device->masteringVoice;
|
|
instance->effectSends.pSends[1].Flags = 0;
|
|
instance->effectSends.pSends[1].pOutputVoice = instance->effectVoice;
|
|
|
|
instance->effectChainAttached = 1;
|
|
}
|
|
else
|
|
{
|
|
FAudioVoice_SetEffectChain(
|
|
instance->effectVoice,
|
|
&effectChain->fAudioEffectChain);
|
|
}
|
|
|
|
for (i = 0; i < effectChain->fAudioEffectChain.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(
|
|
instance->effectVoice,
|
|
i,
|
|
parameters,
|
|
parametersSize,
|
|
0);
|
|
}
|
|
|
|
FAudioVoice_SetOutputVoices(
|
|
instance->handle,
|
|
&instance->effectSends);
|
|
|
|
/* TODO: move this to a different API call */
|
|
float *outputMatrix = instance->dspSettings.pMatrixCoefficients;
|
|
|
|
outputMatrix[0] = effectGain;
|
|
if (instance->dspSettings.SrcChannelCount == 2)
|
|
{
|
|
outputMatrix[1] = effectGain;
|
|
}
|
|
|
|
FAudioVoice_SetOutputMatrix(
|
|
instance->handle,
|
|
instance->effectVoice,
|
|
instance->dspSettings.SrcChannelCount,
|
|
1,
|
|
outputMatrix,
|
|
0);
|
|
|
|
instance->effectGain = effectGain;
|
|
}
|
|
}
|
|
|
|
static void FAudioGMS_INTERNAL_EffectChain_Destroy(FAudioGMS_EffectChain *effectChain)
|
|
{
|
|
uint32_t i;
|
|
if (effectChain != NULL)
|
|
{
|
|
device->effectChains[effectChain->id] = NULL;
|
|
IdStack_Push(&device->effectChainIndexStack, effectChain->id);
|
|
|
|
for (i = 0; i < effectChain->fAudioEffectChain.EffectCount; i += 1)
|
|
{
|
|
FAPOBase_Release((FAPOBase*)effectChain->fAudioEffectChain.pEffectDescriptors[i].pEffect);
|
|
}
|
|
|
|
SDL_free(effectChain->effectParameters);
|
|
SDL_free(effectChain->effectTypes);
|
|
SDL_free(effectChain->fAudioEffectChain.pEffectDescriptors);
|
|
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->handle, &state, FAUDIO_VOICE_NOSAMPLESPLAYED);
|
|
|
|
if (state.BuffersQueued == 0)
|
|
{
|
|
FAudioGMS_INTERNAL_SoundInstance_Destroy(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]);
|
|
}
|
|
|
|
FAudio_Release(device->handle);
|
|
SDL_free(device);
|
|
device = NULL;
|
|
|
|
Log("FAudio cleaned up successfully!");
|
|
}
|