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:
parent
824d8fd8a0
commit
630f2eaa5d
Binary file not shown.
132
src/Audio.c
132
src/Audio.c
@ -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 */
|
||||
alGetError();
|
||||
|
||||
@ -93,14 +96,41 @@ const i32 audio_init()
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
const i32 audio_rendererInit(AudioRenderer *audioRenderer)
|
||||
INTERNAL i32 rendererAcquire(AudioManager *audioManager,
|
||||
AudioRenderer *audioRenderer)
|
||||
{
|
||||
/* Generate number of concurrent audio file listeners */
|
||||
alGenSources(ARRAY_COUNT(audioRenderer->sourceId), audioRenderer->sourceId);
|
||||
AL_CHECK_ERROR();
|
||||
i32 vacantSource = audioManager->freeSourceIndex;
|
||||
if (audioManager->sourceList[vacantSource].nextFreeIndex ==
|
||||
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 */
|
||||
alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), audioRenderer->bufferId);
|
||||
@ -111,9 +141,38 @@ const i32 audio_rendererInit(AudioRenderer *audioRenderer)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define AUDIO_CHUNK_SIZE_ 65536
|
||||
void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis)
|
||||
INTERNAL void rendererRelease(AudioManager *audioManager,
|
||||
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 */
|
||||
audioRenderer->format = AL_FORMAT_MONO16;
|
||||
if (vorbis->info.channels == 2)
|
||||
@ -121,36 +180,66 @@ void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis)
|
||||
else if (vorbis->info.channels != 1)
|
||||
{
|
||||
#ifdef DENGINE_DEBUG
|
||||
DEBUG_LOG("audio_streamVorbis() warning: Unaccounted channel format");
|
||||
DEBUG_LOG(
|
||||
"audio_beginVorbisStream() warning: Unaccounted channel format");
|
||||
ASSERT(INVALID_CODE_PATH);
|
||||
#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;
|
||||
if (!audio)
|
||||
{
|
||||
#ifdef DENGINE_DEBUG
|
||||
DEBUG_LOG("audio_updateAndPlay() early exit: No audio stream connected");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (audioRenderer->numPlays != AUDIO_REPEAT_INFINITE &&
|
||||
audioRenderer->numPlays <= 0)
|
||||
{
|
||||
rendererRelease(audioManager, audioRenderer);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 alSourceId = audioManager->sourceList[audioRenderer->sourceIndex].id;
|
||||
|
||||
ALint audioState;
|
||||
alGetSourcei(audioRenderer->sourceId[0], AL_SOURCE_STATE, &audioState);
|
||||
alGetSourcei(alSourceId, AL_SOURCE_STATE, &audioState);
|
||||
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 (audioRenderer->numPlays != AUDIO_REPEAT_INFINITE)
|
||||
audioRenderer->numPlays--;
|
||||
|
||||
alDeleteBuffers(ARRAY_COUNT(audioRenderer->bufferId),
|
||||
audioRenderer->bufferId);
|
||||
alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId),
|
||||
audioRenderer->bufferId);
|
||||
|
||||
if (audioRenderer->numPlays == AUDIO_REPEAT_INFINITE ||
|
||||
audioRenderer->numPlays > 0)
|
||||
{
|
||||
alGenBuffers(ARRAY_COUNT(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);
|
||||
for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++)
|
||||
{
|
||||
@ -164,22 +253,21 @@ void audio_updateAndPlay(AudioRenderer *audioRenderer)
|
||||
audio->info.sample_rate);
|
||||
}
|
||||
|
||||
alSourceQueueBuffers(audioRenderer->sourceId[0],
|
||||
ARRAY_COUNT(audioRenderer->bufferId),
|
||||
alSourceQueueBuffers(alSourceId, ARRAY_COUNT(audioRenderer->bufferId),
|
||||
audioRenderer->bufferId);
|
||||
alSourcePlay(audioRenderer->sourceId[0]);
|
||||
alSourcePlay(alSourceId);
|
||||
}
|
||||
else if (audioState == AL_PLAYING)
|
||||
{
|
||||
ALint numProcessedBuffers;
|
||||
alGetSourcei(audioRenderer->sourceId[0], AL_BUFFERS_PROCESSED,
|
||||
alGetSourcei(alSourceId, AL_BUFFERS_PROCESSED,
|
||||
&numProcessedBuffers);
|
||||
if (numProcessedBuffers > 0)
|
||||
{
|
||||
ALint numBuffersToUnqueue = 1;
|
||||
ALuint emptyBufferId;
|
||||
alSourceUnqueueBuffers(audioRenderer->sourceId[0],
|
||||
numBuffersToUnqueue, &emptyBufferId);
|
||||
alSourceUnqueueBuffers(alSourceId, numBuffersToUnqueue,
|
||||
&emptyBufferId);
|
||||
|
||||
i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0};
|
||||
i32 sampleCount = stb_vorbis_get_samples_short_interleaved(
|
||||
@ -189,12 +277,10 @@ void audio_updateAndPlay(AudioRenderer *audioRenderer)
|
||||
/* There are still samples to play */
|
||||
if (sampleCount > 0)
|
||||
{
|
||||
DEBUG_LOG("Buffering new audio data");
|
||||
alBufferData(emptyBufferId, audioRenderer->format, audioChunk,
|
||||
sampleCount * audio->info.channels * sizeof(i16),
|
||||
audio->info.sample_rate);
|
||||
alSourceQueueBuffers(audioRenderer->sourceId[0], 1,
|
||||
&emptyBufferId);
|
||||
alSourceQueueBuffers(alSourceId, 1, &emptyBufferId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +167,20 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize)
|
||||
{
|
||||
AssetManager *assetManager = &state->assetManager;
|
||||
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
|
||||
@ -352,9 +366,10 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize)
|
||||
addEntity(arena, world, pos, size, type, dir, tex, collides);
|
||||
|
||||
soundscape->audio = PLATFORM_MEM_ALLOC(arena, 1, AudioRenderer);
|
||||
audio_rendererInit(soundscape->audio);
|
||||
audio_streamVorbis(soundscape->audio,
|
||||
asset_getVorbis(assetManager, audiolist_battle));
|
||||
soundscape->audio->sourceIndex = AUDIO_SOURCE_UNASSIGNED;
|
||||
audio_beginVorbisStream(&state->audioManager, soundscape->audio,
|
||||
asset_getVorbis(assetManager, audiolist_battle),
|
||||
AUDIO_REPEAT_INFINITE);
|
||||
|
||||
/* Init hero entity */
|
||||
world->heroIndex = world->freeEntityIndex;
|
||||
@ -937,7 +952,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
|
||||
|
||||
if (entity->audio)
|
||||
{
|
||||
audio_updateAndPlay(entity->audio);
|
||||
audio_updateAndPlay(&state->audioManager, entity->audio);
|
||||
}
|
||||
|
||||
if (entity->state == entitystate_dead)
|
||||
|
@ -93,19 +93,6 @@ int main()
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glCullFace(GL_BACK);
|
||||
|
||||
/*
|
||||
*******************
|
||||
* INITIALISE AUDIO
|
||||
*******************
|
||||
*/
|
||||
i32 result = audio_init();
|
||||
if (result)
|
||||
{
|
||||
#ifdef DENGINE_DEBUG
|
||||
ASSERT(INVALID_CODE_PATH);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
*******************
|
||||
* INITIALISE GAME
|
||||
|
@ -5,19 +5,40 @@
|
||||
|
||||
#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
|
||||
{
|
||||
ALuint sourceId[1];
|
||||
i32 sourceIndex;
|
||||
ALuint bufferId[4];
|
||||
|
||||
AudioVorbis *audio;
|
||||
ALuint format;
|
||||
i32 numPlays;
|
||||
};
|
||||
|
||||
typedef struct AudioRenderer AudioRenderer;
|
||||
|
||||
const i32 audio_init();
|
||||
const i32 audio_rendererInit(AudioRenderer *audioRenderer);
|
||||
void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis);
|
||||
void audio_updateAndPlay(AudioRenderer *audioRenderer);
|
||||
const i32 audio_init(AudioManager *audioManager);
|
||||
void audio_beginVorbisStream(AudioManager *audioManager,
|
||||
AudioRenderer *audioRenderer, AudioVorbis *vorbis,
|
||||
i32 numPlays);
|
||||
void audio_updateAndPlay(AudioManager *audioManager,
|
||||
AudioRenderer *audioRenderer);
|
||||
#endif
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define WORLDTRAVELLER_GAME_H
|
||||
|
||||
#include "Dengine/AssetManager.h"
|
||||
#include "Dengine/Audio.h"
|
||||
#include "Dengine/Common.h"
|
||||
#include "Dengine/Entity.h"
|
||||
#include "Dengine/Math.h"
|
||||
@ -14,7 +15,6 @@ typedef struct MemoryArena MemoryArena;
|
||||
#define NUM_KEYS 1024
|
||||
#define METERS_TO_PIXEL 240
|
||||
|
||||
enum State;
|
||||
|
||||
typedef struct World
|
||||
{
|
||||
@ -45,6 +45,7 @@ typedef struct GameState
|
||||
i32 tileSize;
|
||||
|
||||
AssetManager assetManager;
|
||||
AudioManager audioManager;
|
||||
MemoryArena arena;
|
||||
} GameState;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user