Add audio looping, simplify audio API

Audio can be played with 1 call, audio_beginVorbisStream which
encapsulates the initialisation of the renderer and release on finish.
OpenAL is quoted to have a limitation of around 32 sources. Audio
renderers hence acquire a source slot on playback and immediately release
on finish. This ensures that sources are held optimally and minimises the
possibility of saturating the available sources.

Renderers hold an index to the source list, which the index reveals the
source ID for their use.
This commit is contained in:
Doyle Thai 2016-07-27 22:50:14 +10:00
parent 824d8fd8a0
commit 630f2eaa5d
6 changed files with 156 additions and 46 deletions

Binary file not shown.

View File

@ -40,8 +40,11 @@ void alCheckError_(const char *file, int line)
} }
}; };
const i32 audio_init() const i32 audio_init(AudioManager *audioManager)
{ {
#ifdef DENGINE_DEBUG
ASSERT(audioManager);
#endif
/* Clear error stack */ /* Clear error stack */
alGetError(); alGetError();
@ -93,14 +96,41 @@ const i32 audio_init()
} }
AL_CHECK_ERROR(); AL_CHECK_ERROR();
for (i32 i = 0; i < ARRAY_COUNT(audioManager->sourceList); i++)
{
alGenSources(1, &audioManager->sourceList[i].id);
// NOTE(doyle): If last entry, loop the free source to front of list
if (i + 1 >= ARRAY_COUNT(audioManager->sourceList))
audioManager->sourceList[i].nextFreeIndex = 0;
else
audioManager->sourceList[i].nextFreeIndex = i+1;
}
audioManager->freeSourceIndex = 0;
return 0; return 0;
} }
const i32 audio_rendererInit(AudioRenderer *audioRenderer) INTERNAL i32 rendererAcquire(AudioManager *audioManager,
AudioRenderer *audioRenderer)
{ {
/* Generate number of concurrent audio file listeners */ i32 vacantSource = audioManager->freeSourceIndex;
alGenSources(ARRAY_COUNT(audioRenderer->sourceId), audioRenderer->sourceId); if (audioManager->sourceList[vacantSource].nextFreeIndex ==
AL_CHECK_ERROR(); AUDIO_NO_FREE_SOURCE)
{
// TODO(doyle): Error messaging return paths
return -1;
}
/* Assign a vacant source slot to renderer */
audioRenderer->sourceIndex = vacantSource;
/* Update the immediate free source index */
audioManager->freeSourceIndex =
audioManager->sourceList[vacantSource].nextFreeIndex;
/* Mark current source as in use */
audioManager->sourceList[vacantSource].nextFreeIndex = AUDIO_NO_FREE_SOURCE;
/* Generate audio data buffers */ /* Generate audio data buffers */
alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), audioRenderer->bufferId); alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), audioRenderer->bufferId);
@ -111,9 +141,38 @@ const i32 audio_rendererInit(AudioRenderer *audioRenderer)
return 0; return 0;
} }
#define AUDIO_CHUNK_SIZE_ 65536 INTERNAL void rendererRelease(AudioManager *audioManager,
void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis) AudioRenderer *audioRenderer)
{ {
u32 sourceIndexToFree = audioRenderer->sourceIndex;
audioManager->sourceList[sourceIndexToFree].nextFreeIndex =
audioManager->freeSourceIndex;
audioManager->freeSourceIndex = sourceIndexToFree;
audioRenderer->sourceIndex = AUDIO_SOURCE_UNASSIGNED;
}
#define AUDIO_CHUNK_SIZE_ 65536
void audio_beginVorbisStream(AudioManager *audioManager,
AudioRenderer *audioRenderer, AudioVorbis *vorbis,
i32 numPlays)
{
#ifdef DENGINE_DEBUG
ASSERT(audioManager && audioRenderer && vorbis);
if (numPlays != AUDIO_REPEAT_INFINITE && numPlays <= 0)
{
DEBUG_LOG("audio_beginVorbisStream() warning: Number of plays is less than 0");
}
#endif
i32 result = rendererAcquire(audioManager, audioRenderer);
if (result)
{
DEBUG_LOG("audio_beginVorbisStream() failed: Could not acquire renderer");
return;
}
/* Determine format */ /* Determine format */
audioRenderer->format = AL_FORMAT_MONO16; audioRenderer->format = AL_FORMAT_MONO16;
if (vorbis->info.channels == 2) if (vorbis->info.channels == 2)
@ -121,36 +180,66 @@ void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis)
else if (vorbis->info.channels != 1) else if (vorbis->info.channels != 1)
{ {
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG("audio_streamVorbis() warning: Unaccounted channel format"); DEBUG_LOG(
"audio_beginVorbisStream() warning: Unaccounted channel format");
ASSERT(INVALID_CODE_PATH); ASSERT(INVALID_CODE_PATH);
#endif #endif
} }
audioRenderer->audio = vorbis; audioRenderer->audio = vorbis;
audioRenderer->numPlays = numPlays;
} }
void audio_updateAndPlay(AudioRenderer *audioRenderer) void audio_updateAndPlay(AudioManager *audioManager,
AudioRenderer *audioRenderer)
{ {
AudioVorbis *audio = audioRenderer->audio; AudioVorbis *audio = audioRenderer->audio;
if (!audio) if (!audio)
{ {
#ifdef DENGINE_DEBUG
DEBUG_LOG("audio_updateAndPlay() early exit: No audio stream connected"); DEBUG_LOG("audio_updateAndPlay() early exit: No audio stream connected");
#endif
return; return;
} }
if (audioRenderer->numPlays != AUDIO_REPEAT_INFINITE &&
audioRenderer->numPlays <= 0)
{
rendererRelease(audioManager, audioRenderer);
return;
}
u32 alSourceId = audioManager->sourceList[audioRenderer->sourceIndex].id;
ALint audioState; ALint audioState;
alGetSourcei(audioRenderer->sourceId[0], AL_SOURCE_STATE, &audioState); alGetSourcei(alSourceId, AL_SOURCE_STATE, &audioState);
if (audioState == AL_STOPPED || audioState == AL_INITIAL) if (audioState == AL_STOPPED || audioState == AL_INITIAL)
{ {
// TODO(doyle): This fixes clicking when reusing old buffers // TODO(doyle): Delete and recreate fixes clicking when reusing buffers
if (audioState == AL_STOPPED) if (audioState == AL_STOPPED)
{ {
if (audioRenderer->numPlays != AUDIO_REPEAT_INFINITE)
audioRenderer->numPlays--;
alDeleteBuffers(ARRAY_COUNT(audioRenderer->bufferId), alDeleteBuffers(ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId); audioRenderer->bufferId);
if (audioRenderer->numPlays == AUDIO_REPEAT_INFINITE ||
audioRenderer->numPlays > 0)
{
alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId); audioRenderer->bufferId);
} }
else
{
rendererRelease(audioManager, audioRenderer);
return;
}
}
// TODO(doyle): Possible bug! Multiple sources playing same file seeking
// file ptr to start may interrupt other stream
stb_vorbis_seek_start(audio->file); stb_vorbis_seek_start(audio->file);
for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++) for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++)
{ {
@ -164,22 +253,21 @@ void audio_updateAndPlay(AudioRenderer *audioRenderer)
audio->info.sample_rate); audio->info.sample_rate);
} }
alSourceQueueBuffers(audioRenderer->sourceId[0], alSourceQueueBuffers(alSourceId, ARRAY_COUNT(audioRenderer->bufferId),
ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId); audioRenderer->bufferId);
alSourcePlay(audioRenderer->sourceId[0]); alSourcePlay(alSourceId);
} }
else if (audioState == AL_PLAYING) else if (audioState == AL_PLAYING)
{ {
ALint numProcessedBuffers; ALint numProcessedBuffers;
alGetSourcei(audioRenderer->sourceId[0], AL_BUFFERS_PROCESSED, alGetSourcei(alSourceId, AL_BUFFERS_PROCESSED,
&numProcessedBuffers); &numProcessedBuffers);
if (numProcessedBuffers > 0) if (numProcessedBuffers > 0)
{ {
ALint numBuffersToUnqueue = 1; ALint numBuffersToUnqueue = 1;
ALuint emptyBufferId; ALuint emptyBufferId;
alSourceUnqueueBuffers(audioRenderer->sourceId[0], alSourceUnqueueBuffers(alSourceId, numBuffersToUnqueue,
numBuffersToUnqueue, &emptyBufferId); &emptyBufferId);
i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0}; i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0};
i32 sampleCount = stb_vorbis_get_samples_short_interleaved( i32 sampleCount = stb_vorbis_get_samples_short_interleaved(
@ -189,12 +277,10 @@ void audio_updateAndPlay(AudioRenderer *audioRenderer)
/* There are still samples to play */ /* There are still samples to play */
if (sampleCount > 0) if (sampleCount > 0)
{ {
DEBUG_LOG("Buffering new audio data");
alBufferData(emptyBufferId, audioRenderer->format, audioChunk, alBufferData(emptyBufferId, audioRenderer->format, audioChunk,
sampleCount * audio->info.channels * sizeof(i16), sampleCount * audio->info.channels * sizeof(i16),
audio->info.sample_rate); audio->info.sample_rate);
alSourceQueueBuffers(audioRenderer->sourceId[0], 1, alSourceQueueBuffers(alSourceId, 1, &emptyBufferId);
&emptyBufferId);
} }
} }
} }

