From 04be8cbee209c333932aa951d5caf5147f487d2b Mon Sep 17 00:00:00 2001 From: ToadKing Date: Wed, 28 Aug 2013 00:03:25 -0400 Subject: [PATCH] new audio core, RWebAudio. Glitchy, but works well, even with requestAnimationFrame callbacks --- Makefile.emscripten | 11 +-- config.def.h | 5 + driver.c | 3 + driver.h | 1 + emscripten/RWebAudio.c | 90 ++++++++++++++++++ emscripten/RWebAudio.h | 29 ++++++ emscripten/library_rwebaudio.js | 161 ++++++++++++++++++++++++++++++++ frontend/frontend_emscripten.c | 6 ++ settings.c | 2 + 9 files changed, 300 insertions(+), 8 deletions(-) create mode 100644 emscripten/RWebAudio.c create mode 100644 emscripten/RWebAudio.h create mode 100644 emscripten/library_rwebaudio.js diff --git a/Makefile.emscripten b/Makefile.emscripten index 0c15c8bd81..559638ab75 100644 --- a/Makefile.emscripten +++ b/Makefile.emscripten @@ -22,6 +22,7 @@ OBJ = frontend/frontend_emscripten.o \ screenshot.o \ cheats.o \ audio/utils.o \ + emscripten/RWebAudio.o \ input/overlay.o \ fifo_buffer.o \ gfx/scaler/scaler.o \ @@ -39,7 +40,6 @@ OBJ = frontend/frontend_emscripten.o \ performance.o HAVE_OPENGL = 1 -HAVE_AL = 1 HAVE_RGUI = 1 HAVE_SDL = 1 HAVE_SDL_IMAGE = 1 @@ -59,7 +59,7 @@ libretro = libretro_emscripten.bc LIBS = -lm DEFINES = -DHAVE_SCREENSHOTS -DHAVE_NULLAUDIO -DHAVE_BSV_MOVIE -DPACKAGE_VERSION=\"0.9.9.3\" -LDFLAGS = -L. -s TOTAL_MEMORY=$(MEMORY) +LDFLAGS = -L. -s TOTAL_MEMORY=$(MEMORY) --js-library emscripten/library_rwebaudio.js ifeq ($(SCALER_NO_SIMD), 1) DEFINES += -DSCALER_NO_SIMD @@ -90,12 +90,6 @@ ifeq ($(HAVE_OPENGL), 1) DEFINES += -DHAVE_OPENGL -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DHAVE_EGL -DHAVE_OVERLAY -DHAVE_GLSL endif -ifeq ($(HAVE_AL), 1) - OBJ += audio/openal.o - DEFINES += -DHAVE_AL - LIBS += -lopenal -endif - ifeq ($(HAVE_ZLIB), 1) OBJ += gfx/rpng/rpng.o file_extract.o DEFINES += -DHAVE_ZLIB @@ -165,6 +159,7 @@ clean: rm -f gfx/fonts/*.o rm -f gfx/py_state/*.o rm -f gfx/rpng/*.o + rm -f gfx/glsym/*.o rm -f record/*.o rm -f input/*.o rm -f tools/*.o diff --git a/config.def.h b/config.def.h index a677cc2778..3294cd73b2 100644 --- a/config.def.h +++ b/config.def.h @@ -63,6 +63,7 @@ enum AUDIO_PS3, AUDIO_XENON360, AUDIO_WII, + AUDIO_RWEBAUDIO, AUDIO_NULL, INPUT_ANDROID, @@ -130,6 +131,8 @@ enum #define AUDIO_DEFAULT_DRIVER AUDIO_SL #elif defined(HAVE_DSOUND) #define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND +#elif defined(EMSCRIPTEN) +#define AUDIO_DEFAULT_DRIVER AUDIO_RWEBAUDIO #elif defined(HAVE_SDL) #define AUDIO_DEFAULT_DRIVER AUDIO_SDL #elif defined(HAVE_XAUDIO) @@ -335,6 +338,8 @@ static const bool rate_control = false; // Rate control delta. Defines how much rate_control is allowed to adjust input rate. #if defined(__QNX__) static const float rate_control_delta = 0.000; +#elif defined(EMSCRIPTEN) +static const float rate_control_delta = 0.002; #else static const float rate_control_delta = 0.005; #endif diff --git a/driver.c b/driver.c index 761e8cdbdb..51e8e6a577 100644 --- a/driver.c +++ b/driver.c @@ -84,6 +84,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef GEKKO &audio_gx, #endif +#ifdef EMSCRIPTEN + &audio_rwebaudio, +#endif #ifdef HAVE_NULLAUDIO &audio_null, #endif diff --git a/driver.h b/driver.h index bc4cdd7d1a..91dd004005 100644 --- a/driver.h +++ b/driver.h @@ -511,6 +511,7 @@ extern const audio_driver_t audio_coreaudio; extern const audio_driver_t audio_xenon360; extern const audio_driver_t audio_ps3; extern const audio_driver_t audio_gx; +extern const audio_driver_t audio_rwebaudio; extern const audio_driver_t audio_null; extern const video_driver_t video_gl; extern const video_driver_t video_psp1; diff --git a/emscripten/RWebAudio.c b/emscripten/RWebAudio.c new file mode 100644 index 0000000000..b384c828a8 --- /dev/null +++ b/emscripten/RWebAudio.c @@ -0,0 +1,90 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Michael Lelli + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "../driver.h" +#include "../general.h" + +#include "RWebAudio.h" + +static void ra_free(void *data) +{ + RWebAudioFree(); +} + +static void *ra_init(const char *device, unsigned rate, unsigned latency) +{ + (void)device; + (void)rate; + void *data = RWebAudioInit(latency); + g_settings.audio.out_rate = RWebAudioSampleRate(); + RARCH_LOG("audio out rate: %u\n", g_settings.audio.out_rate); + return data; +} + +static ssize_t ra_write(void *data, const void *buf, size_t size) +{ + (void)data; + return RWebAudioWrite(buf, size); +} + +static bool ra_stop(void *data) +{ + (void)data; + return RWebAudioStop(); +} + +static void ra_set_nonblock_state(void *data, bool state) +{ + (void)data; + RWebAudioSetNonblockState(state); +} + +static bool ra_start(void *data) +{ + (void)data; + return RWebAudioStart(); +} + +static bool ra_use_float(void *data) +{ + (void)data; + return true; +} + +static size_t ra_write_avail(void *data) +{ + (void)data; + return RWebAudioWriteAvail(); +} + +static size_t ra_buffer_size(void *data) +{ + (void)data; + return RWebAudioBufferSize(); +} + +const audio_driver_t audio_rwebaudio = { + ra_init, + ra_write, + ra_stop, + ra_start, + ra_set_nonblock_state, + ra_free, + ra_use_float, + "rwebaudio", + ra_write_avail, + ra_buffer_size, +}; + diff --git a/emscripten/RWebAudio.h b/emscripten/RWebAudio.h new file mode 100644 index 0000000000..fd928cd3d6 --- /dev/null +++ b/emscripten/RWebAudio.h @@ -0,0 +1,29 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Michael Lelli + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include + +unsigned RWebAudioSampleRate(void); +void *RWebAudioInit(unsigned latency); +ssize_t RWebAudioWrite(const void *buf, size_t size); +bool RWebAudioStop(void); +bool RWebAudioStart(void); +void RWebAudioSetNonblockState(bool state); +void RWebAudioFree(void); +size_t RWebAudioWriteAvail(void); +size_t RWebAudioBufferSize(void); +int RWebAudioEnoughSpace(void); diff --git a/emscripten/library_rwebaudio.js b/emscripten/library_rwebaudio.js new file mode 100644 index 0000000000..d2197ceccb --- /dev/null +++ b/emscripten/library_rwebaudio.js @@ -0,0 +1,161 @@ +//"use strict"; + +var LibraryRWebAudio = { + $RA__deps: ['$Browser'], + $RA: { + SCRIPTNODE_BUFFER: 1024, + + context: null, + leftBuffer: null, + rightBuffer: null, + blank: null, + scriptNode: null, + bufferNode: null, + start: 0, + end: 0, + size: 0, + lastWrite: 0, + nonblock: false, + + npot: function(n) { + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; + }, + + process: function(e) { + var left = e.outputBuffer.getChannelData(0); + var right = e.outputBuffer.getChannelData(1); + var samples1 = RA.size; + var samples2 = 0; + samples1 = e.outputBuffer.length > samples1 ? samples1 : e.outputBuffer.length; + + if (samples1 + RA.start > RA.leftBuffer.length) { + samples2 = samples1 + RA.start - RA.leftBuffer.length; + samples1 = samples1 - samples2; + } + + var remaining = e.outputBuffer.length - (samples1 + samples2); + + if (samples1) { + left.set(RA.leftBuffer.subarray(RA.start, RA.start + samples1), 0); + right.set(RA.rightBuffer.subarray(RA.start, RA.start + samples1), 0); + } + + if (samples2) { + left.set(RA.leftBuffer.subarray(0, samples2), samples1); + right.set(RA.rightBuffer.subarray(0, samples2), samples1); + } + + /*if (remaining) { + left.set(RA.blank.subarray(0, remaining), samples1 + samples2); + right.set(RA.blank.subarray(0, remaining), samples1 + samples2); + }*/ + + RA.start = (RA.start + samples1 + samples2) % RA.leftBuffer.length; + RA.size -= samples1 + samples2; + } + }, + + RWebAudioSampleRate: function() { + return RA.context.sampleRate; + }, + + RWebAudioInit: function(latency) { + var ac = window['AudioContext'] || window['webkitAudioContext']; + var bufferSize; + + if (!ac) return 0; + + RA.context = new ac(); + // account for script processor overhead + latency -= 32; + // because we have to guess on how many samples the core will send when + // returning early, we double the buffer size to account for times when it + // sends more than we expect it to without losing samples + bufferSize = RA.npot(RA.context.sampleRate * latency / 1000) * 2; + RA.leftBuffer = new Float32Array(bufferSize); + RA.rightBuffer = new Float32Array(bufferSize); + RA.blank = new Float32Array(RA.SCRIPTNODE_BUFFER); + RA.bufferNode = RA.context.createBufferSource(); + RA.bufferNode.buffer = RA.context.createBuffer(2, RA.SCRIPTNODE_BUFFER, RA.context.sampleRate); + RA.bufferNode.loop = true; + RA.scriptNode = RA.context.createScriptProcessor(RA.SCRIPTNODE_BUFFER, 2, 2); + RA.scriptNode.onaudioprocess = RA.process; + RA.bufferNode.connect(RA.scriptNode); + RA.scriptNode.connect(RA.context.destination); + RA.bufferNode.start(0); + RA.start = RA.end = RA.size = 0; + RA.nonblock = false; + return 1; + }, + + RWebAudioWrite: function (buf, size) { + var samples = size / 8; + var free = RA.leftBuffer.length - RA.size; + if (free < samples) + RA.start = (RA.start + free) % RA.leftBuffer.length; + + for (var i = 0; i < samples; i++) { + RA.leftBuffer[RA.end] = {{{ makeGetValue('buf', 'i * 8', 'float') }}}; + RA.rightBuffer[RA.end] = {{{ makeGetValue('buf', 'i * 8 + 4', 'float') }}}; + RA.end = (RA.end + 1) % RA.leftBuffer.length; + } + + RA.lastWrite = size; + RA.size += samples; + return size; + }, + + RWebAudioStop: function() { + RA.scriptNode.onaudioprocess = null; + return true; + }, + + RWebAudioStart: function() { + RA.scriptNode.onaudioprocess = RA.process; + return true; + }, + + RWebAudioSetNonblockState: function(state) { + RA.nonblock = state; + }, + + RWebAudioFree: function() { + RA.scriptNode.onaudioprocess = null; + RA.start = RA.end = RA.size = RA.lastWrite = 0; + return; + }, + + RWebAudioWriteAvail: function() { + var free = (RA.leftBuffer.length / 2) - RA.size; + // 4 byte samples, 2 channels + free *= 8; + + if (free < 0) + return 0; + else + return free; + }, + + RWebAudioBufferSize: function() { + return RA.leftBuffer.length / 2; + }, + + RWebAudioEnoughSpace__deps: ['RWebAudioWriteAvail'], + RWebAudioEnoughSpace: function() { + var guess = RA.lastWrite; + var available = _RWebAudioWriteAvail(); + if (RA.nonblock) return true; + if (!guess) return true; + return (guess < available) ? 1 : 0; + } +}; + +autoAddDeps(LibraryRWebAudio, '$RA'); +mergeInto(LibraryManager.library, LibraryRWebAudio); diff --git a/frontend/frontend_emscripten.c b/frontend/frontend_emscripten.c index cfa762ae1f..452c7f41ea 100644 --- a/frontend/frontend_emscripten.c +++ b/frontend/frontend_emscripten.c @@ -18,6 +18,7 @@ #include "../general.h" #include "../conf/config_file.h" #include "../file.h" +#include "../emscripten/RWebAudio.h" #ifdef HAVE_RGUI #include "../frontend/menu/rgui.h" @@ -55,6 +56,11 @@ static void endloop(void) static void mainloop(void) { + if (!RWebAudioEnoughSpace()) + { + return; + } + if (g_extern.system.shutdown) { endloop(); diff --git a/settings.c b/settings.c index 4b9dd32237..682774941a 100644 --- a/settings.c +++ b/settings.c @@ -67,6 +67,8 @@ const char *config_get_default_audio(void) return "ps3"; case AUDIO_WII: return "gx"; + case AUDIO_RWEBAUDIO: + return "rwebaudio"; case AUDIO_NULL: return "null"; default: