Skip to main content

max / audiofiles

2.6 KB · 80 lines History Blame Raw
1 //! MIDI input: enumerate ports, connect/disconnect, and parse incoming messages.
2
3 use std::sync::Arc;
4 use std::time::Instant;
5
6 use audiofiles_browser::state::{MidiNoteEvent, SharedState};
7 use audiofiles_core::instrument::note_name;
8 use midir::{MidiInput, MidiInputConnection};
9 use tracing::instrument;
10
11 /// An active MIDI input connection. Dropping this disconnects.
12 pub struct MidiConnection {
13 _conn: MidiInputConnection<()>,
14 }
15
16 /// List available MIDI input port names.
17 #[instrument(skip_all)]
18 pub fn list_input_ports() -> Vec<String> {
19 let Ok(midi_in) = MidiInput::new("audiofiles-enumerate") else {
20 return Vec::new();
21 };
22 midi_in
23 .ports()
24 .iter()
25 .filter_map(|p| midi_in.port_name(p).ok())
26 .collect()
27 }
28
29 /// Connect to a MIDI input port by index.
30 ///
31 /// The callback parses note-on/note-off messages and calls `note_on`/`note_off`
32 /// on the instrument directly (low latency). Also pushes `MidiNoteEvent`s to
33 /// `shared.midi_recent_notes` for the GUI activity display.
34 #[instrument(skip_all)]
35 pub fn connect(port_index: usize, shared: Arc<SharedState>) -> Result<MidiConnection, String> {
36 let midi_in = MidiInput::new("audiofiles-input")
37 .map_err(|e| format!("MIDI init: {e}"))?;
38 let ports = midi_in.ports();
39 let port = ports
40 .get(port_index)
41 .ok_or_else(|| format!("MIDI port index {port_index} out of range"))?;
42
43 let shared_cb = shared.clone();
44 let conn = midi_in
45 .connect(
46 port,
47 "audiofiles-midi-in",
48 move |_timestamp, message, _| {
49 if message.len() < 3 {
50 return;
51 }
52 let status = message[0] & 0xF0;
53 let note = message[1];
54 let velocity = message[2];
55
56 match status {
57 0x90 if velocity > 0 => {
58 // Note On
59 shared_cb.instrument.lock().note_on(note, velocity);
60 shared_cb.midi_recent_notes.lock().push(MidiNoteEvent {
61 note,
62 velocity,
63 note_name: note_name(note),
64 timestamp: Instant::now(),
65 });
66 }
67 0x80 | 0x90 => {
68 // Note Off (0x80 or 0x90 with velocity 0)
69 shared_cb.instrument.lock().note_off(note);
70 }
71 _ => {}
72 }
73 },
74 (),
75 )
76 .map_err(|e| format!("MIDI connect: {e}"))?;
77
78 Ok(MidiConnection { _conn: conn })
79 }
80