View File

@ -167,6 +167,20 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize)
{ {
AssetManager *assetManager = &state->assetManager; AssetManager *assetManager = &state->assetManager;
MemoryArena *arena = &state->arena; MemoryArena *arena = &state->arena;
/*
************************
* INITIALISE GAME AUDIO
************************
*/
i32 result = audio_init(&state->audioManager);
if (result)
{
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
/* /*
******************* *******************
* INITIALISE ASSETS * INITIALISE ASSETS
@ -352,9 +366,10 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize)
addEntity(arena, world, pos, size, type, dir, tex, collides); addEntity(arena, world, pos, size, type, dir, tex, collides);
soundscape->audio = PLATFORM_MEM_ALLOC(arena, 1, AudioRenderer); soundscape->audio = PLATFORM_MEM_ALLOC(arena, 1, AudioRenderer);
audio_rendererInit(soundscape->audio); soundscape->audio->sourceIndex = AUDIO_SOURCE_UNASSIGNED;
audio_streamVorbis(soundscape->audio, audio_beginVorbisStream(&state->audioManager, soundscape->audio,
asset_getVorbis(assetManager, audiolist_battle)); asset_getVorbis(assetManager, audiolist_battle),
AUDIO_REPEAT_INFINITE);
/* Init hero entity */ /* Init hero entity */
world->heroIndex = world->freeEntityIndex; world->heroIndex = world->freeEntityIndex;
@ -937,7 +952,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
if (entity->audio) if (entity->audio)
{ {
audio_updateAndPlay(entity->audio); audio_updateAndPlay(&state->audioManager, entity->audio);
} }
if (entity->state == entitystate_dead) if (entity->state == entitystate_dead)

View File

@ -93,19 +93,6 @@ int main()
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glCullFace(GL_BACK); glCullFace(GL_BACK);
/*
*******************
* INITIALISE AUDIO
*******************
*/
i32 result = audio_init();
if (result)
{
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
}
/* /*
******************* *******************
* INITIALISE GAME * INITIALISE GAME

View File

@ -5,19 +5,40 @@
#include "Dengine/Common.h" #include "Dengine/Common.h"
#define AUDIO_NO_FREE_SOURCE -1
typedef struct AudioSourceEntry
{
u32 id;
i32 nextFreeIndex;
} AudioSourceEntry;
#define AUDIO_MAX_SOURCES 32
typedef struct AudioManager
{
// NOTE(doyle): Source entries point to the next free index
AudioSourceEntry sourceList[AUDIO_MAX_SOURCES];
i32 freeSourceIndex;
} AudioManager;
#define AUDIO_REPEAT_INFINITE -10
#define AUDIO_SOURCE_UNASSIGNED -1
struct AudioRenderer struct AudioRenderer
{ {
ALuint sourceId[1]; i32 sourceIndex;
ALuint bufferId[4]; ALuint bufferId[4];
AudioVorbis *audio; AudioVorbis *audio;
ALuint format; ALuint format;
i32 numPlays;
}; };
typedef struct AudioRenderer AudioRenderer; typedef struct AudioRenderer AudioRenderer;
const i32 audio_init(); const i32 audio_init(AudioManager *audioManager);
const i32 audio_rendererInit(AudioRenderer *audioRenderer); void audio_beginVorbisStream(AudioManager *audioManager,
void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis); AudioRenderer *audioRenderer, AudioVorbis *vorbis,
void audio_updateAndPlay(AudioRenderer *audioRenderer); i32 numPlays);
void audio_updateAndPlay(AudioManager *audioManager,
AudioRenderer *audioRenderer);
#endif #endif

View File

@ -2,6 +2,7 @@
#define WORLDTRAVELLER_GAME_H #define WORLDTRAVELLER_GAME_H
#include "Dengine/AssetManager.h" #include "Dengine/AssetManager.h"
#include "Dengine/Audio.h"
#include "Dengine/Common.h" #include "Dengine/Common.h"
#include "Dengine/Entity.h" #include "Dengine/Entity.h"
#include "Dengine/Math.h" #include "Dengine/Math.h"
@ -14,7 +15,6 @@ typedef struct MemoryArena MemoryArena;
#define NUM_KEYS 1024 #define NUM_KEYS 1024
#define METERS_TO_PIXEL 240 #define METERS_TO_PIXEL 240
enum State;
typedef struct World typedef struct World
{ {
@ -45,6 +45,7 @@ typedef struct GameState
i32 tileSize; i32 tileSize;
AssetManager assetManager; AssetManager assetManager;
AudioManager audioManager;
MemoryArena arena; MemoryArena arena;
} GameState; } GameState;