first working version with sound

This commit is contained in:
dranke committed 2022-03-04 09:50:10 +01:00
1 parent 35ec5fdb59
commit 85240599e8
5 files changed
+99 -18

No files matched your search

+4 -6
View File
@@ -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
}
(sample & 255) as f32 / 255 as f32 - 0.5
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html><head><meta charset="utf8"><title>uw8-run</title><style>body,canvas,html{background-color:#202024;margin:0;padding:0}body,html{height:100%}body{display:grid;grid-template-rows:1fr}#screen{align-self:center;border:4px solid #303040;image-rendering:pixelated;justify-self:center}#message{background-color:rgba(0,0,0,.4);color:#c64;font:700 12pt sans-serif;padding:8px;position:absolute;width:calc(100% - 16px);z-index:2}@media (min-width:648px) and (min-height:488px){#screen{height:480px;width:640px}}@media (min-width:968px) and (min-height:728px){#screen{height:720px;width:960px}}</style></head><body> <canvas id="screen" width="320" height="240" tabindex="1"> </canvas> <div id="message"></div> </body><script type="module">function A(A){return A&&A.__esModule?A.default:A}let e=function(e,t={}){t.setMessage||(t.setMessage=(A,e)=>{e&&console.log("error: "+e)});let a,B,n,r,o=e.getContext("2d"),i=o.createImageData(320,240),s=t.devkitMode,F=A=>new Uint8Array(A),g=A=>new Uint32Array(A),l=0,Q=null==t.keyboardElement?e:t.keyboardElement;if(Q){let A=A=>{let a,o="keydown"==A.type;switch(A.code){case"ArrowUp":a=1;break;case"ArrowDown":a=2;break;case"ArrowLeft":a=4;break;case"ArrowRight":a=8;break;case"KeyZ":a=16;break;case"KeyX":a=32;break;case"KeyA":a=64;break;case"KeyS":a=128;break;case"KeyR":o&&c(B,!0);break;case"F9":o&&e.toBlob((A=>{f(A,".png")})),A.preventDefault();break;case"F10":o&&function(){if(n)return n.stop(),void(n=null);n=new MediaRecorder(e.captureStream(),{mimeType:"video/webm",videoBitsPerSecond:25e6});let A=[];n.ondataavailable=e=>{A.push(e.data)};let a=t.timerElement;a&&(a.hidden=!1,a.innerText="00:00");function B(){if(r){if(a){let A=Math.floor((Date.now()-r)/1e3);a.innerText=Math.floor(A/60).toString().padStart(2,"0")+":"+(A%60).toString().padStart(2,"0")}setTimeout(B,1e3)}}n.onstop=()=>{a&&(a.hidden=!0),f(new Blob(A,{type:"video/webm"}),".webm")},n.start(),r=Date.now(),setTimeout(B,1e3)}(),A.preventDefault()}o?l|=a:l&=~a};Q.onkeydown=A,Q.onkeyup=A}async function c(n,r){a&&(a(),a=null);let Q=n.byteLength;if(t.setMessage(Q),0==Q)return;B=n;let f=window.location.pathname;if(Q<=1024&&!r){let A="";for(let e of F(n))A+=String.fromCharCode(e);f+="#"+btoa(A),f!=window.location.pathname+window.location.hash&&history.pushState(null,null,f)}e.width=e.width;try{let e={initial:4};s||(e.maximum=4);let r,f=new WebAssembly.Memory({initial:4,maximum:s?16:4}),E=F(f.buffer),I={env:{memory:f}},d=A=>{if(r&&(!s||0!=F(A)[0])){E.set(F(A));let e=r.exports.load_uw8(A.byteLength);A=new ArrayBuffer(e),F(A).set(E.slice(0,e))}return A},M=async A=>(await WebAssembly.instantiate(A,I)).instance,C=async A=>M(d(await(await fetch(A)).arrayBuffer()));r=await C(A("data:application/wasm;base64,AGFzbQEAAAABFQRgAX8Bf2ACf38Bf2ADf39%2FAGAAAAIPAQNlbnYGbWVtb3J5AgAEAwkIAAABAgEAAAMGEAN%2FAUEAC38BQQALfwFBAAsHGQIIbG9hZF91dzgAAAp1bmNvbXByZXNzAAQIAQcKngUIxwEBB39BAC0AAEEBayIBQQBIBEAgAA8LQYDAByAAaiECIAFBAXEEQEEBQYHABxAEIQIFQYDAB0EAIAAQAwtBAEGAhA9BCBADQYiEDyEDQQghBEGBwAchBQNAIAUgAkggAyMATiAFLQAAIAMtAABMcnEEQCAEIAQgBRACIgZqIQQgAyMASCAFLQAAIAMtAABGcQRAIAMgAxABaiEDCyAFIAZqIQUMAQsgAyMASARAIAQgBCADEAIiB2ohBCADIAdqIQMMAQsLIAQLPAEEfyAAIQEDQCACIAFBAWoiAS0AACIEQf8AcSADdHIhAiADQQdqIQMgBEEHdQ0ACyABQQFqIABrIAJqCxIBAX8gACABIAEQASICEAMgAgskAANAIAJBAEoEQCAAIAJBAWsiAmogASACai0AADoAAAwBCwsLqQEBBn8gACQBQQAkAgNAIANBgAE6AICADyADQQFqIgNBgQNIDQALAkADQEEAEAYiBQRAIAQEf0EBBUGAAhAGCwRAQYECEAVBAWsiAkUNAwtBwQIQBSEGA0AgASABIAJrLQAAOgAAIAFBAWohASAGQQFrIgYNAAsFQQEhBwNAIAdBAXQgBxAGciIHQYACSA0ACyABIAc6AAAgAUEBaiEBCyAFIQQMAAsLIAELNAECfwNAIAAgAmoQBgRAIAEgACACakEgahAGIAJ0ciEBIAJBAWohAgwBCwsgAUEBIAJ0cgtxAQR%2FA0AjAkGAIEgEQCMCQQh0IwEtAAByJAIjAUEBaiQBDAELCyMCQf8BcSICIAAtAICADyIBIwJBCHUiA2xBgAIgAWsgA2wgAWsgAiABSCIEG2okAiAAIAFBByAEQYECbGogAWtBBHVqOgCAgA8gBAsMAEEAQYCEDxAEJAALC9gDAQBBAAvRAwF%2F%2F9fTKCTONmi84%2BnrbVSOH%2Fc6vSf%2BD4d9SJNBc3FX7QfUHxwdZoJ2gbrkOxXMlCFQohTpOfrqH2lON%2BKLJNVfU5Nu4GOO2WMojT29uFcnCJS3%2B1hbxaL%2Fh9lmLYb3zvWsMJ%2BjBoF3XH%2BX3PiQRGj3lSO6d6ROB1tLHAjBvc8KRcJC8ezAHO2KxgN0C60LhqDnEMNRGkiZIGd7vFakdzsTK52rXTWTEF%2BRtXf5UnppLVtDQaKioHW9qgBgNM8z076axgmmzZdgskOHHn3MVWuglavoCFh4dRFW5XmuZwoBT3HKAX%2F6xPFRb56MQoyggxfIPc7Po49uFG7BLqNaZm%2FVzLkVsqcnLbkOu67BrqPS6In7L9Zomf6E5PQp%2FK7DKV5%2FS%2Be15wnC3zqP1Bj6xLe3xzckXir7SHPZsep5NtRy%2FGpcseCHgPNHMos7xPQKdDO%2BAXqvMZuNdOwFZm6TmQ3vmMOdSSpn62axAi0QlRgoxhrA%2F42mIF6TkGbDAElqkMfF0EyKfip%2B2EPJtH7AksMALac%2BkjT9fyIQRGiD5HMZwCTKLxBLX3A07qdkKrXlR5i8pYOXZxAX4vyy9BKV8RfUfvEuqyNXXnBg4e%2BB%2FPnR%2Bg%3D%3D"));for(let A of["acos","asin","atan","atan2","cos","exp","log","sin","tan","pow"])I.env[A]=Math[A];for(let A=9;A<64;++A)I.env["reserved"+A]=()=>{};for(let A=0;A<16;++A)I.env["g_reserved"+A]=0;n=d(n);let u=await C(A("data:;base64,AnevpqDvapimRNPOGhqee07n%2Bv1yTm1n5XES6y7%2BqPUQFqw5livsDo%2Fkram7E1zzZg6qmLzXsUswfnD9AZjEuJOrUjm2TzbQ%2BWO1hv6bvL0OA5NIRBHdnLCD2DJC5a7Jh%2Bn20qczMOvGlUk700JPiog%2BRZrdnPfnjdZrltFOZBk1oyEomoNCnRBIG%2Bd2U%2BJ%2FfcPD%2FV5KVii8nTeMgUfwVbKV79ZcQGuE4VPTr0nA80eJso3NQUtKG8IWxj9LEus4f2c0dEpDNmDI9qq9a12KBBCrzzqR046Line truncated
<!doctype html><html><head><meta charset="utf8"><title>uw8-run</title><style>body,canvas,html{background-color:#202024;margin:0;padding:0}body,html{height:100%}body{display:grid;grid-template-rows:1fr}#screen{align-self:center;border:4px solid #303040;image-rendering:pixelated;justify-self:center}#message{background-color:rgba(0,0,0,.4);color:#c64;font:700 12pt sans-serif;padding:8px;position:absolute;width:calc(100% - 16px);z-index:2}@media (min-width:648px) and (min-height:488px){#screen{height:480px;width:640px}}@media (min-width:968px) and (min-height:728px){#screen{height:720px;width:960px}}</style></head><body> <canvas id="screen" width="320" height="240" tabindex="1"> </canvas> <div id="message"></div> </body><script type="module">function e(e){return e&&e.__esModule?e.default:e}class t extends AudioWorkletNode{constructor(e){super(e,"apu",{outputChannelCount:[2]})}}let A=e=>new Uint8Array(e),a=e=>new Uint32Array(e);let B=function(B,n={}){n.setMessage||(n.setMessage=(e,t)=>{t&&console.log("error: "+t)});let r,o,s,i,l=B.getContext("2d"),F=l.createImageData(320,240),c=n.devkitMode,g=0,d=null==n.keyboardElement?B:n.keyboardElement;if(d){let e=e=>{let t,A="keydown"==e.type;switch(e.code){case"ArrowUp":t=1;break;case"ArrowDown":t=2;break;case"ArrowLeft":t=4;break;case"ArrowRight":t=8;break;case"KeyZ":t=16;break;case"KeyX":t=32;break;case"KeyA":t=64;break;case"KeyS":t=128;break;case"KeyR":A&&f(o,!0);break;case"F9":A&&B.toBlob((e=>{D(e,".png")})),e.preventDefault();break;case"F10":A&&function(){if(s)return s.stop(),void(s=null);s=new MediaRecorder(B.captureStream(),{mimeType:"video/webm",videoBitsPerSecond:25e6});let e=[];s.ondataavailable=t=>{e.push(t.data)};let t=n.timerElement;t&&(t.hidden=!1,t.innerText="00:00");function A(){if(i){if(t){let e=Math.floor((Date.now()-i)/1e3);t.innerText=Math.floor(e/60).toString().padStart(2,"0")+":"+(e%60).toString().padStart(2,"0")}setTimeout(A,1e3)}}s.onstop=()=>{t&&(t.hidden=!0),D(new Blob(e,{type:"video/webm"}),".webm")},s.start(),i=Date.now(),setTimeout(A,1e3)}(),e.preventDefault()}A?g|=t:g&=~t};d.onkeydown=e,d.onkeyup=e}async function f(s,i){r&&(r(),r=null);let d=new AudioContext({sampleRate:44100}),D=!0;r=()=>{d.close(),D=!1},await d.audioWorklet.addModule(e("data:application/javascript,class%20e%20extends%20AudioWorkletProcessor%7Bconstructor%28%29%7Bsuper%28%29%2Cthis.sampleIndex%3D0%2Cthis.port.onmessage%3De%3D%3E%7Bthis.load%28e.data%5B0%5D%2Ce.data%5B1%5D%29%7D%7Dasync%20load%28e%2Cs%29%7Blet%20t%3D%7Benv%3A%7Bmemory%3Anew%20WebAssembly.Memory%28%7Binitial%3A4%2Cmaximum%3A4%7D%29%7D%7D%3Bfor%28let%20e%20of%5B%22acos%22%2C%22asin%22%2C%22atan%22%2C%22atan2%22%2C%22cos%22%2C%22exp%22%2C%22log%22%2C%22sin%22%2C%22tan%22%2C%22pow%22%5D%29t.env%5Be%5D%3DMath%5Be%5D%3Bfor%28let%20e%3D9%3Be%3C64%3B%2B%2Be%29t.env%5B%22reserved%22%2Be%5D%3D%28%29%3D%3E%7B%7D%3Bfor%28let%20e%3D0%3Be%3C16%3B%2B%2Be%29t.env%5B%22g_reserved%22%2Be%5D%3D0%3Blet%20a%3Dasync%20e%3D%3E%28await%20WebAssembly.instantiate%28e%2Ct%29%29.instance%2Cn%3Dawait%20a%28e%29%3Bfor%28let%20e%20in%20n.exports%29t.env%5Be%5D%3Dn.exports%5Be%5D%3Blet%20o%3Dawait%20a%28s%29%3Bthis.snd%3Do.exports.snd%7Dprocess%28e%2Cs%2Ct%29%7Bif%28this.snd%29%7Blet%20e%3Ds%5B0%5D%2Ct%3Dthis.sampleIndex%2Ca%3De%5B0%5D.length%3Bfor%28let%20s%3D0%3Bs%3Ca%3B%2B%2Bs%29e%5B0%5D%5Bs%5D%3Dthis.snd%28t%2B%2B%29%2Ce%5B1%5D%5Bs%5D%3Dthis.snd%28t%2B%2B%29%3Bthis.sampleIndex%3D4294967295%26t%7Dreturn%210%7D%7DregisterProcessor%28%22apu%22%2Ce%29%3B"));let Q=new t(d),C=s.byteLength;if(n.setMessage(C),0==C)return;o=s;let E=window.location.pathname;if(C<=1024&&!i){let e="";for(let t of A(s))e+=String.fromCharCode(t);E+="#"+btoa(e),E!=window.location.pathname+window.location.hash&&history.pushState(null,null,E)}B.width=B.width;try{let t={initial:4};c||(t.maximum=4);let B,r=new WebAssembly.Memory(t),i=A(r.buffer),E={env:{memory:r}},I=e=>{if(B&&(!c||0!=A(e)[0])){i.set(A(e));let t=B.exports.load_uw8(e.byteLength);e=new ArrayBuffer(t),A(e).set(i.slice(0,t))}return e},u=async e=>(await WebAssembly.instantiate(e,E)).instance,M=async e=>I(await(await fetch(e)).arrayBuffer());B=await u(await M(e("data:application/wasm;base64,AGFzbQEAAAABFQRgAX8Bf2ACf38Bf2ADf39%2FAGAAAAIPAQNlbnYGbWVtb3J5AgAEAwkIAAABAgEAAAMGEAN%2FAUEAC38BQQALfwFBAAsHGQIIbG9hZF91dzgAAAp1bmNvbXByZXNzAAQIAQcKngUIxwEBB39BAC0AAEEBayIBQQBIBEAgAA8LQYDAByAAaiECIAFBAXEEQEEBQYHABxAEIQIFQYDAB0EAIAAQAwtBAEGAhA9BCBADQYiEDyEDQQghBEGBwAchBQNAIAUgAkggAyMATiAFLQAAIAMtAABMcnEEQCAEIAQgBRACIgZqIQQgAyMASCAFLQAAIAMtAABGcQRAIAMgAxABaiEDCyAFIAZqIQUMAQsgAyMASARAIAQgBCADEAIiB2ohBCADIAdqIQMMAQsLIAQLPAEEfyAAIQEDQCACIAFBAWoiAS0AACIEQf8AcSADdHIhAiADQQdqIQMgBEEHdQ0ACyABQQFqIABrIAJqCxIBAX8gACABIAEQASICEAMgAgskAANAIAJBAEoEQCAAIAJBAWsiAmogASACai0AADoAAAwBCwsLqQEBBn8gACQBQQAkAgNAIANBgAE6AICADyADQQFqIgNBgQNIDQALAkADQEEAEAYiBQRAIAQEf0EBBUGAAhAGCwRAQYECEAVBAWsiAkUNAwtBwQIQBSEGA0AgASABIAJrLQAAOgAAIAFBAWohASAGQQFrIgYNAAsFQQEhBwNAIAdBAXQgBxAGciIHQYACSA0ACyABIAc6AAAgAUEBaiEBCyAFIQQMAAsLIAELNAECfwNAIAAgAmoQBgRAIAEgACACakEgaLine truncated
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
rm -rf .parcel-cache && yarn parcel src/index.html
+60
View File
@@ -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);
+32 -11
View File
@@ -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));