use futures::SinkExt; use midir::{MidiInput, MidiOutput, MidiInputConnection, MidiOutputConnection}; use std::sync::mpsc as std_mpsc; use tokio::sync::mpsc; use crate::app::Message; use crate::protocol; const DEVICE_NAME: &str = "EIS4"; pub async fn connect_and_stream( output: &mut futures::channel::mpsc::Sender, ) -> Result<(), Box> { eprintln!("BLE: scanning for MIDI device '{DEVICE_NAME}'..."); let (midi_in, in_port, midi_out, out_port) = loop { match find_midi_ports() { Some(found) => break found, None => { tokio::time::sleep(std::time::Duration::from_millis(500)).await; } } }; eprintln!("BLE: found ports, connecting..."); let _ = output.send(Message::BleStatus("Connecting MIDI...".into())).await; let (sysex_tx, sysex_rx) = std_mpsc::channel::>(); let _in_conn: MidiInputConnection<()> = midi_in.connect( &in_port, "cue-in", move |_ts, data, _| { if let Some(sysex) = extract_sysex(data) { let _ = sysex_tx.send(sysex); } }, (), ).map_err(|e| format!("MIDI input connect: {e}"))?; let mut out_conn: MidiOutputConnection = midi_out.connect( &out_port, "cue-out", ).map_err(|e| format!("MIDI output connect: {e}"))?; eprintln!("BLE: connected"); let _ = output.send(Message::BleStatus("Connected".into())).await; let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::>(); let _ = output.send(Message::BleReady(cmd_tx)).await; loop { while let Ok(sysex) = sysex_rx.try_recv() { if let Some(msg) = protocol::parse_sysex(&sysex) { let _ = output.send(Message::BleData(msg)).await; } } loop { match cmd_rx.try_recv() { Ok(pkt) => { if let Err(e) = out_conn.send(&pkt) { eprintln!("BLE: MIDI send error: {e}"); return Err(e.into()); } } Err(mpsc::error::TryRecvError::Disconnected) => { eprintln!("BLE: cmd channel closed"); return Ok(()); } Err(mpsc::error::TryRecvError::Empty) => break, } } tokio::time::sleep(std::time::Duration::from_millis(5)).await; } } fn find_midi_ports() -> Option<( MidiInput, midir::MidiInputPort, MidiOutput, midir::MidiOutputPort, )> { let midi_in = MidiInput::new("cue-in").ok()?; let midi_out = MidiOutput::new("cue-out").ok()?; let in_ports = midi_in.ports(); let out_ports = midi_out.ports(); let in_names: Vec<_> = in_ports.iter() .filter_map(|p| midi_in.port_name(p).ok()) .collect(); let out_names: Vec<_> = out_ports.iter() .filter_map(|p| midi_out.port_name(p).ok()) .collect(); if !in_names.is_empty() || !out_names.is_empty() { eprintln!("BLE: MIDI ports — in: {:?}, out: {:?}", in_names, out_names); } let in_port = in_ports.into_iter().find(|p| { midi_in.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME)) })?; let out_port = out_ports.into_iter().find(|p| { midi_out.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME)) })?; Some((midi_in, in_port, midi_out, out_port)) } fn extract_sysex(data: &[u8]) -> Option> { if data.first() != Some(&0xF0) { return None; } let end = data.iter().position(|&b| b == 0xF7)?; Some(data[1..end].to_vec()) }