diff --git a/Dengine.vcxproj b/Dengine.vcxproj index b2030e8..2c138a2 100644 --- a/Dengine.vcxproj +++ b/Dengine.vcxproj @@ -121,6 +121,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/Dengine.vcxproj.filters b/Dengine.vcxproj.filters index 10360f9..d14e0fc 100644 --- a/Dengine.vcxproj.filters +++ b/Dengine.vcxproj.filters @@ -45,6 +45,9 @@ Source Files + + Source Files + @@ -95,5 +98,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/src/AssetManager.c b/src/AssetManager.c index 71b0909..b1aced2 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -1,3 +1,5 @@ +#define _CRT_SECURE_NO_WARNINGS + #define STBI_FAILURE_USERMSG #define STB_IMAGE_IMPLEMENTATION #include @@ -6,17 +8,32 @@ #define STB_TRUETYPE_IMPLEMENTATION #include -#include "Dengine/AssetManager.h" -#include "Dengine/Debug.h" -#include "Dengine/OpenGL.h" -#include "Dengine/Platform.h" - //#define WT_RENDER_FONT_FILE #ifdef WT_RENDER_FONT_FILE #define STB_IMAGE_WRITE_IMPLEMENTATION #include #endif +#include + +#include "Dengine/AssetManager.h" +#include "Dengine/Debug.h" +#include "Dengine/OpenGL.h" +#include "Dengine/Platform.h" + +AudioVorbis *asset_getVorbis(AssetManager *assetManager, + const enum AudioList type) +{ + if (type < audiolist_count) + return &assetManager->audio[type]; + +#ifdef DENGINE_DEBUG + ASSERT(INVALID_CODE_PATH); +#endif + + return NULL; +} + Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type) { if (type < texlist_count) @@ -40,7 +57,7 @@ TexAtlas *asset_getTextureAtlas(AssetManager *assetManager, const enum TexList t return NULL; } -Animation *asset_getAnim(AssetManager *assetManager, i32 type) +Animation *asset_getAnim(AssetManager *assetManager, const enum AnimList type) { if (type < animlist_count) return &assetManager->anims[type]; @@ -52,6 +69,35 @@ Animation *asset_getAnim(AssetManager *assetManager, i32 type) return NULL; } +const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena, + const char *const path, const enum AudioList type) +{ + // TODO(doyle): Remember to free vorbis file if we remove from memory + PlatformFileRead fileRead = {0}; + platform_readFileToBuffer(arena, path, &fileRead); + + i32 error; + AudioVorbis audio = {0}; + audio.file = + stb_vorbis_open_memory(fileRead.buffer, fileRead.size, &error, NULL); + + if (!audio.file) + { + printf("stb_vorbis_open_memory() failed: Error code %d\n", error); + platform_closeFileRead(arena, &fileRead); + stb_vorbis_close(audio.file); + return 0; + } + + audio.info = stb_vorbis_get_info(audio.file); + audio.lengthInSamples = stb_vorbis_stream_length_in_samples(audio.file); + audio.lengthInSeconds = stb_vorbis_stream_length_in_seconds(audio.file); + + assetManager->audio[type] = audio; + + return 0; +} + const i32 asset_loadTextureImage(AssetManager *assetManager, const char *const path, const enum TexList type) { diff --git a/src/Audio.c b/src/Audio.c new file mode 100644 index 0000000..99ce6ac --- /dev/null +++ b/src/Audio.c @@ -0,0 +1,170 @@ +#include + +#define STB_VORBIS_HEADER_ONLY +#include + +#include "Dengine/Assets.h" +#include "Dengine/Audio.h" +#include "Dengine/Common.h" +#include "Dengine/Debug.h" + +#define AL_CHECK_ERROR() alCheckError_(__FILE__, __LINE__); +void alCheckError_(const char *file, int line) +{ + + ALenum errorCode; + while ((errorCode = alGetError()) != AL_NO_ERROR) + { + printf("OPENAL "); + switch(errorCode) + { + case AL_INVALID_NAME: + printf("INVALID_NAME | "); + break; + case AL_INVALID_ENUM: + printf("INVALID_ENUM | "); + break; + case AL_INVALID_VALUE: + printf("INVALID_VALUE | "); + break; + case AL_INVALID_OPERATION: + printf("INVALID_OPERATION | "); + break; + case AL_OUT_OF_MEMORY: + printf("OUT_OF_MEMORY | "); + break; + default: + printf("UNRECOGNISED ERROR CODE | "); + break; + } + printf("Error %08x, %s (%d)\n", errorCode, file, line); + } +}; + +const i32 audio_rendererInit(AudioRenderer *audioRenderer) +{ + alGetError(); + ALCdevice *deviceAL = alcOpenDevice(NULL); + if (!deviceAL) + { + printf("alcOpenDevice() failed: Failed to init OpenAL device.\n"); + return -1; + } + + ALCcontext *contextAL = alcCreateContext(deviceAL, NULL); + alcMakeContextCurrent(contextAL); + if (!contextAL) + { + printf("alcCreateContext() failed: Failed create AL context.\n"); + return -1; + } + AL_CHECK_ERROR(); + + /* Generate number of concurrent audio file listeners */ + alGenSources(ARRAY_COUNT(audioRenderer->sourceId), audioRenderer->sourceId); + AL_CHECK_ERROR(); + + /* Generate audio data buffers */ + alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), audioRenderer->bufferId); + AL_CHECK_ERROR(); + + return 0; +} + +#define AUDIO_CHUNK_SIZE_ 65536 +void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis) +{ + /* Determine format */ + audioRenderer->format = AL_FORMAT_MONO16; + if (vorbis->info.channels == 2) + audioRenderer->format = AL_FORMAT_STEREO16; + else if (vorbis->info.channels != 1) + DEBUG_LOG("audio_streamVorbis() warning: Unaccounted channel format"); + + audioRenderer->audio = vorbis; + AudioVorbis *audio = audioRenderer->audio; + + /* Pre-load vorbis data into audio chunks */ + for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++) + { + i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0}; + stb_vorbis_get_samples_short_interleaved( + audio->file, audio->info.channels, audioChunk, AUDIO_CHUNK_SIZE_); + + alBufferData(audioRenderer->bufferId[i], audioRenderer->format, + audioChunk, AUDIO_CHUNK_SIZE_ * sizeof(i16), + audio->info.sample_rate); + } + + /* Queue and play buffers */ + alSourceQueueBuffers(audioRenderer->sourceId[0], + ARRAY_COUNT(audioRenderer->bufferId), + audioRenderer->bufferId); + alSourcePlay(audioRenderer->sourceId[0]); +} + +void audio_updateAndPlay(AudioRenderer *audioRenderer) +{ + AudioVorbis *audio = audioRenderer->audio; + + ALint audioState; + alGetSourcei(audioRenderer->sourceId[0], AL_SOURCE_STATE, &audioState); + if (audioState == AL_STOPPED || audioState == AL_INITIAL) + { + // TODO(doyle): This fixes clicking when reusing old buffers + if (audioState == AL_STOPPED) + { + alDeleteBuffers(ARRAY_COUNT(audioRenderer->bufferId), + audioRenderer->bufferId); + alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), + audioRenderer->bufferId); + } + + stb_vorbis_seek_start(audio->file); + for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++) + { + i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0}; + stb_vorbis_get_samples_short_interleaved( + audio->file, audio->info.channels, audioChunk, + AUDIO_CHUNK_SIZE_); + + alBufferData(audioRenderer->bufferId[i], audioRenderer->format, + audioChunk, AUDIO_CHUNK_SIZE_ * sizeof(i16), + audio->info.sample_rate); + } + + alSourceQueueBuffers(audioRenderer->sourceId[0], + ARRAY_COUNT(audioRenderer->bufferId), + audioRenderer->bufferId); + alSourcePlay(audioRenderer->sourceId[0]); + } + else if (audioState == AL_PLAYING) + { + ALint numProcessedBuffers; + alGetSourcei(audioRenderer->sourceId[0], AL_BUFFERS_PROCESSED, + &numProcessedBuffers); + if (numProcessedBuffers > 0) + { + ALint numBuffersToUnqueue = 1; + ALuint emptyBufferId; + alSourceUnqueueBuffers(audioRenderer->sourceId[0], + numBuffersToUnqueue, &emptyBufferId); + + i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0}; + i32 sampleCount = stb_vorbis_get_samples_short_interleaved( + audio->file, audio->info.channels, audioChunk, + AUDIO_CHUNK_SIZE_); + + /* 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); + } + } + } +} diff --git a/src/dengine.c b/src/dengine.c index 543e31c..b2265c7 100644 --- a/src/dengine.c +++ b/src/dengine.c @@ -1,11 +1,6 @@ #if 1 -#include -#include - -#define _CRT_SECURE_NO_WARNINGS -#include - #include "Dengine/AssetManager.h" +#include "Dengine/Audio.h" #include "Dengine/Common.h" #include "Dengine/Debug.h" #include "Dengine/Math.h" @@ -14,38 +9,8 @@ #include "WorldTraveller/WorldTraveller.h" -void alCheckError_(const char *file, int line) -{ - - ALenum errorCode; - while ((errorCode = alGetError()) != AL_NO_ERROR) - { - printf("OPENAL "); - switch(errorCode) - { - case AL_INVALID_NAME: - printf("INVALID_NAME | "); - break; - case AL_INVALID_ENUM: - printf("INVALID_ENUM | "); - break; - case AL_INVALID_VALUE: - printf("INVALID_VALUE | "); - break; - case AL_INVALID_OPERATION: - printf("INVALID_OPERATION | "); - break; - case AL_OUT_OF_MEMORY: - printf("OUT_OF_MEMORY | "); - break; - default: - printf("UNRECOGNISED ERROR CODE | "); - break; - } - printf("Error %08x, %s (%d)\n", errorCode, file, line); - } -}; -#define AL_CHECK_ERROR() alCheckError_(__FILE__, __LINE__); +// TODO(doyle): Temporary +struct AudioRenderer; void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode) { @@ -148,64 +113,20 @@ int main() * INITIALISE AUDIO ******************* */ - alGetError(); - // TODO(doyle): Read this - // http://www.gamedev.net/page/resources/_/technical/game-programming/basic-openal-sound-manager-for-your-project-r3791 - // https://gist.github.com/Oddity007/965399 - // https://jogamp.org/joal-demos/www/devmaster/lesson8.html - // http://basic-converter.proboards.com/thread/818/play-files-using-vorbis-openal - ALCdevice *deviceAL = alcOpenDevice(NULL); - if (!deviceAL) - { - printf("alcOpenDevice() failed: Failed to init OpenAL device.\n"); - return; - } + AudioRenderer audioRenderer = {0}; + audio_rendererInit(&audioRenderer); - ALCcontext *contextAL = alcCreateContext(deviceAL, NULL); - alcMakeContextCurrent(contextAL); - if (!contextAL) - { - printf("alcCreateContext() failed: Failed create AL context.\n"); - return; - } - AL_CHECK_ERROR(); + /* Load audio assets */ + char *audioPath = "data/audio/Nobuo Uematsu - Battle 1.ogg"; + asset_loadVorbis(&worldTraveller.assetManager, &worldTraveller.arena, + audioPath, audiolist_battle); + audioPath = "data/audio/Yuki Kajiura - Swordland.ogg"; + asset_loadVorbis(&worldTraveller.assetManager, &worldTraveller.arena, + audioPath, audiolist_overworld); + AudioVorbis *audio = + asset_getVorbis(&worldTraveller.assetManager, audiolist_battle); - /* Open audio file */ - PlatformFileRead fileRead = {0}; -#if 0 - platform_readFileToBuffer(&worldTraveller.arena, - "data/audio/Yuki Kajiura - Swordland.ogg", - &fileRead); -#else - platform_readFileToBuffer(&worldTraveller.arena, - "data/audio/Nobuo Uematsu - Battle 1.ogg", - &fileRead); -#endif - - i32 error; - stb_vorbis *vorbisFile = stb_vorbis_open_memory(fileRead.buffer, fileRead.size, - &error, NULL); - stb_vorbis_info vorbisInfo = stb_vorbis_get_info(vorbisFile); - - //platform_closeFileRead(&worldTraveller.arena, &fileRead); - - /* Generate number of concurrent audio file listeners */ - ALuint audioSourceId; - alGenSources(1, &audioSourceId); - AL_CHECK_ERROR(); - - /* Generate audio data buffers */ - ALuint audioBufferId[4]; - alGenBuffers(ARRAY_COUNT(audioBufferId), audioBufferId); - AL_CHECK_ERROR(); - - -#if 0 - ALuint audioFormat = AL_FORMAT_MONO16; - if (vorbisInfo.channels == 2) audioFormat = AL_FORMAT_STEREO16; - i32 audioState; - alGetSourcei(audioSourceIds[0], AL_SOURCE_STATE, &audioState); -#endif + audio_streamVorbis(&audioRenderer, audio); /* ******************* @@ -237,6 +158,7 @@ int main() worldTraveller_gameUpdateAndRender(&worldTraveller, secondsElapsed); GL_CHECK_ERROR(); + audio_updateAndPlay(&audioRenderer); /* Swap the buffers */ glfwSwapBuffers(window); @@ -244,64 +166,6 @@ int main() f32 endTime = CAST(f32)glfwGetTime(); secondsElapsed = endTime - startTime; -#define AUDIO_CHUNK_SIZE 65536 - ALint audioState; - alGetSourcei(audioSourceId, AL_SOURCE_STATE, &audioState); - if (audioState == AL_STOPPED || audioState == AL_INITIAL) - { - // TODO(doyle): This fixes clicking when reusing old buffers - if (audioState == AL_STOPPED) - { - alDeleteBuffers(ARRAY_COUNT(audioBufferId), audioBufferId); - alGenBuffers(ARRAY_COUNT(audioBufferId), audioBufferId); - } - - stb_vorbis_seek_start(vorbisFile); - for (i32 i = 0; i < ARRAY_COUNT(audioBufferId); i++) - { - i16 audioChunk[AUDIO_CHUNK_SIZE] = {0}; - stb_vorbis_get_samples_short_interleaved( - vorbisFile, vorbisInfo.channels, audioChunk, - AUDIO_CHUNK_SIZE); - - alBufferData(audioBufferId[i], AL_FORMAT_STEREO16, audioChunk, - AUDIO_CHUNK_SIZE * sizeof(i16), - vorbisInfo.sample_rate); - } - - alSourceQueueBuffers(audioSourceId, ARRAY_COUNT(audioBufferId), - audioBufferId); - alSourcePlay(audioSourceId); - } - else if (audioState == AL_PLAYING) - { - ALint numProcessedBuffers; - alGetSourcei(audioSourceId, AL_BUFFERS_PROCESSED, - &numProcessedBuffers); - if (numProcessedBuffers > 0) - { - ALint numBuffersToUnqueue = 1; - ALuint emptyBufferId; - alSourceUnqueueBuffers(audioSourceId, numBuffersToUnqueue, - &emptyBufferId); - - i16 audioChunk[AUDIO_CHUNK_SIZE] = {0}; - i32 sampleCount = stb_vorbis_get_samples_short_interleaved( - vorbisFile, vorbisInfo.channels, audioChunk, - AUDIO_CHUNK_SIZE); - - /* There are still samples to play */ - if (sampleCount > 0) - { - DEBUG_LOG("Buffering new audio data"); - alBufferData(emptyBufferId, AL_FORMAT_STEREO16, audioChunk, - sampleCount * vorbisInfo.channels * - sizeof(i16), - vorbisInfo.sample_rate); - alSourceQueueBuffers(audioSourceId, 1, &emptyBufferId); - } - } - } #if 0 // TODO(doyle): Busy waiting, should sleep while (secondsElapsed < targetSecondsPerFrame) diff --git a/src/include/Dengine/AssetManager.h b/src/include/Dengine/AssetManager.h index 0f0be67..040d7b6 100644 --- a/src/include/Dengine/AssetManager.h +++ b/src/include/Dengine/AssetManager.h @@ -2,30 +2,38 @@ #define DENGINE_ASSET_MANAGER_H #include "Dengine/Assets.h" -#include "Dengine/MemoryArena.h" #include "Dengine/Shader.h" #include "Dengine/Texture.h" +/* Forward declaration */ +typedef struct MemoryArena MemoryArena; + #define MAX_TEXTURE_SIZE 1024 // TODO(doyle): Switch to hash based lookup +// TODO(doyle): Use pointers, so we can forward declare all assets? typedef struct AssetManager { Texture textures[32]; TexAtlas texAtlas[32]; Shader shaders[32]; Animation anims[32]; + AudioVorbis audio[32]; Font font; } AssetManager; GLOBAL_VAR AssetManager assetManager; +AudioVorbis *asset_getVorbis(AssetManager *assetManager, + const enum AudioList type); Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type); Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type); TexAtlas *asset_getTextureAtlas(AssetManager *assetManager, const enum TexList type); Animation *asset_getAnim(AssetManager *assetManager, i32 type); +const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena, + const char *const path, const enum AudioList type); const i32 asset_loadTextureImage(AssetManager *assetManager, const char *const path, const enum TexList type); diff --git a/src/include/Dengine/Assets.h b/src/include/Dengine/Assets.h index 2d3392b..841bdf4 100644 --- a/src/include/Dengine/Assets.h +++ b/src/include/Dengine/Assets.h @@ -1,6 +1,9 @@ #ifndef DENGINE_ASSETS_H #define DENGINE_ASSETS_H +#define STB_VORBIS_HEADER_ONLY +#include + #include "Dengine/Math.h" #include "Dengine/Texture.h" @@ -52,6 +55,23 @@ enum AnimList animlist_invalid, }; +enum AudioList +{ + audiolist_battle, + audiolist_overworld, + audiolist_count, + audiolist_invalid, +}; + +typedef struct AudioVorbis +{ + stb_vorbis *file; + stb_vorbis_info info; + + u32 lengthInSamples; + f32 lengthInSeconds; +} AudioVorbis; + typedef struct TexAtlas { // TODO(doyle): String hash based lookup diff --git a/src/include/Dengine/Audio.h b/src/include/Dengine/Audio.h new file mode 100644 index 0000000..e172f5f --- /dev/null +++ b/src/include/Dengine/Audio.h @@ -0,0 +1,18 @@ +#ifndef DENGINE_AUDIO_H +#define DENGINE_AUDIO_H + +#include + +typedef struct AudioRenderer +{ + ALuint sourceId[1]; + ALuint bufferId[4]; + + AudioVorbis *audio; + ALuint format; +} AudioRenderer; + +const i32 audio_rendererInit(AudioRenderer *audioRenderer); +void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis); +void audio_updateAndPlay(AudioRenderer *audioRenderer); +#endif diff --git a/src/include/Dengine/MemoryArena.h b/src/include/Dengine/MemoryArena.h index ebf2618..255249b 100644 --- a/src/include/Dengine/MemoryArena.h +++ b/src/include/Dengine/MemoryArena.h @@ -1,9 +1,11 @@ #ifndef DENGINE_MEMORY_ARENA_H #define DENGINE_MEMORY_ARENA_H -typedef struct MemoryArena +#include "Dengine/Common.h" + +struct MemoryArena { i32 bytesAllocated; -} MemoryArena; +}; #endif diff --git a/src/include/Dengine/Platform.h b/src/include/Dengine/Platform.h index f1afdf7..23b036a 100644 --- a/src/include/Dengine/Platform.h +++ b/src/include/Dengine/Platform.h @@ -2,7 +2,9 @@ #define DENGINE_PLATFORM_H #include "Dengine/Common.h" -#include "Dengine/MemoryArena.h" + +/* Forward Declaration */ +typedef struct MemoryArena MemoryArena; typedef struct PlatformFileRead { diff --git a/src/include/WorldTraveller/WorldTraveller.h b/src/include/WorldTraveller/WorldTraveller.h index 0ea6723..a74a36c 100644 --- a/src/include/WorldTraveller/WorldTraveller.h +++ b/src/include/WorldTraveller/WorldTraveller.h @@ -8,6 +8,9 @@ #include "Dengine/MemoryArena.h" #include "Dengine/Renderer.h" +/* Forward Declaration */ +typedef struct MemoryArena MemoryArena; + #define NUM_KEYS 1024 #define METERS_TO_PIXEL 240