/* 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" #define DISABLE_XNASONG #include "FAudioGMS.h" #include "FAPOBase.h" #include "FAudioFX.h" #include "F3DAudio.h" #include "FAudio.h" #define DR_WAV_IMPLEMENTATION #define DRWAV_MALLOC(sz) SDL_malloc((sz)) #define DRWAV_REALLOC(p, sz) SDL_realloc((p), (sz)) #define DRWAV_FREE(p) SDL_free((p)) #define DRWAV_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz)) #define DRWAV_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz)) #include "../lib/dr_wav.h" /* stb vorbis defines. this is kind of a mess but oh well */ #include "../lib/FAudio/src/FAudio_internal.h" #define malloc FAudio_malloc #define realloc FAudio_realloc #define free FAudio_free #ifdef memset /* Thanks, Apple! */ #undef memset #endif #define memset SDL_memset #ifdef memcpy /* Thanks, Apple! */ #undef memcpy #endif #define memcpy SDL_memcpy #define memcmp FAudio_memcmp #define pow FAudio_pow #define log(x) FAudio_log(x) #define sin(x) FAudio_sin(x) #define cos(x) FAudio_cos(x) #define floor FAudio_floor #define abs(x) FAudio_abs(x) #define ldexp(v, e) FAudio_ldexp((v), (e)) #define exp(x) FAudio_exp(x) #define qsort FAudio_qsort #ifdef assert #undef assert #endif #define assert FAudio_assert #define FILE FAudioIOStream #ifdef SEEK_SET #undef SEEK_SET #endif #ifdef SEEK_END #undef SEEK_END #endif #ifdef EOF #undef EOF #endif #define SEEK_SET FAUDIO_SEEK_SET #define SEEK_END FAUDIO_SEEK_END #define EOF FAUDIO_EOF #define fopen(path, mode) FAudio_fopen(path) #define fopen_s(io, path, mode) (!(*io = FAudio_fopen(path))) #define fclose(io) FAudio_close(io) #define fread(dst, size, count, io) io->read(io->data, dst, size, count) #define fseek(io, offset, whence) io->seek(io->data, offset, whence) #define ftell(io) io->seek(io->data, 0, FAUDIO_SEEK_CUR) #define STB_VORBIS_NO_PUSHDATA_API 1 #define STB_VORBIS_NO_INTEGER_CONVERSION 1 #include "../lib/FAudio/src/stb_vorbis.h" #include static inline void Log(char *string) { printf("%s\n", string); fflush(stdout); } typedef struct IdStack { uint32_t* array; uint32_t count; uint32_t capacity; } IdStack; static inline void IdStack_Init(IdStack *stack) { stack->array = NULL; stack->capacity = 0; stack->count = 0; } static inline void IdStack_Push(IdStack *stack, uint32_t id) { if (stack->count >= stack->capacity) { stack->array = SDL_realloc(stack->array, (stack->capacity + 1) * sizeof(uint32_t)); stack->capacity += 1; } stack->array[stack->count] = id; stack->count += 1; } static inline uint32_t IdStack_Pop(IdStack* stack) { stack->count -= 1; return stack->array[stack->count]; } typedef struct BufferQueue { uint32_t head; uint32_t tail; uint32_t size; /* DO NOT MUTATE */ uint32_t count; /* number of currently enqueued elements */ uint8_t** buffers; } BufferQueue; static inline void BufferQueue_Init(BufferQueue* queue, uint32_t size) { queue->head = 0; queue->tail = 0; queue->size = size; queue->count = 0; queue->buffers = SDL_malloc(size * sizeof(uint8_t*)); } static inline uint8_t* BufferQueue_Dequeue(BufferQueue *queue) { if (queue->tail == queue->head) { return NULL; } uint8_t* buffer = queue->buffers[queue->tail]; queue->buffers[queue->tail] = NULL; queue->tail = (queue->tail + 1) % queue->size; queue->count -= 1; return buffer; } static inline void BufferQueue_Enqueue(BufferQueue *queue, uint8_t* buffer) { if (((queue->head + 1) % queue->size) == queue->tail) { return; } queue->buffers[queue->head] = buffer; queue->head = (queue->head + 1) % queue->size; queue->count += 1; } 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_StreamingSound { stb_vorbis* fileHandle; stb_vorbis_info info; BufferQueue bufferQueue; } FAudioGMS_StreamingSound; typedef struct FAudioGMS_SoundInstance { uint32_t id; FAudioSourceVoice *handle; FAudioWaveFormatEx format; uint8_t loop; /* bool */ FAudioGMS_SoundState soundState; F3DAUDIO_DSP_SETTINGS dspSettings; float pan; float pitch; float volume; float reverb; float lowPassFilter; float highPassFilter; float bandPassFilter; uint8_t reverbAttached; FAudioSubmixVoice* reverbVoice; FAudioVoiceSends reverbSends; uint8_t isStatic; uint8_t destroyOnFinish; uint8_t is3D; F3DAUDIO_EMITTER* emitter; /* must not be NULL if is3D */ float stereoAzimuth[2]; union { FAudioGMS_StaticSound *staticSound; /* static sounds are loaded separately, so they do not belong to the instance */ FAudioGMS_StreamingSound streamingSound; } soundData; } FAudioGMS_SoundInstance; static const float SPEED_OF_SOUND = 343.5f; static const float DOPPLER_SCALE = 1.0f; static const uint32_t MINIMUM_BUFFER_CHECK = 3; #define MAX_BUFFER_QUEUE_COUNT 16 #define STREAMING_BUFFER_SIZE 1024 * 128 * sizeof(float) /* FIXME: what should this value be? */ typedef struct FAudioGMS_Device { FAudio* handle; F3DAUDIO_HANDLE handle3D; FAudioDeviceDetails deviceDetails; FAudioMasteringVoice *masteringVoice; F3DAUDIO_LISTENER listener; float spatialDistanceScale; FAudioGMS_StaticSound **staticSounds; uint32_t staticSoundCount; IdStack staticSoundIndexStack; FAudioGMS_SoundInstance **soundInstances; uint32_t soundInstanceCount; IdStack soundInstanceIndexStack; float streamStagingBuffer[STREAMING_BUFFER_SIZE]; } FAudioGMS_Device; static FAudioGMS_Device* device = NULL; 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; } } void FAudioGMS_Init(double spatialDistanceScale) { device = SDL_malloc(sizeof(FAudioGMS_Device)); uint32_t result = FAudioCreate(&device->handle, 0, FAUDIO_DEFAULT_PROCESSOR); if (result != 0) { Log("Failed to create device! Bailing!"); SDL_free(device); device = NULL; return; } /* Find a suitable device */ uint32_t deviceCount; FAudio_GetDeviceCount(device->handle, &deviceCount); if (deviceCount == 0) { Log("No audio devices found! Bailing!"); FAudio_Release(device->handle); SDL_free(device); device = NULL; return; } FAudioDeviceDetails deviceDetails; uint32_t i = 0; for (i = 0; i < deviceCount; i += 1) { FAudio_GetDeviceDetails( device->handle, i, &deviceDetails); if ((deviceDetails.Role & FAudioDefaultGameDevice) == FAudioDefaultGameDevice) { device->deviceDetails = deviceDetails; break; } } if (i == deviceCount) { i = 0; /* whatever we'll just use the first one i guess */ FAudio_GetDeviceDetails( device->handle, i, &deviceDetails); device->deviceDetails = deviceDetails; } if (FAudio_CreateMasteringVoice( device->handle, &device->masteringVoice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, i, NULL ) != 0) { Log("No mastering voice found! Bailing!"); FAudio_Release(device->handle); SDL_free(device); device = NULL; return; } device->spatialDistanceScale = spatialDistanceScale; F3DAudioInitialize( device->deviceDetails.OutputFormat.dwChannelMask, SPEED_OF_SOUND, device->handle3D ); device->listener.OrientFront.x = 0; device->listener.OrientFront.y = 0; device->listener.OrientFront.z = -1; device->listener.OrientTop.x = 0; device->listener.OrientTop.y = 1; device->listener.OrientTop.z = 0; device->listener.Position.x = 0; device->listener.Position.y = 0; device->listener.Position.z = 0; device->listener.Velocity.x = 0; device->listener.Velocity.y = 0; device->listener.Velocity.z = 0; device->listener.pCone = NULL; device->staticSounds = NULL; device->staticSoundCount = 0; IdStack_Init(&device->staticSoundIndexStack); device->soundInstances = NULL; device->soundInstanceCount = 0; IdStack_Init(&device->soundInstanceIndexStack); Log("FAudio initialized successfully!"); printf("Device: %ls", device->deviceDetails.DisplayName); fflush(stdout); } /* Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs */ static void SetPanMatrixCoefficients(FAudioGMS_SoundInstance *instance) { /* Two major things to notice: * 1. The spec assumes any speaker count >= 2 has Front Left/Right. * 2. Stereo panning is WAY more complicated than you think. * The main thing is that hard panning does NOT eliminate an * entire channel; the two channels are blended on each side. * -flibit */ float* outputMatrix = (float*) instance->dspSettings.pMatrixCoefficients; if (instance->dspSettings.SrcChannelCount == 1) { if (instance->dspSettings.DstChannelCount == 1) { outputMatrix[0] = 1.0f; } else { outputMatrix[0] = (instance->pan > 0.0f) ? (1.0f - instance->pan) : 1.0f; outputMatrix[1] = (instance->pan < 0.0f) ? (1.0f + instance->pan) : 1.0f; } } else { if (instance->dspSettings.DstChannelCount == 1) { outputMatrix[0] = 1.0f; outputMatrix[1] = 1.0f; } else { if (instance->pan <= 0.0f) { // Left speaker blends left/right channels outputMatrix[0] = 0.5f * instance->pan + 1.0f; outputMatrix[1] = 0.5f * -instance->pan; // Right speaker gets less of the right channel outputMatrix[2] = 0.0f; outputMatrix[3] = instance->pan + 1.0f; } else { // Left speaker gets less of the left channel outputMatrix[0] = -instance->pan + 1.0f; outputMatrix[1] = 0.0f; // Right speaker blends right/left channels outputMatrix[2] = 0.5f * instance->pan; outputMatrix[3] = 0.5f * -instance->pan + 1.0f; } } } } double FAudioGMS_StaticSound_LoadWAV(char *filePath) { drwav_uint64 frameCount; FAudioGMS_StaticSound *sound = SDL_malloc(sizeof(FAudioGMS_StaticSound)); float *pSampleData = drwav_open_file_and_read_pcm_frames_f32(filePath, &sound->channels, &sound->samplesPerSecond, &frameCount, NULL); if (pSampleData == NULL) { Log("Error opening WAV file: "); Log(filePath); SDL_free(sound); return -1; } sound->buffer.AudioBytes = (uint32_t)(frameCount * sound->channels * sizeof(float)); sound->buffer.Flags = FAUDIO_END_OF_STREAM; sound->buffer.LoopBegin = 0; sound->buffer.LoopCount = 0; sound->buffer.LoopLength = 0; sound->buffer.pAudioData = (uint8_t*) pSampleData; sound->buffer.pContext = NULL; sound->buffer.PlayBegin = 0; sound->buffer.PlayLength = 0; sound->loopStart = 0; sound->loopLength = 0; if (device->staticSoundIndexStack.count > 0) { sound->id = IdStack_Pop(&device->staticSoundIndexStack); } else { sound->id = device->staticSoundCount; device->staticSounds = SDL_realloc(device->staticSounds, (device->staticSoundCount + 1) * sizeof(FAudioGMS_StaticSound*)); device->staticSoundCount += 1; } device->staticSounds[sound->id] = sound; return (double)sound->id; } static void FAudioGMS_INTERNAL_SoundInstance_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_UpdatePitch(FAudioGMS_SoundInstance* instance) { float doppler = 1.0f; if (!instance->is3D || DOPPLER_SCALE == 0.0f) { doppler = 1.0f; } else { doppler = instance->dspSettings.DopplerFactor * DOPPLER_SCALE; } FAudioSourceVoice_SetFrequencyRatio( instance->handle, SDL_powf(2.0f, instance->pitch - 1) * doppler, /* FAudio expects pitch range to be -1.0 to 1.0 while GM uses 0.0 to 2.0 so we adjust here */ 0 ); } static void FAudioGMS_INTERNAL_SoundInstance_SetPitch(FAudioGMS_SoundInstance* instance, float pitch) { pitch = SDL_max(0.0, SDL_min(2.0, pitch)); instance->pitch = pitch; FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(instance); } static void FAudioGMS_INTERNAL_SoundInstance_SetVolume(FAudioGMS_SoundInstance* instance, float volume) { FAudioVoice_SetVolume(instance->handle, volume, 0); } static void FAudioGMS_INTERNAL_SoundInstance_SetProperties(FAudioGMS_SoundInstance* instance, double pan, double pitch, double volume, double reverb) { FAudioGMS_INTERNAL_SoundInstance_SetPan(instance, pan); FAudioGMS_INTERNAL_SoundInstance_SetPitch(instance, pitch); FAudioGMS_INTERNAL_SoundInstance_SetVolume(instance, volume); if (reverb > 0.0f) { FAudioGMS_INTERNAL_SoundInstance_SetReverb(instance, reverb); } } static void FAudioGMS_INTERNAL_Apply3D(FAudioGMS_SoundInstance* instance) { F3DAUDIO_EMITTER* emitter = instance->emitter; if (emitter == NULL) { Log("Sound instance does not have 3D data! Did you forget to initialize?"); return; } F3DAudioCalculate( device->handle3D, &device->listener, emitter, F3DAUDIO_CALCULATE_MATRIX | F3DAUDIO_CALCULATE_DOPPLER, &instance->dspSettings ); FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(instance); FAudioVoice_SetOutputMatrix( instance->handle, device->masteringVoice, instance->dspSettings.SrcChannelCount, instance->dspSettings.DstChannelCount, instance->dspSettings.pMatrixCoefficients, 0 ); } static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_Init( uint32_t channelCount, uint32_t samplesPerSecond ) { FAudioGMS_SoundInstance* instance = SDL_malloc(sizeof(FAudioGMS_SoundInstance)); instance->handle = NULL; instance->loop = 0; instance->destroyOnFinish = 0; instance->format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; instance->format.wBitsPerSample = 32; instance->format.nChannels = channelCount; instance->format.nBlockAlign = (uint16_t)(4 * channelCount); instance->format.nSamplesPerSec = samplesPerSecond; instance->format.nAvgBytesPerSec = instance->format.nBlockAlign * samplesPerSecond; FAudio_CreateSourceVoice( device->handle, &instance->handle, &instance->format, FAUDIO_VOICE_USEFILTER, FAUDIO_DEFAULT_FREQ_RATIO, 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 = 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->reverbAttached = 0; instance->reverb = 0.0f; 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->is3D = 0; instance->emitter = NULL; instance->stereoAzimuth[0] = 0.0f; instance->stereoAzimuth[1] = 0.0f; if (device->soundInstanceIndexStack.count > 0) { instance->id = IdStack_Pop(&device->soundInstanceIndexStack); } else { instance->id = device->soundInstanceCount; device->soundInstances = SDL_realloc(device->soundInstances, (device->soundInstanceCount + 1) * sizeof(FAudioGMS_SoundInstance*)); device->soundInstanceCount += 1; } device->soundInstances[instance->id] = instance; return instance; } static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound( FAudioGMS_StaticSound *staticSound ) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_Init( staticSound->channels, staticSound->samplesPerSecond ); instance->isStatic = 1; instance->soundData.staticSound = staticSound; return instance; } double FAudioGMS_StreamingSound_LoadOGG(char* filePath) { int error = -1; stb_vorbis *fileHandle = stb_vorbis_open_filename(filePath, &error, NULL); if (error != 0) { Log("Error opening OGG file!"); Log(filePath); return -1; } stb_vorbis_info info = stb_vorbis_get_info(fileHandle); FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_Init( info.channels, info.sample_rate ); instance->isStatic = 0; instance->soundData.streamingSound.fileHandle = fileHandle; instance->soundData.streamingSound.info = info; BufferQueue_Init(&instance->soundData.streamingSound.bufferQueue, MAX_BUFFER_QUEUE_COUNT); return instance->id; } static void FAudioGMS_INTERNAL_StaticSound_AddEmitter(FAudioGMS_SoundInstance* instance, float x, float y, float z) { instance->is3D = 1; instance->emitter = SDL_malloc(sizeof(F3DAUDIO_EMITTER)); /* defaults based on XNA behavior */ instance->emitter->pCone = NULL; instance->emitter->ChannelRadius = 1.0f; instance->emitter->pChannelAzimuths = instance->stereoAzimuth; instance->emitter->pVolumeCurve = NULL; instance->emitter->pLFECurve = NULL; instance->emitter->pLPFDirectCurve = NULL; instance->emitter->pLPFReverbCurve = NULL; instance->emitter->pReverbCurve = NULL; instance->emitter->InnerRadius = 0; instance->emitter->InnerRadiusAngle = 0; instance->emitter->ChannelCount = instance->dspSettings.SrcChannelCount; instance->emitter->CurveDistanceScaler = device->spatialDistanceScale; instance->emitter->DopplerScaler = 1.0f; instance->emitter->Position.x = x; instance->emitter->Position.y = y; instance->emitter->Position.z = z; instance->emitter->Velocity.x = 0; instance->emitter->Velocity.y = 0; instance->emitter->Velocity.z = 0; instance->emitter->OrientTop.x = 0; instance->emitter->OrientTop.y = 1; instance->emitter->OrientTop.z = 0; instance->emitter->OrientFront.x = 0; instance->emitter->OrientFront.y = 0; instance->emitter->OrientFront.z = 1; FAudioGMS_INTERNAL_Apply3D(instance); } static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance* instance) { /* NOTE: this function returns samples per channel, not total samples */ uint32_t samples = stb_vorbis_get_samples_float_interleaved( instance->soundData.streamingSound.fileHandle, instance->format.nChannels, device->streamStagingBuffer, STREAMING_BUFFER_SIZE ); uint32_t sampleCount = samples * instance->format.nChannels; uint32_t bufferLength = sampleCount * sizeof(float); uint8_t* nextBuffer = SDL_malloc(bufferLength); SDL_memcpy(nextBuffer, device->streamStagingBuffer, bufferLength); BufferQueue_Enqueue(&instance->soundData.streamingSound.bufferQueue, nextBuffer); if (instance->soundState != SoundState_Stopped) { FAudioBuffer buffer; buffer.AudioBytes = bufferLength; buffer.pAudioData = nextBuffer; buffer.PlayLength = bufferLength / instance->format.nChannels / (instance->format.wBitsPerSample / 8); buffer.PlayBegin = 0; buffer.Flags = 0; buffer.LoopBegin = 0; buffer.LoopCount = 0; buffer.LoopLength = 0; buffer.pContext = NULL; FAudioSourceVoice_SubmitSourceBuffer(instance->handle, &buffer, NULL); } /* We have reached the end of the file! */ if (sampleCount < STREAMING_BUFFER_SIZE) { if (instance->loop) { stb_vorbis_seek_start(instance->soundData.streamingSound.fileHandle); } else { instance->soundState = SoundState_Stopped; } } } static void FAudioGMS_INTERNAL_SoundInstance_StreamingUpdate(FAudioGMS_SoundInstance* instance) { uint32_t i; FAudioVoiceState voiceState; if (instance->isStatic) { return; } FAudioSourceVoice_GetState( instance->handle, &voiceState, FAUDIO_VOICE_NOSAMPLESPLAYED ); while (instance->soundData.streamingSound.bufferQueue.count > voiceState.BuffersQueued) { uint8_t* buffer = BufferQueue_Dequeue(&instance->soundData.streamingSound.bufferQueue); SDL_free(buffer); } for (i = MINIMUM_BUFFER_CHECK - instance->soundData.streamingSound.bufferQueue.count; i > 0; i -= 1) { FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); } } static void FAudioGMS_INTERNAL_SoundInstance_ClearBuffers(FAudioGMS_SoundInstance* instance) { uint32_t i; uint32_t bufferCount = instance->soundData.streamingSound.bufferQueue.count; for (i = 0; i < bufferCount; i += 1) { uint8_t* buffer = BufferQueue_Dequeue(&instance->soundData.streamingSound.bufferQueue); SDL_free(buffer); } } static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance* instance) { if (instance->soundState == SoundState_Playing) { return; } if (instance->isStatic) { if (instance->loop) { instance->soundData.staticSound->buffer.LoopCount = FAUDIO_LOOP_INFINITE; instance->soundData.staticSound->buffer.LoopBegin = instance->soundData.staticSound->loopStart; instance->soundData.staticSound->buffer.LoopLength = instance->soundData.staticSound->loopLength; } else { instance->soundData.staticSound->buffer.LoopCount = 0; instance->soundData.staticSound->buffer.LoopBegin = 0; instance->soundData.staticSound->buffer.LoopLength = 0; } FAudioSourceVoice_SubmitSourceBuffer(instance->handle, &instance->soundData.staticSound->buffer, NULL); } else { FAudioGMS_INTERNAL_SoundInstance_StreamingUpdate(instance); } 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 = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID); if (staticSound != NULL) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound); instance->destroyOnFinish = 1; FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, pan, pitch, volume, reverb); FAudioGMS_INTERNAL_SoundInstance_Play(instance); } else { Log("StaticSound_PlayOneOff: Invalid static sound ID! Did you destroy this sound?"); } } void FAudioGMS_StaticSound_PlayOneOffSpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double reverb) { FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID); if (staticSound != NULL) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound); instance->destroyOnFinish = 1; FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, 0, pitch, volume, reverb); FAudioGMS_INTERNAL_StaticSound_AddEmitter(instance, x, y, z); FAudioGMS_INTERNAL_SoundInstance_Play(instance); } else { Log("StaticSound_PlayOneOffSpatial: Invalid static sound ID! Did you destroy this sound?"); } } double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double reverb, double loop) { FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID); if (staticSound != NULL) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound); instance->loop = (uint8_t)loop; FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, pan, pitch, volume, reverb); FAudioGMS_INTERNAL_SoundInstance_Play(instance); return (double)instance->id; } else { Log("StaticSound_Play: Invalid static sound ID! Did you destroy this sound?"); return -1; } } double FAudioGMS_StaticSound_PlaySpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double reverb, double loop) { FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID); if (staticSound != NULL) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound); instance->loop = (uint8_t)loop; FAudioGMS_INTERNAL_SoundInstance_SetProperties(instance, 0, pitch, volume, reverb); FAudioGMS_INTERNAL_StaticSound_AddEmitter(instance, x, y, z); FAudioGMS_INTERNAL_SoundInstance_Play(instance); return (double)instance->id; } else { Log("StaticSound_PlaySpatial: Invalid static sound ID! Did you destroy this sound?"); return -1; } } void FAudioGMS_SoundInstance_Play(double id) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id); if (instance != NULL) { FAudioGMS_INTERNAL_SoundInstance_Play(instance); } } void FAudioGMS_SoundInstance_Pause(double id) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id); if (instance != NULL) { 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?"); } } static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance* instance) { if (instance != NULL) { if (instance->isStatic) { FAudioSourceVoice_ExitLoop(instance->handle, 0); } instance->soundState = SoundState_Stopped; } else { Log("SoundInstance_Stop: Invalid sound instance ID! Did you destroy this instance?"); } } void FAudioGMS_SoundInstance_Stop(double id) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id); FAudioGMS_INTERNAL_SoundInstance_Stop(instance); } static void FAudioGMS_INTERNAL_SoundInstance_StopImmediate(FAudioGMS_SoundInstance* instance) { if (instance != NULL) { FAudioSourceVoice_Stop(instance->handle, 0, 0); FAudioSourceVoice_FlushSourceBuffers(instance->handle); if (!instance->isStatic) { FAudioGMS_INTERNAL_SoundInstance_ClearBuffers(instance); } instance->soundState = SoundState_Stopped; } else { Log("SoundInstance_Stop: Invalid sound instance ID! Did you destroy this instance?"); } } void FAudioGMS_SoundInstance_StopImmediate(double id) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id); FAudioGMS_INTERNAL_SoundInstance_StopImmediate(instance); } void FAudioGMS_SoundInstance_Set3DPosition(double soundInstanceID, double x, double y, double z) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID); if (instance != NULL) { if (!instance->is3D) { Log("Not a 3D sound!"); return; } instance->emitter->Velocity.x = x - instance->emitter->Position.x; instance->emitter->Velocity.y = y - instance->emitter->Position.y; instance->emitter->Velocity.z = z - instance->emitter->Position.z; instance->emitter->Position.x = x; instance->emitter->Position.y = y; instance->emitter->Position.z = z; } } void FAudioGMS_SetListenerPosition(double x, double y, double z) { device->listener.Velocity.x = x - device->listener.Position.x; device->listener.Velocity.y = y - device->listener.Position.y; device->listener.Velocity.z = z - device->listener.Position.z; device->listener.Position.x = x; device->listener.Position.y = y; device->listener.Position.z = z; } static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance* instance) { if (instance != NULL) { device->soundInstances[instance->id] = NULL; IdStack_Push(&device->soundInstanceIndexStack, instance->id); FAudioGMS_INTERNAL_SoundInstance_StopImmediate(instance); FAudioVoice_DestroyVoice(instance->handle); if (!instance->isStatic) { SDL_free(instance->soundData.streamingSound.bufferQueue.buffers); stb_vorbis_close(instance->soundData.streamingSound.fileHandle); } if (instance->is3D) { SDL_free(instance->emitter); } SDL_free(instance->dspSettings.pMatrixCoefficients); SDL_free(instance); } } void FAudioGMS_SoundInstance_Destroy(double id) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id); if (instance != NULL) { FAudioGMS_INTERNAL_SoundInstance_Destroy(instance); } } void FAudioGMS_SoundInstance_DestroyWhenFinished(double id) { FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((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; IdStack_Push(&device->staticSoundIndexStack, sound->id); SDL_free((void*)sound->buffer.pAudioData); SDL_free(sound); } } void FAudioGMS_StaticSound_Destroy(double id) { if (id >= 0 && id < device->staticSoundCount) { FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[(uint32_t)id]); } else { Log("Invalid ID for destroy!"); } } 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->is3D) { FAudioGMS_INTERNAL_Apply3D(instance); } /* Update streaming instance */ if (!instance->isStatic && instance->soundState == SoundState_Playing) { FAudioGMS_INTERNAL_SoundInstance_StreamingUpdate(instance); } if (instance->destroyOnFinish) { FAudioVoiceState state; FAudioSourceVoice_GetState(instance->handle, &state, FAUDIO_VOICE_NOSAMPLESPLAYED); if (state.BuffersQueued == 0) { FAudioGMS_INTERNAL_SoundInstance_Destroy(instance); } } } } } void FAudioGMS_Destroy() { uint32_t i = 0; for (i = 0; i < device->soundInstanceCount; i += 1) { FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[i]); } for (i = 0; i < device->staticSoundCount; i += 1) { FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[i]); } FAudio_Release(device->handle); SDL_free(device); device = NULL; Log("FAudio cleaned up successfully!"); }