bbx_draw/
bridge.rs

1//! Audio and MIDI data bridges for thread-safe communication.
2//!
3//! Uses `bbx_core::SpscRingBuffer` for lock-free, real-time-safe communication
4//! between audio/MIDI threads and the visualization thread.
5
6use bbx_core::spsc::{Consumer, Producer, SpscRingBuffer};
7use bbx_dsp::Frame;
8use bbx_midi::message::MidiMessage;
9
10/// Producer side of the audio bridge (used in audio thread).
11pub struct AudioBridgeProducer {
12    producer: Producer<Frame>,
13}
14
15impl AudioBridgeProducer {
16    /// Try to send a frame to the visualization thread.
17    ///
18    /// Returns `true` if the frame was sent, `false` if the buffer was full.
19    /// Dropping frames is acceptable for visualization purposes.
20    pub fn try_send(&mut self, frame: Frame) -> bool {
21        self.producer.try_push(frame).is_ok()
22    }
23
24    /// Check if the buffer is full.
25    pub fn is_full(&self) -> bool {
26        self.producer.is_full()
27    }
28}
29
30/// Consumer side of the audio bridge (used in visualization or audio thread).
31///
32/// All methods are realtime-safe and do not allocate.
33pub struct AudioBridgeConsumer {
34    consumer: Consumer<Frame>,
35}
36
37impl AudioBridgeConsumer {
38    /// Pop a single frame from the buffer (realtime-safe).
39    ///
40    /// Returns `Some(Frame)` if available, `None` if empty.
41    #[inline]
42    pub fn try_pop(&mut self) -> Option<Frame> {
43        self.consumer.try_pop()
44    }
45
46    /// Check if there are frames available.
47    #[inline]
48    pub fn is_empty(&self) -> bool {
49        self.consumer.is_empty()
50    }
51
52    /// Returns the number of frames currently available.
53    #[inline]
54    pub fn len(&self) -> usize {
55        self.consumer.len()
56    }
57}
58
59/// Create an audio bridge pair for thread-safe audio data transfer.
60///
61/// The `capacity` determines how many audio frames can be buffered.
62/// A typical value is 4-16 frames.
63pub fn audio_bridge(capacity: usize) -> (AudioBridgeProducer, AudioBridgeConsumer) {
64    let (producer, consumer) = SpscRingBuffer::new(capacity);
65    (AudioBridgeProducer { producer }, AudioBridgeConsumer { consumer })
66}
67
68/// Producer side of the MIDI bridge (used in MIDI input thread).
69pub struct MidiBridgeProducer {
70    producer: Producer<MidiMessage>,
71}
72
73impl MidiBridgeProducer {
74    /// Try to send a MIDI message to the visualization thread.
75    ///
76    /// Returns `true` if the message was sent, `false` if the buffer was full.
77    pub fn try_send(&mut self, message: MidiMessage) -> bool {
78        self.producer.try_push(message).is_ok()
79    }
80
81    /// Check if the buffer is full.
82    pub fn is_full(&self) -> bool {
83        self.producer.is_full()
84    }
85}
86
87/// Consumer side of the MIDI bridge (used in visualization thread).
88pub struct MidiBridgeConsumer {
89    consumer: Consumer<MidiMessage>,
90}
91
92impl MidiBridgeConsumer {
93    /// Drain all available MIDI messages from the buffer.
94    pub fn drain(&mut self) -> Vec<MidiMessage> {
95        let mut messages = Vec::new();
96        while let Some(msg) = self.consumer.try_pop() {
97            messages.push(msg);
98        }
99        messages
100    }
101
102    /// Check if there are messages available.
103    pub fn is_empty(&self) -> bool {
104        self.consumer.is_empty()
105    }
106}
107
108/// Create a MIDI bridge pair for thread-safe MIDI message transfer.
109///
110/// The `capacity` determines how many MIDI messages can be buffered.
111/// A typical value is 64-256 messages.
112pub fn midi_bridge(capacity: usize) -> (MidiBridgeProducer, MidiBridgeConsumer) {
113    let (producer, consumer) = SpscRingBuffer::new(capacity);
114    (MidiBridgeProducer { producer }, MidiBridgeConsumer { consumer })
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_audio_bridge_send_receive() {
123        let (mut producer, mut consumer) = audio_bridge(4);
124
125        let samples = [0.5f32, -0.5, 0.25, -0.25];
126        let frame = Frame::new(&samples, 44100, 2);
127        assert!(producer.try_send(frame));
128
129        let frame = consumer.try_pop().expect("should have frame");
130        assert_eq!(frame.samples.len(), 4);
131        assert_eq!(frame.sample_rate, 44100);
132        assert!(consumer.try_pop().is_none());
133    }
134
135    #[test]
136    fn test_audio_bridge_overflow() {
137        let (mut producer, _consumer) = audio_bridge(2);
138
139        let samples = [0.0f32];
140        let frame = Frame::new(&samples, 44100, 1);
141        assert!(producer.try_send(frame.clone()));
142        assert!(producer.try_send(frame.clone()));
143        assert!(!producer.try_send(frame));
144    }
145
146    #[test]
147    fn test_try_pop_empty() {
148        let (_producer, mut consumer) = audio_bridge(4);
149        assert!(consumer.try_pop().is_none());
150        assert!(consumer.is_empty());
151        assert_eq!(consumer.len(), 0);
152    }
153
154    #[test]
155    fn test_try_pop_multiple() {
156        let (mut producer, mut consumer) = audio_bridge(4);
157
158        let samples1 = [1.0f32, 2.0];
159        let samples2 = [3.0f32, 4.0];
160        producer.try_send(Frame::new(&samples1, 44100, 1));
161        producer.try_send(Frame::new(&samples2, 44100, 1));
162
163        assert_eq!(consumer.len(), 2);
164
165        let f1 = consumer.try_pop().unwrap();
166        let f2 = consumer.try_pop().unwrap();
167        assert!(consumer.try_pop().is_none());
168
169        assert_eq!(f1.samples.as_slice(), &[1.0, 2.0]);
170        assert_eq!(f2.samples.as_slice(), &[3.0, 4.0]);
171    }
172
173    #[test]
174    fn test_midi_bridge_send_receive() {
175        let (mut producer, mut consumer) = midi_bridge(8);
176
177        let msg = MidiMessage::new([0x90, 60, 100]);
178        assert!(producer.try_send(msg));
179
180        let messages = consumer.drain();
181        assert_eq!(messages.len(), 1);
182    }
183}