From fe6fff77636b354b21ab9e26fe4f62bb030b3a17 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 7 Oct 2022 14:28:47 -0400 Subject: pm_start, this --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 8 +++++++ config.rhai | 14 ++++++++---- examples/fun.rhai | 16 ++++++++++++++ src/main.rs | 66 +++++++++++++++++++++++++++++++++++-------------------- src/soundfont.rs | 6 +++-- 7 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 examples/fun.rhai diff --git a/Cargo.lock b/Cargo.lock index 551905d..a392a7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ dependencies = [ [[package]] name = "progmidi" -version = "0.0.0" +version = "1.0.0" dependencies = [ "cc", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 65e2294..04f1cc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "progmidi" -version = "0.0.0" +version = "1.0.0" edition = "2021" [build-dependencies] diff --git a/README.md b/README.md index c2f0452..6c440d2 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ starting with `pm_` or `PM_`. Below, `i64` is an integer, and `f64` is a floating-point number (number with decimals). `bool`s are either `true` or `false`. +You can keep track of state between function calls using `this` (see `examples/fun.rhai`). + ### user-supplied constants - `PM_DEVICE_ID: i64` - define this to control which MIDI device is used. @@ -57,6 +59,8 @@ will be used. Otherwise, the device with ID `PM_DEVICE_ID` will be used. ### user-supplied functions +- `pm_start()` - called when progmidi is started. + - `pm_note_played(channel: i64, note: i64, velocity: i64)` - Called when a note is played (MIDI "note on" event, or "note on" with velocity 0). `note` is a number from 0 to 127 — 60±n indicates n semitones above/below middle C. @@ -118,6 +122,10 @@ If `falloff` = 1, the note's volume is not affected when it is released. - `pm_stop_midi_recording()` - Stop the current .wav recording if there is one. +- `pm_print()` - `print` with no added newline. + +- `pm_get_time() -> i64` - get timestamp in milliseconds since application was started. + ## building from source You can build progmidi with `cargo build --release`, diff --git a/config.rhai b/config.rhai index 4ff9ea6..07d3136 100644 --- a/config.rhai +++ b/config.rhai @@ -45,7 +45,13 @@ fn pm_control_changed(channel, controller, value) { } } -pm_load_soundfont("soundfont.sf3"); -pm_load_preset(-1, "grand piano"); // default = piano -pm_load_preset(9, "standard"); // drum pad -pm_load_preset(16, "standard"); // metronome +fn pm_start() { + pm_print("Loading soundfont..."); + let start = pm_get_time(); + pm_load_soundfont("soundfont.sf3"); + pm_load_preset(-1, "grand piano"); // default = piano + pm_load_preset(9, "standard"); // drum pad + pm_load_preset(16, "standard"); // metronome + let load_time = pm_get_time() - start; + print("\rLoaded in " + load_time + "ms. "); +} diff --git a/examples/fun.rhai b/examples/fun.rhai new file mode 100644 index 0000000..05b915c --- /dev/null +++ b/examples/fun.rhai @@ -0,0 +1,16 @@ +// plays ode to joy, no matter which keys you press + +const data = [64, 64, 65, 67, 67, 65, 64, 62, 60, 60, 62, 64, 64, 62, 62, + 64, 64, 65, 67, 67, 65, 64, 62, 60, 60, 62, 64, 62, 60, 60]; + +fn pm_note_played(channel, note, vel) { + pm_play_note(0, data[this.i], vel); + this.i += 1; + this.i %= data.len; +} + +fn pm_start() { + this.i = 0; + pm_load_soundfont("soundfont.sf3"); + pm_load_preset(-1, "grand piano"); +} diff --git a/src/main.rs b/src/main.rs index 7bbeb51..de6da99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,10 +85,7 @@ static NOTE_INFO: Mutex = Mutex::new(NoteInfo { channel_volumes: [1.0; CHANNEL_COUNT], master_volume: 1.0, release_falloff: 0.1, - metronome: Metronome { - bpm: 0.0, - key: 0, - }, + metronome: Metronome { bpm: 0.0, key: 0 }, }); // unfortunately, this needs to be a separate mutex because otherwise @@ -414,7 +411,7 @@ fn play_note(channel: i64, note: i64, vel: i64) { n.cut = true; } } - + let preset = note_info.presets[channel]; if let Some(sf) = maybe_sf.as_mut() { match sf.request(preset, note, vel) { @@ -523,7 +520,7 @@ fn load_preset(channel: i64, preset: i64) { if let Err(e) = sf.load_samples_for_preset(preset) { eprintln!("error loading preset {}: {}", preset, e); } - + if channel == -1 { for p in note_info.presets.iter_mut() { *p = preset; @@ -553,11 +550,14 @@ fn load_preset_by_name(channel: i64, name: &str) { } } } else { - eprintln!("Can't load preset \"{}\", since no soundfont is loaded.", name); + eprintln!( + "Can't load preset \"{}\", since no soundfont is loaded.", + name + ); return; } } - preset_names.sort_by(|(_, a),(_, b)| { + preset_names.sort_by(|(_, a), (_, b)| { use std::cmp::Ordering::*; if a.len() < b.len() { Less @@ -650,12 +650,15 @@ fn stop_wav_recording() { fn call_fn_if_exists( engine: &rhai::Engine, ast: &rhai::AST, + this: &mut rhai::Dynamic, name: &str, args: impl rhai::FuncArgs, ) { + let mut arg_vec = vec![]; + args.parse(&mut arg_vec); let mut scope = rhai::Scope::new(); match engine - .call_fn::<()>(&mut scope, ast, name, args) + .call_fn_raw(&mut scope, ast, true, false, name, Some(this), &mut arg_vec) .map_err(|e| *e) { Ok(_) => {} @@ -674,13 +677,18 @@ extern "C" { } extern "C" fn sig_handler(_signum: c_int) { - stop_midi_recording(); - stop_wav_recording(); - std::process::exit(0); + // do this in a separate thread in case note_info is locked in this thread + std::thread::spawn(|| { + stop_midi_recording(); + stop_wav_recording(); + std::process::exit(0); + }); } fn main() { unsafe { signal(SIGINT, sig_handler) }; + + let application_start = Instant::now(); let mut engine = rhai::Engine::new(); engine.set_max_expr_depths(0, 0); @@ -712,7 +720,19 @@ fn main() { engine.register_fn("pm_stop_midi_recording", stop_midi_recording); engine.register_fn("pm_start_wav_recording", start_wav_recording); engine.register_fn("pm_stop_wav_recording", stop_wav_recording); - + engine.register_fn("pm_print", |s: &str| { + print!("{s}"); + if std::io::stdout().flush().is_err() { + // whatever + } + }); + engine.register_fn("pm_get_time", move || -> i64 { + Instant::now().duration_since(application_start) + .as_millis() + .try_into() + .expect("i'm gonna stop you right there. you've been running this program for HOW LONG?") + }); + let engine = engine; // de-multablify let args: Vec = std::env::args().collect(); let config_filename = match args.len() { @@ -723,7 +743,7 @@ fn main() { return; } }; - let mut ast = match engine.compile_file(config_filename.into()) { + let ast = match engine.compile_file(config_filename.into()) { Ok(x) => x, Err(e) => { eprintln!("Config error: {}", e); @@ -761,11 +781,10 @@ fn main() { } }; } + + let mut this = rhai::Dynamic::from(rhai::Map::new()); + call_fn_if_exists(&engine, &ast, &mut this, "pm_start", ()); - // without this, top-level statements will be executed each time a function is called - ast.clear_statements(); - - let ast = ast; // de-mutablify let (stream, sample_rate) = match get_audio_stream() { Ok(s) => s, Err(e) => { @@ -795,11 +814,7 @@ fn main() { metronome = note_info.metronome.clone(); } if metronome.bpm > 0.0 && metronome_time >= 60.0 / metronome.bpm { - play_note( - METRONOME_CHANNEL, - metronome.key, - 127 - ); + play_note(METRONOME_CHANNEL, metronome.key, 127); last_metronome_tick = Instant::now(); } @@ -809,18 +824,20 @@ fn main() { NoteOn { channel, note, vel } => call_fn_if_exists( &engine, &ast, + &mut this, "pm_note_played", (channel as i64, note as i64, vel as i64), ), NoteOff { channel, note, vel } => call_fn_if_exists( &engine, &ast, + &mut this, "pm_note_released", (channel as i64, note as i64, vel as i64), ), PitchBend { channel, amount } => { let amount = (amount as f64 - 8192.0) * (1.0 / 8192.0); - call_fn_if_exists(&engine, &ast, "pm_pitch_bent", (channel as i64, amount)); + call_fn_if_exists(&engine, &ast, &mut this, "pm_pitch_bent", (channel as i64, amount)); } ControlChange { channel, @@ -830,6 +847,7 @@ fn main() { call_fn_if_exists( &engine, &ast, + &mut this, "pm_control_changed", (channel as i64, controller as i64, value as i64), ); diff --git a/src/soundfont.rs b/src/soundfont.rs index 35a24aa..7d3628b 100644 --- a/src/soundfont.rs +++ b/src/soundfont.rs @@ -291,7 +291,8 @@ impl Sample { FileType::SF2 => { self.data = Vec::with_capacity(len + 1); for i in 0..len as usize { - self.data.push(i16::from_le_bytes([data8[2 * i], data8[2 * i + 1]])); + self.data + .push(i16::from_le_bytes([data8[2 * i], data8[2 * i + 1]])); } } FileType::SF3 => { @@ -953,7 +954,8 @@ impl SoundFont { if end <= start { return Err(OpenError::BadSoundFont(format!( "sample starts at {}, and ends before then (at {})", - start, end as i64 - 1 + start, + end as i64 - 1 ))); } let mut startloop = read_u32(&mut file)?; -- cgit v1.2.3