1use crate::{event::MidiEvent, router::MidiRouter};
4use midir::{MidiInput, MidiInputConnection};
5use std::sync::{Arc, Mutex};
6
7pub struct MidiEngine {
9 router: Arc<Mutex<MidiRouter>>,
10 _connections: Vec<MidiInputConnection<()>>,
11 sample_counter: Arc<Mutex<u64>>,
12}
13
14impl MidiEngine {
15 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 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 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 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 pub fn router(&self) -> Arc<Mutex<MidiRouter>> {
84 Arc::clone(&self.router)
85 }
86
87 pub fn advance_samples(&self, samples: u64) {
89 *self.sample_counter.lock().unwrap() += samples;
90 }
91
92 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}