More robust midi implementation
This commit is contained in:
107
src/midi.rs
107
src/midi.rs
@@ -4,6 +4,9 @@ use midir::{MidiInput, MidiOutput};
|
||||
|
||||
use crate::model::CcMemory;
|
||||
|
||||
pub const MAX_MIDI_OUTPUTS: usize = 4;
|
||||
pub const MAX_MIDI_INPUTS: usize = 4;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MidiDeviceInfo {
|
||||
pub name: String,
|
||||
@@ -42,10 +45,10 @@ pub fn list_midi_inputs() -> Vec<MidiDeviceInfo> {
|
||||
}
|
||||
|
||||
pub struct MidiState {
|
||||
output_conn: Option<midir::MidiOutputConnection>,
|
||||
input_conn: Option<midir::MidiInputConnection<CcMemory>>,
|
||||
pub selected_output: Option<usize>,
|
||||
pub selected_input: Option<usize>,
|
||||
output_conns: [Option<midir::MidiOutputConnection>; MAX_MIDI_OUTPUTS],
|
||||
input_conns: [Option<midir::MidiInputConnection<(CcMemory, usize)>>; MAX_MIDI_INPUTS],
|
||||
pub selected_outputs: [Option<usize>; MAX_MIDI_OUTPUTS],
|
||||
pub selected_inputs: [Option<usize>; MAX_MIDI_INPUTS],
|
||||
pub cc_memory: CcMemory,
|
||||
}
|
||||
|
||||
@@ -58,82 +61,105 @@ impl Default for MidiState {
|
||||
impl MidiState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
output_conn: None,
|
||||
input_conn: None,
|
||||
selected_output: None,
|
||||
selected_input: None,
|
||||
cc_memory: Arc::new(Mutex::new([[0u8; 128]; 16])),
|
||||
output_conns: [None, None, None, None],
|
||||
input_conns: [None, None, None, None],
|
||||
selected_outputs: [None; MAX_MIDI_OUTPUTS],
|
||||
selected_inputs: [None; MAX_MIDI_INPUTS],
|
||||
cc_memory: Arc::new(Mutex::new([[[0u8; 128]; 16]; MAX_MIDI_OUTPUTS])),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_output(&mut self, index: usize) -> Result<(), String> {
|
||||
let midi_out = MidiOutput::new("cagire-out").map_err(|e| e.to_string())?;
|
||||
pub fn connect_output(&mut self, slot: usize, port_index: usize) -> Result<(), String> {
|
||||
if slot >= MAX_MIDI_OUTPUTS {
|
||||
return Err("Invalid output slot".to_string());
|
||||
}
|
||||
let midi_out = MidiOutput::new(&format!("cagire-out-{slot}")).map_err(|e| e.to_string())?;
|
||||
let ports = midi_out.ports();
|
||||
let port = ports.get(index).ok_or("MIDI output port not found")?;
|
||||
let port = ports.get(port_index).ok_or("MIDI output port not found")?;
|
||||
let conn = midi_out
|
||||
.connect(port, "cagire-midi-out")
|
||||
.connect(port, &format!("cagire-midi-out-{slot}"))
|
||||
.map_err(|e| e.to_string())?;
|
||||
self.output_conn = Some(conn);
|
||||
self.selected_output = Some(index);
|
||||
self.output_conns[slot] = Some(conn);
|
||||
self.selected_outputs[slot] = Some(port_index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn connect_input(&mut self, index: usize) -> Result<(), String> {
|
||||
let midi_in = MidiInput::new("cagire-in").map_err(|e| e.to_string())?;
|
||||
pub fn disconnect_output(&mut self, slot: usize) {
|
||||
if slot < MAX_MIDI_OUTPUTS {
|
||||
self.output_conns[slot] = None;
|
||||
self.selected_outputs[slot] = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_input(&mut self, slot: usize, port_index: usize) -> Result<(), String> {
|
||||
if slot >= MAX_MIDI_INPUTS {
|
||||
return Err("Invalid input slot".to_string());
|
||||
}
|
||||
let midi_in = MidiInput::new(&format!("cagire-in-{slot}")).map_err(|e| e.to_string())?;
|
||||
let ports = midi_in.ports();
|
||||
let port = ports.get(index).ok_or("MIDI input port not found")?;
|
||||
let port = ports.get(port_index).ok_or("MIDI input port not found")?;
|
||||
|
||||
let cc_mem = Arc::clone(&self.cc_memory);
|
||||
let conn = midi_in
|
||||
.connect(
|
||||
port,
|
||||
"cagire-midi-in",
|
||||
move |_timestamp, message, cc_mem| {
|
||||
&format!("cagire-midi-in-{slot}"),
|
||||
move |_timestamp, message, (cc_mem, slot)| {
|
||||
if message.len() >= 3 {
|
||||
let status = message[0];
|
||||
let data1 = message[1] as usize;
|
||||
let data2 = message[2];
|
||||
// CC message: 0xBn where n is channel 0-15
|
||||
if (status & 0xF0) == 0xB0 && data1 < 128 {
|
||||
let channel = (status & 0x0F) as usize;
|
||||
if let Ok(mut mem) = cc_mem.lock() {
|
||||
mem[channel][data1] = data2;
|
||||
mem[*slot][channel][data1] = data2;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
cc_mem,
|
||||
(cc_mem, slot),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
self.input_conn = Some(conn);
|
||||
self.selected_input = Some(index);
|
||||
self.input_conns[slot] = Some(conn);
|
||||
self.selected_inputs[slot] = Some(port_index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_note_on(&mut self, channel: u8, note: u8, velocity: u8) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn disconnect_input(&mut self, slot: usize) {
|
||||
if slot < MAX_MIDI_INPUTS {
|
||||
self.input_conns[slot] = None;
|
||||
self.selected_inputs[slot] = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_note_on(&mut self, device: u8, channel: u8, note: u8, velocity: u8) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let status = 0x90 | (channel & 0x0F);
|
||||
let _ = conn.send(&[status, note & 0x7F, velocity & 0x7F]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_note_off(&mut self, channel: u8, note: u8) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn send_note_off(&mut self, device: u8, channel: u8, note: u8) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let status = 0x80 | (channel & 0x0F);
|
||||
let _ = conn.send(&[status, note & 0x7F, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_cc(&mut self, channel: u8, cc: u8, value: u8) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn send_cc(&mut self, device: u8, channel: u8, cc: u8, value: u8) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let status = 0xB0 | (channel & 0x0F);
|
||||
let _ = conn.send(&[status, cc & 0x7F, value & 0x7F]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_pitch_bend(&mut self, channel: u8, value: u16) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn send_pitch_bend(&mut self, device: u8, channel: u8, value: u16) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let status = 0xE0 | (channel & 0x0F);
|
||||
let lsb = (value & 0x7F) as u8;
|
||||
let msb = ((value >> 7) & 0x7F) as u8;
|
||||
@@ -141,22 +167,25 @@ impl MidiState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_pressure(&mut self, channel: u8, value: u8) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn send_pressure(&mut self, device: u8, channel: u8, value: u8) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let status = 0xD0 | (channel & 0x0F);
|
||||
let _ = conn.send(&[status, value & 0x7F]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_program_change(&mut self, channel: u8, program: u8) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn send_program_change(&mut self, device: u8, channel: u8, program: u8) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let status = 0xC0 | (channel & 0x0F);
|
||||
let _ = conn.send(&[status, program & 0x7F]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_realtime(&mut self, msg: u8) {
|
||||
if let Some(conn) = &mut self.output_conn {
|
||||
pub fn send_realtime(&mut self, device: u8, msg: u8) {
|
||||
let slot = (device as usize).min(MAX_MIDI_OUTPUTS - 1);
|
||||
if let Some(conn) = &mut self.output_conns[slot] {
|
||||
let _ = conn.send(&[msg]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user