diff --git a/data/blackboard.art b/data/blackboard.art index 55c9699..28dcd03 100644 Binary files a/data/blackboard.art and b/data/blackboard.art differ diff --git a/src/Audio.c b/src/Audio.c index 3a304f6..f3a14de 100644 --- a/src/Audio.c +++ b/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); } } } diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 0c5c239..df08fd8 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -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) diff --git a/src/dengine.c b/src/dengine.c index 003def7..6af4e13 100644 --- a/src/dengine.c +++ b/src/dengine.c @@ -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 diff --git a/src/include/Dengine/Audio.h b/src/include/Dengine/Audio.h index 81f9ce6..4ba35f4 100644 --- a/src/include/Dengine/Audio.h +++ b/src/include/Dengine/Audio.h @@ -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 diff --git a/src/include/WorldTraveller/WorldTraveller.h b/src/include/WorldTraveller/WorldTraveller.h index a74a36c..4c4ebf4 100644 --- a/src/include/WorldTraveller/WorldTraveller.h +++ b/src/include/WorldTraveller/WorldTraveller.h @@ -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;