mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 19:26:43 +01:00
add support for non 44.1kHz audio configs (resampling)
This commit is contained in:
80
Cargo.lock
generated
80
Cargo.lock
generated
@@ -1578,6 +1578,15 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -1589,6 +1598,16 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -1795,6 +1814,15 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-check"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01419cee72c1a1ca944554e23d83e483e1bccf378753344e881de28b5487511d"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -1928,6 +1956,15 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "realfft"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a83b876fe55da7e1bf5deeacb93d6411edf81eba0e1a497e79c067734729053a"
|
||||||
|
dependencies = [
|
||||||
|
"rustfft",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@@ -1996,6 +2033,18 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rubato"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ceb88cff5e2414abd59f1470673e1bba0627118dfc13ce33cebf67ea4a8acb0"
|
||||||
|
dependencies = [
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"realfft",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.21"
|
version = "0.1.21"
|
||||||
@@ -2008,6 +2057,20 @@ 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 = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustfft"
|
||||||
|
version = "6.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1d089e5c57521629a59f5f39bca7434849ff89bd6873b521afe389c1c602543"
|
||||||
|
dependencies = [
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"primal-check",
|
||||||
|
"strength_reduce",
|
||||||
|
"transpose",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.33.3"
|
version = "0.33.3"
|
||||||
@@ -2218,6 +2281,12 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
|
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strength_reduce"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -2434,6 +2503,16 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "transpose"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95f9c900aa98b6ea43aee227fd680550cdec726526aab8ac801549eadb25e39f"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"strength_reduce",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -2567,6 +2646,7 @@ dependencies = [
|
|||||||
"minifb",
|
"minifb",
|
||||||
"notify",
|
"notify",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
|
"rubato",
|
||||||
"same-file",
|
"same-file",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ edition = "2021"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["native", "browser"]
|
default = ["native", "browser"]
|
||||||
native = ["wasmtime"]
|
native = ["wasmtime", "minifb", "cpal", "rubato"]
|
||||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = { version = "0.35.3", optional = true }
|
wasmtime = { version = "0.35.3", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = { version = "0.22", default-features = false, features = ["x11"] }
|
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true }
|
||||||
notify = "4"
|
notify = "4"
|
||||||
pico-args = "0.4"
|
pico-args = "0.4"
|
||||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "aac7bbd" }
|
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "aac7bbd" }
|
||||||
@@ -25,4 +25,5 @@ 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"
|
cpal = { version = "0.13.5", optional = true }
|
||||||
|
rubato = { version = "0.11.0", optional = true }
|
||||||
12
src/main.rs
12
src/main.rs
@@ -16,7 +16,7 @@ fn main() -> Result<()> {
|
|||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
// try to enable ansi support in win10 cmd shell
|
// try to enable ansi support in win10 cmd shell
|
||||||
#[cfg(target_os="windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let _ = ansi_term::enable_ansi_support();
|
let _ = ansi_term::enable_ansi_support();
|
||||||
|
|
||||||
match args.subcommand()?.as_deref() {
|
match args.subcommand()?.as_deref() {
|
||||||
@@ -79,6 +79,8 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
let run_browser = args.contains(["-b", "--browser"]) || true;
|
let run_browser = args.contains(["-b", "--browser"]) || true;
|
||||||
|
|
||||||
|
let disable_audio = args.contains(["-m", "--disable-audio"]);
|
||||||
|
|
||||||
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let mut watcher = uw8::FileWatcher::new()?;
|
let mut watcher = uw8::FileWatcher::new()?;
|
||||||
@@ -89,7 +91,13 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
#[cfg(feature = "native")]
|
#[cfg(feature = "native")]
|
||||||
Box::new(MicroW8::new()?)
|
{
|
||||||
|
let mut microw8 = MicroW8::new()?;
|
||||||
|
if disable_audio {
|
||||||
|
microw8.disable_audio();
|
||||||
|
}
|
||||||
|
Box::new(microw8)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(feature = "browser"))]
|
#[cfg(not(feature = "browser"))]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::{thread, time::Instant};
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cpal::traits::*;
|
use cpal::traits::*;
|
||||||
use minifb::{Key, Window, WindowOptions};
|
use minifb::{Key, Window, WindowOptions};
|
||||||
|
use rubato::Resampler;
|
||||||
use wasmtime::{
|
use wasmtime::{
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||||
};
|
};
|
||||||
@@ -27,6 +28,7 @@ pub struct MicroW8 {
|
|||||||
window_buffer: Vec<u32>,
|
window_buffer: Vec<u32>,
|
||||||
instance: Option<UW8Instance>,
|
instance: Option<UW8Instance>,
|
||||||
timeout: u32,
|
timeout: u32,
|
||||||
|
disable_audio: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UW8Instance {
|
struct UW8Instance {
|
||||||
@@ -37,7 +39,7 @@ struct UW8Instance {
|
|||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
module: Vec<u8>,
|
module: Vec<u8>,
|
||||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||||
sound: Uw8Sound,
|
sound: Option<Uw8Sound>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UW8Instance {
|
impl Drop for UW8Instance {
|
||||||
@@ -77,6 +79,7 @@ impl MicroW8 {
|
|||||||
window_buffer: vec![0u32; 320 * 240],
|
window_buffer: vec![0u32; 320 * 240],
|
||||||
instance: None,
|
instance: None,
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
|
disable_audio: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,11 +89,10 @@ impl MicroW8 {
|
|||||||
*v = 0;
|
*v = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct Uw8Sound {
|
pub fn disable_audio(&mut self) {
|
||||||
stream: cpal::Stream,
|
self.disable_audio = true;
|
||||||
tx: mpsc::SyncSender<[u8; 32]>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Runtime for MicroW8 {
|
impl super::Runtime for MicroW8 {
|
||||||
@@ -126,60 +128,8 @@ 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", "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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
add_native_functions(&mut linker, &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(
|
|
||||||
"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 platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
||||||
|
|
||||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||||
@@ -215,84 +165,20 @@ impl super::Runtime for MicroW8 {
|
|||||||
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").ok();
|
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
||||||
|
|
||||||
let sound = {
|
let sound = if self.disable_audio {
|
||||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
None
|
||||||
|
} else {
|
||||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
match init_sound(&self.engine, &platform_module, &module) {
|
||||||
|
Ok(sound) => {
|
||||||
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 start_time = Instant::now();
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let time = start_time.elapsed().as_millis() as i32;
|
|
||||||
let mem = memory.data_mut(&mut store);
|
|
||||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
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()?;
|
sound.stream.play()?;
|
||||||
|
Some(sound)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to init sound: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.instance = Some(UW8Instance {
|
self.instance = Some(UW8Instance {
|
||||||
store,
|
store,
|
||||||
@@ -345,7 +231,9 @@ impl super::Runtime for MicroW8 {
|
|||||||
|
|
||||||
let mut sound_regs = [0u8; 32];
|
let mut sound_regs = [0u8; 32];
|
||||||
sound_regs.copy_from_slice(&memory[80..112]);
|
sound_regs.copy_from_slice(&memory[80..112]);
|
||||||
instance.sound.tx.send(sound_regs)?;
|
if let Some(ref sound) = instance.sound {
|
||||||
|
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..];
|
||||||
@@ -371,3 +259,210 @@ impl super::Runtime for MicroW8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
"env",
|
||||||
|
export.name(),
|
||||||
|
export
|
||||||
|
.into_func()
|
||||||
|
.expect("platform surely only exports functions"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(platform_instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Uw8Sound {
|
||||||
|
stream: cpal::Stream,
|
||||||
|
tx: mpsc::SyncSender<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_sound(
|
||||||
|
engine: &wasmtime::Engine,
|
||||||
|
platform_module: &wasmtime::Module,
|
||||||
|
module: &wasmtime::Module,
|
||||||
|
) -> Result<Uw8Sound> {
|
||||||
|
let mut store = wasmtime::Store::new(engine, ());
|
||||||
|
|
||||||
|
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||||
|
|
||||||
|
let mut linker = wasmtime::Linker::new(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 mut configs: Vec<_> = device
|
||||||
|
.supported_output_configs()?
|
||||||
|
.filter(|config| {
|
||||||
|
config.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
configs.sort_by_key(|config| {
|
||||||
|
let rate = 44100
|
||||||
|
.max(config.min_sample_rate().0)
|
||||||
|
.min(config.max_sample_rate().0);
|
||||||
|
if rate >= 44100 {
|
||||||
|
rate - 44100
|
||||||
|
} else {
|
||||||
|
(44100 - rate) * 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let config = configs
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("Could not find float output config"))?;
|
||||||
|
let sample_rate = cpal::SampleRate(44100)
|
||||||
|
.max(config.min_sample_rate())
|
||||||
|
.max(config.max_sample_rate());
|
||||||
|
let config = config.with_sample_rate(sample_rate);
|
||||||
|
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 sample_rate = config.sample_rate.0 as usize;
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
struct Resampler {
|
||||||
|
resampler: rubato::FftFixedIn<f32>,
|
||||||
|
input_buffers: Vec<Vec<f32>>,
|
||||||
|
output_buffers: Vec<Vec<f32>>,
|
||||||
|
output_index: usize,
|
||||||
|
}
|
||||||
|
let mut resampler: Option<Resampler> = if sample_rate == 44100 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
|
||||||
|
let input_buffers = rs.input_buffer_allocate();
|
||||||
|
let output_buffers = rs.output_buffer_allocate();
|
||||||
|
Some(Resampler {
|
||||||
|
resampler: rs,
|
||||||
|
input_buffers,
|
||||||
|
output_buffers,
|
||||||
|
output_index: usize::MAX,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sample_index = 0;
|
||||||
|
let stream = device.build_output_stream(
|
||||||
|
&config,
|
||||||
|
move |mut buffer: &mut [f32], _| {
|
||||||
|
if let Ok(regs) = rx.try_recv() {
|
||||||
|
memory.write(&mut store, 80, ®s).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let time = start_time.elapsed().as_millis() as i32;
|
||||||
|
let mem = memory.data_mut(&mut store);
|
||||||
|
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut resampler) = resampler {
|
||||||
|
while !buffer.is_empty() {
|
||||||
|
let copy_size = resampler.output_buffers[0]
|
||||||
|
.len()
|
||||||
|
.saturating_sub(resampler.output_index)
|
||||||
|
.min(buffer.len() / 2);
|
||||||
|
if copy_size == 0 {
|
||||||
|
resampler.input_buffers[0].clear();
|
||||||
|
resampler.input_buffers[1].clear();
|
||||||
|
for _ in 0..resampler.resampler.input_frames_next() {
|
||||||
|
resampler.input_buffers[0]
|
||||||
|
.push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||||
|
resampler.input_buffers[1]
|
||||||
|
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
|
||||||
|
sample_index = sample_index.wrapping_add(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
resampler
|
||||||
|
.resampler
|
||||||
|
.process_into_buffer(
|
||||||
|
&resampler.input_buffers,
|
||||||
|
&mut resampler.output_buffers,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
resampler.output_index = 0;
|
||||||
|
} else {
|
||||||
|
for i in 0..copy_size {
|
||||||
|
buffer[i * 2] = resampler.output_buffers[0][resampler.output_index + i];
|
||||||
|
buffer[i * 2 + 1] =
|
||||||
|
resampler.output_buffers[1][resampler.output_index + i];
|
||||||
|
}
|
||||||
|
resampler.output_index += copy_size;
|
||||||
|
buffer = &mut buffer[copy_size * 2..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Uw8Sound { stream, tx })
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user