implement sound in native runtime

This commit is contained in:
dranke committed 2022-04-17 22:16:17 +02:00
1 parent 893158e136
commit 2839fe5be4
4 files changed
+278 -12

No files matched your search

Generated
+163 -2
View File
@@ -35,6 +35,28 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alsa"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.12.1" version = "0.12.1"
@@ -83,9 +105,9 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
@@ -314,6 +336,56 @@ dependencies = [
"tiny-keccak", "tiny-keccak",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "coreaudio-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88"
dependencies = [
"bitflags",
"coreaudio-sys",
]
[[package]]
name = "coreaudio-sys"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca4679a59dbd8c15f064c012dfe8c1163b9453224238b59bb9328c142b8b248b"
dependencies = [
"bindgen",
]
[[package]]
name = "cpal"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116"
dependencies = [
"alsa",
"core-foundation-sys",
"coreaudio-rs",
"jni",
"js-sys",
"lazy_static",
"libc",
"mach",
"ndk",
"ndk-glue",
"nix",
"oboe",
"parking_lot",
"stdweb",
"thiserror",
"web-sys",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "cpp_demangle" name = "cpp_demangle"
version = "0.3.5" version = "0.3.5"
@@ -1148,6 +1220,16 @@ version = "0.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7"
[[package]]
name = "lock_api"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.14"
@@ -1384,6 +1466,19 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@@ -1421,6 +1516,17 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@@ -1472,6 +1578,29 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "oboe"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2463c8f2e19b4e0d0710a21f8e4011501ff28db1c95d7a5482a553b2100502d2"
dependencies = [
"jni",
"ndk",
"ndk-glue",
"num-derive",
"num-traits",
"oboe-sys",
]
[[package]]
name = "oboe-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.9.0" version = "1.9.0"
@@ -1499,6 +1628,31 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.6" version = "1.0.6"
@@ -1990,6 +2144,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "stdweb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
@@ -2334,6 +2494,7 @@ version = "0.1.2"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"anyhow", "anyhow",
"cpal",
"curlywas", "curlywas",
"minifb", "minifb",
"notify", "notify",
+1
View File
@@ -25,3 +25,4 @@ tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true } tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
webbrowser = { version = "0.6.0", optional = true } webbrowser = { version = "0.6.0", optional = true }
ansi_term = "0.12.1" ansi_term = "0.12.1"
cpal = "0.13.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 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,o={}){o.setMessage||(o.setMessage=(e,t)=>{t&&console.log("error: "+t)});let n,s,r,i,l=B.getContext("2d"),F=l.createImageData(320,240),d=o.devkitMode,D=0,c=null==o.keyboardElement?B:o.keyboardElement;if(c){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(s,!0);break;case"F9":A&&B.toBlob((e=>{p(e,".png")})),e.preventDefault();break;case"F10":A&&function(){if(r)return r.stop(),void(r=null);r=new MediaRecorder(B.captureStream(),{mimeType:"video/webm",videoBitsPerSecond:25e6});let e=[];r.ondataavailable=t=>{e.push(t.data)};let t=o.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)}}r.onstop=()=>{t&&(t.hidden=!0),p(new Blob(e,{type:"video/webm"}),".webm")},r.start(),i=Date.now(),setTimeout(A,1e3)}(),e.preventDefault()}A?D|=t:D&=~t};c.onkeydown=e,c.onkeyup=e}async function f(r,i){n&&(n(),n=null);let c=new AudioContext({sampleRate:44100}),p=!0;n=()=>{c.close(),p=!1};let g=r.byteLength;if(o.setMessage(g),0==g)return;await c.audioWorklet.addModule(e("data:application/javascript,let%20e%3D%28...e%29%3D%3Enew%20Uint8Array%28...e%29%3Bclass%20s%20extends%20AudioWorkletProcessor%7Bconstructor%28%29%7Bsuper%28%29%2Cthis.sampleIndex%3D0%2Cthis.port.onmessage%3Ds%3D%3E%7Bthis.memory%3Fe%28this.memory.buffer%2C80%2C32%29.set%28e%28s.data%29%29%3Athis.load%28s.data%5B0%5D%2Cs.data%5B1%5D%29%7D%7Dasync%20load%28e%2Cs%29%7Blet%20t%3Dnew%20WebAssembly.Memory%28%7Binitial%3A4%2Cmaximum%3A4%7D%29%2Ca%3D%7Benv%3A%7Bmemory%3At%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%29a.env%5Be%5D%3DMath%5Be%5D%3Bfor%28let%20e%3D9%3Be%3C64%3B%2B%2Be%29a.env%5B%22reserved%22%2Be%5D%3D%28%29%3D%3E%7B%7D%3Bfor%28let%20e%3D0%3Be%3C16%3B%2B%2Be%29a.env%5B%22g_reserved%22%2Be%5D%3D0%3Blet%20r%3Dasync%20e%3D%3E%28await%20WebAssembly.instantiate%28e%2Ca%29%29.instance%2Cn%3Dawait%20r%28e%29%3Bfor%28let%20e%20in%20n.exports%29a.env%5Be%5D%3Dn.exports%5Be%5D%3Blet%20o%3Dawait%20r%28s%29%3Bthis.memory%3Dt%2Cthis.snd%3Do.exports.snd%7C%7Cn.exports.gesSnd%2Cthis.port.postMessage%282%29%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%2Cs%29%3B"));let Q,C=new t(c),m=0,u=new Promise((e=>Q=e)),E=e=>{m|=e,3==m&&Q&&(Q(!0),Q=null)};C.port.onmessage=e=>E(e.data);let w=()=>{"suspended"==c.state?(o.startButton&&(o.startButton.style="",B.style="display:none"),(o.startButton||B).onclick=()=>{c.resume()}):(o.startButton&&(o.startButton.style="display:none",B.style=""),E(1))};c.onstatechange=w,w(),s=r;let I=window.location.pathname;if(g<=1024&&!i){let e="";for(let t of A(r))e+=String.fromCharCode(t);I+="#"+btoa(e),I!=window.location.pathname+window.location.hash&&history.pushState(null,null,I)}B.width=B.width;try{let t={initial:4};d||(t.maximum=4);let B,n=new WebAssembly.Memory(t),i=A(n.buffer),Q={env:{memory:n}},m=e=>{if(B&&(!d||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},E=async e=>(await WebAssembly.instantiate(e,Q)).instance,w=async e=>m(await(await fetch(e)).arrayBuffer());B=await E(await w(e("data:application/wasm;base64,AGFzbQEAAAABFQRgAX8Bf2ACf38Bf2ADf39%2FAGAAAAIPAQNlbnYGbWVtb3J5AgAEAwkIAAABAgEAAAMGEAN%2FAUEAC38BQQALfwFBAAsHGQIIbG9hZF91dzgAAAp1bmNvbXByZXNzAAQIAQcKngUIxwEBB39BAC0AAEEBayIBQQBIBEAgAA8LQYDAByAAaiECIAFBAXEEQEEBQYHABxAEIQIFQYDAB0EAIAAQAwtBAEGAhA9BCBADQYiEDyEDQQghBEGBwAchBQNAIAUgAkLine 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 n=function(n,B={}){B.setMessage||(B.setMessage=(e,t)=>{t&&console.log("error: "+t)});let o,s,r,i,l=n.getContext("2d"),d=l.createImageData(320,240),F=B.devkitMode,c=0,D=null==B.keyboardElement?n:B.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(s,!0);break;case"F9":A&&n.toBlob((e=>{p(e,".png")})),e.preventDefault();break;case"F10":A&&function(){if(r)return r.stop(),void(r=null);r=new MediaRecorder(n.captureStream(),{mimeType:"video/webm",videoBitsPerSecond:25e6});let e=[];r.ondataavailable=t=>{e.push(t.data)};let t=B.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)}}r.onstop=()=>{t&&(t.hidden=!0),p(new Blob(e,{type:"video/webm"}),".webm")},r.start(),i=Date.now(),setTimeout(A,1e3)}(),e.preventDefault()}A?c|=t:c&=~t};D.onkeydown=e,D.onkeyup=e}async function f(r,i){o&&(o(),o=null);let D=new AudioContext({sampleRate:44100}),p=!0,g=new AbortController;o=()=>{D.close(),p=!1,g.abort()};let Q=r.byteLength;if(B.setMessage(Q),0==Q)return;await D.audioWorklet.addModule(e("data:application/javascript,let%20e%3D%28...e%29%3D%3Enew%20Uint8Array%28...e%29%3Bclass%20s%20extends%20AudioWorkletProcessor%7Bconstructor%28%29%7Bsuper%28%29%2Cthis.sampleIndex%3D0%2Cthis.port.onmessage%3Ds%3D%3E%7Bthis.memory%3Fe%28this.memory.buffer%2C80%2C32%29.set%28e%28s.data%29%29%3Athis.load%28s.data%5B0%5D%2Cs.data%5B1%5D%29%7D%7Dasync%20load%28e%2Cs%29%7Blet%20t%3Dnew%20WebAssembly.Memory%28%7Binitial%3A4%2Cmaximum%3A4%7D%29%2Ca%3D%7Benv%3A%7Bmemory%3At%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%29a.env%5Be%5D%3DMath%5Be%5D%3Bfor%28let%20e%3D9%3Be%3C64%3B%2B%2Be%29a.env%5B%22reserved%22%2Be%5D%3D%28%29%3D%3E%7B%7D%3Bfor%28let%20e%3D0%3Be%3C16%3B%2B%2Be%29a.env%5B%22g_reserved%22%2Be%5D%3D0%3Blet%20r%3Dasync%20e%3D%3E%28await%20WebAssembly.instantiate%28e%2Ca%29%29.instance%2Cn%3Dawait%20r%28e%29%3Bfor%28let%20e%20in%20n.exports%29a.env%5Be%5D%3Dn.exports%5Be%5D%3Blet%20o%3Dawait%20r%28s%29%3Bthis.memory%3Dt%2Cthis.snd%3Do.exports.snd%7C%7Cn.exports.gesSnd%2Cthis.port.postMessage%282%29%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%2Cs%29%3B"));let m,C=new t(D),u=0,w=new Promise((e=>m=e)),E=e=>{u|=e,3==u&&m&&(m(!0),m=null)};C.port.onmessage=e=>E(e.data);let I=()=>{"suspended"==D.state?(B.startButton&&(B.startButton.style="",n.style="display:none"),(B.startButton||n).onclick=()=>{D.resume()}):(B.startButton&&(B.startButton.style="display:none",n.style=""),E(1))};D.onstatechange=I,I(),s=r;let b=window.location.pathname;if(Q<=1024&&!i){let e="";for(let t of A(r))e+=String.fromCharCode(t);b+="#"+btoa(e),b!=window.location.pathname+window.location.hash&&history.pushState(null,null,b)}n.width=n.width;try{let t={initial:4};F||(t.maximum=4);let n,o=new WebAssembly.Memory(t),i=A(o.buffer),m={env:{memory:o}},u=e=>{if(n&&(!F||0!=A(e)[0])){i.set(A(e));let t=n.exports.load_uw8(e.byteLength);e=new ArrayBuffer(t),A(e).set(i.slice(0,t))}return e},E=async e=>(await WebAssembly.instantiate(e,m)).instance,I=async e=>u(await(await fetch(e)).arrayBuffer());n=await E(await I(e("data:application/wasm;base64,AGFzbQEAAAABFQRgAX8Bf2ACf38Bf2ADf39%2FAGAAAAIPAQNlbnYGbWVtb3J5AgAEAwkIAAABAgEAAAMGEAN%2FAUEAC38BQQALfwFBAAsHGQIIbG9hZF91dzgAAAp1bmNvbXByZXNzAAQIAQcKngUIxwEBB39BAC0AAEEBayIBQQBIBEAgAA8LQYDAByAAaiECIAFBAXEEQEEBQYHABxAEIQIFQYDAB0EAIAAQAwtBAEGAhA9BCBLine truncated
+113 -9
View File
@@ -1,8 +1,9 @@
use std::sync::{Arc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use std::{thread, time::Instant}; use std::{thread, time::Instant};
use anyhow::Result; use anyhow::{anyhow, Result};
use cpal::traits::*;
use minifb::{Key, Window, WindowOptions}; use minifb::{Key, Window, WindowOptions};
use wasmtime::{ use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
@@ -32,10 +33,11 @@ struct UW8Instance {
store: Store<()>, store: Store<()>,
memory: Memory, memory: Memory,
end_frame: TypedFunc<(), ()>, end_frame: TypedFunc<(), ()>,
update: TypedFunc<(), ()>, update: Option<TypedFunc<(), ()>>,
start_time: Instant, start_time: Instant,
module: Vec<u8>, module: Vec<u8>,
watchdog: Arc<Mutex<UW8WatchDog>>, watchdog: Arc<Mutex<UW8WatchDog>>,
sound: Uw8Sound,
} }
impl Drop for UW8Instance { impl Drop for UW8Instance {
@@ -86,6 +88,11 @@ impl MicroW8 {
} }
} }
struct Uw8Sound {
stream: cpal::Stream,
tx: mpsc::SyncSender<[u8; 32]>,
}
impl super::Runtime for MicroW8 { impl super::Runtime for MicroW8 {
fn is_open(&self) -> bool { fn is_open(&self) -> bool {
self.window.is_open() && !self.window.is_key_down(Key::Escape) self.window.is_open() && !self.window.is_key_down(Key::Escape)
@@ -119,6 +126,10 @@ impl super::Runtime for MicroW8 {
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize; let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?; let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
fn add_native_functions(
linker: &mut wasmtime::Linker<()>,
store: &mut wasmtime::Store<()>,
) -> Result<()> {
linker.func_wrap("env", "acos", |v: f32| v.acos())?; linker.func_wrap("env", "acos", |v: f32| v.acos())?;
linker.func_wrap("env", "asin", |v: f32| v.asin())?; linker.func_wrap("env", "asin", |v: f32| v.asin())?;
linker.func_wrap("env", "atan", |v: f32| v.atan())?; linker.func_wrap("env", "atan", |v: f32| v.atan())?;
@@ -129,7 +140,7 @@ impl super::Runtime for MicroW8 {
linker.func_wrap("env", "sin", |v: f32| v.sin())?; linker.func_wrap("env", "sin", |v: f32| v.sin())?;
linker.func_wrap("env", "tan", |v: f32| v.tan())?; linker.func_wrap("env", "tan", |v: f32| v.tan())?;
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?; linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
for i in 9..64 { for i in 10..64 {
linker.func_wrap("env", &format!("reserved{}", i), || {})?; linker.func_wrap("env", &format!("reserved{}", i), || {})?;
} }
for i in 0..16 { for i in 0..16 {
@@ -137,16 +148,26 @@ impl super::Runtime for MicroW8 {
"env", "env",
&format!("g_reserved{}", i), &format!("g_reserved{}", i),
wasmtime::Global::new( wasmtime::Global::new(
&mut store, &mut *store,
GlobalType::new(ValType::I32, Mutability::Const), GlobalType::new(ValType::I32, Mutability::Const),
0.into(), 0.into(),
)?, )?,
)?; )?;
} }
let platform_instance = linker.instantiate(&mut store, &platform_module)?; Ok(())
}
for export in platform_instance.exports(&mut store) { add_native_functions(&mut linker, &mut store)?;
fn instantiate_platform(
linker: &mut wasmtime::Linker<()>,
store: &mut wasmtime::Store<()>,
platform_module: &wasmtime::Module,
) -> Result<wasmtime::Instance> {
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
for export in platform_instance.exports(&mut *store) {
linker.define( linker.define(
"env", "env",
export.name(), export.name(),
@@ -156,6 +177,11 @@ impl super::Runtime for MicroW8 {
)?; )?;
} }
Ok(platform_instance)
}
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
let watchdog = Arc::new(Mutex::new(UW8WatchDog { let watchdog = Arc::new(Mutex::new(UW8WatchDog {
interupt: store.interrupt_handle()?, interupt: store.interrupt_handle()?,
timeout: self.timeout, timeout: self.timeout,
@@ -187,7 +213,77 @@ impl super::Runtime for MicroW8 {
watchdog.timeout = 0; watchdog.timeout = 0;
} }
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?; let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?; let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
let sound = {
let mut store = wasmtime::Store::new(&self.engine, ());
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(&self.engine);
linker.define("env", "memory", memory)?;
add_native_functions(&mut linker, &mut store)?;
let platform_instance =
instantiate_platform(&mut linker, &mut store, &platform_module)?;
let instance = linker.instantiate(&mut store, &module)?;
let snd = instance
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
.or_else(|_| {
platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd")
})?;
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or_else(|| anyhow!("No audio output device available"))?;
let config = device
.supported_output_configs()?
.find(|config| {
config.min_sample_rate().0 <= 44100
&& config.max_sample_rate().0 >= 44100
&& config.channels() == 2
&& config.sample_format() == cpal::SampleFormat::F32
})
.ok_or_else(|| anyhow!("Could not find 44.1kHz float config"))?;
let config = config.with_sample_rate(cpal::SampleRate(44100));
let buffer_size = match *config.buffer_size() {
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
cpal::SupportedBufferSize::Range { min, max } => {
cpal::BufferSize::Fixed(256.max(min).min(max))
}
};
let config = cpal::StreamConfig {
buffer_size,
..config.config()
};
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
let mut sample_index = 0;
let stream = {
device.build_output_stream(
&config,
move |buffer: &mut [f32], _| {
if let Ok(regs) = rx.try_recv() {
memory.write(&mut store, 80, &regs).unwrap();
}
for v in buffer {
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
sample_index = sample_index.wrapping_add(1);
}
},
move |err| {
dbg!(err);
},
)?
};
Uw8Sound { stream, tx }
};
sound.stream.play()?;
self.instance = Some(UW8Instance { self.instance = Some(UW8Instance {
store, store,
@@ -197,6 +293,7 @@ impl super::Runtime for MicroW8 {
start_time: Instant::now(), start_time: Instant::now(),
module: module_data.into(), module: module_data.into(),
watchdog, watchdog,
sound,
}); });
Ok(()) Ok(())
@@ -227,13 +324,20 @@ impl super::Runtime for MicroW8 {
if let Ok(mut watchdog) = instance.watchdog.lock() { if let Ok(mut watchdog) = instance.watchdog.lock() {
watchdog.timeout = self.timeout; watchdog.timeout = self.timeout;
} }
result = instance.update.call(&mut instance.store, ()); if let Some(ref update) = instance.update {
result = update.call(&mut instance.store, ());
}
if let Ok(mut watchdog) = instance.watchdog.lock() { if let Ok(mut watchdog) = instance.watchdog.lock() {
watchdog.timeout = 0; watchdog.timeout = 0;
} }
instance.end_frame.call(&mut instance.store, ())?; instance.end_frame.call(&mut instance.store, ())?;
let memory = instance.memory.data(&instance.store); let memory = instance.memory.data(&instance.store);
let mut sound_regs = [0u8; 32];
sound_regs.copy_from_slice(&memory[80..112]);
instance.sound.tx.send(sound_regs)?;
let framebuffer = &memory[120..(120 + 320 * 240)]; let framebuffer = &memory[120..(120 + 320 * 240)];
let palette = &memory[0x13000..]; let palette = &memory[0x13000..];
for (i, &color_index) in framebuffer.iter().enumerate() { for (i, &color_index) in framebuffer.iter().enumerate() {