diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 222 |
1 files changed, 132 insertions, 90 deletions
diff --git a/src/main.rs b/src/main.rs index 5d8e110..2a0e8e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,16 @@ +extern crate chrono; extern crate cpal; extern crate rhai; -extern crate chrono; + +mod midi_input; +mod soundfont; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use std::fs::File; +// use std::fs::File; +use soundfont::SoundFont; use std::io::Write; use std::sync::Mutex; -mod midi_input; -mod soundfont; - const NOTE_FALLOFF: f32 = 0.1; // falloff when note is released const CHANNEL_COUNT: usize = 17; // 16 MIDI channels + metronome const METRONOME_CHANNEL: i64 = 16; @@ -25,16 +26,16 @@ struct Note { struct Metronome { bpm: f32, key: i64, - volume: f32 + volume: f32, } -struct MidiRecording { - file: File, - last_event_time: std::time::Instant, -} +// struct MidiRecording { +// file: File, +// last_event_time: std::time::Instant, +// } struct NoteInfo { - midi_out: Option<MidiRecording>, + // midi_out: Option<MidiRecording>, pitch_bend: i32, // in cents pedal_down: bool, presets: [usize; CHANNEL_COUNT], @@ -45,10 +46,28 @@ struct NoteInfo { } static NOTE_INFO: Mutex<NoteInfo> = Mutex::new(NoteInfo { - midi_out: None, + // 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![], 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], @@ -56,31 +75,39 @@ static NOTE_INFO: Mutex<NoteInfo> = Mutex::new(NoteInfo { metronome: Metronome { bpm: 0.0, key: 0, - volume: 0.0 + volume: 0.0, }, }); -static SOUNDFONT: Mutex<Option<soundfont::SoundFont>> = Mutex::new(None); +static SOUNDFONT: Mutex<Option<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 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 lock_note_info_and_soundfont<'a>() -> ( + std::sync::MutexGuard<'a, NoteInfo>, + std::sync::MutexGuard<'a, Option<SoundFont>>, +) { + ( + lock_note_info(), + SOUNDFONT.lock().expect("couldn't lock soundfont"), + ) +} + 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()?; - + let device_idx = match idx { 0 => devices.default, - i if i >= 1 => { - (i - 1) as usize - }, + i if i >= 1 => (i - 1) as usize, _ => { // user selects device for (index, device) in (&devices).into_iter().enumerate() { @@ -99,7 +126,7 @@ fn get_midi_device(idx: i32) -> Result<midi_input::Device, String> { if std::io::stdout().flush().is_err() { //who cares } - + let mut buf = String::new(); std::io::stdin() .read_line(&mut buf) @@ -109,9 +136,7 @@ fn get_midi_device(idx: i32) -> Result<midi_input::Device, String> { devices.default } else { match s.parse::<usize>() { - Ok(idx) if idx >= 1 && idx <= devices.len() => { - idx - 1 - } + Ok(idx) if idx >= 1 && idx <= devices.len() => idx - 1, _ => { return Err(format!("Bad device ID: {}", s)); } @@ -152,8 +177,7 @@ fn get_audio_stream() -> Result<cpal::Stream, String> { .build_output_stream( &config, move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { - let mut note_info = NOTE_INFO.lock().expect("couldn't lock notes."); - let mut maybe_sf = SOUNDFONT.lock().expect("couldn't lock soundfont."); + let (mut note_info, mut maybe_sf) = lock_note_info_and_soundfont(); if let Some(sf) = maybe_sf.as_mut() { let sample_rate = config.sample_rate.0 as f64; for x in data.iter_mut() { @@ -161,7 +185,8 @@ fn get_audio_stream() -> Result<cpal::Stream, String> { } let pitch_bend = note_info.pitch_bend; for channel in 0..CHANNEL_COUNT { - let volume = 0.1 * note_info.master_volume * note_info.channel_volumes[channel]; + let volume = + 0.1 * note_info.master_volume * note_info.channel_volumes[channel]; let notes = &mut note_info.notes[channel]; for note in notes.iter_mut() { note.req.set_tune(pitch_bend as i32); @@ -179,7 +204,8 @@ fn get_audio_stream() -> Result<cpal::Stream, String> { move |err| { eprintln!("audio stream error: {}", err); }, - ).map_err(|e| format!("{}", e))?; + ) + .map_err(|e| format!("{}", e))?; Ok(stream) } @@ -197,15 +223,12 @@ 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."); + + let (mut note_info, mut maybe_sf) = lock_note_info_and_soundfont(); note_info.notes[channel].retain(|n| n.key != note); let preset = note_info.presets[channel]; if let Some(sf) = maybe_sf.as_mut() { @@ -229,12 +252,10 @@ fn release_note(channel: i64, note: i64) { return; } let note = note as u8; - - let mut note_info = NOTE_INFO - .lock() - .expect("couldn't lock notes"); + + let mut note_info = NOTE_INFO.lock().expect("couldn't lock notes"); let pedal_down = note_info.pedal_down; - let notes = &mut note_info.notes[channel]; + let notes = &mut note_info.notes[channel]; if let Some(n) = notes.iter_mut().find(|n| n.key == note && n.down) { n.down = false; if !pedal_down { @@ -244,12 +265,10 @@ fn release_note(channel: i64, note: i64) { } fn set_pedal_down(down: bool) { - let mut note_info = NOTE_INFO - .lock() - .expect("couldn't lock notes"); + let mut note_info = NOTE_INFO.lock().expect("couldn't lock notes"); note_info.pedal_down = down; for channel in 0..CHANNEL_COUNT { - let notes = &mut note_info.notes[channel]; + let notes = &mut note_info.notes[channel]; if down { // disable falloff for all notes for note in notes.iter_mut() { @@ -268,14 +287,12 @@ fn set_pedal_down(down: bool) { fn set_pitch_bend(amount: f64) { let amount = amount as i32; - let mut note_info = NOTE_INFO - .lock() - .expect("couldn't lock notes"); + 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) { + if let Ok(sf) = SoundFont::open(filename) { for i in 0..sf.preset_count() { println!("{}. {}", i, sf.preset_name(i).unwrap()); } @@ -304,7 +321,6 @@ fn load_preset(channel: i64, preset: i64) { } } } - } fn set_volume(channel: i64, volume: f64) { @@ -334,15 +350,23 @@ fn set_metronome(key: i64, bpm: f64, volume: f64) { // 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) { +// +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) { - Ok(_) => {}, - Err(rhai::EvalAltResult::ErrorFunctionNotFound(s, _)) if s == name => - {/* function not found */}, + match engine + .call_fn::<()>(&mut scope, ast, name, args) + .map_err(|e| *e) + { + Ok(_) => {} + Err(rhai::EvalAltResult::ErrorFunctionNotFound(s, _)) if s == name => { /* function not found */ + } Err(e) => eprintln!("Warning: rhai error: {}", e), } } @@ -362,13 +386,14 @@ fn playmidi_main() -> Result<(), String> { 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); - + // 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))?; + 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; @@ -376,64 +401,81 @@ fn playmidi_main() -> Result<(), String> { 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), - }, + 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()); -// } + + // 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))?; - + 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"); + let note_info = lock_note_info(); 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); + 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 { - NoteOn { channel, note, vel } => - call_fn_if_exists(&engine, &ast, "pm_note_played", (channel as i64, note as i64, vel as i64)), - NoteOff { channel, note, vel } => - call_fn_if_exists(&engine, &ast, "pm_note_released", (channel as i64, note as i64, vel as i64)), + NoteOn { channel, note, vel } => call_fn_if_exists( + &engine, + &ast, + "pm_note_played", + (channel as i64, note as i64, vel as i64), + ), + NoteOff { channel, note, vel } => call_fn_if_exists( + &engine, + &ast, + "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)); } ControlChange { - channel, controller, value + channel, + controller, + value, } => { - call_fn_if_exists(&engine, &ast, "pm_control_changed", (channel as i64, controller as i64, value as i64)); + call_fn_if_exists( + &engine, + &ast, + "pm_control_changed", + (channel as i64, controller as i64, value as i64), + ); } _ => {} } |