midi-keys/src/bin/main.rs
2025-01-03 14:25:28 -05:00

131 lines
3.8 KiB
Rust

use std::{
fs::File,
io::{stdin, Write},
};
use anyhow::{anyhow, Result};
use midi_keys::{log::load_raw_log, midi::daemon::Category, parser::parse_message};
//use enigo::{Direction, Enigo, Key, Keyboard, Settings};
use midir::{MidiInput, MidiInputPort};
fn main() {
//let mut enigo = Enigo::new(&Settings::default()).unwrap();
//enigo.text("echo \"hello world\"").unwrap();
//enigo.key(Key::Return, Direction::Press).unwrap();
let (dbg_send, dbg_recv) = crossbeam::channel::unbounded();
let (ws_send, ws_recv) = crossbeam::channel::unbounded();
let (drum_send, drum_recv) = crossbeam::channel::unbounded();
let all = Category::All;
std::thread::spawn(move || {
midi_keys::midi::daemon::run_daemon(vec![
(all, dbg_send),
(Category::WindSynth, ws_send),
(Category::Drums, drum_send),
]);
});
std::thread::spawn(move || loop {
match dbg_recv.recv() {
Ok(m) => println!("debug: {m:?}"),
Err(err) => println!("err: {err:?}"),
}
});
std::thread::spawn(move || loop {
match ws_recv.recv() {
Ok(m) => println!("windsynth: {m:?}"),
Err(err) => println!("err(ws): {err:?}"),
}
});
std::thread::spawn(move || loop {
match drum_recv.recv() {
Ok(m) => println!("drum: {m:?}"),
Err(err) => println!("err(drum): {err:?}"),
}
});
midi_keys::ui::run();
}
pub fn run() -> Result<()> {
let midi_in = MidiInput::new("keyboard")?;
let midi_device = match find_first_midi_device(&midi_in) {
Ok(m) => m,
Err(e) => {
println!("error: {}", e);
return Ok(());
}
};
let port_name = midi_in.port_name(&midi_device)?;
let output_file = File::options().append(true).create(true).open("midi.log")?;
let _midi_connection = match midi_in.connect(
&midi_device,
&port_name,
handle_midi_event,
Some(output_file),
) {
Ok(m) => m,
Err(err) => return Err(anyhow!("failed to connect to device: {}", err)),
};
// TODO: instead, listen for a signal like control-d
let mut buf = String::new();
stdin().read_line(&mut buf)?;
// TODO
let _ = replay_file("assets/windsynth.log");
Ok(())
}
fn replay_file(filename: &str) -> Result<()> {
let entries = load_raw_log(filename)?;
for entry in entries {
handle_midi_event(entry.ts, &entry.message, &mut None);
}
Ok(())
}
fn handle_midi_event(timestamp: u64, message: &[u8], file: &mut Option<File>) {
if let Some(file) = file {
let ts_buf = timestamp.to_be_bytes();
let len_buf = message.len().to_be_bytes();
file.write_all(&ts_buf).unwrap();
file.write_all(&len_buf).unwrap();
file.write_all(message).unwrap();
}
let hex_msg = hex::encode(message);
let msg = match parse_message(message) {
Ok((_n, msg)) => format!("{:?}", msg),
Err(_) => "failed to parse message".to_string(),
};
println!("{timestamp} > {hex_msg} - {msg}");
}
/// Finds the first MIDI input port which corresponds to a physical MIDI device
/// connected to this computer.
fn find_first_midi_device(midi_in: &MidiInput) -> Result<MidiInputPort> {
let ports = midi_in.ports();
// "Midi Through" is automatically created by the snd-seq-dummy kernel module.
// We can ignore it, since it's not a physical MIDI device, which is what we
// are looking for.
//
// Source: https://www.reddit.com/r/linuxaudio/comments/jsrl31/comment/gc16qwu/
let mut physical_ports = ports.iter().filter(|p| {
midi_in
.port_name(p)
.map_or(false, |n| !n.starts_with("Midi Through:"))
});
physical_ports
.next()
.cloned()
.ok_or(anyhow!("no physical MIDI devices found"))
}