reaper_medium/
midi.rs

1use helgoboss_midi::{ShortMessage, U7};
2use reaper_low::raw;
3
4use std::os::raw::c_int;
5use std::ptr::NonNull;
6
7/// Pointer to a MIDI input device.
8//
9// Case 3: Internals exposed: no | vtable: yes
10// ===========================================
11//
12// It's important that this type is not cloneable! Otherwise consumers could easily let it escape
13// its intended usage scope (audio hook), which would lead to undefined behavior.
14//
15// Internals exposed: no | vtable: yes (Rust => REAPER)
16#[derive(Eq, PartialEq, Hash, Debug)]
17pub struct MidiInput(pub(crate) NonNull<raw::midi_Input>);
18
19impl MidiInput {
20    /// Returns the list of MIDI events which are currently in the buffer.
21    ///
22    /// This must only be called in the real-time audio thread! See [`get_midi_input()`].
23    ///
24    /// # Design
25    ///
26    /// In the past this function was unsafe and expected a closure which let the consumer do
27    /// something with the event list. All of that is not necessary anymore since we ensure in
28    /// [`get_midi_input()`] that we only ever publish valid [`MidiInput`] instances, and those only
29    /// by a very short-lived reference that's not possible to cache anywhere. That makes it
30    /// possible to bind the lifetime of the event list to the one of the [`MidiInput`] and
31    /// everything is fine!
32    ///
33    /// Returning an owned event list would be wasteful because we would need to copy all events
34    /// first. That would be especially bad because this code is supposed to run in the audio
35    /// thread and therefore has real-time requirements.
36    ///
37    /// [`MidiInput`]: struct.MidiInput.html
38    /// [`get_midi_input()`]: struct.ReaperFunctions.html#method.get_midi_input
39    pub fn get_read_buf(&self) -> MidiEventList<'_> {
40        let raw_evt_list = unsafe { self.0.as_ref().GetReadBuf() };
41        MidiEventList::new(unsafe { &*raw_evt_list })
42    }
43}
44
45/// A list of MIDI events borrowed from REAPER.
46//
47// Internals exposed: no | vtable: yes (Rust => REAPER)
48#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
49pub struct MidiEventList<'a>(&'a raw::MIDI_eventlist);
50
51impl<'a> MidiEventList<'a> {
52    pub(super) fn new(raw_evt_list: &'a raw::MIDI_eventlist) -> Self {
53        MidiEventList(raw_evt_list)
54    }
55
56    /// Returns an iterator exposing the contained MIDI events.
57    ///
58    /// `bpos` is the iterator start position.
59    pub fn enum_items(&self, bpos: u32) -> impl Iterator<Item = MidiEvent<'a>> {
60        EnumItems {
61            raw_list: self.0,
62            bpos: bpos as i32,
63        }
64    }
65}
66
67/// A MIDI event borrowed from REAPER.
68// # Internals exposed: yes | vtable: no
69// TODO-low Can be converted into an owned MIDI event in case it needs to live longer than REAPER
70//  keeps  the event around.
71#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
72pub struct MidiEvent<'a>(&'a raw::MIDI_event_t);
73
74/// A MIDI message borrowed from REAPER.
75#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
76pub struct MidiMessage<'a>(&'a raw::MIDI_event_t);
77
78impl<'a> MidiEvent<'a> {
79    pub(crate) unsafe fn new(raw_evt: &'a raw::MIDI_event_t) -> Self {
80        MidiEvent(raw_evt)
81    }
82
83    /// Returns the frame offset.
84    ///
85    /// Unit: 1/1024000 of a second, *not* sample frames!
86    pub fn frame_offset(&self) -> u32 {
87        self.0.frame_offset as u32
88    }
89
90    /// Returns the actual message.
91    pub fn message(&self) -> MidiMessage<'a> {
92        MidiMessage::new(self.0)
93    }
94}
95
96impl<'a> MidiMessage<'a> {
97    pub(super) fn new(raw_evt: &'a raw::MIDI_event_t) -> Self {
98        MidiMessage(raw_evt)
99    }
100}
101
102struct EnumItems<'a> {
103    raw_list: &'a raw::MIDI_eventlist,
104    bpos: i32,
105}
106
107impl<'a> Iterator for EnumItems<'a> {
108    type Item = MidiEvent<'a>;
109
110    fn next(&mut self) -> Option<Self::Item> {
111        let raw_evt = unsafe { self.raw_list.EnumItems(&mut self.bpos as *mut c_int) };
112        if raw_evt.is_null() {
113            // No MIDI events left
114            return None;
115        }
116        let evt = unsafe { MidiEvent::new(&*raw_evt) };
117        Some(evt)
118    }
119}
120
121impl<'a> ShortMessage for MidiMessage<'a> {
122    fn status_byte(&self) -> u8 {
123        self.0.midi_message[0]
124    }
125
126    fn data_byte_1(&self) -> U7 {
127        unsafe { U7::new_unchecked(self.0.midi_message[1]) }
128    }
129
130    fn data_byte_2(&self) -> U7 {
131        unsafe { U7::new_unchecked(self.0.midi_message[2]) }
132    }
133}