Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 23 additions & 18 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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
Expand All @@ -39,18 +49,11 @@ FetchContent_Declare(
FetchContent_MakeAvailable(sfsexp)
include("${PROJECT_SOURCE_DIR}/cmake/sfsexp.cmake")

option(NEOTUX_USE_MIXER "Build with SDL3 Mixer support." ON)

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)
if (PSP)
message(STATUS "Building PSP version...")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when merging, try to rebase, I moved this stuff now to psp.cmake

set(NEOTUX_PSP ON)
set(NEOTUX_DATA_DIR "data")
endif()

set(SSQ_BUILD_STATIC_ONLY Off)

Expand Down Expand Up @@ -116,16 +119,18 @@ 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
miniaudio_libvorbis
)
if(NEOTUX_USE_MIXER)
target_link_libraries(NeoTux PUBLIC SDL3_mixer::SDL3_mixer)
endif()

# 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")
Expand Down
1 change: 0 additions & 1 deletion config.h.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#cmakedefine NEOTUX_BGFX
#cmakedefine NEOTUX_USE_MIXER
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No go

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a comment explicity saying not to remove this bit but I mustve forgot to submit it; please add this logic back though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic can be emulated by using the Null miniaudio backend.

#cmakedefine NEOTUX_PSP
#cmakedefine NEOTUX_UNITTESTS_DIR "@NEOTUX_UNITTESTS_DIR@"
#cmakedefine NEOTUX_DATA_DIR "@NEOTUX_DATA_DIR@"
224 changes: 138 additions & 86 deletions src/audio/mixer.cpp
Original file line number Diff line number Diff line change
@@ -1,125 +1,177 @@
// SuperTux
// Copyright (C) 2025 Hyland B. <[email protected]>
//
// 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. <[email protected]>
//
// 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 <http://www.gnu.org/licenses/>.

#ifdef NEOTUX_USE_MIXER
#include <SDL3_mixer/SDL_mixer.h>
#endif
#include <format>
#include <miniaudio.h>
#include <miniaudio_libvorbis.h>

#include "mixer.hpp"
#include "sdl_exception.hpp"
#include "util/filesystem.hpp"
#include "util/logger.hpp"
#include "mixer.hpp"
#include "audio/sound_manager.hpp"
#include "audio/music_reader.hpp"

MAException::MAException(const std::string& what, int result) :
std::runtime_error(std::format("{} (ma error: {})", what, result))
{}

struct Mixer::Impl
Copy link
Member

@swagtoy swagtoy Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment. We can abstract it all at compile time...

{
ma_engine engine;
ma_resource_manager resource_manager;

ma_sound music;
MusicData music_data;
};

Mixer g_mixer;

#ifdef NEOTUX_USE_MIXER
Mixer::Mixer() :
m_music(nullptr, Mix_FreeMusic),
m_cache(),
m_current_channel(0)
Mixer::Mixer():
impl(std::make_unique<Impl>())
{
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"));
ma_result result;

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]);

result = ma_resource_manager_init(&resource_manager_cfg, &impl->resource_manager);
if (result != MA_SUCCESS)
{
throw MAException("Failed to initialize resource manager", result);
}

ma_engine_config engine_cfg;
engine_cfg = ma_engine_config_init();
engine_cfg.pResourceManager = &impl->resource_manager;

result = ma_engine_init(&engine_cfg, &impl->engine);
if (result != MA_SUCCESS)
{
throw MAException("Failed to initialize engine", result);
}

ma_device* dev;
ma_device_info dev_info;
dev = ma_engine_get_device(&impl->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)));
}
#else
Mixer::Mixer() {
Logger::info("Built without SDL3 Mixer support. Hope you enjoy crickets.");

std::string Mixer::get_channels_name(u32 channels)
{
if (channels > 2)
return "surround";
else if (channels > 1)
return "stereo";
else
return "mono";
}
#endif

void
Mixer::shutdown()
{
#ifdef NEOTUX_USE_MIXER
m_music.reset();
m_cache.clear();
//m_soundcache.clear();
#endif
ma_sound_uninit(&impl->music);
ma_engine_uninit(&impl->engine);
ma_resource_manager_uninit(&impl->resource_manager);
}

bool
Mixer::is_playing_music()
{
#ifdef NEOTUX_USE_MIXER
return Mix_PlayingMusic();
#else
return true;
#endif
return ma_sound_is_playing(&impl->music);
}

void
Mixer::stop_playing_music()
{
#ifdef NEOTUX_USE_MIXER
Mix_HaltMusic();
#endif
ma_sound_stop(&impl->music);
}

ma_engine*
Mixer::engine()
{
return &impl->engine;
}

// 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<Mix_Chunk, decltype(&Mix_FreeChunk)>(chunk, Mix_FreeChunk)});
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 MAException("Failed to play sound", result);
}

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)
Mixer::play_music(std::string filename)
{
#ifdef NEOTUX_USE_MIXER
Mix_Music *music;
music = Mix_LoadMUS(FS::path(filename).c_str());

if (!music)
if (filename.ends_with(".music"))
{
throw SDLException("Couldn't load music");
MusicReader reader;
impl->music_data = reader.open(FS::path(filename));
filename = FS::join(FS::parent_dir(filename), impl->music_data.file);
} else {
impl->music_data = {};
}

Mix_FadeInMusic(music, true, 2000);

m_music.reset(music);
#endif
}

ma_result result;
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 MAException(std::format("Failed to load music {}", FS::path(filename)), result);
}

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, impl->music_data.loop_begin * samplerate,
impl->music_data.loop_at * samplerate);
ma_sound_set_looping(&impl->music, MA_TRUE);

result = ma_sound_start(&impl->music);
if (result != MA_SUCCESS) {
throw MAException(std::format("Failed to play music {}", FS::path(filename)), result);
}

}
Loading