From fc242138fa27e23a151111dc8981d23ff00585ee Mon Sep 17 00:00:00 2001 From: Artem Egorkine Date: Sat, 9 Nov 2024 20:21:45 +0200 Subject: [PATCH] USB: implement USB-specific auto-detect * Implement USB-specific autodetect where we enumerate USB devices and run single port MIDI auto-detect on each one; * Implement a compound `run_autodetect` to run MIDI and then USB autodetect code; * When dealing with USB deviced, set fixed MIDI channel 1 since they do not care about it; --- core/src/midi_io.rs | 16 +++++++++---- gui/src/autodetect.rs | 45 +++++++++++++++++++++++++++++-------- gui/src/settings.rs | 10 ++++----- gui/src/usb.rs | 52 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 101 insertions(+), 22 deletions(-) diff --git a/core/src/midi_io.rs b/core/src/midi_io.rs index 458d238..70217aa 100644 --- a/core/src/midi_io.rs +++ b/core/src/midi_io.rs @@ -417,7 +417,14 @@ async fn detect_channel(in_port: &mut BoxedMidiIn, out_port: &mut BoxedMidiOut) Ok(channel) } -pub async fn autodetect(channel: Option) -> Result<(BoxedMidiIn, BoxedMidiOut, u8, &'static Config)> { +pub struct AutodetectResult { + pub in_port: BoxedMidiIn, + pub out_port: BoxedMidiOut, + pub channel: u8, + pub config: &'static Config +} + +pub async fn autodetect(channel: Option) -> Result { let in_port_names = MidiInPort::ports()?; let mut in_port_errors = vec![]; @@ -464,7 +471,7 @@ pub async fn autodetect(channel: Option) -> Result<(BoxedMidiIn, BoxedMidiOu } pub async fn autodetect_with_ports(in_ports: Vec, out_ports: Vec, - channel: Option) -> Result<(BoxedMidiIn, BoxedMidiOut, u8, &'static Config)> { + channel: Option) -> Result { let config: Option<&Config>; let mut in_ports = in_ports.into_iter().collect::>(); let mut out_ports = out_ports.into_iter().collect::>(); @@ -546,8 +553,9 @@ pub async fn autodetect_with_ports(in_ports: Vec, out_ports: Vec Result<(BoxedMidiIn, BoxedMidiOut, u8)> { diff --git a/gui/src/autodetect.rs b/gui/src/autodetect.rs index 7b9997d..b90d5f6 100644 --- a/gui/src/autodetect.rs +++ b/gui/src/autodetect.rs @@ -8,8 +8,7 @@ use pod_core::midi_io::*; use pod_core::model::Config; use pod_gtk::prelude::*; use crate::opts::Opts; -use crate::{set_midi_in_out, State}; -use crate::usb::{usb_open_addr, usb_open_name}; +use crate::{set_midi_in_out, State, usb}; fn config_for_str(config_str: &str) -> Result<&'static Config> { use std::str::FromStr; @@ -42,6 +41,7 @@ fn config_for_str(config_str: &str) -> Result<&'static Config> { pub fn detect(state: Arc>, opts: Opts, window: >k::Window) -> Result<()> { let mut ports: Option<(BoxedMidiIn, BoxedMidiOut)> = None; + let mut midi_channel: Option = None; let mut config = None; // autodetect/open midi @@ -74,19 +74,20 @@ pub fn detect(state: Arc>, opts: Opts, window: >k::Window) -> Res } // USB (None, None, Some(u), None) => { - let (midi_in, midi_out) = usb_open_addr(u)?; + let (midi_in, midi_out) = usb::usb_open_addr(u)?; ports = Some((Box::new(midi_in), Box::new(midi_out))); + midi_channel = Some(Channel::num(0)); // USB devices don't care about the MIDI channel true } (None, None, Some(u), Some(m)) => { - let (midi_in, midi_out) = usb_open_addr(u)?; + let (midi_in, midi_out) = usb::usb_open_addr(u)?; ports = Some((Box::new(midi_in), Box::new(midi_out))); config = Some(config_for_str(m)?); false } }; let midi_channel = match opts.channel { - None => None, + None => midi_channel, // use channel default of None, unless set by the logic above Some(x) if x == 0 => Some(Channel::all()), Some(x) if (1u8 ..= 16).contains(&x) => Some(x - 1), Some(x) => { @@ -109,10 +110,10 @@ pub fn detect(state: Arc>, opts: Opts, window: >k::Window) -> Res ).await } else { // autodetect device - pod_core::midi_io::autodetect(midi_channel).await + run_autodetect(midi_channel).await }; - let res = res.and_then(|(midi_in, midi_out, chan, config)| - Ok((midi_in, midi_out, chan, false, config))); + let res = res.and_then(|res| + Ok((res.in_port, res.out_port, res.channel, false, res.config))); tx.send(res).ok(); }); } else { @@ -170,7 +171,7 @@ pub fn open(in_name: &str, out_name: &str, is_usb: bool) -> Result<(BoxedMidiIn, if in_name != out_name { bail!("USB device input/output names do not match"); } - let (midi_in, midi_out) = usb_open_name(in_name)?; + let (midi_in, midi_out) = usb::usb_open_name(in_name)?; (box_midi_in(midi_in), box_midi_out(midi_out)) } else { let midi_in = MidiInPort::new_for_name(in_name)?; @@ -179,3 +180,29 @@ pub fn open(in_name: &str, out_name: &str, is_usb: bool) -> Result<(BoxedMidiIn, }; Ok(res) } + +/** + * Run MIDI device auto-detect. + * + * Run MIDI-specific device auto-detect and, if failed, USB-specific + * device auto-detect. The latter ignores the MIDI channel that may + * have been previously set. + */ +pub async fn run_autodetect(channel: Option) -> Result { + match autodetect(channel).await { + res @ Ok(_) => res, + Err(e) => { + if !usb::autodetect_supported() { + return Err(e); + } + + match usb::autodetect().await { + res @ Ok(_) => res, + Err(e1) => { + // do something clever with the replies here + bail!("MIDI: {}\nUSB: {}\n", e.to_string(), e1.to_string()) + } + } + } + } +} diff --git a/gui/src/settings.rs b/gui/src/settings.rs index 40d0d64..c3a4fbb 100644 --- a/gui/src/settings.rs +++ b/gui/src/settings.rs @@ -9,9 +9,9 @@ use crate::{gtk, midi_in_out_start, midi_in_out_stop, set_midi_in_out, State}; use log::*; use pod_core::config::configs; use pod_core::midi::Channel; -use pod_core::midi_io::{MidiInPort, MidiOutPort, MidiPorts}; +use pod_core::midi_io::{AutodetectResult, MidiInPort, MidiOutPort, MidiPorts}; use pod_gtk::prelude::glib::bitflags::bitflags; -use crate::autodetect::{open, test}; +use crate::autodetect::{open, run_autodetect, test}; use crate::usb; #[derive(Clone)] @@ -318,7 +318,7 @@ fn wire_autodetect_button(settings: &SettingsDialog) { settings.autodetect_button.clone().connect_clicked(move |button| { let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); tokio::spawn(async move { - let res = pod_core::midi_io::autodetect(None).await; + let res = run_autodetect(None).await; tx.send(res).ok(); }); @@ -327,13 +327,13 @@ fn wire_autodetect_button(settings: &SettingsDialog) { rx.attach(None, move |autodetect| { match autodetect { - Ok((in_, out_, channel, config)) => { + Ok(AutodetectResult { in_port, out_port, channel, config }) => { let msg = format!("Autodetect successful!"); settings.work_finish("dialog-ok", &msg); // update in/out port selection, channel, device populate_midi_combos(&settings, - &Some(in_.name()), &Some(out_.name())); + &Some(in_port.name()), &Some(out_port.name())); let index = midi_channel_to_combo_index(channel); settings.midi_channel_combo.set_active(index); populate_model_combo(&settings, &Some(config.name.clone())); diff --git a/gui/src/usb.rs b/gui/src/usb.rs index 861591f..4adc505 100644 --- a/gui/src/usb.rs +++ b/gui/src/usb.rs @@ -8,10 +8,12 @@ pub use nop::*; #[cfg(feature = "usb")] mod imp { use anyhow::*; + use core::result::Result::Ok; use futures::executor; + use log::warn; + use pod_core::midi::Channel; use pod_core::midi_io; - use pod_core::midi_io::{box_midi_in, box_midi_out, BoxedMidiIn, BoxedMidiOut, MidiIn, MidiOut}; - use pod_core::model::Config; + use pod_core::midi_io::{AutodetectResult, MidiIn, MidiOut}; pub fn start_usb() { pod_usb::usb_start().unwrap(); @@ -31,12 +33,46 @@ mod imp { pub fn usb_open_name(name: &str) -> Result<(impl MidiIn, impl MidiOut)> { pod_usb::usb_device_for_name(name) } + + /** + * USB-specific auto-detect that is called whenever MIDI autodetect + * didn't return any results. + */ + pub async fn autodetect() -> Result { + let devices = usb_list_devices(); + if devices.is_empty() { + bail!("No compatible USB devices found") + } + + for (name, is_error) in usb_list_devices() { + if is_error { continue } + + let (in_port, out_port) = match usb_open_name(&name) { + Ok(r) => r, + Err(e) => { + warn!("USB auto-detect failed for device {name:?}: {e}"); + continue; + } + }; + let res = midi_io::autodetect_with_ports( + vec![Box::new(in_port)], vec![Box::new(out_port)], Some(Channel::num(0)) + ).await; + if res.is_ok() { + return res; + } + } + + bail!("USB auto-detect failed"); + } + + pub const fn autodetect_supported() -> bool { + true + } } mod nop { use anyhow::*; - use pod_core::midi_io::{BoxedMidiIn, BoxedMidiOut, MidiInPort, MidiOutPort}; - use pod_core::model::Config; + use pod_core::midi_io::{AutodetectResult, MidiInPort, MidiOutPort}; fn start_usb() { } @@ -52,4 +88,12 @@ mod nop { fn usb_open_name(_addr: &str) -> Result<(MidiInPort, MidiOutPort)> { unimplemented!() } + + pub async fn autodetect() -> Result { + unimplemented!() + } + + pub const fn autodetect_supported() -> bool { + false + } }