Skip to main content

rill_core/queues/
telemetry_block.rs

1use crate::math::Transcendental;
2use crate::traits::NodeId;
3
4/// Fixed-size telemetry frame for RT-safe ring buffer communication.
5///
6/// Contains a full signal block plus computed metrics (peak, RMS, DC offset).
7/// Implements `Copy` + `Default`, compatible with `SpscQueue` (overwrite-oldest).
8#[repr(C)]
9#[derive(Debug, Clone, Copy)]
10pub struct TelemetryBlock<T: Transcendental, const BUF_SIZE: usize> {
11    /// Source node identifier
12    pub node_id: NodeId,
13    /// Audio channel index
14    pub channel: u32,
15    /// Timestamp (microseconds since UNIX epoch)
16    pub timestamp: u64,
17    /// Sample rate at capture time
18    pub sample_rate: f32,
19    /// Monotonic block counter
20    pub block_index: u64,
21    /// Peak amplitude of the block
22    pub peak: T,
23    /// RMS (root mean square) of the block
24    pub rms: T,
25    /// DC offset (average) of the block
26    pub dc_offset: T,
27    /// Full signal block data
28    pub data: [T; BUF_SIZE],
29}
30
31impl<T: Transcendental, const BUF_SIZE: usize> Default for TelemetryBlock<T, BUF_SIZE> {
32    fn default() -> Self {
33        Self {
34            node_id: NodeId(0),
35            channel: 0,
36            timestamp: 0,
37            sample_rate: 44100.0,
38            block_index: 0,
39            peak: T::ZERO,
40            rms: T::ZERO,
41            dc_offset: T::ZERO,
42            data: [T::ZERO; BUF_SIZE],
43        }
44    }
45}
46
47impl<T: Transcendental, const BUF_SIZE: usize> TelemetryBlock<T, BUF_SIZE> {
48    /// Compute metrics (peak, RMS, DC offset) from the block data.
49    #[inline]
50    pub fn compute_metrics(&mut self) {
51        let mut sum = T::ZERO;
52        let mut sq_sum = T::ZERO;
53        let mut peak = T::ZERO;
54
55        for &sample in self.data.iter() {
56            let abs = sample.abs();
57            if abs > peak {
58                peak = abs;
59            }
60            sum = sum + sample;
61            sq_sum = sq_sum + sample * sample;
62        }
63
64        let len = T::from_f32(BUF_SIZE as f32);
65        self.dc_offset = sum / len;
66        self.rms = (sq_sum / len).sqrt();
67        self.peak = peak;
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_telemetry_block_default() {
77        let block = TelemetryBlock::<f32, 64>::default();
78        assert_eq!(block.node_id, NodeId(0));
79        assert_eq!(block.channel, 0);
80        assert_eq!(block.timestamp, 0);
81        assert_eq!(block.block_index, 0);
82        assert_eq!(block.peak, 0.0);
83        assert_eq!(block.rms, 0.0);
84        assert_eq!(block.dc_offset, 0.0);
85        assert_eq!(block.data.len(), 64);
86    }
87
88    #[test]
89    fn test_telemetry_block_copy() {
90        let block = TelemetryBlock::<f32, 64>::default();
91        let copied = block;
92        assert_eq!(copied.node_id, block.node_id);
93    }
94
95    #[test]
96    fn test_compute_metrics_sine() {
97        let mut block = TelemetryBlock::<f32, 64>::default();
98        for (i, sample) in block.data.iter_mut().enumerate() {
99            *sample = (i as f32 * std::f32::consts::TAU / 64.0).sin();
100        }
101        block.compute_metrics();
102        assert!((block.peak - 1.0).abs() < 0.01, "peak={}", block.peak);
103        assert!((block.rms - 0.707).abs() < 0.01, "rms={}", block.rms);
104        assert!(
105            block.dc_offset.abs() < 0.01,
106            "dc_offset={}",
107            block.dc_offset
108        );
109    }
110
111    #[test]
112    fn test_compute_metrics_dc() {
113        let mut block = TelemetryBlock::<f32, 64>::default();
114        for sample in block.data.iter_mut() {
115            *sample = 0.5;
116        }
117        block.compute_metrics();
118        assert_eq!(block.dc_offset, 0.5);
119        assert_eq!(block.peak, 0.5);
120        assert_eq!(block.rms, 0.5);
121    }
122}