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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 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]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.6.0" version = "0.6.0"
@ -639,27 +617,6 @@ dependencies = [
"libc", "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]] [[package]]
name = "cosmic-text" name = "cosmic-text"
version = "0.12.1" version = "0.12.1"
@ -764,7 +721,6 @@ dependencies = [
"dirs-next", "dirs-next",
"futures", "futures",
"iced", "iced",
"midir",
"muda", "muda",
"rusqlite", "rusqlite",
"serde", "serde",
@ -1393,7 +1349,7 @@ dependencies = [
"presser", "presser",
"thiserror 1.0.69", "thiserror 1.0.69",
"winapi", "winapi",
"windows 0.52.0", "windows",
] ]
[[package]] [[package]]
@ -2013,23 +1969,6 @@ dependencies = [
"paste", "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]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"
@ -4222,17 +4161,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [ dependencies = [
"windows-core 0.52.0", "windows-core",
"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-targets 0.52.6", "windows-targets 0.52.6",
] ]
@ -4245,55 +4174,12 @@ dependencies = [
"windows-targets 0.52.6", "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]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"

View File

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

View File

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