From dc28fa1bd871d9c4e9644894fb69df4f604ffb96 Mon Sep 17 00:00:00 2001 From: swagween <109032389+swagween@users.noreply.github.com> Date: Wed, 25 Jun 2025 07:06:30 +0200 Subject: [PATCH 1/4] create API for use in other apps --- library/include/juke/core/MediaPlayer.hpp | 19 ++----- library/include/juke/juke.hpp | 36 +++++++++++++ library/src/CMakeLists.txt | 3 ++ library/src/core/Application.cpp | 10 +--- library/src/core/MediaPlayer.cpp | 66 ++++------------------- library/src/juke.cpp | 61 +++++++++++++++++++++ 6 files changed, 114 insertions(+), 81 deletions(-) create mode 100644 library/include/juke/juke.hpp create mode 100644 library/src/juke.cpp diff --git a/library/include/juke/core/MediaPlayer.hpp b/library/include/juke/core/MediaPlayer.hpp index 29b44f1..87ecf25 100644 --- a/library/include/juke/core/MediaPlayer.hpp +++ b/library/include/juke/core/MediaPlayer.hpp @@ -1,35 +1,22 @@ #pragma once #include -#include -#include -#include +#include #include #include #include namespace juke { -enum class MediaStatus { playing, paused, stopped }; - class MediaPlayer { public: explicit MediaPlayer(capo::IEngine& audio_engine); bool load_media(std::filesystem::path const& path); void handle_input(); - void update(std::chrono::duration dt); - - [[nodiscard]] auto playing() const -> bool { return m_status == MediaStatus::playing; } - [[nodiscard]] auto paused() const -> bool { return m_status == MediaStatus::paused; } - [[nodiscard]] auto stopped() const -> bool { return m_status == MediaStatus::stopped; } + void update(); private: - MediaStatus m_status{MediaStatus::stopped}; - [[maybe_unused]] bool m_trigger{}; - - std::unique_ptr m_media_file{}; - std::unique_ptr m_source{}; - + Jukebox m_jukebox; std::string m_status_string{"stopped"}; }; diff --git a/library/include/juke/juke.hpp b/library/include/juke/juke.hpp new file mode 100644 index 0000000..fe8c649 --- /dev/null +++ b/library/include/juke/juke.hpp @@ -0,0 +1,36 @@ + +#pragma once + +#include +#include +#include +#include +#include + +namespace juke { + +enum class MediaStatus { playing, paused, stopped }; + +class Jukebox { + public: + explicit Jukebox(capo::IEngine& audio_engine); + bool load_media(std::filesystem::path const& path); + void play(bool looping = true); + void pause(); + void stop(); + void update(); + + [[nodiscard]] auto has_file() const -> bool { return static_cast(m_media_file); } + [[nodiscard]] auto playing() const -> bool { return m_status == MediaStatus::playing; } + [[nodiscard]] auto paused() const -> bool { return m_status == MediaStatus::paused; } + [[nodiscard]] auto stopped() const -> bool { return m_status == MediaStatus::stopped; } + [[nodiscard]] auto get_filename() const& -> std::string { return m_media_file->get_filename(); } + + private: + MediaStatus m_status{MediaStatus::stopped}; + + std::unique_ptr m_media_file{}; + std::unique_ptr m_source{}; +}; + +} // namespace juke diff --git a/library/src/CMakeLists.txt b/library/src/CMakeLists.txt index 6b0579e..7d7d371 100644 --- a/library/src/CMakeLists.txt +++ b/library/src/CMakeLists.txt @@ -1,2 +1,5 @@ add_subdirectory(core) +target_sources(juke PRIVATE + juke.cpp +) diff --git a/library/src/core/Application.cpp b/library/src/core/Application.cpp index 47dc924..aef4b1c 100644 --- a/library/src/core/Application.cpp +++ b/library/src/core/Application.cpp @@ -19,18 +19,10 @@ Application::Application() { } void Application::run() { - using Clock = std::chrono::steady_clock; - - auto now = Clock::now(); - while (m_context->next_frame()) { - auto const new_now = Clock::now(); - auto const dt = std::chrono::duration_cast>(new_now - now); - now = new_now; - m_player->handle_input(); - m_player->update(dt); + m_player->update(); m_context->render(); } diff --git a/library/src/core/MediaPlayer.cpp b/library/src/core/MediaPlayer.cpp index cd16279..d2c9c4e 100644 --- a/library/src/core/MediaPlayer.cpp +++ b/library/src/core/MediaPlayer.cpp @@ -12,81 +12,35 @@ namespace juke { using namespace std::chrono_literals; -MediaPlayer::MediaPlayer(capo::IEngine& audio_engine) : m_source(audio_engine.create_source()) { - if (!m_source) { throw std::runtime_error{"Failed to create Audio Source"}; } -} - -bool MediaPlayer::load_media(std::filesystem::path const& path) { - try { - auto media_file = std::unique_ptr{}; - if (XMFile::is_xm(path)) { - media_file = std::make_unique(path); - } else { - media_file = std::make_unique(path); - } - m_source->stop(); - if (!media_file->bind_to(*m_source)) { - std::println("ERROR: Failed to bind Source to PCM"); - return false; - } - m_media_file = std::move(media_file); - } catch (MediaError const& e) { // only MediaErrors are caught here, others are raised all the way to main. - std::println("ERROR: {}", e.what()); - return false; - } +MediaPlayer::MediaPlayer(capo::IEngine& audio_engine) : m_jukebox(audio_engine) {} - std::println("Media file loaded: {}", path.string()); - - m_source->play(); - m_status = m_source->is_playing() ? MediaStatus::playing : MediaStatus::stopped; - - return true; -} +bool MediaPlayer::load_media(std::filesystem::path const& path) { return m_jukebox.load_media(path); } void MediaPlayer::handle_input() { ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove; if (ImGui::Begin("Player", nullptr, flags)) { - if (auto const duration = m_source->get_duration(); duration > 0s) { - /* Progress Bar */ - auto const fraction = m_source->get_cursor() / duration; - ImGui::ProgressBar(fraction); - } - + static auto looping{true}; /* Control Buttons */ - ImGui::Separator(); - if (ImGui::Button("play")) { - m_source->play(); - m_status = MediaStatus::playing; - } + if (ImGui::Button("play")) { m_jukebox.play(looping); } ImGui::SameLine(); - if (ImGui::Button("pause")) { - m_source->stop(); - m_status = MediaStatus::paused; - } + if (ImGui::Button("pause")) { m_jukebox.pause(); } ImGui::SameLine(); - if (ImGui::Button("stop")) { - m_source->stop(); - m_source->set_cursor({}); // seek to start - m_status = MediaStatus::stopped; - } + if (ImGui::Button("stop")) { m_jukebox.stop(); } ImGui::SameLine(); - auto loop = m_source->is_looping(); - if (ImGui::Checkbox("loop", &loop)) { m_source->set_looping(loop); } + ImGui::Checkbox("loop", &looping); ImGui::Separator(); ImGui::Text("File Name: "); ImGui::SameLine(); - m_media_file ? ImGui::TextColored(colors::blue, "%s", m_media_file->get_filename().c_str()) : ImGui::TextColored(colors::grey, ""); + m_jukebox.has_file() ? ImGui::TextColored(colors::blue, "%s", m_jukebox.get_filename().c_str()) : ImGui::TextColored(colors::grey, ""); m_status_string.clear(); - std::format_to(std::back_inserter(m_status_string), "{}", playing() ? "playing" : paused() ? "paused" : "stopped"); + std::format_to(std::back_inserter(m_status_string), "{}", m_jukebox.playing() ? "playing" : m_jukebox.paused() ? "paused" : "stopped"); ImGui::Text("Media Status: %s", m_status_string.c_str()); } ImGui::End(); } -void MediaPlayer::update([[maybe_unused]] std::chrono::duration const dt) { - if (m_status == MediaStatus::playing && !m_source->is_playing()) { m_status = MediaStatus::stopped; } -} +void MediaPlayer::update() { m_jukebox.update(); } } // namespace juke diff --git a/library/src/juke.cpp b/library/src/juke.cpp new file mode 100644 index 0000000..24ba550 --- /dev/null +++ b/library/src/juke.cpp @@ -0,0 +1,61 @@ + +#include +#include +#include +#include +#include +#include +#include + +namespace juke { + +using namespace std::chrono_literals; + +Jukebox::Jukebox(capo::IEngine& audio_engine) : m_source(audio_engine.create_source()) { + if (!m_source) { throw std::runtime_error{"Failed to create Audio Source"}; } +} + +bool Jukebox::load_media(std::filesystem::path const& path) { + try { + auto media_file = std::unique_ptr{}; + if (XMFile::is_xm(path)) { + media_file = std::make_unique(path); + } else { + media_file = std::make_unique(path); + } + m_source->stop(); + if (!media_file->bind_to(*m_source)) { + std::println("ERROR: Failed to bind Source to PCM"); + return false; + } + m_media_file = std::move(media_file); + } catch (MediaError const& e) { // only MediaErrors are caught here, others are raised all the way to main. + std::println("ERROR: {}", e.what()); + return false; + } + + return true; +} + +void Jukebox::play(bool looping) { + m_source->play(); + m_status = m_source->is_playing() ? MediaStatus::playing : MediaStatus::stopped; + m_source->set_looping(looping); +} + +void Jukebox::pause() { + m_source->stop(); + m_status = MediaStatus::paused; +} + +void Jukebox::stop() { + m_source->stop(); + if (!m_source->set_cursor({})) { std::println("failed to set cursor"); }; // seek to start + m_status = MediaStatus::stopped; +} + +void Jukebox::update() { + if (m_status == MediaStatus::playing && !m_source->is_playing()) { m_status = MediaStatus::stopped; } +} + +} // namespace juke From c67c5e032b05c28bf167a68dd856650489425f86 Mon Sep 17 00:00:00 2001 From: swagween <109032389+swagween@users.noreply.github.com> Date: Wed, 25 Jun 2025 08:59:56 +0200 Subject: [PATCH 2/4] extract MediaPlayer and Application from the lib --- app/CMakeLists.txt | 24 ++++++++++++++----- .../core => app/include/app}/Application.hpp | 2 +- .../graphics => app/include/app}/Colors.hpp | 0 .../core => app/include/app}/MediaPlayer.hpp | 0 {library/src/core => app/src}/Application.cpp | 2 +- app/src/CMakeLists.txt | 15 ++++++++++++ {library/src/core => app/src}/MediaPlayer.cpp | 4 ++-- app/src/main.cpp | 2 +- library/src/CMakeLists.txt | 1 + library/src/core/CMakeLists.txt | 2 -- library/src/juke.cpp | 3 +-- 11 files changed, 40 insertions(+), 15 deletions(-) rename {library/include/juke/core => app/include/app}/Application.hpp (93%) rename {library/include/juke/graphics => app/include/app}/Colors.hpp (100%) rename {library/include/juke/core => app/include/app}/MediaPlayer.hpp (100%) rename {library/src/core => app/src}/Application.cpp (96%) create mode 100644 app/src/CMakeLists.txt rename {library/src/core => app/src}/MediaPlayer.cpp (95%) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 2171386..040e84b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,13 +1,25 @@ project(jukebox-app) -add_executable(jukebox) +add_library(jukebox-app) +add_library(jukebox-app::lib ALIAS jukebox-app) -target_link_libraries(jukebox - PRIVATE - jukebox::deps +target_link_libraries(jukebox-app + PUBLIC juke::lib ) -target_sources(jukebox PRIVATE - src/main.cpp +# Glob all headers +file(GLOB_RECURSE headers "include/*.hpp") + +# Set as FILE_SET, this also sets the include directory +target_sources(jukebox-app PUBLIC FILE_SET HEADERS + BASE_DIRS include + FILES ${headers} +) + +# Setup our include structure +target_include_directories(jukebox-app + PRIVATE "src" ) + +add_subdirectory(src) diff --git a/library/include/juke/core/Application.hpp b/app/include/app/Application.hpp similarity index 93% rename from library/include/juke/core/Application.hpp rename to app/include/app/Application.hpp index cb4b1fa..d778cfb 100644 --- a/library/include/juke/core/Application.hpp +++ b/app/include/app/Application.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include -#include #include #include diff --git a/library/include/juke/graphics/Colors.hpp b/app/include/app/Colors.hpp similarity index 100% rename from library/include/juke/graphics/Colors.hpp rename to app/include/app/Colors.hpp diff --git a/library/include/juke/core/MediaPlayer.hpp b/app/include/app/MediaPlayer.hpp similarity index 100% rename from library/include/juke/core/MediaPlayer.hpp rename to app/include/app/MediaPlayer.hpp diff --git a/library/src/core/Application.cpp b/app/src/Application.cpp similarity index 96% rename from library/src/core/Application.cpp rename to app/src/Application.cpp index aef4b1c..a66b1b4 100644 --- a/library/src/core/Application.cpp +++ b/app/src/Application.cpp @@ -1,5 +1,5 @@ -#include +#include #include namespace juke { diff --git a/app/src/CMakeLists.txt b/app/src/CMakeLists.txt new file mode 100644 index 0000000..04e07f0 --- /dev/null +++ b/app/src/CMakeLists.txt @@ -0,0 +1,15 @@ + +target_sources(jukebox-app PRIVATE + MediaPlayer.cpp + Application.cpp +) + +add_executable(jukebox-player) + +target_sources(jukebox-player PRIVATE + main.cpp +) + +target_link_libraries(jukebox-player PRIVATE + jukebox-app::lib +) diff --git a/library/src/core/MediaPlayer.cpp b/app/src/MediaPlayer.cpp similarity index 95% rename from library/src/core/MediaPlayer.cpp rename to app/src/MediaPlayer.cpp index d2c9c4e..673a5f8 100644 --- a/library/src/core/MediaPlayer.cpp +++ b/app/src/MediaPlayer.cpp @@ -1,11 +1,11 @@ #include +#include +#include #include #include #include -#include #include -#include #include namespace juke { diff --git a/app/src/main.cpp b/app/src/main.cpp index 31f9a59..e180d18 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -1,5 +1,5 @@ -#include +#include #include int main() { diff --git a/library/src/CMakeLists.txt b/library/src/CMakeLists.txt index 7d7d371..2ade06a 100644 --- a/library/src/CMakeLists.txt +++ b/library/src/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(core) + target_sources(juke PRIVATE juke.cpp ) diff --git a/library/src/core/CMakeLists.txt b/library/src/core/CMakeLists.txt index 9d7ba19..0dca6b4 100644 --- a/library/src/core/CMakeLists.txt +++ b/library/src/core/CMakeLists.txt @@ -1,8 +1,6 @@ target_sources(juke PRIVATE - Application.cpp AudioFile.cpp - MediaPlayer.cpp XMFile.cpp XMStream.cpp ) diff --git a/library/src/juke.cpp b/library/src/juke.cpp index 24ba550..b01f615 100644 --- a/library/src/juke.cpp +++ b/library/src/juke.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -50,7 +49,7 @@ void Jukebox::pause() { void Jukebox::stop() { m_source->stop(); - if (!m_source->set_cursor({})) { std::println("failed to set cursor"); }; // seek to start + m_source->set_cursor({}); // seek to start m_status = MediaStatus::stopped; } From 4f3ed775e271716569d116285e3853e4c27d3a22 Mon Sep 17 00:00:00 2001 From: swagween <109032389+swagween@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:07:50 +0200 Subject: [PATCH 3/4] update README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18889a8..fe06fe1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ jukebox -a simple .xm music player +a simple library for loading and playing music files + +supported formats: WAV, MP3, FLAC, XM From f25794035f09600ace899399616ea4f17f78bd97 Mon Sep 17 00:00:00 2001 From: swagween <109032389+swagween@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:09:20 +0200 Subject: [PATCH 4/4] remove m_status and add doxygen --- app/include/app/MediaPlayer.hpp | 2 -- app/src/Application.cpp | 3 --- app/src/MediaPlayer.cpp | 9 +++------ library/include/juke/juke.hpp | 25 ++++++++++++++++--------- library/src/juke.cpp | 15 +++------------ 5 files changed, 22 insertions(+), 32 deletions(-) diff --git a/app/include/app/MediaPlayer.hpp b/app/include/app/MediaPlayer.hpp index 87ecf25..0f7ea62 100644 --- a/app/include/app/MediaPlayer.hpp +++ b/app/include/app/MediaPlayer.hpp @@ -13,11 +13,9 @@ class MediaPlayer { explicit MediaPlayer(capo::IEngine& audio_engine); bool load_media(std::filesystem::path const& path); void handle_input(); - void update(); private: Jukebox m_jukebox; - std::string m_status_string{"stopped"}; }; } // namespace juke diff --git a/app/src/Application.cpp b/app/src/Application.cpp index a66b1b4..1105fb3 100644 --- a/app/src/Application.cpp +++ b/app/src/Application.cpp @@ -21,9 +21,6 @@ Application::Application() { void Application::run() { while (m_context->next_frame()) { m_player->handle_input(); - - m_player->update(); - m_context->render(); } } diff --git a/app/src/MediaPlayer.cpp b/app/src/MediaPlayer.cpp index 673a5f8..7712368 100644 --- a/app/src/MediaPlayer.cpp +++ b/app/src/MediaPlayer.cpp @@ -30,17 +30,14 @@ void MediaPlayer::handle_input() { ImGui::SameLine(); ImGui::Checkbox("loop", &looping); ImGui::Separator(); + + /* Metadata & Info */ ImGui::Text("File Name: "); ImGui::SameLine(); m_jukebox.has_file() ? ImGui::TextColored(colors::blue, "%s", m_jukebox.get_filename().c_str()) : ImGui::TextColored(colors::grey, ""); - - m_status_string.clear(); - std::format_to(std::back_inserter(m_status_string), "{}", m_jukebox.playing() ? "playing" : m_jukebox.paused() ? "paused" : "stopped"); - ImGui::Text("Media Status: %s", m_status_string.c_str()); + ImGui::Text("Media Status: %s", m_jukebox.is_playing() ? "playing" : "paused"); } ImGui::End(); } -void MediaPlayer::update() { m_jukebox.update(); } - } // namespace juke diff --git a/library/include/juke/juke.hpp b/library/include/juke/juke.hpp index fe8c649..731e1dd 100644 --- a/library/include/juke/juke.hpp +++ b/library/include/juke/juke.hpp @@ -9,26 +9,33 @@ namespace juke { -enum class MediaStatus { playing, paused, stopped }; - class Jukebox { public: explicit Jukebox(capo::IEngine& audio_engine); + + /// \brief Load audio file. + /// \param path Path to file. + /// \returns True if file loaded successfully, false otherwise. bool load_media(std::filesystem::path const& path); + + /// \brief Play audio. + /// \param looping Whether or not the audio loops indefinitely. void play(bool looping = true); + + /// \brief Pause audio. void pause(); + + /// \brief Stop audio and set cursor to beginning. This does not work for XM format. void stop(); - void update(); - [[nodiscard]] auto has_file() const -> bool { return static_cast(m_media_file); } - [[nodiscard]] auto playing() const -> bool { return m_status == MediaStatus::playing; } - [[nodiscard]] auto paused() const -> bool { return m_status == MediaStatus::paused; } - [[nodiscard]] auto stopped() const -> bool { return m_status == MediaStatus::stopped; } + /// \brief Get the name of the loaded file as a string. [[nodiscard]] auto get_filename() const& -> std::string { return m_media_file->get_filename(); } - private: - MediaStatus m_status{MediaStatus::stopped}; + [[nodiscard]] auto has_file() const -> bool { return static_cast(m_media_file); } + [[nodiscard]] auto is_playing() const -> bool { return m_source->is_playing(); } + [[nodiscard]] auto is_stopped() const -> bool { return !is_playing(); } + private: std::unique_ptr m_media_file{}; std::unique_ptr m_source{}; }; diff --git a/library/src/juke.cpp b/library/src/juke.cpp index b01f615..7692bf9 100644 --- a/library/src/juke.cpp +++ b/library/src/juke.cpp @@ -37,24 +37,15 @@ bool Jukebox::load_media(std::filesystem::path const& path) { } void Jukebox::play(bool looping) { - m_source->play(); - m_status = m_source->is_playing() ? MediaStatus::playing : MediaStatus::stopped; m_source->set_looping(looping); + m_source->play(); } -void Jukebox::pause() { - m_source->stop(); - m_status = MediaStatus::paused; -} +void Jukebox::pause() { m_source->stop(); } void Jukebox::stop() { m_source->stop(); - m_source->set_cursor({}); // seek to start - m_status = MediaStatus::stopped; -} - -void Jukebox::update() { - if (m_status == MediaStatus::playing && !m_source->is_playing()) { m_status = MediaStatus::stopped; } + m_source->set_cursor({}); // seek to start. this will do nothing for XM streams } } // namespace juke