summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock67
-rw-r--r--Cargo.toml1
-rw-r--r--config.rhai11
-rw-r--r--src/main.rs77
-rw-r--r--src/soundfont.rs12
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
@@ -37,6 +37,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -110,6 +119,21 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -194,7 +218,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@@ -204,6 +228,19 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -413,6 +450,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -511,6 +558,7 @@ name = "playmidi"
version = "0.0.0"
dependencies = [
"cc",
+ "chrono",
"cpal",
"libc",
"rhai",
@@ -698,6 +746,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -731,6 +790,12 @@ dependencies = [
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
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<MidiRecording>,
pitch_bend: i32, // in cents
pedal_down: bool,
presets: [usize; CHANNEL_COUNT],
notes: [Vec<Note>; CHANNEL_COUNT],
channel_volumes: [f32; CHANNEL_COUNT],
master_volume: f32,
+ metronome: Metronome,
}
static NOTE_INFO: Mutex<NoteInfo> = 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<Option<soundfont::SoundFont>> = 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<midi_input::Device, String> {
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<Zone>,
@@ -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);