kenwood_thd75/types/
voice.rs

1//! Voice message memory types.
2//!
3//! The TH-D75 provides 4 voice message memory channels for recording
4//! and playing back short audio messages. Channel 1 supports up to
5//! 30 seconds of audio; channels 2-4 support up to 15 seconds each.
6//! Recorded messages can be transmitted, played back locally, or set
7//! to repeat at a configurable interval.
8//!
9//! Per User Manual Chapter 22 and the menu table:
10//!
11//! - Menu No. 310: Voice message list.
12//! - Menu No. 311: TX monitor (Off/On, default: On) -- hear your own
13//!   transmitted voice message through the speaker.
14//! - Menu No. 312: Digital auto reply -- automatically reply to D-STAR
15//!   calls with a voice message (Off / Voice Message 1-4, default: Off).
16//!
17//! These types model voice message settings from Chapter 22 of the
18//! TH-D75 user manual. Derived from the capability gap analysis
19//! feature 145.
20
21// ---------------------------------------------------------------------------
22// Voice message channel
23// ---------------------------------------------------------------------------
24
25/// Voice message memory channel.
26///
27/// The TH-D75 has 4 voice message channels:
28/// - Channel 1: up to 30 seconds recording
29/// - Channels 2-4: up to 15 seconds recording each
30///
31/// Messages can be recorded from the microphone, played back through
32/// the speaker, transmitted on air, or cleared individually.
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub struct VoiceMessage {
35    /// Channel number (1-4).
36    pub channel: VoiceChannel,
37    /// Channel name (up to 8 characters).
38    pub name: VoiceMessageName,
39    /// Recorded duration in seconds (0 = empty/no recording).
40    pub duration_secs: u8,
41    /// Enable repeat playback.
42    pub repeat: bool,
43    /// Repeat playback interval in seconds (0-60).
44    pub repeat_interval: RepeatInterval,
45}
46
47impl Default for VoiceMessage {
48    fn default() -> Self {
49        Self {
50            channel: VoiceChannel::Ch1,
51            name: VoiceMessageName::default(),
52            duration_secs: 0,
53            repeat: false,
54            repeat_interval: RepeatInterval::default(),
55        }
56    }
57}
58
59/// Voice message channel number (1-4).
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub enum VoiceChannel {
62    /// Channel 1 (up to 30 seconds).
63    Ch1,
64    /// Channel 2 (up to 15 seconds).
65    Ch2,
66    /// Channel 3 (up to 15 seconds).
67    Ch3,
68    /// Channel 4 (up to 15 seconds).
69    Ch4,
70}
71
72impl VoiceChannel {
73    /// Returns the 1-based channel number.
74    #[must_use]
75    pub const fn number(self) -> u8 {
76        match self {
77            Self::Ch1 => 1,
78            Self::Ch2 => 2,
79            Self::Ch3 => 3,
80            Self::Ch4 => 4,
81        }
82    }
83
84    /// Returns the maximum recording duration in seconds for this channel.
85    #[must_use]
86    pub const fn max_duration_secs(self) -> u8 {
87        match self {
88            Self::Ch1 => 30,
89            Self::Ch2 | Self::Ch3 | Self::Ch4 => 15,
90        }
91    }
92}
93
94/// Voice message name (up to 8 characters).
95#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
96pub struct VoiceMessageName(String);
97
98impl VoiceMessageName {
99    /// Maximum length of a voice message name.
100    pub const MAX_LEN: usize = 8;
101
102    /// Creates a new voice message name.
103    ///
104    /// # Errors
105    ///
106    /// Returns `None` if the text exceeds 8 characters.
107    #[must_use]
108    pub fn new(text: &str) -> Option<Self> {
109        if text.len() <= Self::MAX_LEN {
110            Some(Self(text.to_owned()))
111        } else {
112            None
113        }
114    }
115
116    /// Returns the name as a string slice.
117    #[must_use]
118    pub fn as_str(&self) -> &str {
119        &self.0
120    }
121}
122
123/// Repeat playback interval in seconds (0-60).
124///
125/// When repeat playback is enabled, the voice message replays after
126/// waiting for the configured interval.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
128pub struct RepeatInterval(u8);
129
130impl RepeatInterval {
131    /// Maximum repeat interval in seconds.
132    pub const MAX: u8 = 60;
133
134    /// Creates a new repeat interval.
135    ///
136    /// # Errors
137    ///
138    /// Returns `None` if the value exceeds 60 seconds.
139    #[must_use]
140    pub const fn new(seconds: u8) -> Option<Self> {
141        if seconds <= Self::MAX {
142            Some(Self(seconds))
143        } else {
144            None
145        }
146    }
147
148    /// Returns the interval in seconds.
149    #[must_use]
150    pub const fn seconds(self) -> u8 {
151        self.0
152    }
153}
154
155// ---------------------------------------------------------------------------
156// Tests
157// ---------------------------------------------------------------------------
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn voice_channel_numbers() {
165        assert_eq!(VoiceChannel::Ch1.number(), 1);
166        assert_eq!(VoiceChannel::Ch2.number(), 2);
167        assert_eq!(VoiceChannel::Ch3.number(), 3);
168        assert_eq!(VoiceChannel::Ch4.number(), 4);
169    }
170
171    #[test]
172    fn voice_channel_max_durations() {
173        assert_eq!(VoiceChannel::Ch1.max_duration_secs(), 30);
174        assert_eq!(VoiceChannel::Ch2.max_duration_secs(), 15);
175        assert_eq!(VoiceChannel::Ch3.max_duration_secs(), 15);
176        assert_eq!(VoiceChannel::Ch4.max_duration_secs(), 15);
177    }
178
179    #[test]
180    fn voice_message_default() {
181        let msg = VoiceMessage::default();
182        assert_eq!(msg.channel, VoiceChannel::Ch1);
183        assert_eq!(msg.duration_secs, 0);
184        assert!(!msg.repeat);
185    }
186
187    #[test]
188    fn voice_message_name_valid() {
189        let name = VoiceMessageName::new("CQ Call").unwrap();
190        assert_eq!(name.as_str(), "CQ Call");
191    }
192
193    #[test]
194    fn voice_message_name_max_length() {
195        let name = VoiceMessageName::new("12345678").unwrap();
196        assert_eq!(name.as_str().len(), 8);
197    }
198
199    #[test]
200    fn voice_message_name_too_long() {
201        assert!(VoiceMessageName::new("123456789").is_none());
202    }
203
204    #[test]
205    fn repeat_interval_valid_range() {
206        assert!(RepeatInterval::new(0).is_some());
207        assert!(RepeatInterval::new(30).is_some());
208        assert!(RepeatInterval::new(60).is_some());
209    }
210
211    #[test]
212    fn repeat_interval_invalid() {
213        assert!(RepeatInterval::new(61).is_none());
214    }
215
216    #[test]
217    fn repeat_interval_value() {
218        let interval = RepeatInterval::new(45).unwrap();
219        assert_eq!(interval.seconds(), 45);
220    }
221
222    #[test]
223    fn repeat_interval_default() {
224        let interval = RepeatInterval::default();
225        assert_eq!(interval.seconds(), 0);
226    }
227}