1use crate::audio::WHISPER_SAMPLE_RATE;
8use crate::error::{Result, UserError};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct SampleRateHz(u32);
13
14impl SampleRateHz {
15 pub fn try_new(hz: u32) -> Result<Self> {
17 if hz == 0 {
18 return Err(UserError::Other {
19 message: "sample rate must be positive".into(),
20 }
21 .into());
22 }
23 Ok(Self(hz))
24 }
25
26 pub fn whisper() -> Self {
28 Self(WHISPER_SAMPLE_RATE)
29 }
30
31 pub fn get(self) -> u32 {
32 self.0
33 }
34
35 pub fn is_whisper(self) -> bool {
36 self.0 == WHISPER_SAMPLE_RATE
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct FiniteDurationSecs(f64);
43
44impl FiniteDurationSecs {
45 pub fn try_new(secs: f64) -> Result<Self> {
46 if !secs.is_finite() || secs < 0.0 {
47 return Err(UserError::Other {
48 message: format!("duration must be finite and non-negative (got {secs})"),
49 }
50 .into());
51 }
52 Ok(Self(secs))
53 }
54
55 pub fn get(self) -> f64 {
56 self.0
57 }
58
59 pub fn zero() -> Self {
60 Self(0.0)
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub struct ModelId(String);
67
68impl ModelId {
69 pub fn try_new(id: impl Into<String>) -> Result<Self> {
70 let s = id.into();
71 let t = s.trim();
72 if t.is_empty() {
73 return Err(UserError::Other {
74 message: "model id must be non-empty".into(),
75 }
76 .into());
77 }
78 if t.chars().any(|c| c.is_control()) {
79 return Err(UserError::Other {
80 message: "model id must not contain control characters".into(),
81 }
82 .into());
83 }
84 Ok(Self(t.to_string()))
85 }
86
87 pub fn as_str(&self) -> &str {
88 &self.0
89 }
90
91 pub fn into_inner(self) -> String {
92 self.0
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn sample_rate_rejects_zero() {
102 assert!(SampleRateHz::try_new(0).is_err());
103 assert_eq!(SampleRateHz::whisper().get(), WHISPER_SAMPLE_RATE);
104 }
105
106 #[test]
107 fn sample_rate_is_whisper_only_for_16000() {
108 assert!(SampleRateHz::whisper().is_whisper());
110 assert!(SampleRateHz::try_new(WHISPER_SAMPLE_RATE)
111 .unwrap()
112 .is_whisper());
113 assert!(!SampleRateHz::try_new(8_000).unwrap().is_whisper());
114 assert!(!SampleRateHz::try_new(44_100).unwrap().is_whisper());
115 }
116
117 #[test]
118 fn duration_rejects_nan() {
119 assert!(FiniteDurationSecs::try_new(f64::NAN).is_err());
120 assert!(FiniteDurationSecs::try_new(-1.0).is_err());
121 assert_eq!(FiniteDurationSecs::try_new(1.5).unwrap().get(), 1.5);
122 }
123
124 #[test]
125 fn model_id_rejects_empty_and_control() {
126 assert!(ModelId::try_new("").is_err());
127 assert!(ModelId::try_new(" ").is_err());
128 assert!(ModelId::try_new("a\nb").is_err());
129 assert_eq!(ModelId::try_new("tiny-q5_1").unwrap().as_str(), "tiny-q5_1");
130 }
131
132 #[test]
133 fn model_id_into_inner_preserves_value() {
134 let id = ModelId::try_new("tiny-q5_1").unwrap();
136 assert_eq!(id.into_inner(), "tiny-q5_1");
137 }
138}