From e3f4f9c94f411dacb31552beb9df74eb84262181 Mon Sep 17 00:00:00 2001 From: Abdelrahman Date: Sun, 25 Sep 2022 02:19:21 +0100 Subject: [PATCH] Implemented load audio and music files as well the basic audio playback functionality. --- include/AssetManager.h | 2 +- include/GameEngine.h | 35 +++- include/ObjectStore.h | 2 +- include/Renderer.h | 2 +- src/AssetManager.c | 75 ++++++--- src/GameEngine.c | 370 +++++++++++++++++++++++++++++++++++++++-- src/ObjectStore.c | 22 +-- src/Renderer.c | 14 +- src/Window.c | 3 +- 9 files changed, 464 insertions(+), 61 deletions(-) diff --git a/include/AssetManager.h b/include/AssetManager.h index c06bebb..07a9d38 100644 --- a/include/AssetManager.h +++ b/include/AssetManager.h @@ -27,7 +27,7 @@ void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr); SDL_Texture *get_texture(AssetManager *am, AssetID id); Mix_Chunk *get_audio_chunk(AssetManager *am, AssetID id); Mix_Music *get_music(AssetManager *am, AssetID id); -void destroy_asset_manager(AssetManager *am); +void destroy_asset_manager(AssetManager **am); #ifdef __cplusplus } diff --git a/include/GameEngine.h b/include/GameEngine.h index 5c3b697..7ed36f1 100644 --- a/include/GameEngine.h +++ b/include/GameEngine.h @@ -6,19 +6,50 @@ #include "SDL_pixels.h" #include "reusable_ids.h" #include +#include #ifdef __cplusplus extern "C" { #endif // !__cplusplus -bool init_game(const char *game_name, int window_width, int window_height, - SDL_Color window_background_color, bool full_screen); +bool init_game(const char *game_name, uint32_t window_width, + uint32_t window_height, SDL_Color window_background_color, + bool full_screen, uint8_t audio_tracks); bool game_running(); ObjectID add_object(void *object, EventHandlerPtr eh, UpdateFuncPtr uf, RenderFuncPtr rf); void remove_object(ObjectID *id); AssetID load_asset(const char *path, AssetType type); void render_texture_asset(AssetID id, float x, float y, float w, float h); +int8_t play_audio_asset(AssetID id, int16_t loops, bool fade_in, + uint32_t fade_length_in_ms); +void pause_audio_asset_on_track(AssetID id, uint8_t track); +void resume_audio_asset_on_track(AssetID id, uint8_t track); +void stop_audio_asset_on_track(AssetID id, uint8_t track); +void fade_out_audio_asset_on_track(AssetID id, uint8_t track, + uint32_t fade_length_in_ms); +void pause_all_instances_of_audio_asset(AssetID id); +void resume_all_instances_of_audio_asset(AssetID id); +void stop_all_instances_of_audio_asset(AssetID id); +void fade_out_all_instances_of_audio_asset(AssetID id, + uint32_t fade_length_in_ms); +int8_t get_audio_asset_volume(AssetID id); +void set_audio_asset_volume(AssetID id, int8_t volume); +void play_music_asset(AssetID id, int16_t loops, bool fade_in, + uint32_t fade_length_in_ms); +void pause_music_asset(AssetID id); +void resume_music_asset(AssetID id); +void stop_music_asset(AssetID id); +void fade_out_music_asset(AssetID id, uint32_t fade_length_in_ms); +int8_t get_music_asset_volume(AssetID id); +void set_music_asset_volume(AssetID id, int8_t volume); +bool is_music_asset_playing(AssetID id); +void pause_playing_music(); +void resume_playing_music(); +void stop_playing_music(); +void fade_out_playing_music(uint32_t fade_length_in_ms); +void set_playing_music_volume(int8_t volume); +bool is_music_playing(); void close_asset(AssetID *id); void handle_events(); void update(); diff --git a/include/ObjectStore.h b/include/ObjectStore.h index c9cec83..05de79f 100644 --- a/include/ObjectStore.h +++ b/include/ObjectStore.h @@ -23,7 +23,7 @@ void remove_object_from_store(ObjectStore *os, ObjectID *id_ptr); void handle_all_objects_events(ObjectStore *os, const SDL_Event *event); void update_all_objects(ObjectStore *os, float delta); void render_all_objects(ObjectStore *os); -void destroy_object_store(ObjectStore *os); +void destroy_object_store(ObjectStore **os); #ifdef DEBUG_ENGINE void print_objects(ObjectStore *os); diff --git a/include/Renderer.h b/include/Renderer.h index 8768ac7..0f2c569 100644 --- a/include/Renderer.h +++ b/include/Renderer.h @@ -19,7 +19,7 @@ void render_clear(const Renderer *renderer, SDL_Color color); void render_texture(const Renderer *renderer, SDL_Texture *texture, float x, float y, float w, float h); void swap_buffers(const Renderer *renderer); -void destroy_renderer(Renderer *renderer); +void destroy_renderer(Renderer **renderer); #ifdef __cplusplus } diff --git a/src/AssetManager.c b/src/AssetManager.c index 3068ef6..8c83b27 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -171,7 +171,16 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, AssetID existing_id = get_asset_id_from_id_node(am, &(am->hashed_path_ids[hash_index]), path); + if (existing_id != INVALID_ID) { + if (am->assets[existing_id].type != type) { + // If the user tries to load an asset that is already loaded, but they + // try to load it as a type that doesn't match the asset's actual type, + // we return an INVALID_ID to ensure that the user doesn't try using the + // asset with functions that work on the wrong type + return INVALID_ID; + } + ++(am->user_counter[existing_id]); return existing_id; } @@ -189,16 +198,25 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, asset.data.texture = load_texture(renderer, path); if (!(asset.data.texture)) { - printf("Error loading %s: %s\n", path, SDL_GetError()); loaded = false; } break; case ASSET_TYPE_AUDIO_FX: - // TODO (Abdelrahman): Implement loading audio chunks + asset.data.chunk = Mix_LoadWAV(path); + + if (!(asset.data.chunk)) { + loaded = false; + } + break; case ASSET_TYPE_MUSIC: - // TODO (Abdelrahman): Implement loading music + asset.data.music = Mix_LoadMUS(path); + + if (!(asset.data.music)) { + loaded = false; + } + break; default: loaded = false; @@ -206,6 +224,7 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, } if (!loaded) { + printf("Error loading %s: %s\n", path, SDL_GetError()); return INVALID_ID; } @@ -266,7 +285,7 @@ void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr) { remove_id_node(&(am->hashed_path_ids[hash_index]), id); free(am->assets[id].path); - am->assets[id].path = 0; + am->assets[id].path = NULL; switch (am->assets[id].type) { case ASSET_TYPE_IMAGE: @@ -274,10 +293,12 @@ void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr) { am->assets[id].data.texture = NULL; break; case ASSET_TYPE_AUDIO_FX: - // TODO (Abdelrahman): Implement closing audio chunk + Mix_FreeChunk(am->assets[id].data.chunk); + am->assets[id].data.chunk = NULL; break; case ASSET_TYPE_MUSIC: - // TODO (Abdelrahman): Implement closing music + Mix_FreeMusic(am->assets[id].data.music); + am->assets[id].data.music = NULL; break; default: break; @@ -289,7 +310,7 @@ void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr) { } SDL_Texture *get_texture(AssetManager *am, AssetID id) { - if (am->assets[id].type != ASSET_TYPE_IMAGE) { + if (id < 0 || am->assets[id].type != ASSET_TYPE_IMAGE) { return NULL; } @@ -297,7 +318,7 @@ SDL_Texture *get_texture(AssetManager *am, AssetID id) { } Mix_Chunk *get_audio_chunk(AssetManager *am, AssetID id) { - if (am->assets[id].type != ASSET_TYPE_AUDIO_FX) { + if (id < 0 || am->assets[id].type != ASSET_TYPE_AUDIO_FX) { return NULL; } @@ -305,46 +326,48 @@ Mix_Chunk *get_audio_chunk(AssetManager *am, AssetID id) { } Mix_Music *get_music(AssetManager *am, AssetID id) { - if (am->assets[id].type != ASSET_TYPE_MUSIC) { + if (id < 0 || am->assets[id].type != ASSET_TYPE_MUSIC) { return NULL; } return am->assets[id].data.music; } -void destroy_asset_manager(AssetManager *am) { - if (!am) { +void destroy_asset_manager(AssetManager **am) { + if (!(*am)) { return; } - for (size_t i = 0; i < am->count; ++i) { - am->user_counter[i] = 0; + for (size_t i = 0; i < (*am)->count; ++i) { + (*am)->user_counter[i] = 0; - size_t hash_index = hash_path(am->assets[i].path); - remove_id_node(&(am->hashed_path_ids[hash_index]), i); + size_t hash_index = hash_path((*am)->assets[i].path); + remove_id_node(&((*am)->hashed_path_ids[hash_index]), i); - free(am->assets[i].path); - am->assets[i].path = NULL; + free((*am)->assets[i].path); + (*am)->assets[i].path = NULL; - switch (am->assets[i].type) { + switch ((*am)->assets[i].type) { case ASSET_TYPE_IMAGE: - SDL_DestroyTexture(am->assets[i].data.texture); - am->assets[i].data.texture = NULL; + SDL_DestroyTexture((*am)->assets[i].data.texture); + (*am)->assets[i].data.texture = NULL; break; case ASSET_TYPE_AUDIO_FX: - // TODO (Abdelrahman): Implement closing audio chunk + Mix_FreeChunk((*am)->assets[i].data.chunk); + (*am)->assets[i].data.chunk = NULL; break; case ASSET_TYPE_MUSIC: - // TODO (Abdelrahman): Implement closing music + Mix_FreeMusic((*am)->assets[i].data.music); + (*am)->assets[i].data.music = NULL; break; default: break; } } - am->count = 0; - am->deleted_count = 0; + (*am)->count = 0; + (*am)->deleted_count = 0; - free(am); - am = NULL; + free(*am); + *am = NULL; } diff --git a/src/GameEngine.c b/src/GameEngine.c index e130d47..02c24b5 100644 --- a/src/GameEngine.c +++ b/src/GameEngine.c @@ -6,6 +6,7 @@ #include "SDL_error.h" #include "SDL_events.h" #include "SDL_image.h" +#include "SDL_mixer.h" #include "SDL_pixels.h" #include "SDL_render.h" #include "SDL_timer.h" @@ -13,8 +14,8 @@ #include "constants.h" #include "engine_build_config.h" #include "reusable_ids.h" -#include #include +#include #include #include @@ -32,8 +33,9 @@ INTERNAL struct game_engine { AssetManager *asset_manager; } GameEngine; -bool init_game(const char *game_name, int window_width, int window_height, - SDL_Color window_background_color, bool full_screen) { +bool init_game(const char *game_name, uint32_t window_width, + uint32_t window_height, SDL_Color window_background_color, + bool full_screen, uint8_t audio_tracks) { bool initialisation_complete = true; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { @@ -47,11 +49,36 @@ bool init_game(const char *game_name, int window_width, int window_height, uint32_t img_flags = IMG_INIT_PNG | IMG_INIT_TIF; if ((IMG_Init(img_flags) & img_flags) != img_flags) { #ifdef DEBUG_ENGINE - printf("Failed to initialise SDL. Error: %s\n", IMG_GetError()); + printf("Failed to initialise SDL_image. Error: %s\n", IMG_GetError()); #endif /* DEBUG_ENGINE */ initialisation_complete = false; } + // We're only interested in initialising SDL_mixer with WAVE files, so no + // need to pass any flags + if (Mix_Init(0) != 0) { +#ifdef DEBUG_ENGINE + printf("Failed to initialise SDL_mixer. Error: %s\n", Mix_GetError()); +#endif /* DEBUG_ENGINE */ + initialisation_complete = false; + } + + if (Mix_OpenAudio(48000, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { +#ifdef DEBUG_ENGINE + printf("Failed to open audio device. Error: %s\n", Mix_GetError()); +#endif /* DEBUG_ENGINE */ + initialisation_complete = false; + } + + if (audio_tracks > 0) { + if (Mix_AllocateChannels(audio_tracks) != audio_tracks) { +#ifdef DEBUG_ENGINE + printf("Failed to allocate the required tracks.\n"); +#endif /* DEBUG_ENGINE */ + initialisation_complete = false; + } + } + if (!init_window(&(GameEngine.window), game_name, window_width, window_height, window_background_color, full_screen)) { #ifdef DEBUG_ENGINE @@ -62,13 +89,11 @@ bool init_game(const char *game_name, int window_width, int window_height, } GameEngine.object_store = create_object_store(); - if (!(GameEngine.object_store)) { initialisation_complete = false; } GameEngine.asset_manager = create_asset_manager(); - if (!(GameEngine.asset_manager)) { initialisation_complete = false; } @@ -125,10 +150,333 @@ void render_texture_asset(AssetID id, float x, float y, float w, float h) { SDL_Texture *texture = get_texture(GameEngine.asset_manager, id); + if (!texture) { + return; + } + render_texture(GameEngine.window.renderer, texture, x, y, w, h); } +int8_t play_audio_asset(AssetID id, int16_t loops, bool fade_in, + uint32_t fade_length_in_ms) { + if (id < 0) { + return -1; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return -1; + } + + if (fade_in) { + return Mix_FadeInChannel(-1, chunk, loops, fade_length_in_ms); + } else { + return Mix_PlayChannel(-1, chunk, loops); + } +} + +void pause_audio_asset_on_track(AssetID id, uint8_t track) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk || chunk != Mix_GetChunk(track)) { + return; + } + + Mix_Pause(track); +} + +void resume_audio_asset_on_track(AssetID id, uint8_t track) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk || chunk != Mix_GetChunk(track)) { + return; + } + + Mix_Resume(track); +} + +void stop_audio_asset_on_track(AssetID id, uint8_t track) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk || chunk != Mix_GetChunk(track)) { + return; + } + + Mix_HaltChannel(track); +} + +void fade_out_audio_asset_on_track(AssetID id, uint8_t track, + uint32_t fade_length_in_ms) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk || chunk != Mix_GetChunk(track)) { + return; + } + + Mix_FadeOutChannel(track, fade_length_in_ms); +} + +void pause_all_instances_of_audio_asset(AssetID id) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return; + } + + size_t channel_count = Mix_AllocateChannels(-1); + + for (size_t i = 0; i < channel_count; ++i) { + if (chunk == Mix_GetChunk(i)) { + Mix_Pause(i); + } + } +} + +void resume_all_instances_of_audio_asset(AssetID id) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return; + } + + size_t channel_count = Mix_AllocateChannels(-1); + + for (size_t i = 0; i < channel_count; ++i) { + if (chunk == Mix_GetChunk(i)) { + Mix_Resume(i); + } + } +} + +void stop_all_instances_of_audio_asset(AssetID id) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return; + } + + size_t channel_count = Mix_AllocateChannels(-1); + + for (size_t i = 0; i < channel_count; ++i) { + if (chunk == Mix_GetChunk(i)) { + Mix_HaltChannel(i); + } + } +} + +void fade_out_all_instances_of_audio_asset(AssetID id, + uint32_t fade_length_in_ms) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return; + } + + size_t channel_count = Mix_AllocateChannels(-1); + + for (size_t i = 0; i < channel_count; ++i) { + if (chunk == Mix_GetChunk(i)) { + Mix_FadeOutChannel(i, fade_length_in_ms); + } + } +} + +int8_t get_audio_asset_volume(AssetID id) { + if (id < 0) { + return -1; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return -1; + } + + return chunk->volume; +} + +void set_audio_asset_volume(AssetID id, int8_t volume) { + if (id < 0) { + return; + } + + Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id); + if (!chunk) { + return; + } + + Mix_VolumeChunk(chunk, volume); +} + +void play_music_asset(AssetID id, int16_t loops, bool fade_in, + uint32_t fade_length_in_ms) { + if (id < 0 || is_music_asset_playing(id)) { + return; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return; + } + + if (fade_in) { + Mix_FadeInMusic(music, loops, fade_length_in_ms); + } else { + Mix_PlayMusic(music, loops); + } +} + +void pause_music_asset(AssetID id) { + if (id < 0 || !is_music_asset_playing(id)) { + return; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return; + } + + Mix_PauseMusic(); +} + +void resume_music_asset(AssetID id) { + if (id < 0 || !is_music_asset_playing(id)) { + return; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return; + } + + Mix_ResumeMusic(); +} + +void stop_music_asset(AssetID id) { + if (id < 0 || !is_music_asset_playing(id)) { + return; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return; + } + + Mix_HaltMusic(); +} + +void fade_out_music_asset(AssetID id, uint32_t fade_length_in_ms) { + if (id < 0 || !is_music_asset_playing(id)) { + return; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return; + } + + Mix_FadeOutMusic(fade_length_in_ms); +} + +int8_t get_music_asset_volume(AssetID id) { + if (id < 0) { + return -1; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return -1; + } + + return Mix_GetMusicVolume(music); +} + +void set_music_asset_volume(AssetID id, int8_t volume) { + if (id < 0 || !is_music_asset_playing(id)) { + return; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return; + } + + Mix_VolumeMusic(volume); +} + +bool is_music_object_playing(Mix_Music *music) { + if (!Mix_PlayingMusic()) { + return false; + } + + // The Mix_GetMusic* functions used here all return the result for the + // currently playing music when passed a NULL pointer. We use that to compare + // the data of the Mix_Music object passed to the function to the data of the + // currently playing music. + // + // TODO (Abdelrahman): This is a bit of a hack. Figure out if there is + // a better alternative to check if a Mix_Music object is currently playing. + return Mix_GetMusicType(music) == Mix_GetMusicType(NULL) && + Mix_GetMusicTitle(music) == Mix_GetMusicTitle(NULL) && + Mix_GetMusicArtistTag(music) == Mix_GetMusicArtistTag(NULL) && + Mix_GetMusicAlbumTag(music) == Mix_GetMusicAlbumTag(NULL); +} + +bool is_music_asset_playing(AssetID id) { + if (id < 0) { + return false; + } + + Mix_Music *music = get_music(GameEngine.asset_manager, id); + if (!music) { + return false; + } + + return is_music_object_playing(music); +} + +void pause_playing_music() { Mix_PauseMusic(); } + +void resume_playing_music() { Mix_ResumeMusic(); } + +void stop_playing_music() { Mix_HaltMusic(); } + +void fade_out_playing_music(uint32_t fade_length_in_ms) { + Mix_FadeOutMusic(fade_length_in_ms); +} + +void set_playing_music_volume(int8_t volume) { Mix_VolumeMusic(volume); } + +bool is_music_playing() { return Mix_PlayingMusic(); } + void close_asset(AssetID *id) { + if (*id < 0) { + return; + } + remove_asset_from_manager(GameEngine.asset_manager, id); } @@ -183,11 +531,9 @@ void render() { } void close_game() { - destroy_asset_manager(GameEngine.asset_manager); - GameEngine.asset_manager = NULL; + destroy_asset_manager(&(GameEngine.asset_manager)); - destroy_object_store(GameEngine.object_store); - GameEngine.object_store = NULL; + destroy_object_store(&(GameEngine.object_store)); destroy_window(&(GameEngine.window)); @@ -197,6 +543,10 @@ void close_game() { GameEngine.running = false; + Mix_CloseAudio(); + + Mix_Quit(); + IMG_Quit(); SDL_Quit(); diff --git a/src/ObjectStore.c b/src/ObjectStore.c index a424b6e..97733f1 100644 --- a/src/ObjectStore.c +++ b/src/ObjectStore.c @@ -136,23 +136,23 @@ void render_all_objects(ObjectStore *os) { } } -void destroy_object_store(ObjectStore *os) { - if (!os) { +void destroy_object_store(ObjectStore **os) { + if (!(*os)) { return; } - for (size_t i = 0; i < os->count; ++i) { - os->object_ptrs[i] = NULL; - os->event_handlers[i] = NULL; - os->update_funcs[i] = NULL; - os->render_funcs[i] = NULL; + for (size_t i = 0; i < (*os)->count; ++i) { + (*os)->object_ptrs[i] = NULL; + (*os)->event_handlers[i] = NULL; + (*os)->update_funcs[i] = NULL; + (*os)->render_funcs[i] = NULL; } - os->count = 0; - os->deleted_count = 0; + (*os)->count = 0; + (*os)->deleted_count = 0; - free(os); - os = NULL; + free(*os); + *os = NULL; } #ifdef DEBUG_ENGINE diff --git a/src/Renderer.c b/src/Renderer.c index 2532a6c..fc2e097 100644 --- a/src/Renderer.c +++ b/src/Renderer.c @@ -68,15 +68,15 @@ SDL_Texture *load_texture(const Renderer *renderer, const char *path) { return IMG_LoadTexture(renderer->SDL_base_renderer, path); } -void destroy_renderer(Renderer *renderer) { - if (renderer) { - if (renderer->SDL_base_renderer) { - SDL_DestroyRenderer(renderer->SDL_base_renderer); - renderer->SDL_base_renderer = NULL; +void destroy_renderer(Renderer **renderer) { + if (*renderer) { + if ((*renderer)->SDL_base_renderer) { + SDL_DestroyRenderer((*renderer)->SDL_base_renderer); + (*renderer)->SDL_base_renderer = NULL; } - free(renderer); - renderer = NULL; + free(*renderer); + *renderer = NULL; #ifdef DEBUG_ENGINE printf("Renderer destroyed successfully\n"); diff --git a/src/Window.c b/src/Window.c index 7ac2f23..ff6e264 100644 --- a/src/Window.c +++ b/src/Window.c @@ -71,8 +71,7 @@ void destroy_window(Window *wnd) { if (wnd->SDL_base_window) { if (wnd->renderer) { - destroy_renderer(wnd->renderer); - wnd->renderer = NULL; + destroy_renderer(&(wnd->renderer)); } SDL_DestroyWindow(wnd->SDL_base_window);