dstar_gateway_core/
voice.rs

1//! D-STAR voice frame types and constants.
2//!
3//! Each voice frame carries 9 bytes of AMBE-encoded audio and 3
4//! bytes of slow data. Frames are transmitted at 50 Hz (one every
5//! 20 ms) per the JARL D-STAR specification. A superframe is 21
6//! frames (frame 0 is sync, frames 1-20 carry slow data) for a
7//! total of 420 ms per superframe.
8//!
9//! See `g4klx/MMDVMHost/DStarDefines.h:44` for `NULL_AMBE_DATA_BYTES`
10//! (the AMBE silence pattern).
11
12/// AMBE silence frame (9 bytes) — used in EOT packets.
13///
14/// Reference: `g4klx/MMDVMHost/DStarDefines.h:44` (`NULL_AMBE_DATA_BYTES`).
15pub const AMBE_SILENCE: [u8; 9] = [0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8];
16
17/// D-STAR sync bytes (3 bytes) — slow data filler for sync frames.
18pub const DSTAR_SYNC_BYTES: [u8; 3] = [0x55, 0x55, 0x55];
19
20/// A D-STAR voice data frame (9 bytes AMBE + 3 bytes slow data).
21///
22/// 21 frames form one superframe. Frame 0 carries the sync pattern,
23/// frames 1-20 carry slow data. At 20 ms per frame, one superframe
24/// is 420 ms of audio.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct VoiceFrame {
27    /// AMBE 3600x2450 codec voice data (9 bytes).
28    pub ambe: [u8; 9],
29    /// Slow data payload (3 bytes).
30    pub slow_data: [u8; 3],
31}
32
33impl VoiceFrame {
34    /// Create a silence frame (used for EOT and padding).
35    #[must_use]
36    pub const fn silence() -> Self {
37        Self {
38            ambe: AMBE_SILENCE,
39            slow_data: DSTAR_SYNC_BYTES,
40        }
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn ambe_silence_is_nine_bytes() {
50        assert_eq!(AMBE_SILENCE.len(), 9);
51    }
52
53    #[test]
54    fn ambe_silence_matches_mmdvmhost() {
55        // Reference: g4klx/MMDVMHost/DStarDefines.h:44
56        assert_eq!(
57            AMBE_SILENCE,
58            [0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8]
59        );
60    }
61
62    #[test]
63    fn dstar_sync_bytes_are_0x555555() {
64        assert_eq!(DSTAR_SYNC_BYTES, [0x55, 0x55, 0x55]);
65    }
66
67    #[test]
68    fn voice_frame_silence_is_ambe_silence_plus_sync() {
69        let frame = VoiceFrame::silence();
70        assert_eq!(frame.ambe, AMBE_SILENCE);
71        assert_eq!(frame.slow_data, DSTAR_SYNC_BYTES);
72    }
73}