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: