summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-10-07 14:28:47 -0400
committerpommicket <pommicket@gmail.com>2022-10-07 14:28:47 -0400
commitfe6fff77636b354b21ab9e26fe4f62bb030b3a17 (patch)
tree04c2746b5309055bd9146f3f2dad1b5d27cbecc0
parent99fefad88661c1b11c55ac3bb3e009baf4eb9893 (diff)
pm_start, this
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md8
-rw-r--r--config.rhai14
-rw-r--r--examples/fun.rhai16
-rw-r--r--src/main.rs66
-rw-r--r--src/soundfont.rs6
7 files changed, 82 insertions, 32 deletions
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<NoteInfo> = 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<String> = 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)?;