From 20f7dfa899ad7bbd907aff9198441cea12ff7dba Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sat, 6 Dec 2025 09:58:01 +0000 Subject: [PATCH 1/9] Stub --- CMakeLists.txt | 26 ++++++---------- config.h.in | 1 - src/audio/mixer.cpp | 74 +++++---------------------------------------- src/audio/mixer.hpp | 12 ++------ 4 files changed, 20 insertions(+), 93 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49ca16d..69a628a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,16 +44,14 @@ if (PSP) set(NEOTUX_DATA_DIR "data") endif() -if (NEOTUX_USE_MIXER) - message(STATUS "Using SDL3 Mixer...") - FetchContent_Declare( - SDL3_mixer - URL https://github.com/libsdl-org/SDL_mixer/archive/68764f35899e133b402336843c046d75136eaf08.tar.gz - ) - FetchContent_MakeAvailable(SDL3_mixer) -else() - message(STATUS "NOT using SDL3 Mixer...") -endif (NEOTUX_USE_MIXER) +set(MINIAUDIO_VERSION "0.11.23" CACHE STRING "Version of miniaudio to use") +set(MINIAUDIO_NO_MP3 YES) +set(MINIAUDIO_NO_FLAC YES) +FetchContent_Declare( + miniaudio + URL https://github.com/mackron/miniaudio/archive/refs/tags/${MINIAUDIO_VERSION}.tar.gz +) +FetchContent_MakeAvailable(miniaudio) set(SSQ_BUILD_STATIC_ONLY Off) @@ -119,16 +117,12 @@ endif() add_executable(NeoTux ${NEOTUX_SRC} ) target_include_directories(NeoTux PRIVATE src tests/game_tests) target_link_libraries(NeoTux PUBLIC - SFSEXP SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image -# PkgConfig::SDL3_image -# PkgConfig::SDL3_ttf + SFSEXP + miniaudio ) -if(NEOTUX_USE_MIXER) - target_link_libraries(NeoTux PUBLIC SDL3_mixer::SDL3_mixer) -endif() if(NEOTUX_BGFX) target_sources(NeoTux PRIVATE "${NEOTUX_DATA_DIR}/shaders/frag.glsl" "${NEOTUX_DATA_DIR}/shaders/vert.glsl") diff --git a/config.h.in b/config.h.in index 6f7be9b..103209e 100644 --- a/config.h.in +++ b/config.h.in @@ -1,5 +1,4 @@ #cmakedefine NEOTUX_BGFX -#cmakedefine NEOTUX_USE_MIXER #cmakedefine NEOTUX_PSP #cmakedefine NEOTUX_UNITTESTS_DIR "@NEOTUX_UNITTESTS_DIR@" #cmakedefine NEOTUX_DATA_DIR "@NEOTUX_DATA_DIR@" diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index 2285e79..7b91ba0 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -25,101 +25,43 @@ Mixer g_mixer; -#ifdef NEOTUX_USE_MIXER -Mixer::Mixer() : - m_music(nullptr, Mix_FreeMusic), - m_cache(), - m_current_channel(0) +Mixer::Mixer() { - SDL_AudioSpec spec; - spec.freq = MIX_DEFAULT_FREQUENCY; - spec.format = MIX_DEFAULT_FORMAT; - spec.channels = MIX_DEFAULT_CHANNELS; - - SDL_Init(SDL_INIT_AUDIO); - Mix_Init(MIX_INIT_OGG | MIX_INIT_WAVPACK); - - Mix_OpenAudio(0, &spec); Logger::info(std::format("Opened audio at {}Hz, {} bit{}, {} audio buffer", - spec.freq, - spec.format & 0xFF, - SDL_AUDIO_ISFLOAT(spec.format) ? " (float)" : "", - (spec.channels > 2) ? "surround" : (spec.channels > 1) ? "stereo" : "mono")); -} -#else -Mixer::Mixer() { - Logger::info("Built without SDL3 Mixer support. Hope you enjoy crickets."); + 420, + 67, + true ? " (float)" : "", + false ? "surround" : true ? "stereo" : "mono")); } -#endif void Mixer::shutdown() { -#ifdef NEOTUX_USE_MIXER - m_music.reset(); - m_cache.clear(); + // m_music.reset(); + // m_cache.clear(); //m_soundcache.clear(); -#endif } bool Mixer::is_playing_music() { -#ifdef NEOTUX_USE_MIXER - return Mix_PlayingMusic(); -#else return true; -#endif } void Mixer::stop_playing_music() { -#ifdef NEOTUX_USE_MIXER - Mix_HaltMusic(); -#endif + // Mix_HaltMusic(); } // TODO Cache sounds void Mixer::play_sound(const std::string &filename) { -#ifdef NEOTUX_USE_MIXER - Mix_Chunk *chunk; - if (m_cache.contains(filename)) - { - chunk = m_cache.at(filename).get(); - } - else { - chunk = Mix_LoadWAV(FS::path(filename).c_str()); - m_cache.insert({filename, std::unique_ptr(chunk, Mix_FreeChunk)}); - } - - if (!chunk) - throw SDLException("Couldn't load chunk"); - - ++m_current_channel; - if (m_current_channel == 4) - m_current_channel = 0; - Mix_PlayChannel(m_current_channel, chunk, false); -#endif } void Mixer::play_music(const std::string &filename) { -#ifdef NEOTUX_USE_MIXER - Mix_Music *music; - music = Mix_LoadMUS(FS::path(filename).c_str()); - - if (!music) - { - throw SDLException("Couldn't load music"); - } - - Mix_FadeInMusic(music, true, 2000); - - m_music.reset(music); -#endif } diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index 2adb281..efc0220 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -21,10 +21,8 @@ #include #include #include -#include "config.h" -#ifdef NEOTUX_USE_MIXER -#include -#endif + +#include class Mixer { @@ -38,12 +36,6 @@ class Mixer bool is_playing_music(); void stop_playing_music(); private: -#ifdef NEOTUX_USE_MIXER - std::unordered_map> m_cache; - std::unique_ptr m_music; - int m_current_channel; -#endif - //std::vector> m_soundcache; }; extern Mixer g_mixer; From 6161ba2523da8582ce5914c8aca1ce555449329d Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sat, 6 Dec 2025 10:51:38 +0000 Subject: [PATCH 2/9] get device info --- src/audio/mixer.cpp | 37 ++++++++++++++++++++++++++++++++----- src/audio/mixer.hpp | 7 +++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index 7b91ba0..7dbd374 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -27,11 +27,38 @@ Mixer g_mixer; Mixer::Mixer() { - Logger::info(std::format("Opened audio at {}Hz, {} bit{}, {} audio buffer", - 420, - 67, - true ? " (float)" : "", - false ? "surround" : true ? "stereo" : "mono")); + ma_result result; + + result = ma_engine_init(nullptr, &m_engine); + if (result != MA_SUCCESS) + { + Logger::error("Mixer", "Oops! ded."); + std::exit(-1); + } + + ma_device* dev; + ma_device_info dev_info; + dev = ma_engine_get_device(&m_engine); + ma_device_get_info(dev, ma_device_type_playback, &dev_info); + + Logger::info("Mixer", "Opened audio device:"); + Logger::info("Mixer", std::format("\tSample rate: {}Hz", + dev_info.nativeDataFormats[0].sampleRate)); + Logger::info("Mixer", std::format("\tChannels: {} sound ({})", + get_channels_name(dev_info.nativeDataFormats[0].channels), + dev_info.nativeDataFormats[0].channels)); + Logger::info("Mixer", std::format("\tFormat: {}", + ma_get_format_name(dev_info.nativeDataFormats[0].format))); +} + +std::string Mixer::get_channels_name(u32 channels) +{ + if (channels > 2) + return "surround"; + else if (channels > 1) + return "stereo"; + else + return "mono"; } void diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index efc0220..19327ca 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -24,8 +24,13 @@ #include +#include "types.hpp" + class Mixer { +public: + static std::string get_channels_name(u32 channels); + public: Mixer(); ~Mixer() = default; @@ -35,7 +40,9 @@ class Mixer void play_music(const std::string &filename); bool is_playing_music(); void stop_playing_music(); + private: + ma_engine m_engine; }; extern Mixer g_mixer; From 6872e4839225bf59cc50b29ad96be27436b59eae Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sat, 6 Dec 2025 15:59:14 +0000 Subject: [PATCH 3/9] play ogg --- CMakeLists.txt | 29 ++++++++++++++++++----------- src/audio/mixer.cpp | 40 +++++++++++++++++++++++++++++++++++++--- src/audio/mixer.hpp | 2 ++ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69a628a..583ea67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,5 @@ +# TODO: Switch to Meson + cmake_minimum_required(VERSION 3.14) project(NeoTux LANGUAGES CXX C) @@ -28,6 +30,16 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") cmake_policy(SET CMP0135 NEW) endif() +set(MINIAUDIO_VERSION "0.11.23" CACHE STRING "Version of miniaudio to use") +set(MINIAUDIO_NO_MP3 YES) +set(MINIAUDIO_NO_FLAC YES) +set(MINIAUDIO_NO_LIBVORBIS NO) +FetchContent_Declare( + miniaudio + URL https://github.com/mackron/miniaudio/archive/refs/tags/${MINIAUDIO_VERSION}.tar.gz +) +FetchContent_MakeAvailable(miniaudio) + set(SFSEXP_VERSION "1.4.1" CACHE STRING "Version of SfSexp to use") FetchContent_Declare( sfsexp @@ -36,23 +48,12 @@ FetchContent_Declare( FetchContent_MakeAvailable(sfsexp) include("${PROJECT_SOURCE_DIR}/cmake/sfsexp.cmake") -option(NEOTUX_USE_MIXER "Build with SDL3 Mixer support." ON) if (PSP) message(STATUS "Building PSP version...") - set(NEOTUX_USE_MIXER OFF) set(NEOTUX_PSP ON) set(NEOTUX_DATA_DIR "data") endif() -set(MINIAUDIO_VERSION "0.11.23" CACHE STRING "Version of miniaudio to use") -set(MINIAUDIO_NO_MP3 YES) -set(MINIAUDIO_NO_FLAC YES) -FetchContent_Declare( - miniaudio - URL https://github.com/mackron/miniaudio/archive/refs/tags/${MINIAUDIO_VERSION}.tar.gz -) -FetchContent_MakeAvailable(miniaudio) - set(SSQ_BUILD_STATIC_ONLY Off) #TODO broken @@ -120,10 +121,16 @@ target_link_libraries(NeoTux PUBLIC SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image + SFSEXP + miniaudio + miniaudio_libvorbis ) +# NOTE: They seem to have forgotten to add this include directory to the miniaudio_libvorbis target. +target_include_directories(NeoTux SYSTEM PUBLIC ${miniaudio_SOURCE_DIR}/extras/decoders/libvorbis) + if(NEOTUX_BGFX) target_sources(NeoTux PRIVATE "${NEOTUX_DATA_DIR}/shaders/frag.glsl" "${NEOTUX_DATA_DIR}/shaders/vert.glsl") target_link_libraries(NeoTux PUBLIC bgfx) diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index 7dbd374..f0526a0 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -23,13 +23,30 @@ #include "util/logger.hpp" #include "mixer.hpp" +#include + Mixer g_mixer; Mixer::Mixer() { ma_result result; - result = ma_engine_init(nullptr, &m_engine); + ma_decoding_backend_vtable* decoders[] = { + ma_decoding_backend_libvorbis + }; + + ma_resource_manager_config resource_manager_cfg = ma_resource_manager_config_init(); + resource_manager_cfg.pCustomDecodingBackendUserData = nullptr; + resource_manager_cfg.ppCustomDecodingBackendVTables = decoders; + resource_manager_cfg.customDecodingBackendCount = sizeof(decoders) / sizeof(decoders[0]); + + ma_resource_manager_init(&resource_manager_cfg, &m_resource_manager); + + ma_engine_config engine_cfg; + engine_cfg = ma_engine_config_init(); + engine_cfg.pResourceManager = &m_resource_manager; + + result = ma_engine_init(&engine_cfg, &m_engine); if (result != MA_SUCCESS) { Logger::error("Mixer", "Oops! ded."); @@ -41,6 +58,8 @@ Mixer::Mixer() dev = ma_engine_get_device(&m_engine); ma_device_get_info(dev, ma_device_type_playback, &dev_info); + ma_engine_start(&m_engine); + Logger::info("Mixer", "Opened audio device:"); Logger::info("Mixer", std::format("\tSample rate: {}Hz", dev_info.nativeDataFormats[0].sampleRate)); @@ -64,6 +83,9 @@ std::string Mixer::get_channels_name(u32 channels) void Mixer::shutdown() { + ma_sound_uninit(&m_music); + ma_engine_uninit(&m_engine); + ma_resource_manager_uninit(&m_resource_manager); // m_music.reset(); // m_cache.clear(); //m_soundcache.clear(); @@ -72,23 +94,35 @@ Mixer::shutdown() bool Mixer::is_playing_music() { - return true; + return ma_sound_is_playing(&m_music); } void Mixer::stop_playing_music() { - // Mix_HaltMusic(); + ma_sound_stop(&m_music); } // TODO Cache sounds void Mixer::play_sound(const std::string &filename) { + ma_engine_play_sound(&m_engine, FS::path(filename).c_str(), nullptr); } void Mixer::play_music(const std::string &filename) { + ma_result result; + result = ma_sound_init_from_file(&m_engine, FS::path(filename).c_str(), + 0, nullptr, nullptr, &m_music); + + if (result != MA_SUCCESS) { + throw std::runtime_error(std::format("Failed to load music {} (ma error: {})", + FS::path(filename), + (int)result)); + } + + ma_sound_start(&m_music); } diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index 19327ca..0268a2a 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -43,6 +43,8 @@ class Mixer private: ma_engine m_engine; + ma_resource_manager m_resource_manager; + ma_sound m_music; }; extern Mixer g_mixer; From d10a3d0e8f26ad0db73aa8f9803cdbbb336fbfab Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sat, 6 Dec 2025 18:45:17 +0000 Subject: [PATCH 4/9] Wip music file --- src/audio/mixer.cpp | 32 +++++++++++++++-- src/audio/mixer.hpp | 7 ++-- src/audio/music_data.hpp | 29 +++++++++++++++ src/audio/music_reader.cpp | 52 +++++++++++++++++++++++++++ src/audio/music_reader.hpp | 33 +++++++++++++++++ src/types.hpp | 1 + src/util/filesystem.cpp | 7 +++- src/util/filesystem.hpp | 1 + tests/game_tests/platforming_test.cpp | 2 +- 9 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 src/audio/music_data.hpp create mode 100644 src/audio/music_reader.cpp create mode 100644 src/audio/music_reader.hpp diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index f0526a0..5bfaa9e 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -41,6 +41,11 @@ Mixer::Mixer() resource_manager_cfg.customDecodingBackendCount = sizeof(decoders) / sizeof(decoders[0]); ma_resource_manager_init(&resource_manager_cfg, &m_resource_manager); + if (result != MA_SUCCESS) + { + Logger::error("Mixer", "Oops! ded."); + std::exit(-1); + } ma_engine_config engine_cfg; engine_cfg = ma_engine_config_init(); @@ -103,16 +108,29 @@ Mixer::stop_playing_music() ma_sound_stop(&m_music); } -// TODO Cache sounds void Mixer::play_sound(const std::string &filename) { - ma_engine_play_sound(&m_engine, FS::path(filename).c_str(), nullptr); + ma_result result = ma_engine_play_sound(&m_engine, FS::path(filename).c_str(), nullptr); + if (result != MA_SUCCESS) { + throw std::runtime_error(std::format("Failed to load/play sound {} (ma error: {})", + FS::path(filename), + (int)result)); + } } void -Mixer::play_music(const std::string &filename) +Mixer::play_music(std::string filename) { + if (filename.ends_with(".music")) + { + MusicReader reader; + m_music_data = reader.open(FS::path(filename)); + filename = FS::join(FS::parent_dir(filename), m_music_data.file); + } else { + m_music_data = {}; + } + ma_result result; result = ma_sound_init_from_file(&m_engine, FS::path(filename).c_str(), 0, nullptr, nullptr, &m_music); @@ -123,6 +141,14 @@ Mixer::play_music(const std::string &filename) (int)result)); } + ma_data_source* music_source = ma_sound_get_data_source(&m_music); + ma_uint32 samplerate; + ma_data_source_get_data_format(music_source, nullptr, nullptr, &samplerate, nullptr, 67); + + ma_data_source_set_loop_point_in_pcm_frames(music_source, m_music_data.loop_begin * samplerate, + m_music_data.loop_end * samplerate); + ma_sound_set_looping(&m_music, MA_TRUE); + ma_sound_start(&m_music); } diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index 0268a2a..692cb25 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -24,6 +24,7 @@ #include +#include "audio/music_reader.hpp" #include "types.hpp" class Mixer @@ -36,15 +37,17 @@ class Mixer ~Mixer() = default; void shutdown(); - void play_sound(const std::string &filename); - void play_music(const std::string &filename); + void play_sound(const std::string& filename); + void play_music(std::string filename); bool is_playing_music(); void stop_playing_music(); private: ma_engine m_engine; ma_resource_manager m_resource_manager; + ma_sound m_music; + MusicData m_music_data; }; extern Mixer g_mixer; diff --git a/src/audio/music_data.hpp b/src/audio/music_data.hpp new file mode 100644 index 0000000..cd868d8 --- /dev/null +++ b/src/audio/music_data.hpp @@ -0,0 +1,29 @@ +// SuperTux +// Copyright (C) 2025 MatusGuy +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +#ifndef SUPERTUX_SRC_AUDIO_MUSIC_DATA_HPP +#define SUPERTUX_SRC_AUDIO_MUSIC_DATA_HPP + +#include + +#include "types.hpp" + +struct MusicData { + std::string file; + u32 loop_begin = 0; + u32 loop_end = UINT_MAX; +}; + +#endif // SUPERTUX_SRC_AUDIO_MUSIC_DATA_HPP diff --git a/src/audio/music_reader.cpp b/src/audio/music_reader.cpp new file mode 100644 index 0000000..7cf9d99 --- /dev/null +++ b/src/audio/music_reader.cpp @@ -0,0 +1,52 @@ +// SuperTux +// Copyright (C) 2025 MatusGuy +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +#include "music_reader.hpp" + +MusicReader::MusicReader() +{ + +} + +MusicData MusicReader::open(const std::string& filename) +{ + SexpElt root; + MusicData out; + + root = m_parser.read_file(filename); + + if (!root.is_list()) + return {}; + + root = root.get_list(); + if (root.get_value() == "supertux-music") + Logger::debug("If it walks like a supertux-music and quacks like a supertux-music..."); + + SexpElt elt; + elt = root.find_car("file"); + if (!elt.is_valid()) + Logger::fatal("MusicReader", "File not specified"); + out.file = elt.get_list().get_value(); + + elt = root.find_car("loop-begin"); + if (elt.is_valid()) + out.loop_begin = elt.get_list().get_int_or(out.loop_begin); + + elt = root.find_car("loop-end"); + if (elt.is_valid()) + out.loop_end = elt.get_list().get_int_or(out.loop_end); + + return out; +} diff --git a/src/audio/music_reader.hpp b/src/audio/music_reader.hpp new file mode 100644 index 0000000..46aa3d0 --- /dev/null +++ b/src/audio/music_reader.hpp @@ -0,0 +1,33 @@ +// SuperTux +// Copyright (C) 2025 MatusGuy +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +#ifndef SUPERTUX_SRC_AUDIO_MUSIC_READER_HPP +#define SUPERTUX_SRC_AUDIO_MUSIC_READER_HPP + +#include "util/sexp.hpp" +#include "util/logger.hpp" +#include "audio/music_data.hpp" + +class MusicReader +{ +public: + MusicReader(); + + MusicData open(const std::string &filename); +private: + SexpParser m_parser; +}; + +#endif diff --git a/src/types.hpp b/src/types.hpp index 7851f46..cba9a8d 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -18,6 +18,7 @@ #define HEADER_SUPERTUX_TYPES_HPP #include +#include using i8 = std::int8_t; using i16 = std::int16_t; diff --git a/src/util/filesystem.cpp b/src/util/filesystem.cpp index 70e1439..c95c9c2 100644 --- a/src/util/filesystem.cpp +++ b/src/util/filesystem.cpp @@ -29,7 +29,7 @@ namespace FS std::string path(const std::string& file) { - return std::string(NEOTUX_DATA_DIR) + "/" + file; + return join(NEOTUX_DATA_DIR, file); } std::string @@ -42,4 +42,9 @@ parent_dir(const std::string &file) return file.substr(0, pos); } +std::string join(const std::string &a, const std::string& b) +{ + return a + "/" + b; +} + } diff --git a/src/util/filesystem.hpp b/src/util/filesystem.hpp index 00cec77..6b3a57f 100644 --- a/src/util/filesystem.hpp +++ b/src/util/filesystem.hpp @@ -23,6 +23,7 @@ namespace FS { std::string path(const std::string &file); std::string parent_dir(const std::string &file); + std::string join(const std::string &a, const std::string& b); } #endif diff --git a/tests/game_tests/platforming_test.cpp b/tests/game_tests/platforming_test.cpp index 3b981a7..3f81a62 100644 --- a/tests/game_tests/platforming_test.cpp +++ b/tests/game_tests/platforming_test.cpp @@ -32,7 +32,7 @@ PlatformingTest::run() g_rtcontext.width = winsize.width; g_rtcontext.height = winsize.height; - g_mixer.play_music("music/antarctic/chipdisko.ogg"); + g_mixer.play_music("music/antarctic/chipdisko.music"); g_tiles_reader.open(); From 3690d6a37e58a075029c8c72985b0bedefb6b4db Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sun, 7 Dec 2025 09:52:51 +0000 Subject: [PATCH 5/9] fix reader --- src/audio/mixer.cpp | 6 ++---- src/audio/music_data.hpp | 2 +- src/audio/music_reader.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index 5bfaa9e..c5192c0 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -40,7 +40,7 @@ Mixer::Mixer() resource_manager_cfg.ppCustomDecodingBackendVTables = decoders; resource_manager_cfg.customDecodingBackendCount = sizeof(decoders) / sizeof(decoders[0]); - ma_resource_manager_init(&resource_manager_cfg, &m_resource_manager); + result = ma_resource_manager_init(&resource_manager_cfg, &m_resource_manager); if (result != MA_SUCCESS) { Logger::error("Mixer", "Oops! ded."); @@ -63,8 +63,6 @@ Mixer::Mixer() dev = ma_engine_get_device(&m_engine); ma_device_get_info(dev, ma_device_type_playback, &dev_info); - ma_engine_start(&m_engine); - Logger::info("Mixer", "Opened audio device:"); Logger::info("Mixer", std::format("\tSample rate: {}Hz", dev_info.nativeDataFormats[0].sampleRate)); @@ -146,7 +144,7 @@ Mixer::play_music(std::string filename) ma_data_source_get_data_format(music_source, nullptr, nullptr, &samplerate, nullptr, 67); ma_data_source_set_loop_point_in_pcm_frames(music_source, m_music_data.loop_begin * samplerate, - m_music_data.loop_end * samplerate); + m_music_data.loop_at * samplerate); ma_sound_set_looping(&m_music, MA_TRUE); ma_sound_start(&m_music); diff --git a/src/audio/music_data.hpp b/src/audio/music_data.hpp index cd868d8..021effc 100644 --- a/src/audio/music_data.hpp +++ b/src/audio/music_data.hpp @@ -23,7 +23,7 @@ struct MusicData { std::string file; u32 loop_begin = 0; - u32 loop_end = UINT_MAX; + u32 loop_at = UINT_MAX; }; #endif // SUPERTUX_SRC_AUDIO_MUSIC_DATA_HPP diff --git a/src/audio/music_reader.cpp b/src/audio/music_reader.cpp index 7cf9d99..e518716 100644 --- a/src/audio/music_reader.cpp +++ b/src/audio/music_reader.cpp @@ -38,15 +38,15 @@ MusicData MusicReader::open(const std::string& filename) elt = root.find_car("file"); if (!elt.is_valid()) Logger::fatal("MusicReader", "File not specified"); - out.file = elt.get_list().get_value(); + out.file = elt.next().get_value(); elt = root.find_car("loop-begin"); if (elt.is_valid()) - out.loop_begin = elt.get_list().get_int_or(out.loop_begin); + out.loop_begin = elt.next().get_int_or(out.loop_begin); - elt = root.find_car("loop-end"); + elt = root.find_car("loop-at"); if (elt.is_valid()) - out.loop_end = elt.get_list().get_int_or(out.loop_end); + out.loop_at = elt.next().get_int_or(out.loop_at); return out; } From fe2fb74b609e86cf1608145a3c0b79a768919ba4 Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sun, 7 Dec 2025 11:09:39 +0000 Subject: [PATCH 6/9] audio: Add sound manager for caching sounds. --- src/audio/mixer.cpp | 18 ++++++++++++---- src/audio/mixer.hpp | 3 +++ src/audio/sound_manager.cpp | 41 +++++++++++++++++++++++++++++++++++++ src/audio/sound_manager.hpp | 37 +++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/audio/sound_manager.cpp create mode 100644 src/audio/sound_manager.hpp diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index c5192c0..2fed2b5 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "mixer.hpp" #ifdef NEOTUX_USE_MIXER #include #endif @@ -21,7 +22,7 @@ #include "sdl_exception.hpp" #include "util/filesystem.hpp" #include "util/logger.hpp" -#include "mixer.hpp" +#include "audio/sound_manager.hpp" #include @@ -109,10 +110,19 @@ Mixer::stop_playing_music() void Mixer::play_sound(const std::string &filename) { - ma_result result = ma_engine_play_sound(&m_engine, FS::path(filename).c_str(), nullptr); + ma_sound* sound = g_sound_manager.load(filename); + play_sound(sound); +} + +void +Mixer::play_sound(ma_sound* sound) +{ + if (ma_sound_is_playing(sound)) + ma_sound_stop(sound); + + ma_result result = ma_sound_start(sound); if (result != MA_SUCCESS) { - throw std::runtime_error(std::format("Failed to load/play sound {} (ma error: {})", - FS::path(filename), + throw std::runtime_error(std::format("Failed to play sound (ma error: {})", (int)result)); } } diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index 692cb25..da87264 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -29,6 +29,8 @@ class Mixer { + friend class SoundManager; + public: static std::string get_channels_name(u32 channels); @@ -38,6 +40,7 @@ class Mixer void shutdown(); void play_sound(const std::string& filename); + void play_sound(ma_sound* sound); void play_music(std::string filename); bool is_playing_music(); void stop_playing_music(); diff --git a/src/audio/sound_manager.cpp b/src/audio/sound_manager.cpp new file mode 100644 index 0000000..27b0304 --- /dev/null +++ b/src/audio/sound_manager.cpp @@ -0,0 +1,41 @@ +// SuperTux +// Copyright (C) 2025 MatusGuy +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +#include "sound_manager.hpp" + +#include "audio/mixer.hpp" +#include "util/filesystem.hpp" + +SoundManager g_sound_manager; + +SoundManager::SoundManager() +{ + +} + +ma_sound* SoundManager::load(const std::string& path) +{ + auto it = m_sounds.find(path); + if (it == m_sounds.end()) + { + auto new_it = m_sounds.insert({path, {}}); + ma_sound* out = &new_it.first->second; + ma_sound_init_from_file(&g_mixer.m_engine, FS::path(path).c_str(), + 0, nullptr, nullptr, out); + return out; + } else { + return &it->second; + } +} diff --git a/src/audio/sound_manager.hpp b/src/audio/sound_manager.hpp new file mode 100644 index 0000000..f2374ce --- /dev/null +++ b/src/audio/sound_manager.hpp @@ -0,0 +1,37 @@ +// SuperTux +// Copyright (C) 2025 MatusGuy +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +#ifndef SUPERTUX_SRC_AUDIO_SOUND_MANAGER +#define SUPERTUX_SRC_AUDIO_SOUND_MANAGER + +#include +#include + +#include + +class SoundManager +{ +public: + SoundManager(); + + ma_sound* load(const std::string& path); + +private: + std::unordered_map m_sounds; +}; + +extern SoundManager g_sound_manager; + +#endif // SUPERTUX_SRC_AUDIO_SOUND_MANAGER From 21a9c0a84b53008632ef4065be11b3b407a98280 Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Sun, 7 Dec 2025 12:23:19 +0000 Subject: [PATCH 7/9] audio: add load flags for sounds depending on context --- src/audio/mixer.cpp | 6 +++++- src/audio/sound_manager.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index 2fed2b5..bc7f4c4 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -139,9 +139,10 @@ Mixer::play_music(std::string filename) m_music_data = {}; } + ma_sound new_music; ma_result result; result = ma_sound_init_from_file(&m_engine, FS::path(filename).c_str(), - 0, nullptr, nullptr, &m_music); + MA_SOUND_FLAG_STREAM, nullptr, nullptr, &new_music); if (result != MA_SUCCESS) { throw std::runtime_error(std::format("Failed to load music {} (ma error: {})", @@ -149,6 +150,9 @@ Mixer::play_music(std::string filename) (int)result)); } + ma_sound_uninit(&m_music); + m_music = new_music; + ma_data_source* music_source = ma_sound_get_data_source(&m_music); ma_uint32 samplerate; ma_data_source_get_data_format(music_source, nullptr, nullptr, &samplerate, nullptr, 67); diff --git a/src/audio/sound_manager.cpp b/src/audio/sound_manager.cpp index 27b0304..f8123af 100644 --- a/src/audio/sound_manager.cpp +++ b/src/audio/sound_manager.cpp @@ -33,7 +33,7 @@ ma_sound* SoundManager::load(const std::string& path) auto new_it = m_sounds.insert({path, {}}); ma_sound* out = &new_it.first->second; ma_sound_init_from_file(&g_mixer.m_engine, FS::path(path).c_str(), - 0, nullptr, nullptr, out); + MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, nullptr, nullptr, out); return out; } else { return &it->second; From b4f1f9e6b2e626fbe923a53a9626185116a3b7c0 Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Mon, 8 Dec 2025 00:16:21 +0000 Subject: [PATCH 8/9] audio: Pimpl for audio classes and MAException --- src/audio/mixer.cpp | 123 ++++++++++++++++++++---------------- src/audio/mixer.hpp | 53 ++++++++-------- src/audio/sound_manager.cpp | 24 ++++--- src/audio/sound_manager.hpp | 6 +- 4 files changed, 113 insertions(+), 93 deletions(-) diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index bc7f4c4..47468b8 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -1,34 +1,47 @@ -// SuperTux -// Copyright (C) 2025 Hyland B. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "mixer.hpp" -#ifdef NEOTUX_USE_MIXER -#include -#endif #include +#include +#include + +#include "mixer.hpp" #include "sdl_exception.hpp" #include "util/filesystem.hpp" #include "util/logger.hpp" #include "audio/sound_manager.hpp" +#include "audio/music_reader.hpp" -#include +MAException::MAException(const std::string& what, int result) : + std::runtime_error(std::format("{} (ma error: {})", what, result)) +{} + +struct Mixer::Impl +{ + ma_engine engine; + ma_resource_manager resource_manager; + + ma_sound music; + MusicData music_data; +}; Mixer g_mixer; -Mixer::Mixer() +Mixer::Mixer(): + impl(std::make_unique()) { ma_result result; @@ -41,27 +54,25 @@ Mixer::Mixer() resource_manager_cfg.ppCustomDecodingBackendVTables = decoders; resource_manager_cfg.customDecodingBackendCount = sizeof(decoders) / sizeof(decoders[0]); - result = ma_resource_manager_init(&resource_manager_cfg, &m_resource_manager); + result = ma_resource_manager_init(&resource_manager_cfg, &impl->resource_manager); if (result != MA_SUCCESS) { - Logger::error("Mixer", "Oops! ded."); - std::exit(-1); + throw MAException("Failed to initialize resource manager", result); } ma_engine_config engine_cfg; engine_cfg = ma_engine_config_init(); - engine_cfg.pResourceManager = &m_resource_manager; + engine_cfg.pResourceManager = &impl->resource_manager; - result = ma_engine_init(&engine_cfg, &m_engine); + result = ma_engine_init(&engine_cfg, &impl->engine); if (result != MA_SUCCESS) { - Logger::error("Mixer", "Oops! ded."); - std::exit(-1); + throw MAException("Failed to initialize engine", result); } ma_device* dev; ma_device_info dev_info; - dev = ma_engine_get_device(&m_engine); + dev = ma_engine_get_device(&impl->engine); ma_device_get_info(dev, ma_device_type_playback, &dev_info); Logger::info("Mixer", "Opened audio device:"); @@ -87,24 +98,27 @@ std::string Mixer::get_channels_name(u32 channels) void Mixer::shutdown() { - ma_sound_uninit(&m_music); - ma_engine_uninit(&m_engine); - ma_resource_manager_uninit(&m_resource_manager); - // m_music.reset(); - // m_cache.clear(); - //m_soundcache.clear(); + ma_sound_uninit(&impl->music); + ma_engine_uninit(&impl->engine); + ma_resource_manager_uninit(&impl->resource_manager); } bool Mixer::is_playing_music() { - return ma_sound_is_playing(&m_music); + return ma_sound_is_playing(&impl->music); } void Mixer::stop_playing_music() { - ma_sound_stop(&m_music); + ma_sound_stop(&impl->music); +} + +ma_engine* +Mixer::engine() +{ + return &impl->engine; } void @@ -122,8 +136,7 @@ Mixer::play_sound(ma_sound* sound) ma_result result = ma_sound_start(sound); if (result != MA_SUCCESS) { - throw std::runtime_error(std::format("Failed to play sound (ma error: {})", - (int)result)); + throw MAException("Failed to play sound", result); } } @@ -133,34 +146,32 @@ Mixer::play_music(std::string filename) if (filename.ends_with(".music")) { MusicReader reader; - m_music_data = reader.open(FS::path(filename)); - filename = FS::join(FS::parent_dir(filename), m_music_data.file); + impl->music_data = reader.open(FS::path(filename)); + filename = FS::join(FS::parent_dir(filename), impl->music_data.file); } else { - m_music_data = {}; + impl->music_data = {}; } - ma_sound new_music; ma_result result; - result = ma_sound_init_from_file(&m_engine, FS::path(filename).c_str(), - MA_SOUND_FLAG_STREAM, nullptr, nullptr, &new_music); + ma_sound_uninit(&impl->music); + result = ma_sound_init_from_file(&impl->engine, FS::path(filename).c_str(), + MA_SOUND_FLAG_STREAM, nullptr, nullptr, &impl->music); if (result != MA_SUCCESS) { - throw std::runtime_error(std::format("Failed to load music {} (ma error: {})", - FS::path(filename), - (int)result)); + throw MAException(std::format("Failed to load music {}", FS::path(filename)), result); } - ma_sound_uninit(&m_music); - m_music = new_music; - - ma_data_source* music_source = ma_sound_get_data_source(&m_music); + ma_data_source* music_source = ma_sound_get_data_source(&impl->music); ma_uint32 samplerate; ma_data_source_get_data_format(music_source, nullptr, nullptr, &samplerate, nullptr, 67); - ma_data_source_set_loop_point_in_pcm_frames(music_source, m_music_data.loop_begin * samplerate, - m_music_data.loop_at * samplerate); - ma_sound_set_looping(&m_music, MA_TRUE); + ma_data_source_set_loop_point_in_pcm_frames(music_source, impl->music_data.loop_begin * samplerate, + impl->music_data.loop_at * samplerate); + ma_sound_set_looping(&impl->music, MA_TRUE); - ma_sound_start(&m_music); -} + result = ma_sound_start(&impl->music); + if (result != MA_SUCCESS) { + throw MAException(std::format("Failed to play music {}", FS::path(filename)), result); + } +} diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index da87264..e43fcc6 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -1,32 +1,34 @@ -// SuperTux -// Copyright (C) 2025 Hyland B. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SUPERTUX_SRC_AUDIO_MIXER_HPP #define SUPERTUX_SRC_AUDIO_MIXER_HPP #include -#include -#include -#include -#include - -#include "audio/music_reader.hpp" #include "types.hpp" +class MAException : public std::runtime_error +{ +public: + MAException(const std::string &what, i32 result); +}; + +struct ma_engine; +struct ma_sound; class Mixer { friend class SoundManager; @@ -37,7 +39,7 @@ class Mixer public: Mixer(); ~Mixer() = default; - + void shutdown(); void play_sound(const std::string& filename); void play_sound(ma_sound* sound); @@ -45,12 +47,11 @@ class Mixer bool is_playing_music(); void stop_playing_music(); -private: - ma_engine m_engine; - ma_resource_manager m_resource_manager; + ma_engine* engine(); - ma_sound m_music; - MusicData m_music_data; +private: + struct Impl; + std::unique_ptr impl; }; extern Mixer g_mixer; diff --git a/src/audio/sound_manager.cpp b/src/audio/sound_manager.cpp index f8123af..b99c95e 100644 --- a/src/audio/sound_manager.cpp +++ b/src/audio/sound_manager.cpp @@ -13,26 +13,34 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "sound_manager.hpp" +#include +#include + +#include "sound_manager.hpp" #include "audio/mixer.hpp" #include "util/filesystem.hpp" +struct SoundManager::Impl { + std::unordered_map sounds; +}; + SoundManager g_sound_manager; -SoundManager::SoundManager() +SoundManager::SoundManager(): + impl(std::make_unique()) { - } -ma_sound* SoundManager::load(const std::string& path) +ma_sound* +SoundManager::load(const std::string& path) { - auto it = m_sounds.find(path); - if (it == m_sounds.end()) + auto it = impl->sounds.find(path); + if (it == impl->sounds.end()) { - auto new_it = m_sounds.insert({path, {}}); + auto new_it = impl->sounds.insert({path, {}}); ma_sound* out = &new_it.first->second; - ma_sound_init_from_file(&g_mixer.m_engine, FS::path(path).c_str(), + ma_sound_init_from_file(g_mixer.engine(), FS::path(path).c_str(), MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, nullptr, nullptr, out); return out; } else { diff --git a/src/audio/sound_manager.hpp b/src/audio/sound_manager.hpp index f2374ce..508b615 100644 --- a/src/audio/sound_manager.hpp +++ b/src/audio/sound_manager.hpp @@ -16,8 +16,7 @@ #ifndef SUPERTUX_SRC_AUDIO_SOUND_MANAGER #define SUPERTUX_SRC_AUDIO_SOUND_MANAGER -#include -#include +#include #include @@ -29,7 +28,8 @@ class SoundManager ma_sound* load(const std::string& path); private: - std::unordered_map m_sounds; + struct Impl; + std::unique_ptr impl; }; extern SoundManager g_sound_manager; From 132e0661bc2651a41f822eaf8caecfd38b953816 Mon Sep 17 00:00:00 2001 From: MatusGuy Date: Mon, 8 Dec 2025 00:19:07 +0000 Subject: [PATCH 9/9] music_reader: style fix --- src/audio/music_reader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio/music_reader.cpp b/src/audio/music_reader.cpp index e518716..3af60ea 100644 --- a/src/audio/music_reader.cpp +++ b/src/audio/music_reader.cpp @@ -20,7 +20,8 @@ MusicReader::MusicReader() } -MusicData MusicReader::open(const std::string& filename) +MusicData +MusicReader::open(const std::string& filename) { SexpElt root; MusicData out;