From 2e27745ef2884cd36fa9ed330699ee21f13a9ba8 Mon Sep 17 00:00:00 2001 From: Nicolas Allemand Date: Tue, 4 Dec 2018 19:20:16 +0100 Subject: [PATCH] SDL2_mixer (see #5995) (#7599) --- AUTHORS | 1 + embuilder.py | 5 ++- site/source/docs/getting_started/FAQ.rst | 5 ++- src/settings.js | 6 ++- tests/sdl2_mixer.c | 43 +++++++++++++++++++++ tests/test_browser.py | 5 +++ tests/test_interactive.py | 7 ++++ tests/test_sanity.py | 1 + tools/ports/__init__.py | 4 +- tools/ports/bullet.py | 10 ----- tools/ports/sdl.py | 16 -------- tools/ports/sdl_image.py | 2 + tools/ports/sdl_mixer.py | 48 ++++++++++++++++++++++++ tools/ports/sdl_net.py | 2 + tools/ports/zlib.py | 9 ----- tools/shared.py | 3 +- 16 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 tests/sdl2_mixer.c create mode 100644 tools/ports/sdl_mixer.py diff --git a/AUTHORS b/AUTHORS index 0a49c7915..fed5ebd2d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -374,3 +374,4 @@ a license to everyone to use it as detailed in LICENSE.) * Kirill Smelkov (copyright owned by Nexedi) * Lutz Hören * Pedro K Custodio +* Nicolas Allemand diff --git a/embuilder.py b/embuilder.py index 64fabf666..f7e002454 100755 --- a/embuilder.py +++ b/embuilder.py @@ -55,6 +55,7 @@ Available operations and tasks: ogg sdl2 sdl2-image + sdl2-mixer sdl2-ttf sdl2-net vorbis @@ -102,7 +103,7 @@ CXX_WITH_STDLIB = ''' ''' SYSTEM_TASKS = ['compiler-rt', 'libc', 'libc-mt', 'libc-extras', 'emmalloc', 'emmalloc_debug', 'dlmalloc', 'dlmalloc_threadsafe', 'pthreads', 'dlmalloc_debug', 'dlmalloc_threadsafe_debug', 'libcxx', 'libcxx_noexcept', 'libcxxabi', 'html5'] -USER_TASKS = ['al', 'gl', 'binaryen', 'bullet', 'freetype', 'icu', 'libpng', 'ogg', 'sdl2', 'sdl2-gfx', 'sdl2-image', 'sdl2-ttf', 'sdl2-net', 'vorbis', 'zlib'] +USER_TASKS = ['al', 'gl', 'binaryen', 'bullet', 'freetype', 'icu', 'libpng', 'ogg', 'sdl2', 'sdl2-gfx', 'sdl2-image', 'sdl2-mixer', 'sdl2-ttf', 'sdl2-net', 'vorbis', 'zlib'] temp_files = shared.configuration.get_temp_files() logger = logging.getLogger(__file__) @@ -254,6 +255,8 @@ def main(): build_port('sdl2-image', 'libsdl2_image.bc', ['-s', 'USE_SDL=2', '-s', 'USE_SDL_IMAGE=2']) elif what == 'sdl2-net': build_port('sdl2-net', 'libsdl2_net.bc', ['-s', 'USE_SDL=2', '-s', 'USE_SDL_NET=2']) + elif what == 'sdl2-mixer': + build_port('sdl2-mixer', 'libsdl2_mixer.a', ['-s', 'USE_SDL=2', '-s', 'USE_SDL_MIXER=2', '-s', 'USE_VORBIS=1']) elif what == 'freetype': build_port('freetype', 'libfreetype.a', ['-s', 'USE_FREETYPE=1']) elif what == 'harfbuzz': diff --git a/site/source/docs/getting_started/FAQ.rst b/site/source/docs/getting_started/FAQ.rst index 3bb5b431c..93a53d87b 100644 --- a/site/source/docs/getting_started/FAQ.rst +++ b/site/source/docs/getting_started/FAQ.rst @@ -174,10 +174,11 @@ Another option is to implement needed C APIs as JavaScript libraries (see ``--js What are my options for audio playback? ======================================= -Emscripten has partial support for SDL (1, not 2) audio, and OpenAL. +Emscripten has partial support for SDL1 and 2 audio, and OpenAL. -To use SDL audio, include it as ``#include ``. You can use it that way alongside SDL1, SDL2, or another library for platform integration. +To use SDL1 audio, include it as ``#include ``. You can use it that way alongside SDL1, SDL2, or another library for platform integration. +To use SDL2 audio, include it as ``#include `` and use `-s SDL_MIXER=2`. Format support is currently limited to OGG. How can my compiled program access files? ========================================= diff --git a/src/settings.js b/src/settings.js index 354fe9e0a..147d65cb9 100644 --- a/src/settings.js +++ b/src/settings.js @@ -395,7 +395,7 @@ var FULL_ES2 = 0; var GL_EMULATE_GLES_VERSION_STRING_FORMAT = 1; // Some old Android WeChat (Chromium 37?) browser has a WebGL bug that it ignores -// the offset of a typed array view pointing to an ArrayBuffer. Set this to +// the offset of a typed array view pointing to an ArrayBuffer. Set this to // 1 to enable a polyfill that works around the issue when it appears. This // bug is only relevant to WebGL 1, the affected browsers do not support WebGL 2. var WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 0; @@ -1070,6 +1070,10 @@ var USE_OGG = 0; // 1 = use freetype from emscripten-ports var USE_FREETYPE = 0; +// Specify the SDL_mixer version that is being linked against. +// Doesn't *have* to match USE_SDL, but a good idea. +var USE_SDL_MIXER = 1; + // 1 = use harfbuzz from harfbuzz upstream var USE_HARFBUZZ = 0; diff --git a/tests/sdl2_mixer.c b/tests/sdl2_mixer.c new file mode 100644 index 000000000..96cd4a2b8 --- /dev/null +++ b/tests/sdl2_mixer.c @@ -0,0 +1,43 @@ +#include +#include +#include + +#define OGG_PATH "/sound.ogg" + +Mix_Chunk *wave = NULL; + +void sound_loop_then_quit() { + if (Mix_Playing(-1)) + return; + printf("Done audio\n"); + Mix_FreeChunk(wave); + Mix_CloseAudio(); + + emscripten_cancel_main_loop(); + printf("Shutting down\n"); + REPORT_RESULT(1); +} + +int main(int argc, char* argv[]){ + if (SDL_Init(SDL_INIT_AUDIO) < 0) + return -1; + int const frequency = EM_ASM_INT_V({ + var context; + try { + context = new AudioContext(); + } catch (e) { + context = new webkitAudioContext(); // safari only + } + return context.sampleRate; + }); + if(Mix_OpenAudio(frequency, MIX_DEFAULT_FORMAT, 2, 1024) == -1) + return -1; + wave = Mix_LoadWAV(OGG_PATH); + if (wave == NULL) + return -1; + if (Mix_PlayChannel(-1, wave, 0) == -1) + return -1; + printf("Starting sound play loop\n"); + emscripten_set_main_loop(sound_loop_then_quit, 0, 1); + return 0; +} diff --git a/tests/test_browser.py b/tests/test_browser.py index e33173fcb..65507adab 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3064,6 +3064,11 @@ window.close = function() { run_process([PYTHON, EMCC, 'test.o', '-s', 'USE_SDL=2', '-o', 'test.html']) self.run_browser('test.html', '...', '/report_result?1') + @requires_sound_hardware + def test_sdl2_mixer(self): + shutil.copyfile(path_from_root('tests', 'sounds', 'alarmvictory_1.ogg'), os.path.join(self.get_dir(), 'sound.ogg')) + self.btest('sdl2_mixer.c', expected='1', args=['--preload-file', 'sound.ogg', '-s', 'USE_SDL=2', '-s', 'USE_SDL_MIXER=2', '-s', 'TOTAL_MEMORY=33554432']) + @requires_graphics_hardware def test_cocos2d_hello(self): cocos2d_root = os.path.join(system_libs.Ports.get_build_dir(), 'Cocos2d') diff --git a/tests/test_interactive.py b/tests/test_interactive.py index 97e134df2..488469558 100644 --- a/tests/test_interactive.py +++ b/tests/test_interactive.py @@ -99,6 +99,13 @@ class interactive(BrowserCore): Popen([PYTHON, EMCC, '-O2', '--closure', '1', '--minify', '0', os.path.join(self.get_dir(), 'sdl_audio_beep.cpp'), '-s', 'DISABLE_EXCEPTION_CATCHING=0', '-o', 'page.html']).communicate() self.run_browser('page.html', '', '/report_result?1') + def test_sdl2_mixer(self): + shutil.copyfile(path_from_root('tests', 'sounds', 'alarmvictory_1.ogg'), os.path.join(self.get_dir(), 'sound.ogg')) + open(os.path.join(self.get_dir(), 'sdl2_mixer.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl2_mixer.c')).read())) + + Popen([PYTHON, EMCC, '-O2', '--minify', '0', os.path.join(self.get_dir(), 'sdl2_mixer.c'), '-s', 'USE_SDL=2', '-s', 'USE_SDL_MIXER=2', '-s', 'TOTAL_MEMORY=33554432', '--preload-file', 'sound.ogg', '-o', 'page.html']).communicate() + self.run_browser('page.html', '', '/report_result?1') + def zzztest_sdl2_audio_beeps(self): open(os.path.join(self.get_dir(), 'sdl2_audio_beep.cpp'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl2_audio_beep.cpp')).read())) diff --git a/tests/test_sanity.py b/tests/test_sanity.py index 0eff735c8..207f0b8d3 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -748,6 +748,7 @@ fi ([PYTHON, EMBUILDER, 'build', 'icu'], ['building and verifying icu', 'success'], True, ['icu.bc']), ([PYTHON, EMBUILDER, 'build', 'sdl2-ttf'], ['building and verifying sdl2-ttf', 'success'], True, ['sdl2-ttf.bc']), ([PYTHON, EMBUILDER, 'build', 'sdl2-net'], ['building and verifying sdl2-net', 'success'], True, ['sdl2-net.bc']), + ([PYTHON, EMBUILDER, 'build', 'sdl2-mixer'], ['building and verifying sdl2-mixer', 'success'], True, ['sdl2-mixer.bc']), ([PYTHON, EMBUILDER, 'build', 'binaryen'], ['building and verifying binaryen', 'success'], True, []), ([PYTHON, EMBUILDER, 'build', 'cocos2d'], ['building and verifying cocos2d', 'success'], True, ['libCocos2d.bc']), ([PYTHON, EMBUILDER, 'build', 'wasm-libc'], ['building and verifying wasm-libc', 'success'], True, ['wasm-libc.bc']), diff --git a/tools/ports/__init__.py b/tools/ports/__init__.py index 7a8d67469..f4aba9124 100644 --- a/tools/ports/__init__.py +++ b/tools/ports/__init__.py @@ -3,10 +3,10 @@ # University of Illinois/NCSA Open Source License. Both these licenses can be # found in the LICENSE file. -from . import binaryen, bullet, cocos2d, freetype, harfbuzz, icu, libpng, ogg, sdl, sdl_gfx, sdl_image, sdl_ttf, sdl_net, vorbis, zlib +from . import binaryen, bullet, cocos2d, freetype, harfbuzz, icu, libpng, ogg, sdl, sdl_gfx, sdl_image, sdl_mixer, sdl_ttf, sdl_net, vorbis, zlib # If port A depends on port B, then A should be _after_ B -ports = [icu, zlib, libpng, sdl, sdl_image, sdl_gfx, ogg, vorbis, bullet, freetype, harfbuzz, sdl_ttf, sdl_net, binaryen, cocos2d] +ports = [icu, zlib, libpng, sdl, sdl_image, sdl_gfx, ogg, vorbis, sdl_mixer, bullet, freetype, harfbuzz, sdl_ttf, sdl_net, binaryen, cocos2d] ports_by_name = {} for port in ports: diff --git a/tools/ports/bullet.py b/tools/ports/bullet.py index 1859fd676..de7c99e20 100644 --- a/tools/ports/bullet.py +++ b/tools/ports/bullet.py @@ -7,16 +7,6 @@ import os, shutil, logging, subprocess, sys, stat TAG = 'version_1' -def build_with_configure(ports, shared, path): # not currently used - if not sys.platform.startswith('win'): #TODO: test on windows - autogen_path = os.path.join(path, 'bullet', 'autogen.sh') - os.chmod(autogen_path, os.stat(autogen_path).st_mode | 0o111) # Make executable - subprocess.Popen(["sh", "autogen.sh"], cwd=os.path.join(path, 'bullet')).wait() - subprocess.Popen(["python", "make.py"], cwd=path).wait() - final = os.path.join(path, 'libbullet.bc') - shutil.copyfile(os.path.join(path, 'bullet', 'build', 'libbullet.bc'), final) - return final - def get(ports, settings, shared): if settings.USE_BULLET == 1: ports.fetch_project('bullet', 'https://github.com/emscripten-ports/bullet/archive/' + TAG + '.zip', 'Bullet-' + TAG) diff --git a/tools/ports/sdl.py b/tools/ports/sdl.py index f33d0de0e..9b4dfb305 100644 --- a/tools/ports/sdl.py +++ b/tools/ports/sdl.py @@ -7,22 +7,6 @@ import os, shutil, logging TAG = 'version_15' -def get_with_configure(ports, settings, shared): # not currently used; no real need for configure on emscripten users' machines! - if settings.USE_SDL == 2: - ports.fetch_project('sdl2', 'https://github.com/emscripten-ports/SDL2/archive/' + TAG + '.zip', 'SDL2-' + TAG) - def setup_includes(): - # copy includes to a location so they can be used as 'SDL2/' - include_path = os.path.join(shared.Cache.get_path('ports-builds'), 'sdl2', 'include') - shared.try_delete(os.path.join(include_path, 'SDL2')) - shutil.copytree(include_path, os.path.join(include_path, 'SDL2')) - ret = [ports.build_project('sdl2', 'SDL2-' + TAG, - ['sh', './configure', '--host=asmjs-unknown-emscripten', '--disable-assembly', '--disable-threads', '--enable-cpuinfo=false', 'CFLAGS=-O2'], - [os.path.join('build', '.libs', 'libSDL2.a')], - setup_includes)] - return ret - else: - return [] - def get(ports, settings, shared): if settings.USE_SDL == 2: # SDL2 uses some things on the Module object, make sure they are exported diff --git a/tools/ports/sdl_image.py b/tools/ports/sdl_image.py index 3f8b5d97a..aba78d0bd 100644 --- a/tools/ports/sdl_image.py +++ b/tools/ports/sdl_image.py @@ -13,6 +13,8 @@ def get(ports, settings, shared): assert os.path.exists(sdl_build), 'You must use SDL2 to use SDL2_image' ports.fetch_project('sdl2-image', 'https://github.com/emscripten-ports/SDL2_image/archive/' + TAG + '.zip', 'SDL2_image-' + TAG) def create(): + # although we shouldn't really do this and could instead use '-Xclang -isystem' as a kind of 'overlay' as sdl_mixer does, + # by now people may be relying on headers being pulled in by '-s USE_SDL=2' if sdl_image was built in the past shutil.copyfile(os.path.join(ports.get_dir(), 'sdl2-image', 'SDL2_image-' + TAG, 'SDL_image.h'), os.path.join(ports.get_build_dir(), 'sdl2', 'include', 'SDL_image.h')) shutil.copyfile(os.path.join(ports.get_dir(), 'sdl2-image', 'SDL2_image-' + TAG, 'SDL_image.h'), os.path.join(ports.get_build_dir(), 'sdl2', 'include', 'SDL2', 'SDL_image.h')) srcs = 'IMG.c IMG_bmp.c IMG_gif.c IMG_jpg.c IMG_lbm.c IMG_pcx.c IMG_png.c IMG_pnm.c IMG_tga.c IMG_tif.c IMG_xcf.c IMG_xpm.c IMG_xv.c IMG_webp.c IMG_ImageIO.m'.split(' ') diff --git a/tools/ports/sdl_mixer.py b/tools/ports/sdl_mixer.py new file mode 100644 index 000000000..248f93d63 --- /dev/null +++ b/tools/ports/sdl_mixer.py @@ -0,0 +1,48 @@ +import os, shutil, logging +import stat + +TAG = 'release-2.0.1' + +def get(ports, settings, shared): + if settings.USE_SDL_MIXER == 2: + sdl_build = os.path.join(ports.get_build_dir(), 'sdl2') + assert os.path.exists(sdl_build), 'You must use SDL2 to use SDL2_mixer' + ports.fetch_project('sdl2-mixer', 'https://github.com/emscripten-ports/SDL2_mixer/archive/' + TAG + '.zip', 'SDL2_mixer-' + TAG) + def create(): + cwd = os.getcwd() + commonflags = ['--disable-shared', '--disable-music-cmd', '--enable-sdltest', '--disable-smpegtest'] + formatflags = ['--disable-music-wave', '--disable-music-mod', '--disable-music-midi', '--enable-music-ogg', '--disable-music-ogg-shared', '--disable-music-flac', '--disable-music-mp3'] + configure = os.path.join(ports.get_dir(), 'sdl2-mixer', 'SDL2_mixer-' + TAG, 'configure') + build_dir = os.path.join(ports.get_build_dir(), 'sdl2-mixer') + dist_dir = os.path.join(ports.get_build_dir(), 'sdl2-mixer', 'dist') + out = os.path.join(dist_dir, 'lib', 'libSDL2_mixer.a') + final = os.path.join(ports.get_build_dir(), 'sdl2-mixer', 'libsdl2_mixer.a') + shared.safe_ensure_dirs(build_dir) + + try: + os.chdir(build_dir) + os.chmod(configure, os.stat(configure).st_mode | stat.S_IEXEC) + ports.run_commands([[shared.EMCONFIGURE, configure, '--prefix=' + dist_dir] + formatflags + commonflags + ['CFLAGS=-s USE_VORBIS=1']]) + ports.run_commands([[shared.EMMAKE, 'make', 'install']]) + shutil.copyfile(out, final) + finally: + os.chdir(cwd) + return final + return [shared.Cache.get('sdl2-mixer', create, what='port')] + else: + return [] + +def process_dependencies(settings): + if settings.USE_SDL_MIXER == 2: + settings.USE_SDL = 2 + settings.USE_VORBIS = 1 + +def process_args(ports, args, settings, shared): + if settings.USE_SDL_MIXER == 2: + get(ports, settings, shared) + args += ['-Xclang', '-isystem' + os.path.join(shared.Cache.get_path('ports-builds'), 'sdl2-mixer', 'dist', 'include')] + return args + +def show(): + return 'SDL2_mixer (USE_SDL_MIXER=2; zlib license)' + diff --git a/tools/ports/sdl_net.py b/tools/ports/sdl_net.py index 9f8bc6fb1..e1a1b138e 100644 --- a/tools/ports/sdl_net.py +++ b/tools/ports/sdl_net.py @@ -14,6 +14,8 @@ def get(ports, settings, shared): ports.fetch_project('sdl2-net', 'https://github.com/emscripten-ports/SDL2_net/archive/' + TAG + '.zip', 'SDL2_net-' + TAG) def create(): logging.info('building port: sdl2-net') + # although we shouldn't really do this and could instead use '-Xclang -isystem' as a kind of 'overlay' as sdl_mixer does, + # by now people may be relying on headers being pulled in by '-s USE_SDL=2' if sdl_net was built in the past shutil.copyfile(os.path.join(ports.get_dir(), 'sdl2-net', 'SDL2_net-' + TAG, 'SDL_net.h'), os.path.join(ports.get_build_dir(), 'sdl2', 'include', 'SDL_net.h')) shutil.copyfile(os.path.join(ports.get_dir(), 'sdl2-net', 'SDL2_net-' + TAG, 'SDL_net.h'), os.path.join(ports.get_build_dir(), 'sdl2', 'include', 'SDL2', 'SDL_net.h')) srcs = 'SDLnet.c SDLnetselect.c SDLnetTCP.c SDLnetUDP.c'.split(' ') diff --git a/tools/ports/zlib.py b/tools/ports/zlib.py index 9f3594d10..fcdf168c5 100644 --- a/tools/ports/zlib.py +++ b/tools/ports/zlib.py @@ -8,15 +8,6 @@ from subprocess import Popen TAG = 'version_1' -def get_with_configure(ports, settings, shared): - if settings.USE_ZLIB == 1: - ports.fetch_project('zlib', 'https://github.com/emscripten-ports/zlib/archive/' + TAG + '.zip', 'zlib-' + TAG) - return [ports.build_project('zlib', 'zlib-' + TAG, - ['sh', './configure'], - ['libz.a'])] - else: - return [] - def get(ports, settings, shared): if settings.USE_ZLIB == 1: ports.fetch_project('zlib', 'https://github.com/emscripten-ports/zlib/archive/' + TAG + '.zip', 'zlib-' + TAG) diff --git a/tools/shared.py b/tools/shared.py index bc7ba7de7..7437b0172 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -784,7 +784,8 @@ EMAR = path_from_root('emar.py') EMRANLIB = path_from_root('emranlib') EMCONFIG = path_from_root('em-config') EMLINK = path_from_root('emlink.py') -EMMAKEN = path_from_root('tools', 'emmaken.py') +EMCONFIGURE = path_from_root('emconfigure.py') +EMMAKE = path_from_root('emmake.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') EXEC_LLVM = path_from_root('tools', 'exec_llvm.py') FILE_PACKAGER = path_from_root('tools', 'file_packager.py')