use std::{
sync::{mpsc, Arc, Mutex},
thread,
time::Duration,
};
use anyhow::anyhow;
use midi_message::MidiMessage;
use midir::{MidiInput, MidiOutput, MidiOutputConnection};
pub mod midi_message;
pub struct Midi;
pub struct MidiOutputPort {
conn: MidiOutputConnection,
}
impl MidiOutputPort {
pub fn send(&mut self, message: &[u8]) -> anyhow::Result<()> {
self.conn
.send(message)
.map_err(|e| anyhow!("MIDI send error: {}", e))
}
pub fn send_cc(&mut self, channel: u8, controller: u8, value: u8) -> anyhow::Result<()> {
let msg = MidiMessage::new(
channel,
midi_message::MidiMessageType::ControlChange { controller, value },
None,
);
self.send(&msg.to_raw_message())
}
pub fn send_note_on(&mut self, channel: u8, key: u8, velocity: u8) -> anyhow::Result<()> {
let msg = MidiMessage::new(
channel,
midi_message::MidiMessageType::NoteOn { key, velocity },
None,
);
self.send(&msg.to_raw_message())
}
}
impl Midi {
pub fn get_devices() -> anyhow::Result<(Vec<String>, Vec<String>)> {
let input = MidiInput::new("inputs")?;
let output = MidiOutput::new("outputs")?;
let mut input_devices: Vec<String> = vec![];
let mut output_devices: Vec<String> = vec![];
for p in input.ports().iter() {
if let Ok(name) = input.port_name(p) {
input_devices.push(name);
}
}
for p in output.ports().iter() {
if let Ok(name) = output.port_name(p) {
output_devices.push(name);
}
}
Ok((input_devices, output_devices))
}
pub fn connect_output(device: &str) -> anyhow::Result<MidiOutputPort> {
let midi_output = MidiOutput::new("cnctd-output")?;
let port = Self::find_port(&midi_output, device)
.ok_or_else(|| anyhow!("MIDI output device '{}' not found", device))?;
let conn = midi_output
.connect(&port, "cnctd-output")
.map_err(|e| anyhow!("Failed to connect MIDI output: {}", e))?;
Ok(MidiOutputPort { conn })
}
pub fn listen_to_device(device: &str, tx: mpsc::Sender<MidiMessage>) -> anyhow::Result<()> {
let midi_input = MidiInput::new("inputs")?;
let in_port = Self::find_port(&midi_input, device)
.ok_or_else(|| anyhow!("MIDI device not connected"))?;
let tx = Arc::new(Mutex::new(tx));
let _conn_in = midi_input.connect(
&in_port,
"midir-read-input",
move |stamp, message, _| {
let tx = tx.clone();
let message = message.to_owned();
if let Some(msg) = MidiMessage::from_raw_message(&message, stamp) {
if let Ok(tx) = tx.lock() {
let _ = tx.send(msg);
}
}
},
(),
);
loop {
thread::sleep(Duration::from_secs(10))
}
}
fn find_port<T>(midi_io: &T, device: &str) -> Option<T::Port>
where
T: midir::MidiIO,
{
for port in midi_io.ports() {
if let Ok(port_name) = midi_io.port_name(&port) {
if port_name.contains(device) {
return Some(port);
}
}
}
None
}
}