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));