strip MIDI/BLE transport, make UDP the sole connection method

Remove ble.rs, midir dependency, TransportMode enum, and all MIDI-related
Message variants (OpenMidiSetup, RefreshMidi, ToggleTransport). Simplify
subscription to UDP-only path. Rename BleReady/BleStatus/BleData to
DeviceReady/DeviceStatus/DeviceData. Replace transport toggle UI with
always-visible UDP address field and Reconnect button.
This commit is contained in:
jess 2026-04-01 00:30:16 -07:00
parent 06f4fa8e71
commit fc0ff900f1
5 changed files with 56 additions and 350 deletions

118
cue/Cargo.lock generated
View File

@ -54,28 +54,6 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "alsa"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
dependencies = [
"alsa-sys",
"bitflags 2.11.0",
"cfg-if",
"libc",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "android-activity"
version = "0.6.0"
@ -639,27 +617,6 @@ dependencies = [
"libc",
]
[[package]]
name = "coremidi"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "964eb3e10ea8b0d29c797086aab3ca730f75e06dced0cb980642fd274a5cca30"
dependencies = [
"block",
"core-foundation",
"core-foundation-sys",
"coremidi-sys",
]
[[package]]
name = "coremidi-sys"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9504310988d938e49fff1b5f1e56e3dafe39bb1bae580c19660b58b83a191e"
dependencies = [
"core-foundation-sys",
]
[[package]]
name = "cosmic-text"
version = "0.12.1"
@ -764,7 +721,6 @@ dependencies = [
"dirs-next",
"futures",
"iced",
"midir",
"muda",
"rusqlite",
"serde",
@ -1393,7 +1349,7 @@ dependencies = [
"presser",
"thiserror 1.0.69",
"winapi",
"windows 0.52.0",
"windows",
]
[[package]]
@ -2013,23 +1969,6 @@ dependencies = [
"paste",
]
[[package]]
name = "midir"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73f8737248ad37b88291a2108d9df5f991dc8555103597d586b5a29d4d703c0"
dependencies = [
"alsa",
"bitflags 1.3.2",
"coremidi",
"js-sys",
"libc",
"parking_lot 0.12.5",
"wasm-bindgen",
"web-sys",
"windows 0.56.0",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -4222,17 +4161,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core 0.52.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [
"windows-core 0.56.0",
"windows-core",
"windows-targets 0.52.6",
]
@ -4245,55 +4174,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-implement"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "windows-interface"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -5,7 +5,6 @@ edition = "2024"
[dependencies]
iced = { version = "0.13", features = ["canvas", "tokio"] }
midir = "0.10"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
muda = { version = "0.17", default-features = false }

View File

@ -9,7 +9,6 @@ use std::fmt::Write;
use std::time::Duration;
use tokio::sync::mpsc;
use crate::ble::BleEvent;
use crate::native_menu::{MenuAction, NativeMenu};
use crate::protocol::{
self, AmpPoint, ClPoint, ClResult, Electrode, EisMessage, EisPoint, LpRtia, LsvPoint,
@ -18,12 +17,6 @@ use crate::protocol::{
use crate::storage::{self, Session, Storage};
use crate::udp::UdpEvent;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportMode {
Midi,
Udp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tab {
Eis,
@ -58,9 +51,9 @@ impl std::fmt::Display for SessionItem {
#[derive(Debug, Clone)]
pub enum Message {
BleReady(mpsc::UnboundedSender<Vec<u8>>),
BleStatus(String),
BleData(EisMessage),
DeviceReady(mpsc::UnboundedSender<Vec<u8>>),
DeviceStatus(String),
DeviceData(EisMessage),
TabSelected(Tab),
PaneResized(pane_grid::ResizeEvent),
DataAction(text_editor::Action),
@ -132,9 +125,7 @@ pub enum Message {
BrowseDeleteMeasurement(i64),
BrowseBack,
/* Misc */
OpenMidiSetup,
RefreshMidi,
ToggleTransport,
Reconnect,
UdpAddrChanged(String),
}
@ -142,7 +133,7 @@ pub struct App {
tab: Tab,
status: String,
cmd_tx: Option<mpsc::UnboundedSender<Vec<u8>>>,
ble_connected: bool,
connected: bool,
panes: pane_grid::State<PaneId>,
native_menu: NativeMenu,
show_sysinfo: bool,
@ -239,8 +230,7 @@ pub struct App {
/* Global */
temp_c: f32,
midi_gen: u64,
transport: TransportMode,
conn_gen: u64,
udp_addr: String,
}
@ -379,7 +369,7 @@ impl App {
tab: Tab::Eis,
status: "Starting...".into(),
cmd_tx: None,
ble_connected: false,
connected: false,
panes: pane_grid::State::with_configuration(pane_grid::Configuration::Split {
axis: pane_grid::Axis::Horizontal,
ratio: 0.55,
@ -469,8 +459,7 @@ impl App {
cal_cell_constant: None,
temp_c: 25.0,
midi_gen: 0,
transport: TransportMode::Midi,
conn_gen: 0,
udp_addr: crate::udp::load_addr(),
}, Task::none())
}
@ -567,20 +556,20 @@ impl App {
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::BleReady(tx) => {
Message::DeviceReady(tx) => {
self.cmd_tx = Some(tx);
self.ble_connected = true;
self.connected = true;
self.send_cmd(&protocol::build_sysex_get_config());
self.send_cmd(&protocol::build_sysex_get_cell_k());
}
Message::BleStatus(s) => {
if s.contains("Reconnecting") || s.contains("Looking") {
self.ble_connected = false;
Message::DeviceStatus(s) => {
if s.contains("Reconnecting") || s.contains("Connecting") {
self.connected = false;
self.cmd_tx = None;
}
self.status = s;
}
Message::BleData(msg) => match msg {
Message::DeviceData(msg) => match msg {
EisMessage::SweepStart { num_points, freq_start, freq_stop } => {
if self.collecting_refs {
/* ref collection: clear temp buffer */
@ -1079,30 +1068,11 @@ impl App {
self.browse_measurements.clear();
}
}
Message::OpenMidiSetup => {
let _ = std::process::Command::new("open")
.arg("-a")
.arg("Audio MIDI Setup")
.spawn();
}
Message::RefreshMidi => {
self.midi_gen += 1;
Message::Reconnect => {
self.conn_gen += 1;
self.cmd_tx = None;
self.ble_connected = false;
self.status = "Looking for MIDI device...".into();
}
Message::ToggleTransport => {
self.transport = match self.transport {
TransportMode::Midi => TransportMode::Udp,
TransportMode::Udp => TransportMode::Midi,
};
self.midi_gen += 1;
self.cmd_tx = None;
self.ble_connected = false;
self.status = match self.transport {
TransportMode::Midi => "Looking for MIDI device...".into(),
TransportMode::Udp => format!("Connecting UDP to {}...", self.udp_addr),
};
self.connected = false;
self.status = format!("Connecting to {}...", self.udp_addr);
}
Message::UdpAddrChanged(s) => {
self.udp_addr = s;
@ -1112,12 +1082,10 @@ impl App {
}
pub fn subscription(&self) -> Subscription<Message> {
let use_udp = self.transport == TransportMode::Udp;
let udp_addr = self.udp_addr.clone();
let transport = Subscription::run_with_id(
self.midi_gen,
self.conn_gen,
iced::stream::channel(100, move |mut output| async move {
if use_udp {
let addr = udp_addr.clone();
loop {
let (udp_tx, mut udp_rx) = mpsc::unbounded_channel::<UdpEvent>();
@ -1136,48 +1104,18 @@ impl App {
let msg = match ev {
UdpEvent::Status(ref s) if s == "Connected" && !ready_sent => {
ready_sent = true;
let _ = output.send(Message::BleReady(cmd_tx.clone())).await;
Message::BleStatus(s.clone())
let _ = output.send(Message::DeviceReady(cmd_tx.clone())).await;
Message::DeviceStatus(s.clone())
}
UdpEvent::Status(s) => Message::BleStatus(s),
UdpEvent::Data(m) => Message::BleData(m),
UdpEvent::Status(s) => Message::DeviceStatus(s),
UdpEvent::Data(m) => Message::DeviceData(m),
};
let _ = output.send(msg).await;
}
let _ = output.send(Message::BleStatus("Reconnecting UDP...".into())).await;
let _ = output.send(Message::DeviceStatus("Reconnecting...".into())).await;
tokio::time::sleep(Duration::from_millis(500)).await;
}
} else {
loop {
let (ble_tx, mut ble_rx) = mpsc::unbounded_channel::<BleEvent>();
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let tx = ble_tx.clone();
tokio::spawn(async move {
if let Err(e) = crate::ble::connect_and_run(tx, cmd_rx).await {
eprintln!("BLE: {e}");
}
});
let mut ready_sent = false;
while let Some(ev) = ble_rx.recv().await {
let msg = match ev {
BleEvent::Status(ref s) if s == "Connected" && !ready_sent => {
ready_sent = true;
let _ = output.send(Message::BleReady(cmd_tx.clone())).await;
Message::BleStatus(s.clone())
}
BleEvent::Status(s) => Message::BleStatus(s),
BleEvent::Data(m) => Message::BleData(m),
};
let _ = output.send(msg).await;
}
let _ = output.send(Message::BleStatus("Reconnecting...".into())).await;
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
}),
);
@ -1208,14 +1146,6 @@ impl App {
]
.spacing(4)
.align_y(iced::Alignment::Center);
if self.transport == TransportMode::Midi {
tabs = tabs.push(
button(text("MIDI Setup").size(13))
.style(style_neutral())
.padding([6, 14])
.on_press(Message::OpenMidiSetup),
);
}
tabs = tabs
.push(iced::widget::horizontal_space())
.push(text("Clean").size(12))
@ -1295,36 +1225,21 @@ impl App {
ref_row = ref_row.push(text("REF").size(11));
}
let connected = self.ble_connected;
let transport_label = match self.transport {
TransportMode::Midi => "MIDI",
TransportMode::Udp => "UDP",
};
let mut status_row = row![text(&self.status).size(16)].spacing(6)
.align_y(iced::Alignment::Center);
status_row = status_row.push(
button(text(transport_label).size(11))
.style(style_neutral())
.padding([4, 10])
.on_press(Message::ToggleTransport),
);
if self.transport == TransportMode::Udp {
status_row = status_row.push(
text_input("IP:port", &self.udp_addr)
.size(12)
.width(160)
.on_input(Message::UdpAddrChanged)
.on_submit(Message::ToggleTransport), // reconnect on enter
.on_submit(Message::Reconnect),
);
}
if !connected && self.transport == TransportMode::Midi {
status_row = status_row.push(
button(text("Refresh MIDI").size(11))
button(text("Reconnect").size(11))
.style(style_apply())
.padding([4, 10])
.on_press(Message::RefreshMidi),
.on_press(Message::Reconnect),
);
}
status_row = status_row
.push(iced::widget::horizontal_space())
.push(ref_row)

View File

@ -1,93 +0,0 @@
use midir::{MidiInput, MidiOutput, MidiInputConnection, MidiOutputConnection};
use std::sync::mpsc as std_mpsc;
use tokio::sync::mpsc;
use crate::protocol::{self, EisMessage};
const DEVICE_NAME: &str = "EIS4";
#[derive(Debug, Clone)]
pub enum BleEvent {
Status(String),
Data(EisMessage),
}
pub async fn connect_and_run(
tx: mpsc::UnboundedSender<BleEvent>,
mut cmd_rx: mpsc::UnboundedReceiver<Vec<u8>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let _ = tx.send(BleEvent::Status("Looking for MIDI device...".into()));
let (midi_in, in_port, midi_out, out_port) = loop {
if let Some(found) = find_midi_ports() {
break found;
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
};
let _ = tx.send(BleEvent::Status("Connecting MIDI...".into()));
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}"))?;
let _ = tx.send(BleEvent::Status("Connected".into()));
loop {
while let Ok(sysex) = sysex_rx.try_recv() {
if let Some(msg) = protocol::parse_sysex(&sysex) {
let _ = tx.send(BleEvent::Data(msg));
}
}
loop {
match cmd_rx.try_recv() {
Ok(pkt) => {
if let Err(e) = out_conn.send(&pkt) {
eprintln!("MIDI send error: {e}");
}
}
Err(mpsc::error::TryRecvError::Disconnected) => 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_port = midi_in.ports().into_iter().find(|p| {
midi_in.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME))
})?;
let out_port = midi_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())
}

View File

@ -1,5 +1,4 @@
mod app;
mod ble;
mod native_menu;
mod plot;
mod protocol;