bbx_midi/
buffer.rs

1//! Lock-free MIDI buffer for thread-safe communication.
2//!
3//! Provides a realtime-safe channel for passing MIDI messages between threads,
4//! suitable for MIDI input thread to audio thread communication.
5
6use bbx_core::spsc::{Consumer, Producer, SpscRingBuffer};
7
8use crate::message::MidiMessage;
9
10/// Producer side of the MIDI buffer (used in MIDI input thread).
11pub struct MidiBufferProducer {
12    producer: Producer<MidiMessage>,
13}
14
15impl MidiBufferProducer {
16    /// Try to send a MIDI message to the consumer.
17    ///
18    /// Returns `true` if the message was sent, `false` if the buffer was full.
19    #[inline]
20    pub fn try_send(&mut self, message: MidiMessage) -> bool {
21        self.producer.try_push(message).is_ok()
22    }
23
24    /// Check if the buffer is full.
25    #[inline]
26    pub fn is_full(&self) -> bool {
27        self.producer.is_full()
28    }
29}
30
31/// Consumer side of the MIDI buffer (used in audio thread).
32///
33/// All methods are realtime-safe and do not allocate.
34pub struct MidiBufferConsumer {
35    consumer: Consumer<MidiMessage>,
36}
37
38impl MidiBufferConsumer {
39    /// Pop a single MIDI message from the buffer (realtime-safe).
40    ///
41    /// Returns `Some(MidiMessage)` if available, `None` if empty.
42    #[inline]
43    pub fn try_pop(&mut self) -> Option<MidiMessage> {
44        self.consumer.try_pop()
45    }
46
47    /// Drain all available MIDI messages into the provided buffer.
48    ///
49    /// Returns the number of messages drained.
50    #[inline]
51    pub fn drain_into(&mut self, buffer: &mut Vec<MidiMessage>) -> usize {
52        let mut count = 0;
53        while let Some(msg) = self.consumer.try_pop() {
54            buffer.push(msg);
55            count += 1;
56        }
57        count
58    }
59
60    /// Check if the buffer is empty.
61    #[inline]
62    pub fn is_empty(&self) -> bool {
63        self.consumer.is_empty()
64    }
65}
66
67/// Create a MIDI buffer pair for thread-safe MIDI message transfer.
68///
69/// The `capacity` determines how many MIDI messages can be buffered.
70/// A typical value is 64-256 messages.
71///
72/// # Examples
73///
74/// ```
75/// use bbx_midi::{MidiMessage, buffer::midi_buffer};
76///
77/// let (mut producer, mut consumer) = midi_buffer(64);
78///
79/// // In MIDI input thread
80/// let msg = MidiMessage::new([0x90, 60, 100]);
81/// producer.try_send(msg);
82///
83/// // In audio thread
84/// while let Some(msg) = consumer.try_pop() {
85///     // Process MIDI message
86/// }
87/// ```
88pub fn midi_buffer(capacity: usize) -> (MidiBufferProducer, MidiBufferConsumer) {
89    let (producer, consumer) = SpscRingBuffer::new(capacity);
90    (MidiBufferProducer { producer }, MidiBufferConsumer { consumer })
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_send_receive() {
99        let (mut producer, mut consumer) = midi_buffer(8);
100
101        let msg = MidiMessage::new([0x90, 60, 100]);
102        assert!(producer.try_send(msg));
103
104        let received = consumer.try_pop().expect("should have message");
105        assert_eq!(received.get_note_number(), Some(60));
106        assert!(consumer.try_pop().is_none());
107    }
108
109    #[test]
110    fn test_buffer_overflow() {
111        let (mut producer, _consumer) = midi_buffer(2);
112
113        let msg = MidiMessage::new([0x90, 60, 100]);
114        assert!(producer.try_send(msg));
115        assert!(producer.try_send(msg));
116        assert!(!producer.try_send(msg));
117        assert!(producer.is_full());
118    }
119
120    #[test]
121    fn test_drain_into() {
122        let (mut producer, mut consumer) = midi_buffer(8);
123
124        producer.try_send(MidiMessage::new([0x90, 60, 100]));
125        producer.try_send(MidiMessage::new([0x90, 64, 80]));
126        producer.try_send(MidiMessage::new([0x80, 60, 0]));
127
128        let mut buffer = Vec::new();
129        let count = consumer.drain_into(&mut buffer);
130
131        assert_eq!(count, 3);
132        assert_eq!(buffer.len(), 3);
133        assert_eq!(buffer[0].get_note_number(), Some(60));
134        assert_eq!(buffer[1].get_note_number(), Some(64));
135        assert_eq!(buffer[2].get_note_number(), Some(60));
136        assert!(consumer.is_empty());
137    }
138
139    #[test]
140    fn test_is_empty() {
141        let (mut producer, mut consumer) = midi_buffer(4);
142
143        assert!(consumer.is_empty());
144
145        producer.try_send(MidiMessage::new([0x90, 60, 100]));
146        assert!(!consumer.is_empty());
147
148        consumer.try_pop();
149        assert!(consumer.is_empty());
150    }
151}