114 lines
3.6 KiB
Rust
114 lines
3.6 KiB
Rust
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<Message>,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
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::<Vec<u8>>();
|
|
|
|
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::<Vec<u8>>();
|
|
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<Vec<u8>> {
|
|
if data.first() != Some(&0xF0) { return None; }
|
|
let end = data.iter().position(|&b| b == 0xF7)?;
|
|
Some(data[1..end].to_vec())
|
|
}
|