From 013f8279d48c0c14b2261583eda27692a74799aa Mon Sep 17 00:00:00 2001 From: Frank DeMarco Date: Fri, 11 Sep 2020 18:01:27 -0400 Subject: [PATCH] audio, sfx and bgm classes; filesystem path added as type to json lib --- demo/Demo.cpp | 14 +++-- src/Audio.cpp | 140 ++++++++++++++++++++++++++++++++++++++++++ src/Audio.hpp | 89 +++++++++++++++++++++++++++ src/Configuration.cpp | 4 ++ src/Configuration.hpp | 35 +++++++++++ src/Game.cpp | 10 ++- src/Game.hpp | 3 + src/Node.cpp | 12 +++- src/Node.hpp | 2 + src/Sprite.cpp | 4 +- src/extension.hpp | 16 ----- 11 files changed, 302 insertions(+), 27 deletions(-) create mode 100644 src/Audio.cpp create mode 100644 src/Audio.hpp diff --git a/demo/Demo.cpp b/demo/Demo.cpp index 52a72c3..3c1cd28 100644 --- a/demo/Demo.cpp +++ b/demo/Demo.cpp @@ -13,11 +13,15 @@ screen resolution, debug display, loading wheel animation, shadowed sprite, separate update and draw, sprite movement cage, multiple windows, multiple renderers, node children list, node animations list, copy constructor for all - base classes, private and public class members, pixel class iterator, sprite - movement history, input history, use seconds instead of milliseconds, store - a node's animations in list, frame object for sprite class, inline short - functions, add box2d to library, load in separate thread and display progress, - add anchor to box class + base classes (esp. sprite, audio), private and public class members, pixel + class iterator, sprite movement history, input history, use seconds instead of + milliseconds, store a node's animations in list, frame object for sprite + class, inline short functions, add box2d to library, load in separate thread + and display progress, add anchor to box class, add comments to code, generate + documentation from comments, get resource function, automatically update + animations, add arguments list to animation call, queue multiple calls to + animation, print list of chunk and music decoders available, allow nodes that + aren't connected to root :) SWEATY HANDS :) OILY SNACKS :) AND BAD HYGIENE :) diff --git a/src/Audio.cpp b/src/Audio.cpp new file mode 100644 index 0000000..1977ab7 --- /dev/null +++ b/src/Audio.cpp @@ -0,0 +1,140 @@ +#include "SDL.h" + +#include "Box.hpp" +#include "Configuration.hpp" +#include "Display.hpp" +#include "Audio.hpp" + +BGM::BGM(Node* parent) : Node(parent) {} + +BGM::BGM() : BGM(nullptr) {} + +BGM::BGM(Node* parent, const fs::path& path) : BGM(parent) +{ + load_music(path); +} + +BGM::BGM(const fs::path& path) : BGM(nullptr, path) {} + +void BGM::load_music(const fs::path& path) +{ + music = Mix_LoadMUS(path.c_str()); +} + +void BGM::set_volume(float v) +{ + volume = v; +} + +void BGM::play(int loops) +{ + Mix_VolumeMusic(std::round(volume * 127)); + Mix_PlayMusic(music, loops); +} + +BGM::~BGM() +{ + Mix_FreeMusic(music); +} + +SoundEffect::SoundEffect(Node* parent) : Node(parent) {} + +SoundEffect::SoundEffect() : SoundEffect(nullptr) {} + +SoundEffect::SoundEffect(Node* parent, const fs::path& path) : SoundEffect(parent) +{ + load_chunk(path); +} + +SoundEffect::SoundEffect(const fs::path& path) : SoundEffect(nullptr, path) {} + +void SoundEffect::load_chunk(const fs::path& path) +{ + chunk = Mix_LoadWAV(path.c_str()); + Mix_VolumeChunk(chunk, std::round(volume * 127)); +} + +void SoundEffect::play(float location) +{ + play(); + Box window_box = get_window_box(); + location = std::clamp(location, window_box.get_left(), window_box.get_right()); + float location_relative = (location - window_box.get_left()) / window_box.get_w(); + int angle = 270 - location_relative * 180; + Mix_SetPosition(channel, angle, 0); +} + +void SoundEffect::play() +{ + channel = Mix_PlayChannel(-1, chunk, loops); +} + +void SoundEffect::play_after(float delay) +{ + play_animation.play_once(delay); +} + +void SoundEffect::set_volume(float v) +{ + volume = v; + Mix_VolumeChunk(chunk, std::round(volume * 127)); +} + +void SoundEffect::set_loop_count(int count) +{ + loops = count; +} + +void SoundEffect::set_loop_forever() +{ + loops = -1; +} + +bool SoundEffect::is_playing(bool include_delay) +{ + if (include_delay) + { + if (play_animation.is_playing()) + { + return true; + } + } + for (int channel_ii = 0; channel_ii < Mix_GroupAvailable(-1); channel_ii++) + { + if (Mix_GetChunk(channel_ii) == chunk) + { + return true; + } + } + return false; +} + +void SoundEffect::update() +{ + play_animation.update(); +} + +SoundEffect::~SoundEffect() +{ + Mix_FreeChunk(chunk); +} + +Audio::Audio(Node* parent) : Node(parent) {} + +void Audio::load_sfx() +{ + load_directory(get_configuration()["audio"]["default-sfx-root"], sfx, this); +} + +void Audio::load_bgm() +{ + load_directory(get_configuration()["audio"]["default-bgm-root"], bgm, this); +} + +void Audio::update() +{ + for (auto& [name, sound_effect] : sfx) + { + sound_effect.update(); + } +} diff --git a/src/Audio.hpp b/src/Audio.hpp new file mode 100644 index 0000000..3cb2557 --- /dev/null +++ b/src/Audio.hpp @@ -0,0 +1,89 @@ +#ifndef Audio_h_ +#define Audio_h_ + +#include +#include + +#include "SDL_mixer.h" +#include "filesystem.hpp" +#include "Node.hpp" +#include "Animation.hpp" +#include "extension.hpp" + +struct BGM : Node +{ + + Mix_Music* music; + float volume = 1.0f; + + BGM(); + BGM(Node*); + BGM(Node*, const fs::path&); + BGM(const fs::path&); + void load_music(const fs::path&); + void set_volume(float); + void play(int = -1); + ~BGM(); + +}; + +struct SoundEffect : Node +{ + + Mix_Chunk* chunk = nullptr; + int channel, loops = 0; + float volume = 1.0f; + Animation play_animation = Animation(&SoundEffect::play, this); + + SoundEffect(Node*); + SoundEffect(); + SoundEffect(Node*, const fs::path&); + SoundEffect(const fs::path&); + void load_chunk(const fs::path&); + void play(float); + void play(); + void play_after(float); + void set_volume(float); + void set_loop_count(int); + void set_loop_forever(); + bool is_playing(bool = false); + void update(); + ~SoundEffect(); + +}; + +struct Audio : Node +{ + + std::map sfx; + std::map bgm; + + Audio(Node*); + void load_sfx(); + void load_bgm(); + void update(); + + template + void load_directory(const fs::path& root, std::map& storage, Node* parent = nullptr) + { + SDL_Log("looking for audio files in %s", root.c_str()); + if (fs::exists(root)) + { + for (const fs::path& path : sfw::glob(root / ".*")) + { + std::regex pattern("([^.]+).*$"); + std::smatch match; + std::string basename = path.filename(); + if (std::regex_match(basename, match, pattern)) + { + std::string key = match[1].str(); + SDL_Log("loading %s as %s", path.c_str(), key.c_str()); + storage.try_emplace(key, parent, path); + } + } + } + } + +}; + +#endif diff --git a/src/Configuration.cpp b/src/Configuration.cpp index b030251..6d51afe 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -51,6 +51,10 @@ void Configuration::set_defaults() {"render-test-spacing", 2}, {"render driver", "opengl"} }; + sys_config["audio"] = { + {"default-sfx-root", "resource/sfx"}, + {"default-bgm-root", "resource/bgm"} + }; sys_config["recording"] = { {"enabled", false}, {"screenshot-prefix", "screenshot-"}, diff --git a/src/Configuration.hpp b/src/Configuration.hpp index 5f2ad4b..5f95b8e 100644 --- a/src/Configuration.hpp +++ b/src/Configuration.hpp @@ -24,4 +24,39 @@ struct Configuration : Node }; +namespace glm +{ + template + void to_json(nlohmann::json& j, const vec<2, T, defaultp>& v) + { + j = nlohmann::json{v.x, v.y}; + } + + template + void from_json(const nlohmann::json& j, vec<2, T, defaultp>& v) + { + j.at(0).get_to(v.x); + j.at(1).get_to(v.y); + } +} + +#if defined(__MINGW32__) +namespace std::experimental::filesystem +#else +namespace std::filesystem +#endif +{ + template + void to_json(nlohmann::json& j, const path& p) + { + j = nlohmann::json{p}; + } + + template + void from_json(const nlohmann::json& j, path& p) + { + j.at(0).get_to(p); + } +} + #endif diff --git a/src/Game.cpp b/src/Game.cpp index 5029daa..46b5562 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -145,6 +145,8 @@ Game::Game() SDL_Log("Found audio capture device %i: %s", ii, SDL_GetAudioDeviceName(ii, SDL_TRUE)); } + audio.load_sfx(); + audio.load_bgm(); #if SDL_BYTEORDER == SDL_BIG_ENDIAN SDL_Log("big endian"); #else @@ -511,6 +513,11 @@ Input& Game::get_input() return input; } +Audio& Game::get_audio() +{ + return audio; +} + glm::vec2 Game::weight(glm::vec2 motion) { return {weight(motion.x), weight(motion.y)}; @@ -556,7 +563,8 @@ void Game::frame(float ticks) recorder.update(); } delegate.dispatch(); - get_root()->input.unsuppress_animation.update(); + audio.update(); + input.unsuppress_animation.update(); update(); framerate_indicator.update(); if (!is_gl_context) diff --git a/src/Game.hpp b/src/Game.hpp index 2692f80..db90eac 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -29,6 +29,7 @@ #include "Recorder.hpp" #include "extension.hpp" #include "Sprite.hpp" +#include "Audio.hpp" struct FramerateIndicator : Sprite { @@ -60,6 +61,7 @@ struct Game : Node Display display = Display(this); Recorder recorder = Recorder(this); Input input = Input(this); + Audio audio = Audio(this); std::vector frame_length_history; TTF_Font* bp_mono_font = NULL; FramerateIndicator framerate_indicator = FramerateIndicator(this); @@ -84,6 +86,7 @@ struct Game : Node SDL_Renderer* get_renderer(); const Input& get_input() const; Input& get_input(); + Audio& get_audio(); glm::vec2 weight(glm::vec2); void run(); void frame(float); diff --git a/src/Node.cpp b/src/Node.cpp index 543bb93..94925f9 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -4,9 +4,10 @@ #include "Input.hpp" #include "Box.hpp" #include "Game.hpp" +#include "Audio.hpp" #include "Node.hpp" -Node::Node() : Node(NULL) {} +Node::Node() : Node(nullptr) {} Node::Node(Node* parent) : parent(parent) { @@ -79,6 +80,11 @@ Input& Node::get_input() return get_root()->get_input(); } +Audio& Node::get_audio() +{ + return get_root()->get_audio(); +} + const Game* Node::get_root() const { const Node* r = this; @@ -117,10 +123,10 @@ void Node::unsuppress_input() void Node::print_branch() { Node* current = this; - while (current != NULL) + while (current != nullptr) { std::cout << current->get_class_name() << " @ " << current; - if (current->parent != NULL) + if (current->parent != nullptr) { std::cout << " -> "; } diff --git a/src/Node.hpp b/src/Node.hpp index ae289d1..1a11622 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -15,6 +15,7 @@ struct Delegate; struct Display; struct Input; struct Box; +struct Audio; struct Node { @@ -41,6 +42,7 @@ struct Node SDL_Window* get_window(); const Input& get_input() const; Input& get_input(); + Audio& get_audio(); const Game* get_root() const; Box get_window_box(); void suppress_input(); diff --git a/src/Sprite.cpp b/src/Sprite.cpp index 480623c..273e318 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -269,11 +269,11 @@ void Sprite::toggle_hidden() { if (is_hidden()) { - hide(); + unhide(); } else { - unhide(); + hide(); } } diff --git a/src/extension.hpp b/src/extension.hpp index 42f0261..8959f67 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -168,22 +168,6 @@ namespace sfw } -namespace glm -{ - template - void to_json(nlohmann::json& j, const vec<2, T, defaultp>& v) - { - j = nlohmann::json{{"x", v.x}, {"y", v.y}}; - } - - template - void from_json(const nlohmann::json& j, vec<2, T, defaultp>& v) - { - j.at(0).get_to(v.x); - j.at(1).get_to(v.y); - } -} - int SDL_SetRenderDrawColor(SDL_Renderer*, const Color&); int SDL_RenderFillRect(SDL_Renderer*, const Box&); int lineColor(SDL_Renderer*, const Segment&, const Color&, std::uint8_t = 1);