From 039a07bb4d3c32ac66a55637d334056aa5661b6f Mon Sep 17 00:00:00 2001 From: Justin Kerk Date: Thu, 4 Feb 2016 04:35:51 +0000 Subject: [PATCH] Substantial improvements to Web Audio sound backend for Emscripten port. [Grant Galitz] --- scripts/src/main.lua | 2 +- src/osd/modules/sound/js_sound.cpp | 6 +- src/osd/modules/sound/js_sound.js | 213 +++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 src/osd/modules/sound/js_sound.js diff --git a/scripts/src/main.lua b/scripts/src/main.lua index 9623a9d5236..2397a489e00 100644 --- a/scripts/src/main.lua +++ b/scripts/src/main.lua @@ -88,7 +88,7 @@ end targetextension ".bc" if os.getenv("EMSCRIPTEN") then postbuildcommands { - os.getenv("EMSCRIPTEN") .. "/emcc -O3 -s DISABLE_EXCEPTION_CATCHING=2 -s USE_SDL=2 --memory-init-file 0 -s ALLOW_MEMORY_GROWTH=0 -s TOTAL_MEMORY=268435456 -s EXCEPTION_CATCHING_WHITELIST='[\"__ZN15running_machine17start_all_devicesEv\"]' -s EXPORTED_FUNCTIONS=\"['_main', '_malloc', '__Z14js_get_machinev', '__Z9js_get_uiv', '__Z12js_get_soundv', '__ZN10ui_manager12set_show_fpsEb', '__ZNK10ui_manager8show_fpsEv', '__ZN13sound_manager4muteEbh', '_SDL_PauseAudio']\" $(TARGET) -o " .. _MAKE.esc(MAME_DIR) .. _OPTIONS["target"] .. _OPTIONS["subtarget"] .. ".js --post-js " .. _MAKE.esc(MAME_DIR) .. "src/osd/sdl/emscripten_post.js", + os.getenv("EMSCRIPTEN") .. "/emcc -O3 -s DISABLE_EXCEPTION_CATCHING=2 -s USE_SDL=2 --memory-init-file 0 -s ALLOW_MEMORY_GROWTH=0 -s TOTAL_MEMORY=268435456 -s EXCEPTION_CATCHING_WHITELIST='[\"__ZN15running_machine17start_all_devicesEv\"]' -s EXPORTED_FUNCTIONS=\"['_main', '_malloc', '__Z14js_get_machinev', '__Z9js_get_uiv', '__Z12js_get_soundv', '__ZN10ui_manager12set_show_fpsEb', '__ZNK10ui_manager8show_fpsEv', '__ZN13sound_manager4muteEbh', '_SDL_PauseAudio']\" $(TARGET) -o " .. _MAKE.esc(MAME_DIR) .. _OPTIONS["target"] .. _OPTIONS["subtarget"] .. ".js --pre-js " .. _MAKE.esc(MAME_DIR) .. "src/osd/modules/sound/js_sound.js --post-js " .. _MAKE.esc(MAME_DIR) .. "src/osd/sdl/emscripten_post.js", } end diff --git a/src/osd/modules/sound/js_sound.cpp b/src/osd/modules/sound/js_sound.cpp index bd95c79dd2e..581de205d7b 100644 --- a/src/osd/modules/sound/js_sound.cpp +++ b/src/osd/modules/sound/js_sound.cpp @@ -6,7 +6,7 @@ Shim for native JavaScript sound interface implementations (Emscripten only). -*******************************************************************c********/ +****************************************************************************/ #include "sound_module.h" #include "modules/osdmodule.h" @@ -34,14 +34,14 @@ public: { EM_ASM_ARGS({ // Forward audio stream update on to JS backend implementation. - jsmess_update_audio_stream($0, $1); + jsmame_update_audio_stream($0, $1); }, (unsigned int)buffer, samples_this_frame); } virtual void set_mastervolume(int attenuation) { EM_ASM_ARGS({ // Forward volume update on to JS backend implementation. - jsmess_set_mastervolume($0); + jsmame_set_mastervolume($0); }, attenuation); } diff --git a/src/osd/modules/sound/js_sound.js b/src/osd/modules/sound/js_sound.js new file mode 100644 index 00000000000..393bcd66e93 --- /dev/null +++ b/src/osd/modules/sound/js_sound.js @@ -0,0 +1,213 @@ +// license:BSD-3-Clause +// copyright-holders:Grant Galitz, Katelyn Gadd +/*************************************************************************** + + JSMAME web audio backend v0.3 + + Original by katelyn gadd - kg at luminance dot org ; @antumbral on twitter + Substantial changes by taisel + +***************************************************************************/ + +var jsmame_web_audio = (function () { + +var context = null; +var gain_node = null; +var eventNode = null; +var sampleScale = 32766; +var inputBuffer = new Float32Array(44100); +var bufferSize = 44100; +var start = 0; +var rear = 0; +var watchDogDateLast = null; +var watchDogTimerEvent = null; + +function lazy_init () { + //Make + if (context) { + //Return if already created: + return; + } + if (typeof AudioContext != "undefined") { + //Standard context creation: + context = new AudioContext(); + } + else if (typeof webkitAudioContext != "undefined") { + //Older webkit context creation: + context = new webkitAudioContext(); + } + else { + //API not found! + return; + } + //Generate a volume control node: + gain_node = context.createGain(); + //Set initial volume to 1: + gain_node.gain.value = 1.0; + //Connect volume node to output: + gain_node.connect(context.destination); + //Initialize the streaming event: + init_event(); +}; + +function init_event() { + //Generate a streaming node point: + if (typeof context.createScriptProcessor == "function") { + //Current standard compliant way: + eventNode = context.createScriptProcessor(4096, 0, 2); + } + else { + //Deprecated way: + eventNode = context.createJavaScriptNode(4096, 0, 2); + } + //Make our tick function the audio callback function: + eventNode.onaudioprocess = tick; + //Connect stream to volume control node: + eventNode.connect(gain_node); + //WORKAROUND FOR FIREFOX BUG: + initializeWatchDogForFirefoxBug(); +}; + +function initializeWatchDogForFirefoxBug() { + //TODO: decide if we want to user agent sniff firefox here, + //since Google Chrome doesn't need this: + watchDogDateLast = (new Date()).getTime(); + if (watchDogTimerEvent === null) { + watchDogTimerEvent = setInterval(function () { + var timeDiff = (new Date()).getTime() - watchDogDateLast; + if (timeDiff > 500) { + disconnect_old_event(); + init_event(); + } + }, 500); + } +}; + +function disconnect_old_event() { + //Disconnect from audio graph: + eventNode.disconnect(); + //IIRC there was a firefox bug that did not GC this event when nulling the node itself: + eventNode.onaudioprocess = null; + //Null the glitched/unused node: + eventNode = null; +}; + +function set_mastervolume ( + // even though it's 'attenuation' the value is negative, so... + attenuation_in_decibels +) { + lazy_init(); + if (!context) return; + + // http://stackoverflow.com/questions/22604500/web-audio-api-working-with-decibels + // seemingly incorrect/broken. figures. welcome to Web Audio + // var gain_web_audio = 1.0 - Math.pow(10, 10 / attenuation_in_decibels); + + // HACK: Max attenuation in JSMESS appears to be 32. + // Hit ' then left/right arrow to test. + // FIXME: This is linear instead of log10 scale. + var gain_web_audio = 1.0 + (+attenuation_in_decibels / +32); + if (gain_web_audio < +0) + gain_web_audio = +0; + else if (gain_web_audio > +1) + gain_web_audio = +1; + + gain_node.gain.value = gain_web_audio; +}; + +function update_audio_stream ( + pBuffer, // pointer into emscripten heap. int16 samples + samples_this_frame // int. number of samples at pBuffer address. +) { + lazy_init(); + if (!context) return; + + for ( + var i = 0, + l = samples_this_frame | 0; + i < l; + i++ + ) { + var offset = + // divide by sizeof(INT16) since pBuffer is offset + // in bytes + ((pBuffer / 2) | 0) + + ((i * 2) | 0); + + var left_sample = HEAP16[offset]; + var right_sample = HEAP16[(offset + 1) | 0]; + + // normalize from signed int16 to signed float + var left_sample_float = left_sample / sampleScale; + var right_sample_float = right_sample / sampleScale; + + inputBuffer[rear++] = left_sample_float; + inputBuffer[rear++] = right_sample_float; + if (rear == bufferSize) { + rear = 0; + } + if (start == rear) { + start += 2; + if (start == bufferSize) { + start = 0; + } + } + } +}; +function tick (event) { + //Find all output channels: + for (var bufferCount = 0, buffers = []; bufferCount < 2; ++bufferCount) { + buffers[bufferCount] = event.outputBuffer.getChannelData(bufferCount); + } + //Copy samples from the input buffer to the Web Audio API: + for (var index = 0; index < 4096 && start != rear; ++index) { + buffers[0][index] = inputBuffer[start++]; + buffers[1][index] = inputBuffer[start++]; + if (start == bufferSize) { + start = 0; + } + } + //Pad with silence if we're underrunning: + while (index < 4096) { + buffers[0][index] = 0; + buffers[1][index++] = 0; + } + //Deep inside the bowels of vendors bugs, + //we're using watchdog for a firefox bug, + //where the user agent decides to stop firing events + //if the user agent lags out due to system load. + //Don't even ask.... + watchDogDateLast = (new Date()).getTime(); +} + +function get_context() { + return context; +}; + +function sample_count() { + //TODO get someone to call this from the emulator, + //so the emulator can do proper audio buffering by + //knowing how many samples are left: + if (!context) { + //Use impossible value as an error code: + return -1; + } + var count = rear - start; + if (start > rear) { + count += bufferSize; + } + return count; +} + +return { + set_mastervolume: set_mastervolume, + update_audio_stream: update_audio_stream, + get_context: get_context, + sample_count: sample_count +}; + +})(); + +window.jsmame_set_mastervolume = jsmame_web_audio.set_mastervolume; +window.jsmame_update_audio_stream = jsmame_web_audio.update_audio_stream; +window.jsmame_sample_count = jsmame_web_audio.sample_count;