Skip to main content

rill_core/queues/
telemetry_block.rs

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