use midir::{Ignore, MidiInput, MidiOutput};
use mseq_core::{InputQueue, MidiMessage, MidiOut};
use promptly::{ReadlineError, prompt_default};
use std::sync::{Arc, Condvar, Mutex};
use thiserror::Error;
const CLOCK: u8 = 0xf8;
const START: u8 = 0xfa;
const CONTINUE: u8 = 0xfb;
const STOP: u8 = 0xfc;
const NOTE_ON: u8 = 0x90;
const NOTE_OFF: u8 = 0x80;
const CC: u8 = 0xB0;
const PC: u8 = 0xC0;
const PITCH_BEND: u8 = 0xE0;
#[derive(Error, Debug)]
pub enum MidiError {
#[error("Init error: {0}")]
Init(#[from] midir::InitError),
#[error("Connect error: {0}")]
OutConnect(#[from] midir::ConnectError<MidiOutput>),
#[error("Connect error: {0}")]
InConnect(#[from] midir::ConnectError<MidiInput>),
#[error("Send error: {0}")]
Send(#[from] midir::SendError),
#[error("Read line [{}: {}]", file!(), line!())]
ReadLine(#[from] ReadlineError),
#[error("Invalid port number selected")]
PortNumber(),
#[error("No midi output found")]
NoOutput(),
#[error("No midi input found")]
NoInput(),
}
pub struct StdMidiOut(midir::MidiOutputConnection);
impl StdMidiOut {
pub(crate) fn new(port: Option<u32>) -> Result<Self, MidiError> {
let midi_out = MidiOutput::new("out")?;
let out_ports = midi_out.ports();
let out_port = if let Some(p) = port {
match out_ports.get(p as usize) {
None => return Err(MidiError::PortNumber()),
Some(x) => x,
}
} else {
match out_ports.len() {
0 => return Err(MidiError::NoOutput()),
1 => {
println!(
"Choosing the only available output port: {}",
midi_out.port_name(&out_ports[0]).unwrap()
);
&out_ports[0]
}
_ => {
println!("\nAvailable output ports:");
for (i, p) in out_ports.iter().enumerate() {
println!("{}: {}", i, midi_out.port_name(p).unwrap());
}
let port_number: usize = prompt_default("Select output port", 0)?;
match out_ports.get(port_number) {
None => return Err(MidiError::PortNumber()),
Some(x) => x,
}
}
}
};
let conn = midi_out.connect(out_port, "output connection")?;
Ok(Self(conn))
}
}
impl MidiOut for StdMidiOut {
type Error = MidiError;
fn send_start(&mut self) -> Result<(), MidiError> {
self.0.send(&[START])?;
Ok(())
}
fn send_continue(&mut self) -> Result<(), MidiError> {
self.0.send(&[CONTINUE])?;
Ok(())
}
fn send_stop(&mut self) -> Result<(), MidiError> {
self.0.send(&[STOP])?;
Ok(())
}
fn send_clock(&mut self) -> Result<(), MidiError> {
self.0.send(&[CLOCK])?;
Ok(())
}
fn send_note_on(&mut self, channel_id: u8, note: u8, velocity: u8) -> Result<(), MidiError> {
self.0.send(&[NOTE_ON | (channel_id - 1), note, velocity])?;
Ok(())
}
fn send_note_off(&mut self, channel_id: u8, note: u8) -> Result<(), MidiError> {
self.0.send(&[NOTE_OFF | (channel_id - 1), note, 0])?;
Ok(())
}
fn send_cc(&mut self, channel_id: u8, parameter: u8, value: u8) -> Result<(), MidiError> {
self.0.send(&[CC | (channel_id - 1), parameter, value])?;
Ok(())
}
fn send_pc(&mut self, channel_id: u8, value: u8) -> Result<(), MidiError> {
self.0.send(&[PC | (channel_id - 1), value])?;
Ok(())
}
fn send_pitch_bend(&mut self, channel_id: u8, value: u16) -> Result<(), Self::Error> {
self.0.send(&[
PITCH_BEND | (channel_id - 1),
value as u8,
(value >> 7) as u8,
])?;
Ok(())
}
}
type QueueCondvar = (Arc<Mutex<InputQueue>>, Arc<Condvar>);
pub(crate) struct InQueues {
pub message: QueueCondvar,
pub slave_system: Option<QueueCondvar>,
_connection: midir::MidiInputConnection<(QueueCondvar, Option<QueueCondvar>)>,
}
#[derive(Clone)]
pub struct MidiInParam {
pub ignore: Ignore,
pub port: Option<u32>,
pub slave: bool,
}
pub(crate) fn connect(params: MidiInParam) -> Result<InQueues, MidiError> {
let mut midi_in = MidiInput::new("in")?;
midi_in.ignore(params.ignore);
let in_ports = midi_in.ports();
let in_port = if let Some(p) = params.port {
match in_ports.get(p as usize) {
None => return Err(MidiError::PortNumber()),
Some(x) => x,
}
} else {
match in_ports.len() {
0 => return Err(MidiError::NoInput()),
1 => {
println!(
"Choosing the only available intput port: {}",
midi_in.port_name(&in_ports[0]).unwrap()
);
&in_ports[0]
}
_ => {
println!("\nAvailable input ports:");
for (i, p) in in_ports.iter().enumerate() {
println!("{}: {}", i, midi_in.port_name(p).unwrap());
}
let port_number: usize = prompt_default("Select input port", 0)?;
match in_ports.get(port_number) {
None => return Err(MidiError::PortNumber()),
Some(x) => x,
}
}
}
};
let message_queue = Arc::new(Mutex::new(InputQueue::new()));
let message = (message_queue.clone(), Arc::new(Condvar::new()));
let slave_system = if params.slave {
Some((
Arc::new(Mutex::new(InputQueue::new())),
Arc::new(Condvar::new()),
))
} else {
None
};
let input = (message.clone(), slave_system.clone());
let _connection = midi_in.connect(
in_port,
"midir-read-input",
move |_, message, input| {
let m = MidiMessage::parse(message);
if let Some(m) = m {
match m {
MidiMessage::Clock
| MidiMessage::Start
| MidiMessage::Stop
| MidiMessage::Continue => {
if let Some((q, cv)) = &input.1 {
q.lock().unwrap().push_back(m);
cv.notify_all();
}
}
_ => {
input.0.0.lock().unwrap().push_back(m);
input.0.1.notify_all();
}
}
}
},
input,
)?;
Ok(InQueues {
message,
slave_system,
_connection,
})
}