Skip to main content

libretro_core/
midi.rs

1//! MIDI service-interface wrappers.
2//!
3//! `MidiInterface` exposes optional frontend MIDI input/output as typed reads,
4//! writes, flushes, and delta-time values without raw callback table access.
5
6use crate::raw;
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
9pub struct MidiDeltaMicros(u32);
10
11impl MidiDeltaMicros {
12    pub const fn new(micros: u32) -> Self {
13        Self(micros)
14    }
15
16    pub const fn as_micros(self) -> u32 {
17        self.0
18    }
19}
20
21impl From<u32> for MidiDeltaMicros {
22    fn from(micros: u32) -> Self {
23        Self::new(micros)
24    }
25}
26
27#[derive(Clone, Copy, Debug, Default)]
28pub struct MidiInterface {
29    raw: raw::retro_midi_interface,
30}
31
32impl MidiInterface {
33    pub(crate) const fn from_raw(raw: raw::retro_midi_interface) -> Self {
34        Self { raw }
35    }
36
37    pub const fn is_available(self) -> bool {
38        self.raw.input_enabled.is_some()
39            && self.raw.output_enabled.is_some()
40            && self.raw.read.is_some()
41            && self.raw.write.is_some()
42            && self.raw.flush.is_some()
43    }
44
45    pub fn input_enabled(self) -> bool {
46        self.raw
47            .input_enabled
48            .map(|input_enabled| unsafe { input_enabled() })
49            .unwrap_or(false)
50    }
51
52    pub fn output_enabled(self) -> bool {
53        self.raw
54            .output_enabled
55            .map(|output_enabled| unsafe { output_enabled() })
56            .unwrap_or(false)
57    }
58
59    pub fn read_byte(self) -> Option<u8> {
60        let read = self.raw.read?;
61        let mut byte = 0u8;
62        if unsafe { read(&mut byte as *mut u8) } {
63            Some(byte)
64        } else {
65            None
66        }
67    }
68
69    pub fn write_byte(self, byte: u8, delta_time: impl Into<MidiDeltaMicros>) -> bool {
70        self.raw
71            .write
72            .map(|write| unsafe { write(byte, delta_time.into().as_micros()) })
73            .unwrap_or(false)
74    }
75
76    pub fn flush(self) -> bool {
77        self.raw
78            .flush
79            .map(|flush| unsafe { flush() })
80            .unwrap_or(false)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::{MidiDeltaMicros, MidiInterface};
87
88    #[test]
89    fn midi_delta_micros_preserves_raw_value() {
90        assert_eq!(MidiDeltaMicros::new(240).as_micros(), 240);
91    }
92
93    #[test]
94    fn empty_midi_interface_reports_unavailable() {
95        let midi = MidiInterface::default();
96
97        assert!(!midi.is_available());
98        assert!(!midi.input_enabled());
99        assert!(!midi.output_enabled());
100        assert_eq!(midi.read_byte(), None);
101        assert!(!midi.write_byte(0x90, MidiDeltaMicros::new(0)));
102        assert!(!midi.flush());
103    }
104}