Implement SDL2_mixer audio for WASM builds, closes #247

Replace no-op audio stubs in SDL2Types.h with real SDL2_mixer-backed
implementations of SoundBuffer, Sound, and Music. This enables audio
playback in the browser with zero changes to Python bindings.

- Add -sUSE_SDL_MIXER=2 to Emscripten compile/link flags (CMakeLists.txt)
- Initialize Mix_OpenAudio in SDL2Renderer::init(), Mix_CloseAudio in shutdown()
- SoundBuffer: Mix_LoadWAV/Mix_LoadWAV_RW with duration computation
- Sound: channel-based playback with Mix_ChannelFinished tracking
- Music: global channel streaming via Mix_LoadMUS/Mix_PlayMusic
- Volume conversion: SFML 0-100 scale to SDL_mixer 0-128 scale

Known limitations on web: Music.duration and Music.position getters
return 0 (SDL_mixer 2.0.2 lacks Mix_MusicDuration/Mix_GetMusicPosition).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-02-08 22:16:21 -05:00
commit 0969f7c2f6
4 changed files with 290 additions and 29 deletions

View file

@ -17,9 +17,11 @@
#include <emscripten/html5.h>
// Emscripten's USE_SDL=2 port puts headers directly in include path
#include <SDL.h>
#include <SDL_mixer.h>
#include <GLES2/gl2.h>
#else
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_opengl.h>
// Desktop OpenGL - we'll use GL 2.1 compatible subset that matches GLES2
#define GL_GLEXT_PROTOTYPES
@ -132,11 +134,22 @@ bool SDL2Renderer::init() {
if (initialized_) return true;
// Initialize SDL2 if not already done
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO) < 0) {
std::cerr << "SDL2Renderer: Failed to initialize SDL: " << SDL_GetError() << std::endl;
return false;
}
// Initialize SDL2_mixer for audio (non-fatal if it fails)
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
std::cerr << "SDL2Renderer: Failed to initialize audio: " << Mix_GetError() << std::endl;
std::cerr << "SDL2Renderer: Continuing without audio support" << std::endl;
} else {
Mix_AllocateChannels(16);
Mix_ChannelFinished(Sound::onChannelFinished);
audioInitialized_ = true;
std::cout << "SDL2Renderer: Audio initialized (16 channels, 44100 Hz)" << std::endl;
}
// Note: Shaders are initialized in initGL() after GL context is created
// Set up initial projection matrix (identity)
@ -170,6 +183,12 @@ void SDL2Renderer::shutdown() {
shapeProgram_ = spriteProgram_ = textProgram_ = 0;
// Close audio before SDL_Quit
if (audioInitialized_) {
Mix_CloseAudio();
audioInitialized_ = false;
}
SDL_Quit();
initialized_ = false;
}
@ -673,6 +692,9 @@ void RenderWindow::setSize(const Vector2u& size) {
if (sdlWindow_) {
SDL_SetWindowSize(static_cast<SDL_Window*>(sdlWindow_), size.x, size.y);
glViewport(0, 0, size.x, size.y);
#ifdef __EMSCRIPTEN__
emscripten_set_canvas_element_size("#canvas", size.x, size.y);
#endif
}
}