Skip to content

Commit

Permalink
feat: allow refreshing attached midi device
Browse files Browse the repository at this point in the history
this is an indirect solution to the problem of "get midi working" in WASM,
but it's also generally useful, I think.

It seems like MIDI wasn't connecting before in browser because
the device wasn't immediately available. Trying again in a few seconds,
it works.

I was able to determine this by recreating this polling example locally
/~https://github.com/Boddlnagg/midir/blob/master/examples/browser/src/lib.rs
  • Loading branch information
nathanleiby committed Nov 17, 2024
1 parent 1efe23f commit 8aacf27
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 3 deletions.
21 changes: 21 additions & 0 deletions src/egui_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct UIState {
miss_margin: f64,

hide_empty_tracks: bool,
midi_device_name: String,
// user interaction state
// is_dragging,
}
Expand Down Expand Up @@ -96,6 +97,8 @@ impl Default for UIState {
miss_margin: 0.,

hide_empty_tracks: false,

midi_device_name: "".to_string(),
}
}
}
Expand Down Expand Up @@ -173,6 +176,10 @@ impl UIState {
pub fn set_hide_empty_tracks(&mut self, val: bool) {
self.hide_empty_tracks = val;
}

pub fn set_midi_device_name(&mut self, val: &str) {
self.midi_device_name = val.to_owned();
}
}

pub fn draw_ui(ctx: &egui::Context, ui_state: &UIState, events: &mut Vec<Events>) {
Expand Down Expand Up @@ -413,6 +420,20 @@ fn draw_right_panel(ctx: &egui::Context, ui_state: &UIState, events: &mut Vec<Ev
events.push(Events::ToggleEmptyTrackVisibility);
}

ui.separator();

ui.group(|ui| {
ui.add(egui::Label::new("**MIDI**"));
ui.add(egui::Label::new(format!(
"attached device: {}",
ui_state.midi_device_name
)));

if ui.button("Refresh Connected Midi Device").clicked() {
events.push(Events::RefreshConnectedMidiDevice);
}
});

ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
ui.add(egui::Hyperlink::from_label_and_url(
"Source code.",
Expand Down
2 changes: 2 additions & 0 deletions src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub enum Events {
ToggleHelpVisibility,
ToggleEmptyTrackVisibility,

RefreshConnectedMidiDevice,

// Dev Tools
ToggleDebugMode,
ToggleDevToolsVisibility,
Expand Down
9 changes: 7 additions & 2 deletions src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::audio::Audio;
use crate::config::AppConfig;
use crate::consts::TxMsg;
use crate::egui_ui::UIState;
use crate::midi_input_handler::MidiInputHandler;
use crate::score::compute_last_loop_summary;
use crate::time::current_time_millis;
use crate::ui::*;
Expand Down Expand Up @@ -97,7 +98,7 @@ impl GameState {
}

// TODO: simplify how we init this.. I don't think all the mutability and helper fns are needed
pub fn compute_ui_state(gs: &GameState, audio: &Audio) -> UIState {
pub fn compute_ui_state(gs: &GameState, audio: &Audio, midi_device_name: &str) -> UIState {
let selector_vec = gs.loops.iter().map(|(name, _)| name.to_string()).collect();
let mut ui_state = UIState::default().selector_vec(&selector_vec);
ui_state.set_selected_idx(gs.selected_loop_idx);
Expand All @@ -116,6 +117,7 @@ pub fn compute_ui_state(gs: &GameState, audio: &Audio) -> UIState {
ui_state.set_miss_margin(gs.miss_margin);
ui_state.set_is_help_visible(gs.flags.help_visible);
ui_state.set_hide_empty_tracks(gs.flags.hide_empty_tracks);
ui_state.set_midi_device_name(midi_device_name);
ui_state
}

Expand Down Expand Up @@ -183,7 +185,6 @@ fn log_user_metric(user_metric: &UserMetric) -> Result<(), Box<dyn Error>> {
let data = serde_json::to_string(&user_metric)?;
let current_dir = env::current_dir()?;
log::info!("user_metric (json) = {}", data);

let dir = current_dir.join("log");
fs::create_dir_all(&dir)?;
let fpath = dir.join(format!("user_metric-{}.json", user_metric.system_time_ms));
Expand All @@ -202,6 +203,7 @@ pub fn process_user_events(
events: &Vec<Events>,
correct_margin: &mut f64,
miss_margin: &mut f64,
midi_input: &mut MidiInputHandler,
) -> Result<(), Box<dyn Error>> {
for event in events {
info!("[user event] {:?}", event);
Expand Down Expand Up @@ -282,6 +284,9 @@ pub fn process_user_events(
Events::ToggleEmptyTrackVisibility => {
flags.hide_empty_tracks = !flags.hide_empty_tracks;
}
Events::RefreshConnectedMidiDevice => {
midi_input.refresh_connected_device();
}
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
&events,
&mut gs.correct_margin,
&mut gs.miss_margin,
&mut midi_input,
)?;

audio.schedule(&gs.voices).await?;

// render UI
ui.render(&compute_ui_state(&gs, &audio));
ui.render(&compute_ui_state(
&gs,
&audio,
midi_input.attached_device_name(),
));

macroquad_console.update(&mut my_cvars);
if gs.flags.ui_debug_mode {
fps_tracker.update();
Expand Down
2 changes: 2 additions & 0 deletions src/midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ impl MidiInputDataRaw {

impl MidiInput {
pub fn new() -> Option<Self> {
log::info!("MidiInput::new()");
let midi_input = midir::MidiInput::new("Input device").unwrap();
log::info!("port count {}", midi_input.port_count());
// grab first device
let input_port = match midi_input.ports().into_iter().next() {
Some(port) => port,
Expand Down
20 changes: 20 additions & 0 deletions src/midi_input_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ impl MidiInputHandler {
Self { midi_input }
}

pub fn refresh_connected_device(&mut self) {
let mut midi_input = MidiInput::new();
match midi_input {
Some(ref mut midi_input) => {
midi_input.connect();
}
None => log::warn!("warning: no midi input device found"),
}

self.midi_input = midi_input;
}

pub fn attached_device_name(&self) -> &str {
if let Some(midi_input) = &self.midi_input {
midi_input.get_device_name()
} else {
"none"
}
}

/// convert any user input from the last frame into Events
pub fn process(&mut self) -> Vec<Events> {
let mut events: Vec<Events> = vec![];
Expand Down

0 comments on commit 8aacf27

Please sign in to comment.