Skip to content

Commit

Permalink
USB: implement USB-specific auto-detect
Browse files Browse the repository at this point in the history
* 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;
  • Loading branch information
arteme committed Nov 9, 2024
1 parent 863f7c7 commit fc24213
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 22 deletions.
16 changes: 12 additions & 4 deletions core/src/midi_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,14 @@ async fn detect_channel(in_port: &mut BoxedMidiIn, out_port: &mut BoxedMidiOut)
Ok(channel)
}

pub async fn autodetect(channel: Option<u8>) -> 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<u8>) -> Result<AutodetectResult> {

let in_port_names = MidiInPort::ports()?;
let mut in_port_errors = vec![];
Expand Down Expand Up @@ -464,7 +471,7 @@ pub async fn autodetect(channel: Option<u8>) -> Result<(BoxedMidiIn, BoxedMidiOu
}

pub async fn autodetect_with_ports(in_ports: Vec<BoxedMidiIn>, out_ports: Vec<BoxedMidiOut>,
channel: Option<u8>) -> Result<(BoxedMidiIn, BoxedMidiOut, u8, &'static Config)> {
channel: Option<u8>) -> Result<AutodetectResult> {
let config: Option<&Config>;
let mut in_ports = in_ports.into_iter().collect::<Vec<_>>();
let mut out_ports = out_ports.into_iter().collect::<Vec<_>>();
Expand Down Expand Up @@ -546,8 +553,9 @@ pub async fn autodetect_with_ports(in_ports: Vec<BoxedMidiIn>, out_ports: Vec<Bo
if channel.is_none() {
bail!("Can't determine POD channel");
}

Ok((in_port, out_port, channel.unwrap(), config.unwrap()))
Ok(AutodetectResult {
in_port, out_port, channel: channel.unwrap(), config: config.unwrap()
})
}

pub async fn test_with_ports(in_port: BoxedMidiIn, out_port: BoxedMidiOut, channel: u8, config: &Config) -> Result<(BoxedMidiIn, BoxedMidiOut, u8)> {
Expand Down
45 changes: 36 additions & 9 deletions gui/src/autodetect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,6 +41,7 @@ fn config_for_str(config_str: &str) -> Result<&'static Config> {
pub fn detect(state: Arc<Mutex<State>>, opts: Opts, window: &gtk::Window) -> Result<()>
{
let mut ports: Option<(BoxedMidiIn, BoxedMidiOut)> = None;
let mut midi_channel: Option<u8> = None;
let mut config = None;

// autodetect/open midi
Expand Down Expand Up @@ -74,19 +74,20 @@ pub fn detect(state: Arc<Mutex<State>>, opts: Opts, window: &gtk::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) => {
Expand All @@ -109,10 +110,10 @@ pub fn detect(state: Arc<Mutex<State>>, opts: Opts, window: &gtk::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 {
Expand Down Expand Up @@ -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)?;
Expand All @@ -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<u8>) -> Result<AutodetectResult> {
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())
}
}
}
}
}
10 changes: 5 additions & 5 deletions gui/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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();
});

Expand All @@ -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()));
Expand Down
52 changes: 48 additions & 4 deletions gui/src/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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<AutodetectResult> {
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() {
}
Expand All @@ -52,4 +88,12 @@ mod nop {
fn usb_open_name(_addr: &str) -> Result<(MidiInPort, MidiOutPort)> {
unimplemented!()
}

pub async fn autodetect() -> Result<AutodetectResult> {
unimplemented!()
}

pub const fn autodetect_supported() -> bool {
false
}
}

0 comments on commit fc24213

Please sign in to comment.