An easy-to-use, modern C++ audio library built on top of SDL2 and SDL2_mixer, featuring automatic resource management, comprehensive error handling, and playlist management.
- Zero-dependency installation: SDL2, SDL2_mixer, and stevensVectorLib are automatically fetched and statically linked via CMake FetchContent
- Modern C++20
- Comprehensive error handling: Thread-safe error system with custom handlers
- Sound variants: Register multiple audio files under one sound name to get random variation on each play (useful for avoiding repetitive UI/SFX fatigue)
- Playlist management: Create, switch between, and control music/sound playlists from a background thread
- Thread-safe playback: Music playback runs on its own thread and is driven by a small command queue (switch/stop/volume)
- Fully tested: Includes a Google Test suite and Google Benchmark suite
mkdir build && cd build
cmake ..
cmake --build .Ninja works too, and avoids a known Makefile-generation issue in SDL2_mixer's build (see BUILD_NOTES.md):
mkdir build && cd build
cmake .. -G Ninja
ninja#include <stevensSound.hpp>
int main()
{
// Initialize SDL
if (!initSound())
{
std::cerr << "Failed to initialize SDL\n";
return 1;
}
// Set up your sounds. Any category whose name contains "music" is loaded
// as streamed Mix_Music; every other category is loaded as a Mix_Chunk.
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> sounds = {
{"sfx", {
{"button_click", "sounds/button.wav"},
{"notification", "sounds/notify.wav"}
}},
{"music", {
{"theme", "music/theme.mp3"}
}}
};
// Initialize the library
stevensSound::init(sounds);
// Play a sound
stevensSound::playSound("sfx", "button_click");
// Control volume
stevensSound::soundControllers["sfx"].volume = 0.5f; // 50% volume
// Clean up
closeSound();
return 0;
}// Enable logging
stevensSound::ErrorHandler::setLogging(true);
// Set custom error handler
stevensSound::ErrorHandler::setErrorHandler([](const stevensSound::ErrorInfo& error) {
std::cout << "Error: " << error.message << "\n";
});
// Check for errors
if (stevensSound::ErrorHandler::hasError())
{
auto error = stevensSound::ErrorHandler::getLastError();
std::cout << error.toString() << "\n";
}Register several files under the same sound; each call to playSound() picks one at random.
stevensSound::Sound footstep = stevensSound::createSound(
"footstep", "sfx", "sfx",
"sounds/footstep1.wav",
{ "sounds/footstep2.wav", "sounds/footstep3.wav" } // variants
);
stevensSound::sounds["sfx"]["footstep"] = footstep;
stevensSound::playSound("sfx", "footstep"); // plays a random variant each time// Create a music playlist and register it
std::vector<std::string> categories = {"music"};
std::vector<std::string> trackOrder = {"song1", "song2", "song3"};
stevensSound::playlists["game_music"] = stevensSound::createMusicPlaylist(
"game_music",
"music",
categories,
trackOrder,
false // Don't shuffle fill
);
// Play the playlist on a separate thread
std::thread musicThread(
stevensSound::playMusicPlaylist,
std::ref(stevensSound::playlists["game_music"]),
"loop" // Loop when finished
);
musicThread.detach();
// Switch to another playlist (fire-and-forget; picked up by the music thread)
stevensSound::switchMusicPlaylist("another_playlist");
// Stop the music (blocks until the music thread acknowledges)
stevensSound::stopMusicPlaylist();This library fetches and builds against:
- SDL2: Version 2.28.5
- SDL2_mixer: Version 2.6.3
Note: Due to CMake/Makefile issues with SDL2_mixer's FetchContent integration in some environments, you can alternatively install SDL2/SDL2_mixer via your system package manager. See BUILD_NOTES.md for details and workarounds.
# Build with all features (default)
cmake ..
# Build without tests
cmake -DSTEVENSSOUND_BUILD_TESTS=OFF ..
# Build without benchmarks
cmake -DSTEVENSSOUND_BUILD_BENCHMARKS=OFF ..
# Build without examples
cmake -DSTEVENSSOUND_BUILD_EXAMPLES=OFF ..These options only default to ON when stevensSound is built as the top-level project. If you add it via add_subdirectory() from your own game's CMake project, none of tests/examples/benchmarks are built unless you opt in.
# Build and run tests
cmake --build . --target tests
./tests/tests
# Build and run benchmarks
cmake --build . --target benchmarks
./benchmarks/benchmarksThe examples/ directory contains complete working examples:
basic_example.cpp- Basic library usageerror_handling_example.cpp- Error handling featuresplaylist_example.cpp- Playlist management
Build and run:
cmake --build . --target basic
./examples/basicstevensSound/
├── stevensSound.hpp # Main library header (includes classes/ below)
├── stevensSound.cpp # Compiled implementation
├── classes/ # Library components
│ ├── AudioCommand.hpp # Internal command-queue message type
│ ├── ErrorHandler.hpp # Thread-safe error handling system
│ ├── Mix_ChunkData.h # Wraps a Mix_Chunk + its file path
│ ├── Mix_MusicData.h # Wraps a Mix_Music + its file path
│ ├── Music.hpp # A named, loaded piece of music
│ ├── PlaybackController.hpp # Per-category volume controller
│ ├── PlaylistSwitchOptions.hpp
│ ├── Sound.hpp # A named sound with optional variants
│ └── SoundPlaylist.hpp # Ordered list of sound/music keys
├── tests/ # Google Test suite
├── benchmarks/ # Google Benchmark suite
├── examples/ # Usage examples
└── CMakeLists.txt # Build configuration
See API.md for the full API reference.
SDL_mixer does not natively support true pitch shifting without tempo changes, and this library does not implement it. A libsoundtouch-based prototype was explored but removed as incomplete. If you need pitch shifting, integrate a DSP library such as libsoundtouch or SoLoud directly in your application, or use createSound()'s variant files for perceived variation instead.
- All sounds/music passed to
stevensSound::init()are loaded once and cached for the lifetime of the program. - Never call
Mix_FreeChunk()/Mix_FreeMusic()on cached resources yourself —closeSound()frees everything on shutdown. - Extra chunks played via
playSound()(not part of the initial load) are tracked in an internal pool and freed automatically when they finish playing, or viastevensSound::freeChunks().
- Error handling is thread-safe (uses thread_local storage)
playMusicPlaylist()is meant to run on a dedicated thread; switching/stopping/volume changes from other threads are delivered via an internal command queue- Sound playback uses mutexes for resource pool management
Thanks to SDL_mixer, supports:
- WAV, AIFF, VOC (native)
- MP3, OGG (enabled by this library's CMake configuration)
- FLAC, tracker formats (MOD/XM/IT) are disabled by default in CMakeLists.txt but can be re-enabled there
When contributing, please:
- Run tests:
./tests/tests - Run benchmarks to check for regressions
- Follow existing code style
- Add tests for new features
See LICENSE file for details.
- Built on SDL2 and SDL2_mixer
- Uses stevensVectorLib for variant selection
- Uses Google Test for testing
- Uses Google Benchmark for performance testing