summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-10-05 21:20:34 -0400
committerpommicket <pommicket@gmail.com>2022-10-05 21:20:34 -0400
commit87b3ef15b80b5420c8f65baa848a100deb6e2dda (patch)
treee4a1f74842812e4d42c3dff4f5a79aaf9793d8ba /src/main.rs
parent606c00d0cd934db900fbccf7916d03a570ee27cc (diff)
start rhai
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs343
1 files changed, 227 insertions, 116 deletions
diff --git a/src/main.rs b/src/main.rs
index bce1b12..4c5ebd5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
extern crate cpal;
+extern crate rhai;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use std::io::Write;
@@ -7,6 +8,9 @@ use std::sync::Mutex;
mod midi_input;
mod soundfont;
+const NOTE_FALLOFF: f32 = 0.1; // falloff when note is released
+const CHANNEL_COUNT: usize = 16;
+
struct Note {
key: u8,
req: soundfont::SamplesRequest,
@@ -15,8 +19,9 @@ struct Note {
}
struct NoteInfo {
- pitch_bend: i16, // in cents
+ pitch_bend: i32, // in cents
pedal_down: bool,
+ presets: [usize; CHANNEL_COUNT],
notes: Vec<Note>,
}
@@ -24,67 +29,63 @@ static NOTE_INFO: Mutex<NoteInfo> = Mutex::new(NoteInfo {
pitch_bend: 0,
notes: vec![],
pedal_down: false,
+ presets: [0; CHANNEL_COUNT],
});
static SOUNDFONT: Mutex<Option<soundfont::SoundFont>> = Mutex::new(None);
-fn playmidi_main() -> Result<(), String> {
+fn get_midi_device(idx: i32) -> Result<midi_input::Device, String> {
let mut device_mgr = midi_input::DeviceManager::new()?;
device_mgr.set_quiet(true);
let devices = device_mgr.list()?;
- for (index, device) in (&devices).into_iter().enumerate() {
- print!("{:3} | ", index + 1);
- let mut first = true;
- for line in device.name.lines() {
- if !first {
- print!(" | ");
- }
- println!("{}", line);
- first = false;
- }
- println!(" -----------------");
- }
- print!("Select a device (default {}): ", devices.default + 1);
- if std::io::stdout().flush().is_err() {
- //who cares
- }
-
- let device_id;
- {
- let mut buf = String::new();
- std::io::stdin()
- .read_line(&mut buf)
- .expect("error reading stdin");
- let s = buf.trim();
- if s.is_empty() {
- device_id = &devices[devices.default].id;
- } else {
- match s.parse::<usize>() {
- Ok(idx) if idx >= 1 && idx <= devices.len() => {
- device_id = &devices[idx - 1].id;
+
+ let device_idx = match idx {
+ 0 => devices.default,
+ i if i >= 1 => {
+ (i - 1) as usize
+ },
+ _ => {
+ // user selects device
+ for (index, device) in (&devices).into_iter().enumerate() {
+ print!("{:3} | ", index + 1);
+ let mut first = true;
+ for line in device.name.lines() {
+ if !first {
+ print!(" | ");
+ }
+ println!("{}", line);
+ first = false;
}
- _ => {
- return Err(format!("Bad device ID: {}", s));
+ println!(" -----------------");
+ }
+ print!("Select a device (default {}): ", devices.default + 1);
+ if std::io::stdout().flush().is_err() {
+ //who cares
+ }
+
+ let mut buf = String::new();
+ std::io::stdin()
+ .read_line(&mut buf)
+ .expect("error reading stdin");
+ let s = buf.trim();
+ if s.is_empty() {
+ devices.default
+ } else {
+ match s.parse::<usize>() {
+ Ok(idx) if idx >= 1 && idx <= devices.len() => {
+ idx - 1
+ }
+ _ => {
+ return Err(format!("Bad device ID: {}", s));
+ }
}
}
}
- }
- let mut midi_device = device_mgr
- .open(device_id)
- .expect("error opening MIDI device");
-
- let mut sf = soundfont::SoundFont::open("/etc/alternatives/default-GM.sf3")?;
-
- for i in 0..sf.preset_count() {
- println!("{}. {}", i, sf.preset_name(i).unwrap());
- }
-
- //sf._debug_preset_zones(125);
- //sf._debug_instrument_zones(148);
+ };
+ let device_id = &devices[device_idx].id;
+ Ok(device_mgr.open(device_id)?)
+}
- // let result = playmidi_main();
- // if let Err(s) = result {
- // eprintln!("Error: {}", s);
- // }
+fn get_audio_stream() -> Result<cpal::Stream, String> {
let host = cpal::default_host();
let audio_device = host
.default_output_device()
@@ -108,19 +109,6 @@ fn playmidi_main() -> Result<(), String> {
let supp_config: cpal::SupportedStreamConfig = chosen_config.with_max_sample_rate();
if supp_config.channels() != 2 {}
let config = supp_config.into();
- let preset = 299;
-
- {
- use std::time::Instant;
- let now = Instant::now();
- sf.load_samples_for_preset(preset).expect("oh no");
- println!("Loaded in {:?}", now.elapsed());
- }
-
- {
- let mut sflock = SOUNDFONT.lock().expect("couldn't lock soundfont.");
- *sflock = Some(sf);
- }
let stream = audio_device
.build_output_stream(
@@ -146,69 +134,192 @@ fn playmidi_main() -> Result<(), String> {
}
},
move |err| {
- println!("audio stream error: {}", err);
+ eprintln!("audio stream error: {}", err);
},
- )
- .expect("couldn't build output stream");
- stream.play().expect("couldn't play stream");
+ ).map_err(|e| format!("{}", e))?;
+ Ok(stream)
+}
+
+#[must_use]
+fn check_channel(channel: usize) -> bool {
+ if channel >= CHANNEL_COUNT {
+ eprintln!("channel {} out of range", channel);
+ false
+ } else {
+ true
+ }
+}
+
+fn play_note(channel: i64, note: i64, vel: i64) {
+ let channel = channel as usize;
+ let note = note as u8;
+ let vel = vel as u8;
+
+ if !check_channel(channel) {
+ return;
+ }
+
+ let mut note_info = NOTE_INFO
+ .lock()
+ .expect("couldn't lock notes");
+ let mut maybe_sf = SOUNDFONT.lock().expect("couldn't lock soundfont.");
+ note_info.notes.retain(|n| n.key != note);
+ let preset = note_info.presets[channel];
+ if let Some(sf) = maybe_sf.as_mut() {
+ match sf.request(preset, note, vel) {
+ Ok(mut req) => {
+ req.set_volume(0.1);
+ note_info.notes.push(Note {
+ key: note,
+ req,
+ down: true,
+ kill: false,
+ });
+ }
+ Err(e) => eprintln!("get samples error: {}", e),
+ }
+ }
+}
+
+fn release_note(note: u8) {
+ let mut note_info = NOTE_INFO
+ .lock()
+ .expect("couldn't lock notes");
+ let pedal_down = note_info.pedal_down;
+ if let Some(n) = note_info.notes.iter_mut().find(|n| n.key == note) {
+ n.down = false;
+ if !pedal_down {
+ n.req.set_falloff(NOTE_FALLOFF);
+ }
+ }
+}
- let note_falloff = 0.1; // falloff when note is released
+fn set_pedal_down(down: bool) {
+ let mut note_info = NOTE_INFO
+ .lock()
+ .expect("couldn't lock notes");
+ note_info.pedal_down = down;
+ if down {
+ // disable falloff for all notes
+ for note in note_info.notes.iter_mut() {
+ note.req.set_falloff(1.0);
+ }
+ } else {
+ // start falloff for all non-down notes
+ for note in note_info.notes.iter_mut() {
+ if !note.down {
+ note.req.set_falloff(NOTE_FALLOFF);
+ }
+ }
+ }
+}
+
+fn set_pitch_bend(amount: i32) {
+ let mut note_info = NOTE_INFO
+ .lock()
+ .expect("couldn't lock notes");
+ note_info.pitch_bend = amount;
+}
+
+fn load_soundfont(filename: &str) {
+ if let Ok(sf) = soundfont::SoundFont::open(filename) {
+ for i in 0..sf.preset_count() {
+ println!("{}. {}", i, sf.preset_name(i).unwrap());
+ }
+ let mut sflock = SOUNDFONT.lock().expect("couldn't lock soundfont.");
+ *sflock = Some(sf);
+ } else {
+ eprintln!("Couldn't open soundfont: {}", filename);
+ }
+}
+
+fn load_preset(channel: i64, preset: i64) {
+ if !check_channel(channel as usize) {
+ return;
+ }
+ let preset = preset as usize;
+ let channel = channel as usize;
+ let mut note_info = NOTE_INFO.lock().expect("couldn't lock notes");
+ let mut soundfont = SOUNDFONT.lock().expect("couldn't lock soundfont.");
+ if let Some(sf) = soundfont.as_mut() {
+ if preset >= sf.preset_count() {
+ eprintln!("preset {} out of range", preset);
+ } else {
+ note_info.presets[channel] = preset;
+ if let Err(e) = sf.load_samples_for_preset(preset) {
+ eprintln!("error loading preset {}: {}", preset, e);
+ }
+ }
+ }
+
+}
+
+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) {
+ Ok(_) => {},
+ Err(e) => eprintln!("Warning: rhai error: {}", e),
+ }
+}
+
+// @TODO change this to -> () and handle errors
+fn playmidi_main() -> Result<(), String> {
+ let mut engine = rhai::Engine::new();
+ engine.register_fn("pm_load_soundfont", load_soundfont);
+ engine.register_fn("pm_load_preset", load_preset);
+ engine.register_fn("pm_play_note", play_note);
+ let engine = engine; // de-multablify
+ let mut ast = engine.compile_file("config.rhai".into()).map_err(|e| format!("{}", e))?;
+ engine.run_ast(&ast).map_err(|e| format!("{}", e))?;
+
+
+ let mut midi_device;
+ {
+ let mut idx = -1;
+ for (name, _, value) in ast.iter_literal_variables(true, true) {
+ if name == "PM_DEVICE_ID" {
+ match value.as_int() {
+ Ok(i) => match i.try_into() {
+ Ok(i) => idx = i,
+ Err(_) => eprintln!("PM_DEVICE_ID {} too large.", i),
+ },
+ Err(t) => eprintln!("Warning: PM_DEVICE_ID should be integer, not {}.", t),
+ }
+ }
+ }
+ midi_device = get_midi_device(idx)?;
+ }
+
+ // without this, top-level statements will be executed each time a function is called
+ ast.clear_statements();
+
+ let ast = ast; // de-mutablify
+
+// load_soundfont("/etc/alternatives/default-GM.sf3");
+// {
+// use std::time::Instant;
+// let now = Instant::now();
+// sf.load_samples_for_preset(preset).expect("oh no");
+// println!("Loaded in {:?}", now.elapsed());
+// }
+
+ let stream = get_audio_stream()?;
+ stream.play().map_err(|e| format!("{}", e))?;
+
while midi_device.is_connected() {
while let Some(event) = midi_device.read_event() {
- let mut note_info = NOTE_INFO
- .lock()
- .map_err(|_| "couldn't lock notes".to_string())?;
use midi_input::Event::*;
match event {
- NoteOn { note, vel, .. } => {
- let mut maybe_sf = SOUNDFONT.lock().expect("couldn't lock soundfont.");
- note_info.notes.retain(|n| n.key != note);
- if let Some(sf) = maybe_sf.as_mut() {
- match sf.request(preset, note, vel) {
- Ok(mut req) => {
- req.set_volume(0.1);
- note_info.notes.push(Note {
- key: note,
- req,
- down: true,
- kill: false,
- });
- }
- Err(e) => eprintln!("get samples error: {}", e),
- }
- }
- }
- NoteOff { note, .. } => {
- let pedal_down = note_info.pedal_down;
- if let Some(n) = note_info.notes.iter_mut().find(|n| n.key == note) {
- n.down = false;
- if !pedal_down {
- n.req.set_falloff(note_falloff);
- }
- }
- }
- PitchBend { amount, .. } => {
- note_info.pitch_bend = amount / 128;
- }
+ NoteOn { channel, note, vel } =>
+ call_fn_if_exists(&engine, &ast, "pm_note_played", (channel as i64, note as i64, vel as i64)),
+ NoteOff { note, .. } => release_note(note),
+ PitchBend { amount, .. } => set_pitch_bend(amount as i32 / 128),
ControlChange {
controller, value, ..
} => {
if controller == 64 {
// oddly, a value of 0 means "down"
- note_info.pedal_down = value < 127;
- if note_info.pedal_down {
- // disable falloff for all notes
- for note in note_info.notes.iter_mut() {
- note.req.set_falloff(1.0);
- }
- } else {
- // start falloff for all non-down notes
- for note in note_info.notes.iter_mut() {
- if !note.down {
- note.req.set_falloff(note_falloff);
- }
- }
- }
+ set_pedal_down(value < 127);
}
}
_ => {}