Skip to main content

wavecraft_metering/
lib.rs

1//! Real-time safe metering for audio→UI communication.
2//!
3//! Provides lock-free SPSC ring buffers for transferring peak/RMS meter data
4//! from the audio thread to the UI thread without allocations or blocking.
5
6pub mod dev;
7
8pub use wavecraft_protocol::MeterFrame;
9
10/// Producer side of meter channel (audio thread).
11///
12/// Real-time safe: no allocations, no locks.
13pub struct MeterProducer {
14    producer: rtrb::Producer<MeterFrame>,
15}
16
17impl MeterProducer {
18    /// Push a meter frame to the ring buffer.
19    ///
20    /// Fails silently if buffer is full (drops oldest frame).
21    /// This is acceptable for metering; UI will get next frame.
22    #[inline]
23    pub fn push(&mut self, frame: MeterFrame) {
24        // Non-blocking write; if full, we just skip this frame
25        let _ = self.producer.push(frame);
26    }
27
28    /// Returns the number of frames that can be written without blocking.
29    #[inline]
30    pub fn available_write(&self) -> usize {
31        self.producer.slots()
32    }
33}
34
35/// Consumer side of meter channel (UI thread).
36///
37/// Not real-time safe (can allocate), but non-blocking on audio thread.
38pub struct MeterConsumer {
39    consumer: rtrb::Consumer<MeterFrame>,
40}
41
42impl MeterConsumer {
43    /// Read the latest meter frame, discarding all older frames.
44    ///
45    /// Returns `None` if no frames are available.
46    /// Efficient for UI polling: only processes most recent data.
47    pub fn read_latest(&mut self) -> Option<MeterFrame> {
48        let mut latest = None;
49        while let Ok(frame) = self.consumer.pop() {
50            latest = Some(frame);
51        }
52        latest
53    }
54
55    /// Pop the oldest meter frame from the buffer.
56    ///
57    /// Returns `None` if no frames are available.
58    pub fn pop(&mut self) -> Option<MeterFrame> {
59        self.consumer.pop().ok()
60    }
61
62    /// Returns the number of frames available to read.
63    pub fn available_read(&self) -> usize {
64        self.consumer.slots()
65    }
66}
67
68/// Create a pair of meter producer/consumer with the given buffer capacity.
69///
70/// Capacity should be large enough to handle UI polling delays without drops,
71/// but small enough to avoid stale data.
72///
73/// Recommended: 32-128 frames (enough for 60 Hz UI @ 512-sample audio blocks).
74pub fn create_meter_channel(capacity: usize) -> (MeterProducer, MeterConsumer) {
75    let (producer, consumer) = rtrb::RingBuffer::new(capacity);
76    (MeterProducer { producer }, MeterConsumer { consumer })
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    fn empty_frame() -> MeterFrame {
84        MeterFrame {
85            peak_l: 0.0,
86            peak_r: 0.0,
87            rms_l: 0.0,
88            rms_r: 0.0,
89            timestamp: 0,
90        }
91    }
92
93    #[test]
94    fn meter_ring_push_pop() {
95        let (mut producer, mut consumer) = create_meter_channel(4);
96
97        let frame = MeterFrame {
98            peak_l: 0.5,
99            peak_r: 0.6,
100            rms_l: 0.3,
101            rms_r: 0.4,
102            timestamp: 1000,
103        };
104
105        producer.push(frame);
106        let read = consumer.pop().expect("should read frame");
107
108        assert_eq!(read.peak_l, 0.5);
109        assert_eq!(read.peak_r, 0.6);
110        assert_eq!(read.rms_l, 0.3);
111        assert_eq!(read.rms_r, 0.4);
112        assert_eq!(read.timestamp, 1000);
113    }
114
115    #[test]
116    fn meter_ring_overflow() {
117        let (mut producer, mut consumer) = create_meter_channel(2);
118
119        // Fill buffer
120        producer.push(MeterFrame {
121            peak_l: 1.0,
122            ..empty_frame()
123        });
124        producer.push(MeterFrame {
125            peak_l: 2.0,
126            ..empty_frame()
127        });
128
129        // Overflow silently drops (ring buffer behavior)
130        producer.push(MeterFrame {
131            peak_l: 3.0,
132            ..empty_frame()
133        });
134
135        // First two frames should still be readable
136        assert_eq!(consumer.pop().unwrap().peak_l, 1.0);
137        assert_eq!(consumer.pop().unwrap().peak_l, 2.0);
138
139        // Third frame was dropped
140        assert!(consumer.pop().is_none());
141    }
142
143    #[test]
144    fn read_latest_discards_old() {
145        let (mut producer, mut consumer) = create_meter_channel(8);
146
147        // Push multiple frames
148        for i in 0..5 {
149            producer.push(MeterFrame {
150                peak_l: i as f32,
151                ..empty_frame()
152            });
153        }
154
155        // read_latest should return only the newest frame
156        let latest = consumer.read_latest().expect("should have frame");
157        assert_eq!(latest.peak_l, 4.0);
158
159        // All frames consumed
160        assert!(consumer.pop().is_none());
161    }
162
163    #[test]
164    fn empty_buffer_returns_none() {
165        let (_, mut consumer) = create_meter_channel(4);
166        assert!(consumer.pop().is_none());
167        assert!(consumer.read_latest().is_none());
168    }
169
170    #[test]
171    fn available_counts() {
172        let (mut producer, mut consumer) = create_meter_channel(4);
173
174        assert_eq!(consumer.available_read(), 0);
175
176        producer.push(empty_frame());
177        producer.push(empty_frame());
178
179        assert_eq!(consumer.available_read(), 2);
180
181        consumer.pop();
182        assert_eq!(consumer.available_read(), 1);
183    }
184}