Skip to main content

aether_midi/
engine.rs

1//! MIDI engine — manages hardware MIDI input via midir.
2
3use crate::{event::MidiEvent, router::MidiRouter};
4use midir::{MidiInput, MidiInputConnection};
5use std::sync::{Arc, Mutex};
6
7/// Manages MIDI hardware connections and routes events.
8pub struct MidiEngine {
9    router: Arc<Mutex<MidiRouter>>,
10    _connections: Vec<MidiInputConnection<()>>,
11    sample_counter: Arc<Mutex<u64>>,
12}
13
14impl MidiEngine {
15    /// Create a new MIDI engine.
16    pub fn new() -> Self {
17        Self {
18            router: Arc::new(Mutex::new(MidiRouter::new())),
19            _connections: Vec::new(),
20            sample_counter: Arc::new(Mutex::new(0)),
21        }
22    }
23
24    /// List available MIDI input port names.
25    pub fn list_ports() -> Vec<String> {
26        let midi_in = match MidiInput::new("aether-midi-list") {
27            Ok(m) => m,
28            Err(_) => return Vec::new(),
29        };
30        let ports = midi_in.ports();
31        ports
32            .iter()
33            .filter_map(|p| midi_in.port_name(p).ok())
34            .collect()
35    }
36
37    /// Connect to a MIDI input port by index.
38    /// Returns Ok(()) if connected, Err with message if failed.
39    pub fn connect_port(&mut self, port_index: usize) -> Result<(), String> {
40        let midi_in =
41            MidiInput::new("aether-midi-in").map_err(|e| format!("MIDI init error: {e}"))?;
42        let ports = midi_in.ports();
43        let port = ports
44            .get(port_index)
45            .ok_or_else(|| format!("MIDI port {port_index} not found"))?;
46        let port_name = midi_in
47            .port_name(port)
48            .unwrap_or_else(|_| format!("Port {port_index}"));
49
50        let router = Arc::clone(&self.router);
51        let counter = Arc::clone(&self.sample_counter);
52
53        let conn = midi_in
54            .connect(
55                port,
56                "aether-midi-conn",
57                move |_timestamp_us, bytes, _| {
58                    let ts = *counter.lock().unwrap();
59                    if let Some(event) = MidiEvent::from_bytes(bytes, ts) {
60                        router.lock().unwrap().dispatch(&event);
61                    }
62                },
63                (),
64            )
65            .map_err(|e| format!("MIDI connect error: {e}"))?;
66
67        println!("MIDI: connected to '{port_name}'");
68        self._connections.push(conn);
69        Ok(())
70    }
71
72    /// Connect to the first available MIDI port.
73    pub fn connect_first(&mut self) -> Result<String, String> {
74        let ports = Self::list_ports();
75        if ports.is_empty() {
76            return Err("No MIDI input ports found".into());
77        }
78        self.connect_port(0)?;
79        Ok(ports[0].clone())
80    }
81
82    /// Get a reference to the router for registering handlers.
83    pub fn router(&self) -> Arc<Mutex<MidiRouter>> {
84        Arc::clone(&self.router)
85    }
86
87    /// Advance the sample counter (call once per audio buffer).
88    pub fn advance_samples(&self, samples: u64) {
89        *self.sample_counter.lock().unwrap() += samples;
90    }
91
92    /// Inject a MIDI event directly (for testing or virtual keyboard).
93    pub fn inject(&self, event: MidiEvent) {
94        self.router.lock().unwrap().dispatch(&event);
95    }
96}
97
98impl Default for MidiEngine {
99    fn default() -> Self {
100        Self::new()
101    }
102}