/* 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 * */ #include "SDL.h" #include "FAudioGMS.h" #include "F3DAudio.h" #include "FAPOBase.h" #include "FAudioFX.h" #include "FAudio.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" static inline void Log(char *string) { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s\n", string); } 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_EffectType { EffectType_Reverb } FAudioGMS_EffectType; typedef union FAudioGMS_EffectParameters { FAudioFXReverbParameters reverbParameters; } FAudioGMS_EffectParameters; typedef struct FAudioGMS_EffectChain { uint32_t id; FAudioEffectChain fAudioEffectChain; FAudioSubmixVoice *effectVoice; FAudioVoiceSends effectSends; FAudioGMS_EffectType *effectTypes; /* length equal to effectCount */ union FAudioGMS_EffectParameters *effectParameters; /* length equal to effectCount */ } FAudioGMS_EffectChain; typedef enum FAudioGMS_SoundState { SoundState_Playing, SoundState_Paused, SoundState_Stopped } FAudioGMS_SoundState; typedef struct FAudioGMS_Voice { FAudioVoice *handle; FAudioVoiceSends sends; uint8_t effectChainAttached; FAudioGMS_EffectChain *effectChain; float effectGain; } FAudioGMS_Voice; typedef struct FAudioGMS_StaticSound { uint32_t id; SDL_AudioSpec spec; FAudioBuffer buffer; uint32_t lengthInSeconds; } FAudioGMS_StaticSound; /* if we don't triple buffer, we'll microstutter the voice while waiting for decode */ #define STREAMING_BUFFER_COUNT 3 #define DEFAULT_STREAMING_BUFFER_SIZE 32768 typedef struct FAudioGMS_StreamingSound { stb_vorbis *vorbisHandle; void *vorbisData; stb_vorbis_info info; float *streamBuffer[STREAMING_BUFFER_COUNT]; uint32_t streamBufferSize; uint32_t nextStreamBufferIndex; /* it's a ring buffer! */ uint32_t buffersLoadedCount; /* how many buffers are we buffering? */ uint32_t mostRecentSampleOffset; /* used for calculating track position */ } 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; uint8_t destroyTimerActive; double destroyTimer; union { FAudioGMS_StaticSound *staticSound; /* static sounds are loaded separately, so they do not belong to the instance */ FAudioGMS_StreamingSound streamingSound; } soundData; }; 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; SDL_mutex *destroyMutex; } FAudioGMS_Device; static FAudioGMS_Device *device = NULL; /* Game Maker doesn't let us control execution order on clean up so we have these * stupid macros to help us not crash on exit */ #define RETURN_ON_NULL_DEVICE_VOID \ if (device == NULL) \ { \ return; \ } #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_AddBuffers(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance *instance, uint32_t operationSet); static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_OnBufferEndCallback( FAudioVoiceCallback *callback, FAudioGMS_SoundInstance *instance) { uint32_t i; if (instance->isStatic) { FAudioVoiceState state; FAudioSourceVoice_GetState(instance->voice.handle, &state, 0); /* We don't want to stop if we re-queued sound for SetLoop or something so we check that we're actually done */ if (state.BuffersQueued == 0) { FAudioGMS_INTERNAL_SoundInstance_Stop(instance); } } else { instance->soundData.streamingSound.buffersLoadedCount -= 1; instance->soundData.streamingSound.mostRecentSampleOffset += instance->soundData.streamingSound.streamBufferSize / (instance->format.nChannels * sizeof(float)); instance->soundData.streamingSound.mostRecentSampleOffset %= instance->playLength; if (stb_vorbis_get_sample_offset(instance->soundData.streamingSound.vorbisHandle) >= stb_vorbis_stream_length_in_samples(instance->soundData.streamingSound.vorbisHandle)) { FAudioGMS_INTERNAL_SoundInstance_Stop(instance); } else if (instance->soundState != SoundState_Stopped) { FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); } /* SDL_LogInfo( SDL_LOG_CATEGORY_APPLICATION, "instance ID: %p, current streaming buffer offset: %u", instance, instance->soundData.streamingSound.mostRecentSampleOffset); */ } if (instance->soundState == SoundState_Stopped) { if (instance->queuedSoundInstance != NULL) { FAudioGMS_INTERNAL_SoundInstance_Play(instance->queuedSoundInstance, 0); instance->queuedSoundInstance = NULL; } if (instance->destroyOnFinish) { SDL_LockMutex(device->destroyMutex); /* this is a callback so we don't want to immediately destroy and screw up data */ instance->destroyTimerActive = 1; instance->destroyTimer = 10; /* a little wiggle room */ if (instance->voice.effectChainAttached) { /* If we have active reverb we don't want to clean up until the decay time is over */ for (i = 0; i < instance->voice.effectChain->fAudioEffectChain.EffectCount; i += 1) { if (instance->voice.effectChain->effectTypes[i] == EffectType_Reverb) { instance->destroyTimer += instance->voice.effectChain->effectParameters[i] .reverbParameters.DecayTime; } } } SDL_UnlockMutex(device->destroyMutex); } } } 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->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; device->destroyMutex = SDL_CreateMutex(); Log("FAudio initialized successfully!"); } double FAudioGMS_StaticSound_LoadWAV(char *filePath) { RETURN_ON_NULL_DEVICE(-1.0) FAudioGMS_StaticSound *sound = SDL_malloc(sizeof(FAudioGMS_StaticSound)); if (SDL_LoadWAV(filePath, &sound->spec, &sound->buffer.pAudioData, &sound->buffer.AudioBytes) == NULL) { Log("Error opening WAV file: "); Log(filePath); return -1; } sound->buffer.Flags = 0; sound->buffer.LoopBegin = 0; sound->buffer.LoopCount = 0; sound->buffer.LoopLength = 0; sound->buffer.PlayBegin = 0; sound->buffer.PlayLength = sound->buffer.AudioBytes / (uint16_t)(sound->spec.channels * SDL_AUDIO_BITSIZE(sound->spec.format) / 8); sound->buffer.pContext = NULL; sound->lengthInSeconds = sound->buffer.PlayLength / sound->spec.freq; 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; } /* 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; } } } } static void FAudioGMS_INTERNAL_SoundInstance_ApplyPan(FAudioGMS_SoundInstance* instance) { 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_SetPan(FAudioGMS_SoundInstance *instance, float pan) { pan = SDL_max(-1.0, SDL_min(1.0, pan)); instance->pan = pan; FAudioGMS_INTERNAL_SoundInstance_ApplyPan(instance); } 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, uint16_t bitSize, 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->destroyTimer = 0; instance->destroyTimerActive = 0; instance->isGlobalPaused = 0; if (isStatic) { instance->format.wFormatTag = FAUDIO_FORMAT_PCM; instance->format.wBitsPerSample = bitSize; instance->format.nChannels = channelCount; instance->format.nBlockAlign = (uint16_t)(channelCount * instance->format.wBitsPerSample / 8); instance->format.nSamplesPerSec = samplesPerSecond; instance->format.nAvgBytesPerSec = instance->format.nBlockAlign * samplesPerSecond; } else { instance->format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; instance->format.wBitsPerSample = bitSize; instance->format.nChannels = channelCount; instance->format.nBlockAlign = (uint16_t)(channelCount * instance->format.wBitsPerSample / 8); 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, &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.effectChain = 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->spec.channels, staticSound->spec.freq, SDL_AUDIO_BITSIZE(staticSound->spec.format), 1); instance->isStatic = 1; instance->soundData.staticSound = staticSound; instance->playLength = instance->soundData.staticSound->buffer.PlayLength; 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_AddBuffers(FAudioGMS_SoundInstance *instance) { uint32_t i; for (i = 0; i < STREAMING_BUFFER_COUNT - instance->soundData.streamingSound.buffersLoadedCount; i += 1) { uint32_t defaultRequestedSampleCount = instance->soundData.streamingSound.streamBufferSize / (instance->format.nChannels * sizeof(float)); uint32_t requestedSampleCount = defaultRequestedSampleCount; if (instance->playLength != 0) { uint32_t distanceToEndPoint = (instance->playBegin + instance->playLength) - stb_vorbis_get_sample_offset(instance->soundData.streamingSound.vorbisHandle); requestedSampleCount = SDL_min(requestedSampleCount, distanceToEndPoint); } /* NOTE: this function returns samples per channel, not total samples */ uint32_t sampleCount = stb_vorbis_get_samples_float_interleaved( instance->soundData.streamingSound.vorbisHandle, instance->format.nChannels, instance->soundData.streamingSound .streamBuffer[instance->soundData.streamingSound.nextStreamBufferIndex], requestedSampleCount * instance->format.nChannels); /* If the stream ran out and we need to loop, seek to beginning of play region and fill in the remaining samples */ if (instance->loop && sampleCount < defaultRequestedSampleCount) { uint32_t fillInSampleCount = defaultRequestedSampleCount - sampleCount; stb_vorbis_seek(instance->soundData.streamingSound.vorbisHandle, instance->playBegin); uint32_t remainingSampleCount = stb_vorbis_get_samples_float_interleaved( instance->soundData.streamingSound.vorbisHandle, instance->format.nChannels, &instance->soundData.streamingSound .streamBuffer[instance->soundData.streamingSound.nextStreamBufferIndex][sampleCount * instance->format.nChannels], fillInSampleCount * instance->format.nChannels); sampleCount += remainingSampleCount; } FAudioBuffer buffer; buffer.AudioBytes = sampleCount * instance->format.nChannels * sizeof(float); buffer.pAudioData = (uint8_t *)instance->soundData.streamingSound .streamBuffer[instance->soundData.streamingSound.nextStreamBufferIndex]; 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.nextStreamBufferIndex = (instance->soundData.streamingSound.nextStreamBufferIndex + 1) % STREAMING_BUFFER_COUNT; instance->soundData.streamingSound.buffersLoadedCount += 1; } } double FAudioGMS_StreamingSound_LoadOGG(char *filePath, double bufferSizeInBytes) { RETURN_ON_NULL_DEVICE(-1.0) uint32_t i; int error = 0; uint32_t bufferSizeInBytesInt = (uint32_t)bufferSizeInBytes; /* load entire vorbis file into memory first */ size_t vorbisSize; void *vorbisData = SDL_LoadFile(filePath, &vorbisSize); stb_vorbis *vorbisHandle = stb_vorbis_open_memory(vorbisData, (int)vorbisSize, &error, NULL); if (error != 0) { Log("Error opening OGG file!"); Log(filePath); return -1; } if (bufferSizeInBytesInt == 0) { bufferSizeInBytesInt = DEFAULT_STREAMING_BUFFER_SIZE; /* A reasonable default! */ } stb_vorbis_info info = stb_vorbis_get_info(vorbisHandle); FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_SoundInstance_Init(info.channels, info.sample_rate, 32, 0); instance->soundData.streamingSound.vorbisData = vorbisData; instance->soundData.streamingSound.vorbisHandle = vorbisHandle; instance->soundData.streamingSound.info = info; for (i = 0; i < STREAMING_BUFFER_COUNT; i += 1) { instance->soundData.streamingSound.streamBuffer[i] = SDL_malloc(bufferSizeInBytesInt); } instance->soundData.streamingSound.streamBufferSize = bufferSizeInBytesInt; instance->soundData.streamingSound.mostRecentSampleOffset = 0; instance->soundData.streamingSound.nextStreamBufferIndex = 0; instance->soundData.streamingSound.buffersLoadedCount = 0; instance->playLength = stb_vorbis_stream_length_in_samples(vorbisHandle); 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, uint32_t operationSet) { 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) { instance->soundData.staticSound->buffer.PlayBegin = instance->voice.handle->src.curBufferOffset; instance->soundData.staticSound->buffer.PlayLength = instance->playLength; FAudioSourceVoice_Stop(instance->voice.handle, 0, 0); FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle); } 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); } else { FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); } FAudioSourceVoice_Start(instance->voice.handle, 0, operationSet); instance->soundState = SoundState_Playing; } void FAudioGMS_SoundInstance_Play(double soundInstanceID) { RETURN_ON_NULL_DEVICE_VOID FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID); if (instance != NULL) { FAudioGMS_INTERNAL_SoundInstance_Play(instance, 0); } } FAUDIOGMSAPI void FAudioGMS_SoundInstance_QueueSyncPlay(double soundInstanceID) { RETURN_ON_NULL_DEVICE_VOID FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID); if (instance != NULL) { FAudioGMS_INTERNAL_SoundInstance_Play(instance, 1); /* operation set */ } } void FAudioGMS_SoundInstance_SyncPlay() { RETURN_ON_NULL_DEVICE_VOID FAudio_CommitOperationSet(device->handle, 1); } 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_VOID 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) { /* set before flush so we dont refill on OnBufferEnd callback */ instance->soundState = SoundState_Stopped; FAudioSourceVoice_Stop(instance->voice.handle, 0, 0); FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle); if (!instance->isStatic) { instance->soundData.streamingSound.buffersLoadedCount = 0; stb_vorbis_seek( instance->soundData.streamingSound.vorbisHandle, instance->playBegin); /* back to the start */ } } else { Log("SoundInstance_Stop: Invalid sound instance ID! Did you destroy " "this instance?"); } } void FAudioGMS_SoundInstance_Stop(double soundInstanceID) { RETURN_ON_NULL_DEVICE_VOID 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_VOID 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, 0); } } } void FAudioGMS_SoundInstance_SetPan(double soundInstanceID, double pan) { RETURN_ON_NULL_DEVICE_VOID 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_VOID 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_VOID 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_VOID 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_VOID 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; } } void FAudioGMS_SoundInstance_Set3DOrientation( double soundInstanceID, double xFront, double yFront, double zFront, double xTop, double yTop, double zTop) { RETURN_ON_NULL_DEVICE_VOID FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID); if (instance != NULL) { if (!instance->is3D) { Log("Not a 3D sound!"); return; } instance->emitter->OrientFront.x = xFront; instance->emitter->OrientFront.y = yFront; instance->emitter->OrientFront.z = zFront; instance->emitter->OrientTop.x = xTop; instance->emitter->OrientTop.y = yTop; instance->emitter->OrientTop.z = zTop; } } /* FIXME: this will die horribly if position is greater than total length */ void FAudioGMS_SoundInstance_SetTrackPositionInSeconds( double soundInstanceID, double trackPositionInSeconds) { RETURN_ON_NULL_DEVICE_VOID FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID); if (instance != NULL) { uint32_t sampleFrame = instance->soundData.staticSound->spec.freq * trackPositionInSeconds; FAudioGMS_SoundState currentState = instance->soundState; if (currentState == SoundState_Playing || !instance->isStatic) { FAudioSourceVoice_Stop(instance->voice.handle, 0, 0); FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle); } if (instance->isStatic) { instance->soundData.staticSound->buffer.PlayBegin = sampleFrame; } else { instance->soundData.streamingSound.buffersLoadedCount = 0; stb_vorbis_seek(instance->soundData.streamingSound.vorbisHandle, sampleFrame); FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); } if (currentState == SoundState_Playing) { FAudioGMS_INTERNAL_SoundInstance_Play(instance, 0); } } } 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.mostRecentSampleOffset + 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_VOID 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, 0); } else if (!instance->isStatic) { FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle); instance->soundData.streamingSound.buffersLoadedCount = 0; stb_vorbis_seek(instance->soundData.streamingSound.vorbisHandle, instance->playBegin); FAudioGMS_INTERNAL_SoundInstance_AddBuffers(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, 0); } } } } void FAudioGMS_SoundInstance_SetVolumeOverTime( double soundInstanceID, double volume, double milliseconds) { RETURN_ON_NULL_DEVICE_VOID 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.vorbisHandle); } } 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_VOID 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_VOID device->listener.Velocity.x = xVelocity; device->listener.Velocity.y = yVelocity; device->listener.Velocity.z = zVelocity; } void FAudioGMS_SetListenerOrientation( double xFront, double yFront, double zFront, double xTop, double yTop, double zTop) { RETURN_ON_NULL_DEVICE_VOID device->listener.OrientFront.x = xFront; device->listener.OrientFront.y = yFront; device->listener.OrientFront.z = zFront; device->listener.OrientTop.x = xTop; device->listener.OrientTop.y = yTop; device->listener.OrientTop.z = zTop; } static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance) { uint32_t i; if (instance != NULL) { device->soundInstances[instance->id] = NULL; IdStack_Push(&device->soundInstanceIndexStack, instance->id); FAudioGMS_INTERNAL_SoundInstance_Stop(instance); SDL_free(instance->voice.sends.pSends); FAudioVoice_DestroyVoice(instance->voice.handle); if (!instance->isStatic) { for (i = 0; i < STREAMING_BUFFER_COUNT; i += 1) { SDL_free(instance->soundData.streamingSound.streamBuffer[i]); } stb_vorbis_close(instance->soundData.streamingSound.vorbisHandle); SDL_free(instance->soundData.streamingSound.vorbisData); } 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_VOID 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_VOID 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_FreeWAV(sound->buffer.pAudioData); SDL_free(sound); } } void FAudioGMS_StaticSound_Destroy(double staticSoundID) { RETURN_ON_NULL_DEVICE_VOID 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->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; effectChain->fAudioEffectChain.EffectCount = 0; effectChain->fAudioEffectChain.pEffectDescriptors = NULL; effectChain->effectSends.SendCount = 1; effectChain->effectSends.pSends = SDL_malloc(sizeof(FAudioSendDescriptor)); effectChain->effectSends.pSends[0].Flags = 0; effectChain->effectSends.pSends[0].pOutputVoice = device->masteringVoice; FAudio_CreateSubmixVoice( device->handle, &effectChain->effectVoice, 1, /* FIXME: is this correct? */ device->deviceDetails.OutputFormat.Format.nSamplesPerSec, 0, 0, &effectChain->effectSends, &effectChain->fAudioEffectChain); return effectChain->id; } static void FAudioGMS_INTERNAL_EffectChain_AddReverb( FAudioGMS_EffectChain *effectChain, FAudioFXReverbParameters *reverbParameters) { uint32_t effectIndex; effectChain->fAudioEffectChain.EffectCount += 1; effectChain->fAudioEffectChain.pEffectDescriptors = SDL_realloc( effectChain->fAudioEffectChain.pEffectDescriptors, effectChain->fAudioEffectChain.EffectCount * sizeof(FAudioEffectDescriptor)); effectIndex = effectChain->fAudioEffectChain.EffectCount - 1; effectChain->effectTypes = SDL_realloc( effectChain->effectTypes, effectChain->fAudioEffectChain.EffectCount * sizeof(FAudioGMS_EffectType)); effectChain->effectTypes[effectIndex] = EffectType_Reverb; effectChain->effectParameters = SDL_realloc( effectChain->effectParameters, effectChain->fAudioEffectChain.EffectCount * sizeof(FAudioGMS_EffectParameters)); SDL_memcpy( &effectChain->effectParameters[effectIndex], reverbParameters, sizeof(FAudioFXReverbParameters)); FAPO *reverb; FAudioCreateReverb(&reverb, 0); FAudioEffectDescriptor *reverbDescriptor = &effectChain->fAudioEffectChain.pEffectDescriptors[effectIndex]; reverbDescriptor->InitialState = 1; reverbDescriptor->OutputChannels = device->deviceDetails.OutputFormat.Format.nChannels == 6 ? 6 : 1; reverbDescriptor->pEffect = reverb; FAudioVoice_SetEffectChain(effectChain->effectVoice, &effectChain->fAudioEffectChain); FAudioVoice_SetEffectParameters( effectChain->effectVoice, effectIndex, &effectChain->effectParameters[effectIndex].reverbParameters, sizeof(FAudioFXReverbParameters), 0); /* All the effect parameters are copied to the voice so we free here */ FAPOBase_Release((FAPOBase*)reverb); } void FAudioGMS_EffectChain_AddDefaultReverb(double effectChainID) { RETURN_ON_NULL_DEVICE_VOID 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_VOID 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->handle->outputChannels); outputMatrix[0] = effectGain; if (voice->handle->outputChannels == 2) { outputMatrix[1] = effectGain; } FAudioVoice_SetOutputMatrix( voice->handle, voice->effectChain->effectVoice, voice->handle->outputChannels, 1, outputMatrix, 0); voice->effectGain = effectGain; SDL_stack_free(outputMatrix); } } void FAudioGMS_SoundInstance_SetEffectGain(double soundInstanceID, double effectGain) { RETURN_ON_NULL_DEVICE_VOID FAudioGMS_SoundInstance *instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID); if (instance != NULL) { FAudioGMS_INTERNAL_Voice_SetEffectGain(&instance->voice, effectGain); } } static void FAudioGMS_INTERNAL_SetEffectChain( FAudioGMS_Voice *voice, FAudioGMS_EffectChain *effectChain, float effectGain) { if (!voice->effectChainAttached) { voice->sends.SendCount = 2; voice->sends.pSends = SDL_realloc(voice->sends.pSends, 2 * sizeof(FAudioSendDescriptor)); } /* 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 = effectChain->effectVoice; voice->effectChain = effectChain; voice->effectChainAttached = 1; FAudioVoice_SetOutputVoices(voice->handle, &voice->sends); FAudioGMS_INTERNAL_Voice_SetEffectGain(voice, effectGain); } void FAudioGMS_SoundInstance_SetEffectChain( double soundInstanceID, double effectChainID, double effectGain) { RETURN_ON_NULL_DEVICE_VOID 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); /* re-apply pan after setting chain because sends may have changed */ FAudioGMS_INTERNAL_SoundInstance_ApplyPan(instance); } } void FAudioGMS_SetMasteringEffectChain(double effectChainID, double effectGain) { RETURN_ON_NULL_DEVICE_VOID 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_VOID 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->fAudioEffectChain.pEffectDescriptors); SDL_free(effectChain->effectParameters); SDL_free(effectChain->effectTypes); SDL_free(effectChain); } } void FAudioGMS_EffectChain_Destroy(double effectChainID) { RETURN_ON_NULL_DEVICE_VOID FAudioGMS_EffectChain *effectChain = FAudioGMS_INTERNAL_LookupEffectChain((uint32_t)effectChainID); if (effectChain != NULL) { FAudioGMS_INTERNAL_EffectChain_Destroy(effectChain); } } void FAudioGMS_Update() { RETURN_ON_NULL_DEVICE_VOID 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); } SDL_LockMutex(device->destroyMutex); if (instance->destroyTimerActive) { instance->destroyTimer -= device->timestep; if (instance->destroyTimer <= 0) { FAudioGMS_INTERNAL_SoundInstance_Destroy(instance); } } SDL_UnlockMutex(device->destroyMutex); } } } void FAudioGMS_PauseAll() { RETURN_ON_NULL_DEVICE_VOID 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_VOID 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, 0); } } } void FAudioGMS_StopAll() { RETURN_ON_NULL_DEVICE_VOID 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_VOID 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_DestroyMutex(device->destroyMutex); SDL_free(device); device = NULL; Log("FAudio cleaned up successfully!"); }