diff --git a/Cargo.lock b/Cargo.lock index d4dbc7f..9a7b011 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,24 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -107,12 +125,32 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -124,7 +162,10 @@ name = "composer" version = "0.1.0" dependencies = [ "env_logger", + "itertools 0.14.0", "midir", + "rustysynth", + "tinyaudio", ] [[package]] @@ -143,6 +184,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen", +] + [[package]] name = "coremidi" version = "0.8.0" @@ -164,6 +214,12 @@ dependencies = [ "core-foundation-sys", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "env_filter" version = "1.0.0" @@ -187,12 +243,82 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "jiff" version = "0.2.23" @@ -217,6 +343,12 @@ dependencies = [ "syn", ] +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.91" @@ -233,6 +365,16 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -271,6 +413,67 @@ dependencies = [ "windows", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -306,6 +509,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkg-config" version = "0.3.32" @@ -327,6 +536,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -383,12 +601,24 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rustysynth" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ca93af923df5fc03beddbf464242620fd24daa9e10f9ecd56eb9571eb7ba38" + [[package]] name = "scopeguard" version = "1.2.0" @@ -415,6 +645,18 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -432,6 +674,73 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyaudio" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2bb6a423a786a941cbc5a6544693727d832ec2c088d7da097a9d3bcf0431b5" +dependencies = [ + "alsa-sys", + "core-foundation-sys", + "coreaudio-sys", + "js-sys", + "ndk", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winapi", +] + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -457,6 +766,20 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.114" @@ -499,6 +822,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.56.0" @@ -630,3 +975,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index f8dea61..535403c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,7 @@ edition = "2024" [dependencies] env_logger = "0.11.9" +itertools = "0.14.0" midir = "0.10.3" +rustysynth = "1.3.6" +tinyaudio = "2.0.0" diff --git a/FatBoy.sf2 b/FatBoy.sf2 new file mode 120000 index 0000000..98553be --- /dev/null +++ b/FatBoy.sf2 @@ -0,0 +1 @@ +/usr/share/soundfonts/FatBoy.sf2 \ No newline at end of file diff --git a/simple_chord.pcm b/simple_chord.pcm new file mode 100644 index 0000000..2f08347 Binary files /dev/null and b/simple_chord.pcm differ diff --git a/src/main.rs b/src/main.rs index c600a42..32efe54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,78 +1,51 @@ -use std::error::Error; -use std::io::{stdin, stdout, Write}; -use std::thread::sleep; -use std::time::Duration; - -use midir::{MidiOutput, MidiOutputPort}; +use itertools::Itertools; +use rustysynth::MidiFile; +use rustysynth::MidiFileSequencer; +use rustysynth::SoundFont; +use rustysynth::Synthesizer; +use rustysynth::SynthesizerSettings; +use std::fs::File; +use std::sync::Arc; +use tinyaudio::prelude::*; fn main() { - env_logger::init(); - match run() { - Ok(_) => (), - Err(err) => println!("Error: {}", err), - } -} + // Load the SoundFont. + let mut sf2 = File::open("FatBoy.sf2").unwrap(); + let sound_font = Arc::new(SoundFont::new(&mut sf2).unwrap()); -fn run() -> Result<(), Box> { - let midi_out = MidiOutput::new("My Test Output")?; - - // Get an output port (read from console if multiple are available) - let out_ports = midi_out.ports(); - let out_port: &MidiOutputPort = match out_ports.len() { - 0 => return Err("no output port found".into()), - 1 => { - println!( - "Choosing the only available output port: {}", - midi_out.port_name(&out_ports[0]).unwrap() - ); - &out_ports[0] - } - _ => { - println!("\nAvailable output ports:"); - for (i, p) in out_ports.iter().enumerate() { - println!("{}: {}", i, midi_out.port_name(p).unwrap()); - } - print!("Please select output port: "); - stdout().flush()?; - let mut input = String::new(); - stdin().read_line(&mut input)?; - out_ports - .get(input.trim().parse::()?) - .ok_or("invalid output port selected")? - } + // Setup the audio output. + let params = OutputDeviceParameters { + channels_count: 2, + sample_rate: 44100, + channel_sample_count: 4410, }; - println!("\nOpening connection"); - let mut conn_out = midi_out.connect(out_port, "midir-test")?; - println!("Connection open. Listen!"); - { - // Define a new scope in which the closure `play_note` borrows conn_out, so it can be called easily - let mut play_note = |note: u8, duration: u64| { - const NOTE_ON_MSG: u8 = 0x90; - const NOTE_OFF_MSG: u8 = 0x80; - const VELOCITY: u8 = 0x64; - // We're ignoring errors in here - let _ = conn_out.send(&[NOTE_ON_MSG, note, VELOCITY]); - sleep(Duration::from_millis(duration * 150)); - let _ = conn_out.send(&[NOTE_OFF_MSG, note, VELOCITY]); - }; + // Create the synthesizer. + let settings = SynthesizerSettings::new(params.sample_rate as i32); + let mut synthesizer = Synthesizer::new(&sound_font, &settings).unwrap(); - sleep(Duration::from_millis(4 * 150)); + // Play some notes (middle C, E, G). + synthesizer.note_on(0, 60, 100); + synthesizer.note_on(0, 64, 100); + synthesizer.note_on(0, 67, 100); - play_note(66, 4); - play_note(65, 3); - play_note(63, 1); - play_note(61, 6); - play_note(59, 2); - play_note(58, 4); - play_note(56, 4); - play_note(54, 4); - } - sleep(Duration::from_millis(150)); - println!("\nClosing connection"); - // This is optional, the connection would automatically be closed as soon as it goes out of scope - conn_out.close(); - println!("Connection closed"); - Ok(()) + // The output buffer + let sample_count = (params.channel_sample_count) as usize; + let mut left: Vec = vec![0_f32; sample_count]; + let mut right: Vec = vec![0_f32; sample_count]; + + // Start the audio output. + let _device = run_output_device(params, { + move |data| { + synthesizer.render(&mut left[..], &mut right[..]); + for (i, value) in left.iter().interleave(right.iter()).enumerate() { + data[i] = *value; + } + } + }) + .unwrap(); + + // Wait for 10 seconds. + std::thread::sleep(std::time::Duration::from_secs(10)); }