From 58a0250c2580cb6bbdd641f780f0b149c840ed04 Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 6 Oct 2022 11:26:49 -0400 Subject: metronome working --- Cargo.lock | 67 +++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + config.rhai | 11 +++++++- src/main.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/soundfont.rs | 12 +++++---- 5 files changed, 158 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b86c97..ede03a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -109,6 +118,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -194,7 +218,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -203,6 +227,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "instant" version = "0.1.12" @@ -412,6 +449,16 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -511,6 +558,7 @@ name = "playmidi" version = "0.0.0" dependencies = [ "cc", + "chrono", "cpal", "libc", "rhai", @@ -697,6 +745,17 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "toml" version = "0.5.9" @@ -729,6 +788,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 907e097..21b6607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" cc = "1.0" [dependencies] +chrono = "0.4.22" cpal = "0.14" libc = "0.2" rhai = { version = "1.10", features = [ "only_i64" ] } diff --git a/config.rhai b/config.rhai index 5483ade..513e34f 100644 --- a/config.rhai +++ b/config.rhai @@ -13,14 +13,23 @@ fn pm_pitch_bent(channel, amount) { } fn pm_control_changed(channel, controller, value) { + print(channel + " " + controller + " " + value); if controller == 64 { // pedal down if value < 127. pm_set_pedal(value < 127); } else if controller == 1 { - pm_set_volume(0, value / 127.0); + pm_set_volume(-1, value / 127.0); + } else if controller == 20 { + let bpm = 0; + if value != 0 { + bpm = round(30.0 + 1.5 * value); + } + print("setting metronome to " + bpm); + pm_set_metronome(60, bpm, 1.0); } } pm_load_soundfont("/etc/alternatives/default-GM.sf3"); pm_load_preset(0, 299); pm_load_preset(9, 102); +pm_load_preset(16, 102); diff --git a/src/main.rs b/src/main.rs index 7dd7706..5d8e110 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ extern crate cpal; extern crate rhai; +extern crate chrono; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use std::fs::File; use std::io::Write; use std::sync::Mutex; @@ -9,7 +11,8 @@ mod midi_input; mod soundfont; const NOTE_FALLOFF: f32 = 0.1; // falloff when note is released -const CHANNEL_COUNT: usize = 16; +const CHANNEL_COUNT: usize = 17; // 16 MIDI channels + metronome +const METRONOME_CHANNEL: i64 = 16; struct Note { key: u8, @@ -18,26 +21,56 @@ struct Note { kill: bool, // only used briefly } +#[derive(Clone)] +struct Metronome { + bpm: f32, + key: i64, + volume: f32 +} + +struct MidiRecording { + file: File, + last_event_time: std::time::Instant, +} + struct NoteInfo { + midi_out: Option, pitch_bend: i32, // in cents pedal_down: bool, presets: [usize; CHANNEL_COUNT], notes: [Vec; CHANNEL_COUNT], channel_volumes: [f32; CHANNEL_COUNT], master_volume: f32, + metronome: Metronome, } static NOTE_INFO: Mutex = Mutex::new(NoteInfo { + midi_out: None, pitch_bend: 0, // this is ridiculous. - notes: [vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![]], + notes: [vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![], vec![]], pedal_down: false, presets: [0; CHANNEL_COUNT], channel_volumes: [1.0; CHANNEL_COUNT], master_volume: 1.0, + metronome: Metronome { + bpm: 0.0, + key: 0, + volume: 0.0 + }, }); + static SOUNDFONT: Mutex> = Mutex::new(None); +fn write_u16_be(file: &mut File, n: u16) -> std::io::Result<()> { + file.write(&mut u16::to_be_bytes(n))?; + Ok(()) +} + +fn lock_note_info<'a>() -> std::sync::MutexGuard<'a, NoteInfo> { + NOTE_INFO.lock().expect("couldn't lock notes") +} + fn get_midi_device(idx: i32) -> Result { let mut device_mgr = midi_input::DeviceManager::new()?; device_mgr.set_quiet(true); @@ -288,6 +321,22 @@ fn set_volume(channel: i64, volume: f64) { note_info.channel_volumes[channel] = volume as f32; } +fn set_metronome(key: i64, bpm: f64, volume: f64) { + let mut note_info = lock_note_info(); + note_info.metronome = Metronome { + key, + bpm: bpm as f32, + volume: volume as f32, + } +} + +// fn start_midi_recording() { +// let name = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S.mid").to_string(); +// let file = File::create(name); +// let note_info = lock_notes(); +// +// } +// fn call_fn_if_exists(engine: &rhai::Engine, ast: &rhai::AST, name: &str, args: impl rhai::FuncArgs) { let mut scope = rhai::Scope::new(); match engine.call_fn::<()>(&mut scope, &ast, name, args).map_err(|e| *e) { @@ -308,6 +357,12 @@ fn playmidi_main() -> Result<(), String> { engine.register_fn("pm_set_pedal", set_pedal_down); engine.register_fn("pm_bend_pitch", set_pitch_bend); engine.register_fn("pm_set_volume", set_volume); + engine.register_fn("pm_set_metronome", set_metronome); + // allow integer bpm as well + engine.register_fn("pm_set_metronome", |key: i64, bpm: i64, volume: f64| { + set_metronome(key, bpm as f64, volume); + }); +// engine.register_fn("pm_start_midi_recording", start_midi_recording); let engine = engine; // de-multablify let mut ast = engine.compile_file("config.rhai".into()).map_err(|e| format!("{}", e))?; @@ -347,7 +402,23 @@ fn playmidi_main() -> Result<(), String> { let stream = get_audio_stream()?; stream.play().map_err(|e| format!("{}", e))?; + let mut last_metronome_tick = std::time::Instant::now(); + while midi_device.is_connected() { + + let metronome_time = last_metronome_tick.elapsed().as_secs_f32(); + + let metronome; + { + let note_info = NOTE_INFO.lock().expect("couldn't lock notes"); + metronome = note_info.metronome.clone(); + } + if metronome.bpm > 0.0 && metronome_time >= 60.0 / metronome.bpm { + play_note(METRONOME_CHANNEL, metronome.key, (metronome.volume * 127.0) as i64); + last_metronome_tick = std::time::Instant::now(); + } + + while let Some(event) = midi_device.read_event() { use midi_input::Event::*; match event { @@ -371,7 +442,7 @@ fn playmidi_main() -> Result<(), String> { eprintln!("Error: {}", err); midi_device.clear_error(); } - std::thread::sleep(std::time::Duration::from_millis(5)); + std::thread::sleep(std::time::Duration::from_millis(1)); } Ok(()) diff --git a/src/soundfont.rs b/src/soundfont.rs index 950ace6..92fc49c 100644 --- a/src/soundfont.rs +++ b/src/soundfont.rs @@ -150,6 +150,7 @@ pub struct SamplesRequest { key: u8, vel: u8, falloff: f32, + falloff_speed: f32, tune: i32, volume: f32, zones: Vec, @@ -647,7 +648,7 @@ impl SamplesRequest { /// every t seconds, volume will be multiplied by amount ^ t /// e.g. when a user lets go of a piano key, you might call `set_falloff(0.01)` pub fn set_falloff(&mut self, amount: f32) { - self.falloff = amount; + self.falloff_speed = amount; } } @@ -1207,6 +1208,7 @@ impl SoundFont { tune: 0, t: 0.0, falloff: 1.0, + falloff_speed: 1.0, zones, }) } @@ -1299,8 +1301,8 @@ impl SoundFont { let t_inc = freq_modulation / sample_rate; let data = &sample.data[data_start as usize..data_end as usize]; let tmul = sample.sample_rate as f64; - let mut falloff = 1.0; // falloff accrued from these samples - let falloff_mul = f32::powf(request.falloff, (1.0 / sample_rate) as f32); + let mut falloff = request.falloff; + let falloff_mul = f32::powf(request.falloff_speed, (1.0 / sample_rate) as f32); for i in 0..samples.len() / 2 { let mut s = (t * tmul) as u64; if zone.loops && s >= startloop { @@ -1319,8 +1321,8 @@ impl SoundFont { t += t_inc; } - request.volume *= falloff; - if request.volume < 1.0 / 32767.0 { + request.falloff = falloff; + if falloff * request.volume < 1.0 / 32767.0 { this_held = false; } final_t = f64::max(final_t, t); -- cgit v1.2.3