diff --git a/examples/curlywas/bytebeat.cwa b/examples/curlywas/bytebeat.cwa
index 8bc5b1e..cce8436 100644
--- a/examples/curlywas/bytebeat.cwa
+++ b/examples/curlywas/bytebeat.cwa
@@ -1,10 +1,8 @@
include "../include/microw8-api.cwa"
-export fn upd() {}
-
export fn snd(index: i32) -> f32 {
- let inline saw = index & 255;
- let inline env = (-index #>> 9) & 255;
+ let inline saw = index;
+ let inline env = (-index #>> 9);
let inline sample = saw & env;
- sample as f32 / 255 as f32 - 0.5
-}
\ No newline at end of file
+ (sample & 255) as f32 / 255 as f32 - 0.5
+}
diff --git a/src/run-web.html b/src/run-web.html
index 66c256d..dfb35a2 100644
--- a/src/run-web.html
+++ b/src/run-web.html
@@ -1 +1 @@
-
uw8-run
\ No newline at end of file
+uw8-run
\ No newline at end of file
diff --git a/web/run b/web/run
new file mode 100755
index 0000000..624d675
--- /dev/null
+++ b/web/run
@@ -0,0 +1,2 @@
+#!/bin/bash
+rm -rf .parcel-cache && yarn parcel src/index.html
diff --git a/web/src/audiolet.js b/web/src/audiolet.js
new file mode 100644
index 0000000..0cf4786
--- /dev/null
+++ b/web/src/audiolet.js
@@ -0,0 +1,60 @@
+class APU extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ this.sampleIndex = 0;
+ this.port.onmessage = (ev) => {
+ this.load(ev.data[0], ev.data[1]);
+ };
+ }
+
+ async load(platform_data, data) {
+ let memory = new WebAssembly.Memory({ initial: 4, maximum: 4 });
+
+ let importObject = {
+ env: {
+ memory
+ },
+ };
+
+ for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
+ importObject.env[n] = Math[n];
+ }
+
+ for (let i = 9; i < 64; ++i) {
+ importObject.env['reserved' + i] = () => { };
+ }
+
+ for (let i = 0; i < 16; ++i) {
+ importObject.env['g_reserved' + i] = 0;
+ }
+
+ let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
+
+ let platform_instance = await instantiate(platform_data);
+
+ for (let name in platform_instance.exports) {
+ importObject.env[name] = platform_instance.exports[name]
+ }
+
+ let instance = await instantiate(data);
+
+ this.snd = instance.exports.snd;
+ }
+
+ process(inputs, outputs, parameters) {
+ if(this.snd) {
+ let channels = outputs[0];
+ let index = this.sampleIndex;
+ let numSamples = channels[0].length;
+ for(let i = 0; i < numSamples; ++i) {
+ channels[0][i] = this.snd(index++);
+ channels[1][i] = this.snd(index++);
+ }
+ this.sampleIndex = index & 0xffffffff;
+ }
+
+ return true;
+ }
+}
+
+registerProcessor('apu', APU);
\ No newline at end of file
diff --git a/web/src/microw8.js b/web/src/microw8.js
index 26f347c..a8d3266 100644
--- a/web/src/microw8.js
+++ b/web/src/microw8.js
@@ -1,5 +1,15 @@
import loaderUrl from "data-url:../../platform/bin/loader.wasm";
import platformUrl from "data-url:../../platform/bin/platform.uw8";
+import audioWorkletUrl from "data-url:./audiolet.js";
+
+class AudioNode extends AudioWorkletNode {
+ constructor(context) {
+ super(context, 'apu', {outputChannelCount: [2]});
+ }
+}
+
+let U8 = (d) => new Uint8Array(d);
+let U32 = (d) => new Uint32Array(d);
export default function MicroW8(screen, config = {}) {
if(!config.setMessage) {
@@ -18,9 +28,6 @@ export default function MicroW8(screen, config = {}) {
let currentData;
- let U8 = (d) => new Uint8Array(d);
- let U32 = (d) => new Uint32Array(d);
-
let pad = 0;
let keyboardElement = config.keyboardElement == undefined ? screen : config.keyboardElement;
if(keyboardElement) {
@@ -90,6 +97,16 @@ export default function MicroW8(screen, config = {}) {
cancelFunction = null;
}
+ let audioContext = new AudioContext({sampleRate: 44100});
+ let keepRunning = true;
+ cancelFunction = () => {
+ audioContext.close();
+ keepRunning = false;
+ }
+
+ await audioContext.audioWorklet.addModule(audioWorkletUrl);
+ let audioNode = new AudioNode(audioContext);
+
let cartridgeSize = data.byteLength;
config.setMessage(cartridgeSize);
@@ -119,7 +136,7 @@ export default function MicroW8(screen, config = {}) {
if(!devkitMode) {
memSize.maximum = 4;
}
- let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 });
+ let memory = new WebAssembly.Memory(memSize);
let memU8 = U8(memory.buffer);
let importObject = {
@@ -142,9 +159,9 @@ export default function MicroW8(screen, config = {}) {
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
- let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer()));
+ let loadModuleURL = async (url) => loadModuleData(await (await fetch(url)).arrayBuffer());
- loader = await loadModuleURL(loaderUrl);
+ loader = await instantiate(await loadModuleURL(loaderUrl));
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
importObject.env[n] = Math[n];
@@ -160,7 +177,10 @@ export default function MicroW8(screen, config = {}) {
data = loadModuleData(data);
- let platform_instance = await loadModuleURL(platformUrl);
+ let platform_data = await loadModuleURL(platformUrl);
+ audioNode.port.postMessage([platform_data, data]);
+
+ let platform_instance = await instantiate(platform_data);
for (let name in platform_instance.exports) {
importObject.env[name] = platform_instance.exports[name]
@@ -172,11 +192,10 @@ export default function MicroW8(screen, config = {}) {
let startTime = Date.now();
- let keepRunning = true;
- cancelFunction = () => keepRunning = false;
-
const timePerFrame = 1000 / 60;
let nextFrame = startTime;
+
+ audioNode.connect(audioContext.destination);
function mainloop() {
if (!keepRunning) {
@@ -216,7 +235,9 @@ export default function MicroW8(screen, config = {}) {
let u32Mem = U32(memory.buffer);
u32Mem[16] = now - startTime;
u32Mem[17] = pad | gamepad;
- instance.exports.upd();
+ if(instance.exports.upd) {
+ instance.exports.upd();
+ }
platform_instance.exports.endFrame();
let palette = U32(memory.buffer.slice(0x13000, 0x13000 + 1024));