Skip to content
Draft
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
4 changes: 4 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,10 @@ var SDL2_IMAGE_FORMATS = [];
// [compile+link]
var SDL2_MIXER_FORMATS = ["ogg"];

// Formats to support in SDL3_mixer. Valid values: ogg, mp3
// [compile+link]
var SDL3_MIXER_FORMATS = ["ogg", "mp3"];

// 1 = use sqlite3 from emscripten-ports
// Alternate syntax: --use-port=sqlite3
// [compile+link]
Expand Down
81 changes: 81 additions & 0 deletions test/browser/test_sdl3_mixer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2025 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*/

#include <stdio.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <emscripten.h>

SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
MIX_Audio *audio = NULL;
MIX_Track *track = NULL;
MIX_Mixer *mixer = NULL;

#define WIDTH 640
#define HEIGHT 480

#ifndef SOUND_PATH
#error "must define SOUND_PATH"
#endif

void sound_loop_then_quit() {
if (MIX_TrackPlaying(track))
return;

MIX_DestroyAudio(audio);
MIX_DestroyTrack(track);
MIX_DestroyMixer(mixer);

emscripten_cancel_main_loop();
printf("Shutting down\n");
exit(0);
}

int main(int argc, char *argv[]) {
SDL_Init(SDL_INIT_VIDEO);

if (!MIX_Init()) {
printf("MIX_Init failed: %s\n", SDL_GetError());
return 1;
}

if (!SDL_CreateWindowAndRenderer("SDL3 MIXER", WIDTH, HEIGHT, 0, &window, &renderer)) {
printf("SDL_CreateWindowAndRenderer: %s\n", SDL_GetError());
return 1;
}

mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
if (!mixer) {
printf("Couldn't create mixer on default device: %s", SDL_GetError());
return 1;
}

audio = MIX_LoadAudio(mixer, SOUND_PATH, false);
if (!audio) {
printf("MIX_LoadAudio: %s\n", SDL_GetError());
return 1;
}

track = MIX_CreateTrack(mixer);
if (!track) {
printf("MIX_CreateTrack: %s\n", SDL_GetError());
return 1;
}

MIX_SetTrackAudio(track, audio);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOPS_NUMBER, 0);

printf("Starting sound play loop\n");
MIX_PlayTrack(track, props);

emscripten_set_main_loop(sound_loop_then_quit, 0, 1);

return 0;
}
27 changes: 27 additions & 0 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3201,6 +3201,33 @@ def test_sdl3_canvas_write(self):
self.cflags.append('-Wno-experimental')
self.btest_exit('test_sdl3_canvas_write.c', cflags=['-sUSE_SDL=3'])

@parameterized({
'': (['-sUSE_SDL=3', '-sUSE_SDL_MIXER=3'],),
'dash_l': (['-lSDL3', '-lSDL3_mixer'],),
})
@requires_sound_hardware
def test_sdl3_mixer_wav(self, flags):
copy_asset('sounds/the_entertainer.wav', 'sound.wav')
self.cflags.append('-Wno-experimental')
self.btest_exit('test_sdl3_mixer.c', cflags=['--preload-file', 'sound.wav', '-DSOUND_PATH="sound.wav"'] + flags)

@parameterized({
'ogg': (['ogg'], 'alarmvictory_1.ogg',),
'mp3': (['mp3'], 'pudinha.mp3'),
})
@requires_sound_hardware
def test_sdl3_mixer_music(self, formats, music_name):
copy_asset(f'sounds/{music_name}')
self.cflags.append('-Wno-experimental')
args = [
'--preload-file', music_name,
'-DSOUND_PATH="%s"' % music_name,
'-sUSE_SDL=3',
'-sUSE_SDL_MIXER=3',
'-sSDL3_MIXER_FORMATS=' + ','.join(formats),
]
self.btest_exit('test_sdl3_mixer.c', cflags=args)

@requires_graphics_hardware
@no_wasm64('cocos2d ports does not compile with wasm64')
def test_cocos2d_hello(self):
Expand Down
6 changes: 6 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -2649,6 +2649,12 @@ def test_sdl3_ttf(self):
self.emcc(test_file('browser/test_sdl3_ttf.c'), args=['-Wno-experimental', '-sUSE_SDL=3', '-sUSE_SDL_TTF=3'])
self.emcc(test_file('browser/test_sdl3_ttf.c'), args=['-Wno-experimental', '--use-port=sdl3', '--use-port=sdl3_ttf'])

@requires_network
def test_sdl3_mixer(self):
self.emcc('browser/test_sdl3_mixer.c', ['-Wno-experimental', '-DSOUND_PATH="sound.wav"', '-sUSE_SDL=3', '-sUSE_SDL_MIXER=3', '-o', 'a.out.js'])
self.emcc('browser/test_sdl3_mixer.c', ['-Wno-experimental', '-DSOUND_PATH="sound.wav"', '--use-port=sdl3_mixer', '-o', 'a.out.js'])
self.emcc('browser/test_sdl3_mixer.c', ['-Wno-experimental', '-DSOUND_PATH="sound.wav"', '--use-port=sdl3_mixer:formats=ogg', '-o', 'a.out.js'])

@requires_network
def test_contrib_ports(self):
# Verify that contrib ports can be used (using the only contrib port available ATM, but can be replaced
Expand Down
124 changes: 124 additions & 0 deletions tools/ports/sdl3_mixer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2025 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.

import os

from typing import Dict, Set

VERSION = '3.2.0'
TAG = f'release-{VERSION}'
HASH = '96f374b3ca96202973fca84228e7775db3d6e38888888573d0ba0d045bc1d3cc6f876984e50dcce1b65875c80f8e263b5ff687570f4b4c720f48ca3cfaff0648'
SUBDIR = f'SDL3_mixer-{TAG}'

deps = ['sdl3']

variants = {
'sdl3_mixer-ogg': {'SDL3_MIXER_FORMATS': ['ogg']},
'sdl3_mixer-none': {'SDL3_MIXER_FORMATS': []},
'sdl3_mixer-ogg-mt': {'SDL3_MIXER_FORMATS': ['ogg'], 'PTHREADS': 1},
'sdl3_mixer-none-mt': {'SDL3_MIXER_FORMATS': [], 'PTHREADS': 1},
}

OPTIONS = {
'formats': 'A comma separated list of formats (ex: --use-port=sdl3_mixer:formats=ogg,mp3)',
}

SUPPORTED_FORMATS = {'ogg', 'mp3'}

# user options (from --use-port)
opts: dict[str, set] = {
'formats': set(),
}


def needed(settings):
return settings.USE_SDL_MIXER == 3


def get_formats(settings):
return opts['formats'].union(settings.SDL3_MIXER_FORMATS)


def get_lib_name(settings):
formats = '-'.join(sorted(get_formats(settings)))

libname = 'libSDL3_mixer'
if formats != '':
libname += '-' + formats
if settings.PTHREADS:
libname += '-mt'
libname += '.a'

return libname


def get(ports, settings, shared):
ports.fetch_project('sdl3_mixer', f'https://github.com/libsdl-org/SDL_mixer/archive/{TAG}.zip', sha512hash=HASH)
libname = get_lib_name(settings)

def create(final):
src_root = ports.get_dir('sdl3_mixer', 'SDL_mixer-' + TAG)
ports.install_header_dir(os.path.join(src_root, 'include'), target='.')
srcs = [
"src/SDL_mixer.c",
"src/SDL_mixer_metadata_tags.c",
"src/SDL_mixer_spatialization.c",
"src/decoder_raw.c",
"src/decoder_sinewave.c",
"src/decoder_wav.c",
]

flags = ['-sUSE_SDL=3', '-DDECODER_WAV','-Wno-format-security', '-Wno-experimental']

if settings.PTHREADS:
flags += ['-pthread']

formats = get_formats(settings)

if "ogg" in formats:
flags += [
'-sUSE_VORBIS',
'-DDECODER_OGGVORBIS_VORBISFILE',
]
srcs += ["src/decoder_vorbis.c",]

if "mp3" in formats:
flags += [
'-sUSE_MPG123',
'-DDECODER_MP3_MPG123',
]
srcs += ["src/decoder_mpg123.c",]

ports.build_port(src_root, final, 'sdl3_mixer', flags=flags, srcs=srcs)
return [shared.cache.get_lib(libname, create, what='port')]


def clear(ports, settings, shared):
shared.cache.erase_lib(get_lib_name(settings))


def process_dependencies(settings):
settings.USE_SDL = 3
formats = get_formats(settings)
if "ogg" in formats:
deps.append('vorbis')
settings.USE_VORBIS = 1
if "mp3" in formats:
deps.append('mpg123')
settings.USE_MPG123 = 1


def handle_options(options, error_handler):
formats = options['formats'].split(',')
for format in formats:
format = format.lower().strip()
if format not in SUPPORTED_FORMATS:
error_handler(f'{format} is not a supported format')
else:
opts['formats'].add(format)


def show():
return 'sdl3_mixer (-sUSE_SDL_MIXER=3 or --use-port=sdl3_mixer; zlib license)'
1 change: 1 addition & 0 deletions tools/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
'USE_FREETYPE',
'SDL2_MIXER_FORMATS',
'SDL2_IMAGE_FORMATS',
'SDL3_MIXER_FORMATS',
'USE_SQLITE3',
}

Expand Down