use crate::core::io::osc::Osc;
use crate::core::io::udp::Udp;
use midir::{MidiInput, MidiOutput, MidiOutputConnection};
use rosc::{OscMessage, OscPacket, OscType, encoder};
use std::net::UdpSocket;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MidiNote {
pub channel: u8,
pub octave: u8,
pub note: char,
pub note_id: u8,
pub velocity: u8,
pub length: usize,
pub is_played: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MidiCc {
pub channel: u8,
pub knob: u8,
pub value: u8,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MidiPb {
pub channel: u8,
pub lsb: u8,
pub msb: u8,
}
#[derive(Debug)]
pub enum MidiMessage {
Cc(MidiCc),
Pb(MidiPb),
}
pub struct MidiState {
pub out: Option<MidiOutputConnection>,
pub stack: Vec<MidiNote>,
pub mono_stack: [Option<MidiNote>; 16],
pub cc_stack: Vec<MidiMessage>,
pub osc: Osc,
pub udp: Udp,
pub cc_offset: u8,
pub device_name: String,
pub input_device_name: String,
pub output_index: i32,
pub input_index: i32,
pub udp_socket: Option<UdpSocket>,
pub ip: String,
pub osc_midi_bidule: Option<String>,
}
impl std::fmt::Debug for MidiState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MidiState")
.field("device_name", &self.device_name)
.field("input_device_name", &self.input_device_name)
.field("output_index", &self.output_index)
.field("input_index", &self.input_index)
.field("cc_offset", &self.cc_offset)
.field("ip", &self.ip)
.field("osc_midi_bidule", &self.osc_midi_bidule)
.field("stack_len", &self.stack.len())
.field("cc_stack_len", &self.cc_stack.len())
.finish_non_exhaustive()
}
}
impl MidiState {
pub fn new() -> Self {
let mut state = Self {
out: None,
stack: Vec::new(),
mono_stack: std::array::from_fn(|_| None),
cc_stack: Vec::new(),
osc: Osc::new(49162),
udp: Udp::new(49161),
cc_offset: 64,
device_name: String::from("No Midi Device"),
input_device_name: String::from("No Input Device"),
output_index: -1,
input_index: -1,
udp_socket: UdpSocket::bind("0.0.0.0:0").ok(),
ip: String::from("127.0.0.1"),
osc_midi_bidule: None,
};
state.select_next_output();
state
}
pub fn select_next_output(&mut self) {
if let Ok(midi) = MidiOutput::new("o2") {
let ports = midi.ports();
if ports.is_empty() {
self.output_index = -1;
self.device_name = String::from("No Output Device");
self.out = None;
return;
}
self.output_index = (self.output_index + 1) % ports.len() as i32;
let port = &ports[self.output_index as usize];
self.device_name = midi
.port_name(port)
.unwrap_or_else(|_| String::from("Unknown Device"));
self.out = midi.connect(port, "o2-output").ok();
}
}
pub fn select_next_input(&mut self) {
if let Ok(midi) = MidiInput::new("o2") {
let ports = midi.ports();
if ports.is_empty() {
self.input_index = -1;
self.input_device_name = String::from("No Input Device");
return;
}
self.input_index = (self.input_index + 1) % ports.len() as i32;
let port = &ports[self.input_index as usize];
self.input_device_name = midi
.port_name(port)
.unwrap_or_else(|_| String::from("Unknown Device"));
}
}
pub fn send_midi_msg(&mut self, msg: &[u8]) {
if let Some(conn) = self.out.as_mut() {
let _ = conn.send(msg);
}
if let Some(bidule_path) = &self.osc_midi_bidule
&& let Some(sock) = &self.udp_socket
{
let mut args = Vec::with_capacity(3);
for &b in msg {
args.push(OscType::Int(b as i32));
}
while args.len() < 3 {
args.push(OscType::Int(0));
}
let packet = OscPacket::Message(OscMessage {
addr: bidule_path.clone(),
args,
});
if let Ok(bytes) = encoder::encode(&packet) {
let _ = sock.send_to(&bytes, (self.ip.as_str(), self.osc.port));
}
}
}
pub fn run(&mut self) {
let mut to_send = Vec::new();
self.stack.retain_mut(|note| {
if !note.is_played {
to_send.push(vec![0x90 + note.channel, note.note_id, note.velocity]);
note.is_played = true;
}
if note.length < 1 {
to_send.push(vec![0x80 + note.channel, note.note_id, 0]);
false
} else {
note.length = note.length.saturating_sub(1);
true
}
});
for slot in self.mono_stack.iter_mut() {
if let Some(note) = slot {
if note.length < 1 {
if note.is_played {
to_send.push(vec![0x80 + note.channel, note.note_id, 0]);
}
*slot = None;
continue;
}
if !note.is_played {
to_send.push(vec![0x90 + note.channel, note.note_id, note.velocity]);
note.is_played = true;
}
note.length = note.length.saturating_sub(1);
}
}
for msg in &self.cc_stack {
match msg {
MidiMessage::Cc(cc) => {
let knob_val = self.cc_offset.saturating_add(cc.knob).min(127);
to_send.push(vec![0xB0 + cc.channel, knob_val, cc.value]);
}
MidiMessage::Pb(pb) => {
to_send.push(vec![0xE0 + pb.channel, pb.lsb, pb.msb]);
}
}
}
for msg in to_send {
self.send_midi_msg(&msg);
}
self.osc.run(self.udp_socket.as_ref(), &self.ip);
self.udp.run(self.udp_socket.as_ref(), &self.ip);
self.cc_stack.clear();
}
pub fn silence(&mut self) {
let mut kill_notes = Vec::new();
for note in &self.stack {
if note.is_played {
kill_notes.push(vec![0x80 + note.channel, note.note_id, 0]);
}
}
for n in self.mono_stack.iter().flatten() {
if n.is_played {
kill_notes.push(vec![0x80 + n.channel, n.note_id, 0]);
}
}
for ch in 0..16 {
kill_notes.push(vec![0xB0 + ch, 123, 0]);
}
for msg in kill_notes {
self.send_midi_msg(&msg);
}
self.stack.clear();
self.mono_stack = std::array::from_fn(|_| None);
self.cc_stack.clear();
self.osc.stack.clear();
self.udp.stack.clear();
}
pub fn send_pg(&mut self, channel: u8, bank: Option<u8>, sub: Option<u8>, pgm: Option<u8>) {
if let Some(b) = bank {
self.send_midi_msg(&[0xB0 + channel, 0, b]);
}
if let Some(s) = sub {
self.send_midi_msg(&[0xB0 + channel, 32, s]);
}
if let Some(p) = pgm {
self.send_midi_msg(&[0xC0 + channel, p.min(127)]);
}
}
pub fn send_clock_start(&mut self) {
self.send_midi_msg(&[0xFA]);
}
pub fn send_clock_stop(&mut self) {
self.send_midi_msg(&[0xFC]);
}
}
impl Default for MidiState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_midi_state_run_lifecycle() {
let mut state = MidiState::new();
state.stack.push(MidiNote {
channel: 0,
octave: 4,
note: 'C',
note_id: 60,
velocity: 100,
length: 2,
is_played: false,
});
state.run();
assert_eq!(state.stack.len(), 1);
assert!(state.stack[0].is_played);
assert_eq!(state.stack[0].length, 1);
state.run();
assert_eq!(state.stack.len(), 1);
assert_eq!(state.stack[0].length, 0);
state.run();
assert_eq!(state.stack.len(), 0);
}
#[test]
fn test_midi_state_silence_clears_all() {
let mut state = MidiState::new();
state.stack.push(MidiNote {
channel: 15,
octave: 2,
note: 'A',
note_id: 45,
velocity: 127,
length: 5,
is_played: true,
});
state.mono_stack[5] = Some(MidiNote {
channel: 5,
octave: 3,
note: 'B',
note_id: 59,
velocity: 64,
length: 1,
is_played: false,
});
state.cc_stack.push(MidiMessage::Cc(MidiCc {
channel: 0,
knob: 10,
value: 127,
}));
state
.osc
.stack
.push(("/test".to_string(), "data".to_string()));
state.udp.stack.push("udp_data".to_string());
state.silence();
assert!(state.stack.is_empty());
assert!(state.mono_stack.iter().all(|s| s.is_none()));
assert!(state.cc_stack.is_empty());
assert!(state.osc.stack.is_empty());
assert!(state.udp.stack.is_empty());
}
#[test]
fn test_midi_state_run_clears_transient_stacks() {
let mut state = MidiState::new();
state.cc_stack.push(MidiMessage::Pb(MidiPb {
channel: 0,
lsb: 0,
msb: 0,
}));
state
.osc
.stack
.push(("/path".to_string(), "body".to_string()));
state.udp.stack.push("datagram".to_string());
state.run();
assert!(state.cc_stack.is_empty());
assert!(state.osc.stack.is_empty());
assert!(state.udp.stack.is_empty());
}
}