/* 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 "../lib/SDL/include/SDL.h" #include "FAudioGMS.h" #include "../lib/FAudio/include/FAPOBase.h" #include "../lib/FAudio/include/FAudioFX.h" #include "../lib/FAudio/include/F3DAudio.h" #include "../lib/FAudio/include/FAudio.h" #define DR_WAV_IMPLEMENTATION #include "../lib/dr_wav.h" #include static inline void Log(char *string) { printf("%s\n", string); fflush(stdout); } 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; } FAudioGMS_StaticSound; 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 reverb; float lowPassFilter; float highPassFilter; float bandPassFilter; uint8_t reverbAttached; FAudioSubmixVoice* reverbVoice; FAudioVoiceSends reverbSends; uint8_t isStatic; uint8_t destroyOnFinish; union { FAudioGMS_StaticSound *staticSound; /* TODO: streaming */ } parent; } FAudioGMS_SoundInstance; typedef struct FAudioGMS_Device { FAudio* handle; FAudioDeviceDetails deviceDetails; FAudioMasteringVoice *masteringVoice; FAudioGMS_StaticSound **staticSounds; uint32_t staticSoundCount; FAudioGMS_SoundInstance **soundInstances; uint32_t soundInstanceCount; } FAudioGMS_Device; static FAudioGMS_Device *device = NULL; void FAudioGMS_Init() { 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->staticSounds = NULL; device->staticSoundCount = 0; device->soundInstances = NULL; device->soundInstanceCount = 0; Log("FAudio initialized successfully!"); printf("Device: %ls", device->deviceDetails.DisplayName); fflush(stdout); } static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance* instance) { if (instance != NULL) { device->soundInstances[instance->id] = NULL; FAudioSourceVoice_Stop(instance->handle, 0, 0); FAudioSourceVoice_FlushSourceBuffers(instance->handle); FAudioVoice_DestroyVoice(instance->handle); SDL_free(instance->dspSettings.pMatrixCoefficients); SDL_free(instance); } } void FAudioGMS_SoundInstance_Destroy(double id) { FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[(uint32_t)id]); } void FAudioGMS_SoundInstance_DestroyWhenFinished(double id) { FAudioGMS_SoundInstance *instance = device->soundInstances[(uint32_t)id]; if (instance != NULL) { instance->destroyOnFinish = 1; } } /* FIXME: 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; SDL_free((void*)sound->buffer.pAudioData); SDL_free(sound); } } void FAudioGMS_StaticSound_Destroy(double id) { FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[(uint32_t)id]); } void FAudioGMS_Update() { uint32_t i; for (i = 0; i < device->soundInstanceCount; i += 1) { FAudioGMS_SoundInstance* instance = device->soundInstances[i]; if (instance != NULL) { if (instance->destroyOnFinish) { FAudioVoiceState state; FAudioSourceVoice_GetState(instance->handle, &state, FAUDIO_VOICE_NOSAMPLESPLAYED); if (state.BuffersQueued == 0) { FAudioGMS_INTERNAL_SoundInstance_Destroy(instance); } } } } } /* 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) { 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); } sound->buffer.AudioBytes = (uint32_t)(frameCount * 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; /* FIXME: id re-use system */ sound->id = device->staticSoundCount; device->staticSounds = SDL_realloc(device->staticSounds, (device->staticSoundCount + 1) * sizeof(FAudioGMS_StaticSound*)); device->staticSounds[device->staticSoundCount] = sound; device->staticSoundCount += 1; return (double)sound->id; } static void FAudioGMS_INTERNAL_SoundInstance_AttachReverb(FAudioGMS_SoundInstance* instance) { if (!instance->reverbAttached) { /* Init reverb */ FAPO* reverb; FAudioCreateReverb(&reverb, 0); FAudioEffectChain* reverbChain = SDL_malloc(sizeof(FAudioEffectChain)); reverbChain->EffectCount = 1; reverbChain->pEffectDescriptors = SDL_malloc(sizeof(FAudioEffectDescriptor)); FAudioEffectDescriptor* reverbDescriptor = reverbChain->pEffectDescriptors; reverbDescriptor->InitialState = 1; reverbDescriptor->OutputChannels = device->deviceDetails.OutputFormat.Format.nChannels == 6 ? 6 : 1; reverbDescriptor->pEffect = reverb; FAudio_CreateSubmixVoice( device->handle, &instance->reverbVoice, 1, /* omnidirectional reverb */ device->deviceDetails.OutputFormat.Format.nSamplesPerSec, 0, 0, NULL, reverbChain); FAPOBase_Release((FAPOBase*)reverb); SDL_free(reverbChain->pEffectDescriptors); SDL_free(reverbChain); FAudioFXReverbParameters* reverbParams = SDL_malloc(sizeof(FAudioFXReverbParameters)); 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; FAudioVoice_SetEffectParameters( instance->reverbVoice, 0, reverbParams, sizeof(FAudioFXReverbParameters), 0); SDL_free(reverbParams); /* Init reverb sends */ instance->reverbSends.SendCount = 2; instance->reverbSends.pSends = SDL_malloc(2 * sizeof(FAudioSendDescriptor)); instance->reverbSends.pSends[0].Flags = 0; instance->reverbSends.pSends[0].pOutputVoice = device->masteringVoice; instance->reverbSends.pSends[1].Flags = 0; instance->reverbSends.pSends[1].pOutputVoice = instance->reverbVoice; instance->reverbAttached = 1; } FAudioVoice_SetOutputVoices( instance->handle, &instance->reverbSends); } static void FAudioGMS_INTERNAL_SoundInstance_SetReverb(FAudioGMS_SoundInstance *instance, float reverb) { if (!instance->reverbAttached) { FAudioGMS_INTERNAL_SoundInstance_AttachReverb(instance); } float* outputMatrix = instance->dspSettings.pMatrixCoefficients; outputMatrix[0] = reverb; if (instance->dspSettings.SrcChannelCount == 2) { outputMatrix[1] = reverb; } FAudioVoice_SetOutputMatrix( instance->handle, instance->reverbVoice, instance->dspSettings.SrcChannelCount, 1, outputMatrix, 0); instance->reverb = reverb; } 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; } 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; } 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; } 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_SetPitch(FAudioGMS_SoundInstance* instance, float pitch) { instance->pitch = pitch; pitch -= 1.0; /* default pitch is 1.0 as per GM standard */ pitch = SDL_max(-1.0, SDL_min(1.0, pitch)); FAudioSourceVoice_SetFrequencyRatio( instance->handle, SDL_powf(2.0f, pitch), 0 ); } static void FAudioGMS_INTERNAL_SoundInstance_SetVolume(FAudioGMS_SoundInstance* instance, float volume) { FAudioVoice_SetVolume(instance->handle, volume, 0); } static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound( FAudioGMS_StaticSound *staticSound, double pan, double pitch, double volume, double reverb ) { FAudioGMS_SoundInstance *instance = SDL_malloc(sizeof(FAudioGMS_SoundInstance)); instance->handle = NULL; instance->parent.staticSound = staticSound; instance->isStatic = 1; instance->loop = 0; instance->destroyOnFinish = 0; instance->format.wFormatTag = 3; instance->format.wBitsPerSample = 32; instance->format.nChannels = staticSound->channels; instance->format.nBlockAlign = (uint16_t)(4 * staticSound->channels); instance->format.nSamplesPerSec = staticSound->samplesPerSecond; instance->format.nAvgBytesPerSec = instance->format.nBlockAlign * staticSound->samplesPerSecond; FAudio_CreateSourceVoice( device->handle, &instance->handle, &instance->format, FAUDIO_VOICE_USEFILTER, FAUDIO_DEFAULT_FREQ_RATIO, NULL, NULL, NULL); if (instance->handle == NULL) { Log("SoundInstance failed to initialize!"); SDL_free(instance); return NULL; } instance->dspSettings.DopplerFactor = 1.0f; instance->dspSettings.SrcChannelCount = staticSound->channels; 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); FAudioGMS_INTERNAL_SoundInstance_SetPan(instance, pan); FAudioGMS_INTERNAL_SoundInstance_SetPitch(instance, pitch); FAudioGMS_INTERNAL_SoundInstance_SetVolume(instance, volume); instance->reverbAttached = 0; instance->reverb = 0.0f; instance->lowPassFilter = 0.0f; instance->highPassFilter = 0.0f; instance->bandPassFilter = 0.0f; if (reverb > 0.0f) { FAudioGMS_INTERNAL_SoundInstance_SetReverb(instance, reverb); } instance->soundState = SoundState_Stopped; /* FIXME: id re-use system */ instance->id = device->soundInstanceCount; device->soundInstances = SDL_realloc(device->soundInstances, (device->soundInstanceCount + 1) * sizeof(FAudioGMS_SoundInstance*)); device->soundInstances[device->soundInstanceCount] = instance; device->soundInstanceCount += 1; return instance; } static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance* instance) { if (instance->isStatic) { if (instance->soundState == SoundState_Playing) { return; } if (instance->loop) { instance->parent.staticSound->buffer.LoopCount = FAUDIO_LOOP_INFINITE; instance->parent.staticSound->buffer.LoopBegin = instance->parent.staticSound->loopStart; instance->parent.staticSound->buffer.LoopLength = instance->parent.staticSound->loopLength; } else { instance->parent.staticSound->buffer.LoopCount = 0; instance->parent.staticSound->buffer.LoopBegin = 0; instance->parent.staticSound->buffer.LoopLength = 0; } FAudioSourceVoice_SubmitSourceBuffer(instance->handle, &instance->parent.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, double reverb) { FAudioGMS_StaticSound* staticSound = device->staticSounds[(uint32_t)staticSoundID]; if (staticSound != NULL) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound, pan, pitch, volume, reverb); instance->destroyOnFinish = 1; FAudioGMS_INTERNAL_SoundInstance_Play(instance); } else { Log("StaticSound_PlayOneOff: Invalid static sound ID! Did you destroy this sound?"); } } double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double reverb, double loop) { FAudioGMS_StaticSound* staticSound = device->staticSounds[(uint32_t)staticSoundID]; if (staticSound != NULL) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound, pan, pitch, volume, reverb); instance->loop = (uint8_t)loop; FAudioGMS_INTERNAL_SoundInstance_Play(instance); return (double)instance->id; } else { Log("StaticSound_Play: Invalid static sound ID! Did you destroy this sound?"); return -1; } } void FAudioGMS_SoundInstance_Play(double id) { FAudioGMS_SoundInstance *instance = device->soundInstances[(uint32_t)id]; if (instance != NULL) { FAudioGMS_INTERNAL_SoundInstance_Play(instance); } } void FAudioGMS_SoundInstance_Pause(double id) { FAudioGMS_SoundInstance* instance = device->soundInstances[(uint32_t)id]; if (instance != NULL) { if (instance->isStatic) { if (instance->soundState == SoundState_Playing) { FAudioSourceVoice_Stop(instance->handle, 0, 0); instance->soundState = SoundState_Paused; } } } else { Log("SoundInstance_Pause: Invalid sound instance ID! Did you destroy this instance?"); } } void FAudioGMS_SoundInstance_Stop(double id) { FAudioGMS_SoundInstance* instance = device->soundInstances[(uint32_t)id]; if (instance != NULL) { if (instance->isStatic) { FAudioSourceVoice_ExitLoop(instance->handle, 0); } } else { Log("SoundInstance_Stop: Invalid sound instance ID! Did you destroy this instance?"); } } void FAudioGMS_Destroy() { uint32_t i = 0; for (i = 0; i < device->staticSoundCount; i += 1) { FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[i]); } for (i = 0; i < device->soundInstanceCount; i += 1) { FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[i]); } FAudio_Release(device->handle); SDL_free(device); device = NULL; Log("FAudio cleaned up successfully!"); }