File Audio.c
Go to the documentation of this file
#include "Audio.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <float.h>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "Log.h"
#include <stdlib.h>
#include <math.h>
#include "EngineUtils.h"
#include "AssertLib.h"
#include "Thread.h"
#include "ThreadSafeQueue.h"
#include "ObjectPool.h"
#include <cglm/cglm.h>
#include "HandleDefs.h"
#define PI 3.14159265358979323846
#define PI2 (2.0 * PI)
typedef int HSFXBuffer;
struct SFXBuffer
{
float lengthSamples;
ALuint hALSource;
ALuint hALBuffer;
float timeLeft;
HSFXBuffer hThis;
HSFXBuffer hNext;
HSFXBuffer hPrev;
};
static ALuint gSFXSource = -1;
static HSFXBuffer gPlayingBuffersListHead = -1;
static int gPlayingBuffersListLen = 0;
static OBJECT_POOL(struct SFXBuffer) gBuffersPool = NULL;
float* gSfxBuffer = NULL;
size_t gSfxBufferSize = 0;
float gMasterVolume = 0.2;
typedef i32 HWavSFX;
enum StreamedSoundPlayerCommandType
{
SSPC_Play,
SSPC_Pause,
SSPC_Stop,
SSPC_SkipTrack,
SSPC_QueueFile,
SSPC_ClearQueue,
SSPC_SetVolume,
SSPC_SetCurrentFileLooping,
SSPC_SetQueueLooping,
};
struct StreamedSoundPlayerCommand
{
enum StreamedSoundPlayerCommandType type;
union
{
float fVal;
char* fileName;
bool bVal;
}data;
};
enum SoundEffectType
{
SET_ZzFX,
SET_PreloadedWav,
};
struct SoundEffect
{
enum SoundEffectType type;
vec2 pos;
union
{
struct ZZFXSound zzfx;
HWavSFX hWav;
}data;
};
enum AudioQueueItemType
{
AQI_PlaySFX,
AQI_StreamPlayerCommand,
AQI_ShutdownAudioThread,
};
struct AudioQueueItem
{
enum AudioQueueItemType type;
union
{
struct SoundEffect sfx;
struct StreamedSoundPlayerCommand streamCmd;
}data;
};
ALCint gDevRate = 0;
ALuint gBasicSource = -1;
CrossPlatformThread gAudioThread;
struct ThreadSafeQueue gAudioTxQueue;
static void InsertBufferIntoPlayingList(HSFXBuffer hBuf)
{
gPlayingBuffersListLen++;
if(gPlayingBuffersListHead == NULL_HANDLE)
{
gPlayingBuffersListHead = hBuf;
return;
}
gBuffersPool[hBuf].hNext = gPlayingBuffersListHead;
gBuffersPool[gPlayingBuffersListHead].hPrev = hBuf;
gPlayingBuffersListHead = hBuf;
}
static void RemoveBufferFromPlayingList(HSFXBuffer hBuf)
{
gPlayingBuffersListLen--;
HSFXBuffer hNext = gBuffersPool[hBuf].hNext;
HSFXBuffer hPrev = gBuffersPool[hBuf].hPrev;
if(hNext != NULL_HANDLE)
{
gBuffersPool[hNext].hPrev = hPrev;
}
if(hPrev != NULL_HANDLE)
{
gBuffersPool[hPrev].hNext = hNext;
}
if(gPlayingBuffersListHead == hBuf)
{
gPlayingBuffersListHead = hNext;
}
}
static HSFXBuffer FindReusableBuffer()
{
HSFXBuffer hBuf = NULL_HANDLE;
hBuf = gPlayingBuffersListHead;
while(hBuf != NULL_HANDLE)
{
if(gBuffersPool[hBuf].timeLeft <= 0)
{
return hBuf;
}
hBuf = gBuffersPool[hBuf].hNext;
}
return NULL_HANDLE;
}
static HSFXBuffer AquireSFXBuffer()
{
HSFXBuffer hR = FindReusableBuffer();
if(hR != NULL_HANDLE)
{
RemoveBufferFromPlayingList(hR);
gBuffersPool[hR].timeLeft = 0;
gBuffersPool[hR].lengthSamples = 0;
}
else
{
gBuffersPool = GetObjectPoolIndex(gBuffersPool, &hR);
gBuffersPool[hR].hALBuffer = NULL_HANDLE;
gBuffersPool[hR].hALSource = NULL_HANDLE;
gBuffersPool[hR].hNext = NULL_HANDLE;
gBuffersPool[hR].hPrev = NULL_HANDLE;
gBuffersPool[hR].hThis = hR;
gBuffersPool[hR].lengthSamples = 0;
alGenSources(1, &gBuffersPool[hR].hALSource);
ALfloat source_x = 0.0f;
ALfloat source_y = 0.0f;
ALfloat source_z = 0.0f;
alSource3f(gBuffersPool[hR].hALSource, AL_POSITION, source_x, source_y, source_z);
}
return hR;
}
static void AllocateWorkingSampleBuffer()
{
const float sfxBufferLenSeconds = 4;
gSfxBufferSize = sizeof(float) * sfxBufferLenSeconds * gDevRate;
gSfxBuffer = malloc(gSfxBufferSize);
ZeroMemory(gSfxBuffer, gSfxBufferSize);
}
float zzfx_struct(struct ZZFXSound* pSound)
{
float v = pSound->volume;
pSound->volume *= gMasterVolume;
int samples = zzfx_Generate(gSfxBuffer, gSfxBufferSize / sizeof(float), (float)gDevRate, pSound);
pSound->volume = v;
HSFXBuffer hBuf = AquireSFXBuffer();
gBuffersPool[hBuf].timeLeft = (float)samples / (float)gDevRate;
gBuffersPool[hBuf].hNext = NULL_HANDLE;
gBuffersPool[hBuf].hPrev = NULL_HANDLE;
alSourceStop(gBuffersPool[hBuf].hALSource);
if(gBuffersPool[hBuf].hALBuffer != NULL_HANDLE)
{
alDeleteBuffers(1, &gBuffersPool[hBuf].hALBuffer);
}
alGenBuffers(1, &gBuffersPool[hBuf].hALBuffer);
alBufferData(gBuffersPool[hBuf].hALBuffer, AL_FORMAT_MONO_FLOAT32, gSfxBuffer, (ALsizei)samples * sizeof(float), (ALsizei)gDevRate);
alSourcei(gBuffersPool[hBuf].hALSource, AL_BUFFER, (ALint)gBuffersPool[hBuf].hALBuffer);
//assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
alSourcePlay(gBuffersPool[hBuf].hALSource);
InsertBufferIntoPlayingList(hBuf);
float seconds = (float)samples / (float)gDevRate;
return seconds;
}
void zzfx_Update(float deltaT)
{
HSFXBuffer hBuf = NULL_HANDLE;
hBuf = gPlayingBuffersListHead;
while(hBuf != NULL_HANDLE)
{
if(gBuffersPool[hBuf].timeLeft > 0.0f)
gBuffersPool[hBuf].timeLeft -= deltaT;
hBuf = gBuffersPool[hBuf].hNext;
}
}
DECLARE_THREAD_PROC(AudioThread, arg)
{
const ALCchar *name;
ALCdevice *device;
ALCcontext *ctx;
struct ThreadSafeQueue* pQueue = arg;
device = alcOpenDevice(NULL);
if(!device)
{
Log_Error("Could not open an OpenAL device!");
return (void*)1;
}
alcGetIntegerv(device, ALC_FREQUENCY, 1, &gDevRate);
Log_Info("audio device sample rate: %i", gDevRate);
ctx = alcCreateContext(device, NULL);
if(ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
{
if(ctx != NULL)
alcDestroyContext(ctx);
alcCloseDevice(device);
Log_Error("Could not set an OpenAL context!");
return (void*)1;
}
name = NULL;
if(alcIsExtensionPresent(device, "ALC_ENUMERATE_ALL_EXT"))
name = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER);
if(!name || alcGetError(device) != AL_NO_ERROR)
name = alcGetString(device, ALC_DEVICE_SPECIFIER);
Log_Info("OpenAL: Opened \"%s\"\n", name);
gBuffersPool = NEW_OBJECT_POOL(struct SFXBuffer, 64);
gPlayingBuffersListHead = NULL_HANDLE;
gPlayingBuffersListLen = 0;
AllocateWorkingSampleBuffer();
bool bQuit = false;
while(!bQuit)
{
struct AudioQueueItem item;
while(TSQ_Dequeue(pQueue, &item))
{
switch(item.type)
{
case AQI_ShutdownAudioThread:
bQuit = true;
break;
case AQI_PlaySFX:
switch(item.data.sfx.type)
{
case SET_ZzFX:
zzfx_struct(&item.data.sfx.data.zzfx);
break;
case SET_PreloadedWav:
break;
}
break;
}
SleepForMS(5);
zzfx_Update(5.0f / 1000.0f);
}
}
ctx = alcGetCurrentContext();
if(ctx == NULL)
return NULL;
device = alcGetContextsDevice(ctx);
alcMakeContextCurrent(NULL);
alcDestroyContext(ctx);
alcCloseDevice(device);
free(gSfxBuffer);
FreeObjectPool(gBuffersPool);
Log_Info("shutting down audio thread");
return NULL;
}
void OnAudioThreadQueueWrapped(void* pItemToBeLost)
{
Log_Warning("Network thread Connection event queue wrapped around, packets lost. It must not have been emptied quick enough");
}
int Au_Init(char ***argv, int *argc)
{
TSQ_Init(&gAudioTxQueue, sizeof(struct AudioQueueItem), 32, &OnAudioThreadQueueWrapped);
gAudioThread = StartThread(&AudioThread, &gAudioTxQueue);
return 0;
}
void Au_DeInit()
{
struct AudioQueueItem aqi =
{
.type = AQI_ShutdownAudioThread
};
TSQ_Enqueue(&gAudioTxQueue, &aqi);
JoinThread(gAudioThread);
}
void Au_PlayZzFX(const struct ZZFXSound* pSound)
{
struct AudioQueueItem aqi =
{
.type = AQI_PlaySFX,
.data.sfx.type = SET_ZzFX,
.data.sfx.data.zzfx = *pSound
};
TSQ_Enqueue(&gAudioTxQueue, &aqi);
}