/* 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 "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 #include static inline void Log(char *string) { printf("%s", 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; } FAudioGMS_SoundInstance; typedef struct FAudioGMS_Device { FAudio* handle; FAudioDeviceDetails deviceDetails; FAudioMasteringVoice *masteringVoice; FAudioSubmixVoice *reverbVoice; FAudioVoiceSends reverbSends; FAudioGMS_StaticSound **staticSounds; uint32_t staticSoundCount; FAudioGMS_SoundInstance **staticSoundInstances; uint32_t staticSoundInstanceCount; } FAudioGMS_Device; static FAudioGMS_Device *device = NULL; void FAudioGMS_Init() { device = malloc(sizeof(FAudioGMS_Device)); uint32_t result = FAudioCreate(&device->handle, 0, FAUDIO_DEFAULT_PROCESSOR); if (result != 0) { Log("Failed to create device! Bailing!"); 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); 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); free(device); device = NULL; return; } /* Init reverb */ FAPO *reverb = malloc(sizeof(FAPO)); FAudioCreateReverb(&reverb, 0); FAudioEffectChain *reverbChain = malloc(sizeof(FAudioEffectChain)); reverbChain->EffectCount = 1; reverbChain->pEffectDescriptors = 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, &device->reverbVoice, 1, /* omnidirectional reverb */ device->deviceDetails.OutputFormat.Format.nSamplesPerSec, 0, 0, NULL, reverbChain); FAPOBase_Release((FAPOBase*)reverb); free(reverb); free(reverbChain->pEffectDescriptors); free(reverbChain); FAudioFXReverbParameters *reverbParams = 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( device->reverbVoice, 0, reverbParams, sizeof(FAudioFXReverbParameters), 0); free(reverbParams); /* Init reverb sends */ device->reverbSends.SendCount = 2; device->reverbSends.pSends = malloc(2 * sizeof(FAudioSendDescriptor)); device->reverbSends.pSends[0].Flags = 0; device->reverbSends.pSends[0].pOutputVoice = device->masteringVoice; device->reverbSends.pSends[1].Flags = 0; device->reverbSends.pSends[1].pOutputVoice = device->reverbVoice; device->staticSounds = NULL; device->staticSoundCount = 0; Log("FAudio initialized successfully!"); } void FAudioGMS_Update() { } /* 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 = 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 = realloc(device->staticSounds, (device->staticSoundCount + 1) * sizeof(FAudioGMS_StaticSound*)); device->staticSounds[device->staticSoundCount] = sound; device->staticSoundCount += 1; return (double)sound->id; } double FAudioGMS_SoundInstance_CreateFromStaticSound(double id) { FAudioGMS_StaticSound *staticSound = device->staticSounds[(uint32_t)id]; FAudioGMS_SoundInstance *instance = malloc(sizeof(FAudioGMS_SoundInstance)); instance->handle = NULL; 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!"); free(instance); return -1; } 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 = malloc(memsize); memset(instance->dspSettings.pMatrixCoefficients, 0, memsize); SetPanMatrixCoefficients(instance); FAudioVoice_SetOutputVoices( instance->handle, &device->reverbSends); instance->soundState = SoundState_Stopped; /* FIXME: id re-use system */ instance->id = device->staticSoundInstanceCount; device->staticSoundInstances = realloc(device->staticSoundInstances, (device->staticSoundCount + 1) * sizeof(FAudioGMS_SoundInstance*)); device->staticSoundInstances[device->staticSoundInstanceCount] = instance; device->staticSoundInstanceCount += 1; return (double)instance->id; } void FAudioGMS_SoundInstance_Destroy(double id) { FAudioGMS_SoundInstance *instance = device->staticSoundInstances[(uint32_t)id]; if (instance != NULL) { FAudioVoice_DestroyVoice(instance->handle); free(instance->dspSettings.pMatrixCoefficients); free(instance); device->staticSoundInstances[(uint32_t)id] = NULL; } } void FAudioGMS_StaticSound_Destroy(double id) { FAudioGMS_StaticSound *sound = device->staticSounds[(uint32_t)id]; if (sound != NULL) { free((void*)sound->buffer.pAudioData); free(sound); device->staticSounds[(uint32_t)id] = NULL; } } void FAudioGMS_Destroy() { FAudio_Release(device->handle); free(device); device = NULL; }