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}