diff --git a/Cargo.lock b/Cargo.lock index e3cbbe5..0d12a29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,6 +35,28 @@ dependencies = [ "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]] name = "ansi_term" version = "0.12.1" @@ -83,9 +105,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" @@ -314,6 +336,56 @@ dependencies = [ "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]] name = "cpp_demangle" version = "0.3.5" @@ -1148,6 +1220,16 @@ version = "0.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "log" version = "0.4.14" @@ -1384,6 +1466,19 @@ dependencies = [ "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]] name = "nom" version = "5.1.2" @@ -1421,6 +1516,17 @@ dependencies = [ "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]] name = "num-traits" version = "0.2.14" @@ -1472,6 +1578,29 @@ dependencies = [ "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]] name = "once_cell" version = "1.9.0" @@ -1499,6 +1628,31 @@ dependencies = [ "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]] name = "paste" version = "1.0.6" @@ -1990,6 +2144,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stdweb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" + [[package]] name = "strsim" version = "0.8.0" @@ -2334,6 +2494,7 @@ version = "0.1.2" dependencies = [ "ansi_term", "anyhow", + "cpal", "curlywas", "minifb", "notify", diff --git a/Cargo.toml b/Cargo.toml index b3abb91..71c3d03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,5 @@ warp = { version = "0.3.2", optional = true } tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true } tokio-stream = { version = "0.1.8", features = ["sync"], optional = true } webbrowser = { version = "0.6.0", optional = true } -ansi_term = "0.12.1" \ No newline at end of file +ansi_term = "0.12.1" +cpal = "0.13.5" \ No newline at end of file diff --git a/src/run-web.html b/src/run-web.html index cfc1614..c8c4956 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/src/run_native.rs b/src/run_native.rs index d310aa8..eb96463 100644 --- a/src/run_native.rs +++ b/src/run_native.rs @@ -1,8 +1,9 @@ -use std::sync::{Arc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; use std::{thread, time::Instant}; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use cpal::traits::*; use minifb::{Key, Window, WindowOptions}; use wasmtime::{ Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, @@ -32,10 +33,11 @@ struct UW8Instance { store: Store<()>, memory: Memory, end_frame: TypedFunc<(), ()>, - update: TypedFunc<(), ()>, + update: Option>, start_time: Instant, module: Vec, watchdog: Arc>, + sound: Uw8Sound, } 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 { fn is_open(&self) -> bool { self.window.is_open() && !self.window.is_key_down(Key::Escape) @@ -119,43 +126,62 @@ impl super::Runtime for MicroW8 { 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])?; - linker.func_wrap("env", "acos", |v: f32| v.acos())?; - linker.func_wrap("env", "asin", |v: f32| v.asin())?; - linker.func_wrap("env", "atan", |v: f32| v.atan())?; - linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?; - linker.func_wrap("env", "cos", |v: f32| v.cos())?; - linker.func_wrap("env", "exp", |v: f32| v.exp())?; - linker.func_wrap("env", "log", |v: f32| v.ln())?; - linker.func_wrap("env", "sin", |v: f32| v.sin())?; - linker.func_wrap("env", "tan", |v: f32| v.tan())?; - linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?; - for i in 9..64 { - linker.func_wrap("env", &format!("reserved{}", i), || {})?; - } - for i in 0..16 { - linker.define( - "env", - &format!("g_reserved{}", i), - wasmtime::Global::new( - &mut store, - GlobalType::new(ValType::I32, Mutability::Const), - 0.into(), - )?, - )?; + 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", "asin", |v: f32| v.asin())?; + linker.func_wrap("env", "atan", |v: f32| v.atan())?; + linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?; + linker.func_wrap("env", "cos", |v: f32| v.cos())?; + linker.func_wrap("env", "exp", |v: f32| v.exp())?; + linker.func_wrap("env", "log", |v: f32| v.ln())?; + linker.func_wrap("env", "sin", |v: f32| v.sin())?; + linker.func_wrap("env", "tan", |v: f32| v.tan())?; + linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?; + for i in 10..64 { + linker.func_wrap("env", &format!("reserved{}", i), || {})?; + } + for i in 0..16 { + linker.define( + "env", + &format!("g_reserved{}", i), + wasmtime::Global::new( + &mut *store, + GlobalType::new(ValType::I32, Mutability::Const), + 0.into(), + )?, + )?; + } + + Ok(()) } - let platform_instance = linker.instantiate(&mut store, &platform_module)?; + add_native_functions(&mut linker, &mut store)?; - for export in platform_instance.exports(&mut store) { - linker.define( - "env", - export.name(), - export - .into_func() - .expect("platform surely only exports functions"), - )?; + fn instantiate_platform( + linker: &mut wasmtime::Linker<()>, + store: &mut wasmtime::Store<()>, + platform_module: &wasmtime::Module, + ) -> Result { + let platform_instance = linker.instantiate(&mut *store, &platform_module)?; + + for export in platform_instance.exports(&mut *store) { + linker.define( + "env", + export.name(), + export + .into_func() + .expect("platform surely only exports functions"), + )?; + } + + Ok(platform_instance) } + let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?; + let watchdog = Arc::new(Mutex::new(UW8WatchDog { interupt: store.interrupt_handle()?, timeout: self.timeout, @@ -187,7 +213,77 @@ impl super::Runtime for MicroW8 { watchdog.timeout = 0; } 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, ®s).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 { store, @@ -197,6 +293,7 @@ impl super::Runtime for MicroW8 { start_time: Instant::now(), module: module_data.into(), watchdog, + sound, }); Ok(()) @@ -227,13 +324,20 @@ impl super::Runtime for MicroW8 { if let Ok(mut watchdog) = instance.watchdog.lock() { 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() { watchdog.timeout = 0; } instance.end_frame.call(&mut 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 palette = &memory[0x13000..]; for (i, &color_index) in framebuffer.iter().enumerate() {