Lots + MIDI implementation

This commit is contained in:
2026-01-31 23:13:51 +01:00
parent b5fe6a1437
commit 03c0baf5b5
34 changed files with 4323 additions and 191 deletions

176
src/midi.rs Normal file
View File

@@ -0,0 +1,176 @@
use midir::{MidiInput, MidiOutput};
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug)]
pub struct MidiDeviceInfo {
pub name: String,
pub port_index: usize,
}
pub fn list_midi_outputs() -> Vec<MidiDeviceInfo> {
let Ok(midi_out) = MidiOutput::new("cagire-probe") else {
return Vec::new();
};
midi_out
.ports()
.iter()
.enumerate()
.filter_map(|(idx, port)| {
midi_out.port_name(port).ok().map(|name| MidiDeviceInfo {
name,
port_index: idx,
})
})
.collect()
}
pub fn list_midi_inputs() -> Vec<MidiDeviceInfo> {
let Ok(midi_in) = MidiInput::new("cagire-probe") else {
return Vec::new();
};
midi_in
.ports()
.iter()
.enumerate()
.filter_map(|(idx, port)| {
midi_in.port_name(port).ok().map(|name| MidiDeviceInfo {
name,
port_index: idx,
})
})
.collect()
}
pub type CcMemory = Arc<Mutex<[[u8; 128]; 16]>>;
pub struct MidiState {
output_conn: Option<midir::MidiOutputConnection>,
input_conn: Option<midir::MidiInputConnection<CcMemory>>,
pub selected_output: Option<usize>,
pub selected_input: Option<usize>,
pub cc_memory: CcMemory,
}
impl Default for MidiState {
fn default() -> Self {
Self::new()
}
}
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])),
}
}
pub fn connect_output(&mut self, index: usize) -> Result<(), String> {
let midi_out = MidiOutput::new("cagire-out").map_err(|e| e.to_string())?;
let ports = midi_out.ports();
let port = ports.get(index).ok_or("MIDI output port not found")?;
let conn = midi_out
.connect(port, "cagire-midi-out")
.map_err(|e| e.to_string())?;
self.output_conn = Some(conn);
self.selected_output = Some(index);
Ok(())
}
pub fn disconnect_output(&mut self) {
if let Some(conn) = self.output_conn.take() {
conn.close();
}
self.selected_output = None;
}
pub fn connect_input(&mut self, index: usize) -> Result<(), String> {
let midi_in = MidiInput::new("cagire-in").map_err(|e| e.to_string())?;
let ports = midi_in.ports();
let port = ports.get(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| {
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;
}
}
}
},
cc_mem,
)
.map_err(|e| e.to_string())?;
self.input_conn = Some(conn);
self.selected_input = Some(index);
Ok(())
}
pub fn disconnect_input(&mut self) {
if let Some(conn) = self.input_conn.take() {
conn.close();
}
self.selected_input = None;
}
pub fn send_note_on(&mut self, channel: u8, note: u8, velocity: u8) {
if let Some(conn) = &mut self.output_conn {
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 {
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 {
let status = 0xB0 | (channel & 0x0F);
let _ = conn.send(&[status, cc & 0x7F, value & 0x7F]);
}
}
pub fn send_all_notes_off(&mut self) {
if let Some(conn) = &mut self.output_conn {
for channel in 0..16u8 {
let status = 0xB0 | channel;
let _ = conn.send(&[status, 123, 0]); // CC 123 = All Notes Off
}
}
}
pub fn get_cc(&self, channel: u8, cc: u8) -> u8 {
let channel = (channel as usize).min(15);
let cc = (cc as usize).min(127);
self.cc_memory
.lock()
.map(|mem| mem[channel][cc])
.unwrap_or(0)
}
pub fn is_output_connected(&self) -> bool {
self.output_conn.is_some()
}
pub fn is_input_connected(&self) -> bool {
self.input_conn.is_some()
}
}