FAudioGMS/src/FAudioGMS.c

434 lines
12 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 "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 <stdlib.h>
#include <stdio.h>
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;
}