1#![cfg_attr(not(feature = "std"), no_std)]
2use core::cmp::Ordering;
41use core::fmt::Display;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum DtmfKey {
46 K1, K2, K3, A,
47 K4, K5, K6, B,
48 K7, K8, K9, C,
49 Star, K0, Hash, D,
50}
51
52impl Display for DtmfKey {
53 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 write!(f, "{}", self.to_char())
55 }
56}
57
58impl DtmfKey {
59 pub const fn from_char(c: char) -> Option<Self> {
61 match c {
62 '1' => Some(Self::K1),
63 '2' => Some(Self::K2),
64 '3' => Some(Self::K3),
65 'A' => Some(Self::A),
66 '4' => Some(Self::K4),
67 '5' => Some(Self::K5),
68 '6' => Some(Self::K6),
69 'B' => Some(Self::B),
70 '7' => Some(Self::K7),
71 '8' => Some(Self::K8),
72 '9' => Some(Self::K9),
73 'C' => Some(Self::C),
74 '*' => Some(Self::Star),
75 '0' => Some(Self::K0),
76 '#' => Some(Self::Hash),
77 'D' => Some(Self::D),
78 _ => None,
79 }
80 }
81
82 pub const fn from_char_or_panic(c: char) -> Self {
84 match Self::from_char(c) {
85 Some(k) => k,
86 None => panic!("invalid DTMF char"),
87 }
88 }
89
90 pub const fn to_char(self) -> char {
92 match self {
93 Self::K1 => '1', Self::K2 => '2', Self::K3 => '3', Self::A => 'A',
94 Self::K4 => '4', Self::K5 => '5', Self::K6 => '6', Self::B => 'B',
95 Self::K7 => '7', Self::K8 => '8', Self::K9 => '9', Self::C => 'C',
96 Self::Star => '*', Self::K0 => '0', Self::Hash => '#', Self::D => 'D',
97 }
98 }
99
100 pub const fn freqs(self) -> (u16, u16) {
102 match self {
103 Self::K1 => (697, 1209),
104 Self::K2 => (697, 1336),
105 Self::K3 => (697, 1477),
106 Self::A => (697, 1633),
107
108 Self::K4 => (770, 1209),
109 Self::K5 => (770, 1336),
110 Self::K6 => (770, 1477),
111 Self::B => (770, 1633),
112
113 Self::K7 => (852, 1209),
114 Self::K8 => (852, 1336),
115 Self::K9 => (852, 1477),
116 Self::C => (852, 1633),
117
118 Self::Star => (941, 1209),
119 Self::K0 => (941, 1336),
120 Self::Hash => (941, 1477),
121 Self::D => (941, 1633),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct DtmfTone {
129 pub key: DtmfKey,
130 pub low_hz: u16,
131 pub high_hz: u16,
132}
133
134impl Display for DtmfTone {
135 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136 write!(f, "{}: ({} Hz, {} Hz)", self.key, self.low_hz, self.high_hz)
137 }
138}
139
140pub struct DtmfTable;
142
143impl DtmfTable {
144 pub const LOWS: [u16; 4] = [697, 770, 852, 941];
146 pub const HIGHS: [u16; 4] = [1209, 1336, 1477, 1633];
147
148 pub const ALL_KEYS: [DtmfKey; 16] = [
150 DtmfKey::K1, DtmfKey::K2, DtmfKey::K3, DtmfKey::A,
151 DtmfKey::K4, DtmfKey::K5, DtmfKey::K6, DtmfKey::B,
152 DtmfKey::K7, DtmfKey::K8, DtmfKey::K9, DtmfKey::C,
153 DtmfKey::Star, DtmfKey::K0, DtmfKey::Hash, DtmfKey::D,
154 ];
155
156 pub const ALL_TONES: [DtmfTone; 16] = [
158 DtmfTone { key: DtmfKey::K1, low_hz: 697, high_hz: 1209 },
159 DtmfTone { key: DtmfKey::K2, low_hz: 697, high_hz: 1336 },
160 DtmfTone { key: DtmfKey::K3, low_hz: 697, high_hz: 1477 },
161 DtmfTone { key: DtmfKey::A, low_hz: 697, high_hz: 1633 },
162
163 DtmfTone { key: DtmfKey::K4, low_hz: 770, high_hz: 1209 },
164 DtmfTone { key: DtmfKey::K5, low_hz: 770, high_hz: 1336 },
165 DtmfTone { key: DtmfKey::K6, low_hz: 770, high_hz: 1477 },
166 DtmfTone { key: DtmfKey::B, low_hz: 770, high_hz: 1633 },
167
168 DtmfTone { key: DtmfKey::K7, low_hz: 852, high_hz: 1209 },
169 DtmfTone { key: DtmfKey::K8, low_hz: 852, high_hz: 1336 },
170 DtmfTone { key: DtmfKey::K9, low_hz: 852, high_hz: 1477 },
171 DtmfTone { key: DtmfKey::C, low_hz: 852, high_hz: 1633 },
172
173 DtmfTone { key: DtmfKey::Star, low_hz: 941, high_hz: 1209 },
174 DtmfTone { key: DtmfKey::K0, low_hz: 941, high_hz: 1336 },
175 DtmfTone { key: DtmfKey::Hash, low_hz: 941, high_hz: 1477 },
176 DtmfTone { key: DtmfKey::D, low_hz: 941, high_hz: 1633 },
177 ];
178
179 pub const fn new() -> Self { DtmfTable }
181
182 pub const fn lookup_key(key: DtmfKey) -> (u16, u16) { key.freqs() }
186
187 pub const fn from_pair_exact(low: u16, high: u16) -> Option<DtmfKey> {
189 match (low, high) {
190 (697, 1209) => Some(DtmfKey::K1),
191 (697, 1336) => Some(DtmfKey::K2),
192 (697, 1477) => Some(DtmfKey::K3),
193 (697, 1633) => Some(DtmfKey::A),
194 (770, 1209) => Some(DtmfKey::K4),
195 (770, 1336) => Some(DtmfKey::K5),
196 (770, 1477) => Some(DtmfKey::K6),
197 (770, 1633) => Some(DtmfKey::B),
198 (852, 1209) => Some(DtmfKey::K7),
199 (852, 1336) => Some(DtmfKey::K8),
200 (852, 1477) => Some(DtmfKey::K9),
201 (852, 1633) => Some(DtmfKey::C),
202 (941, 1209) => Some(DtmfKey::Star),
203 (941, 1336) => Some(DtmfKey::K0),
204 (941, 1477) => Some(DtmfKey::Hash),
205 (941, 1633) => Some(DtmfKey::D),
206 _ => None,
207 }
208 }
209
210 pub const fn from_pair_normalised(a: u16, b: u16) -> Option<DtmfKey> {
212 let (low, high) = if a <= b { (a, b) } else { (b, a) };
213 Self::from_pair_exact(low, high)
214 }
215
216 pub fn iter_keys(&self) -> core::slice::Iter<'static, DtmfKey> {
220 Self::ALL_KEYS.iter()
221 }
222
223 pub fn iter_tones(&self) -> core::slice::Iter<'static, DtmfTone> {
225 Self::ALL_TONES.iter()
226 }
227
228 pub fn from_pair_tol_u32(&self, low: u32, high: u32, tol_hz: u32) -> Option<DtmfKey> {
231 let (lo, hi) = normalise_u32_pair(low, high);
232 for t in Self::ALL_TONES {
233 if abs_diff_u32(lo, t.low_hz as u32) <= tol_hz &&
234 abs_diff_u32(hi, t.high_hz as u32) <= tol_hz {
235 return Some(t.key);
236 }
237 }
238 None
239 }
240
241 pub fn from_pair_tol_f64(&self, low: f64, high: f64, tol_hz: f64) -> Option<DtmfKey> {
243 let (lo, hi) = normalise_f64_pair(low, high);
244 for t in Self::ALL_TONES {
245 if (lo - t.low_hz as f64).abs() <= tol_hz &&
246 (hi - t.high_hz as f64).abs() <= tol_hz {
247 return Some(t.key);
248 }
249 }
250 None
251 }
252
253 pub fn nearest_u32(&self, low: u32, high: u32) -> (DtmfKey, u16, u16) {
256 let (lo, hi) = normalise_u32_pair(low, high);
257 let nearest_low = nearest_in_set_u32(lo, &Self::LOWS);
258 let nearest_high = nearest_in_set_u32(hi, &Self::HIGHS);
259 let key = Self::from_pair_exact(nearest_low, nearest_high)
260 .expect("canonical pair must map to a key");
261 (key, nearest_low, nearest_high)
262 }
263
264 pub fn nearest_f64(&self, low: f64, high: f64) -> (DtmfKey, u16, u16) {
266 let (lo, hi) = normalise_f64_pair(low, high);
267 let nearest_low = nearest_in_set_f64(lo, &Self::LOWS);
268 let nearest_high = nearest_in_set_f64(hi, &Self::HIGHS);
269 let key = Self::from_pair_exact(nearest_low, nearest_high)
270 .expect("canonical pair must map to a key");
271 (key, nearest_low, nearest_high)
272 }
273}
274
275impl Display for DtmfTable {
276 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
277 writeln!(f, "DTMF Table:")?;
278 for tone in Self::ALL_TONES.iter() {
279 writeln!(f, " {}", tone)?;
280 }
281 Ok(())
282 }
283}
284
285const fn abs_diff_u32(a: u32, b: u32) -> u32 {
288 if a >= b { a - b } else { b - a }
289}
290
291fn nearest_in_set_u32(x: u32, set: &[u16]) -> u16 {
292 let mut best = set[0];
293 let mut best_d = abs_diff_u32(x, best as u32);
294 let mut i = 1;
295 while i < set.len() {
296 let d = abs_diff_u32(x, set[i] as u32);
297 if d < best_d { best = set[i]; best_d = d; }
298 i += 1;
299 }
300 best
301}
302
303fn nearest_in_set_f64(x: f64, set: &[u16]) -> u16 {
304 let mut best = set[0];
305 let mut best_d = (x - best as f64).abs();
306 let mut i = 1;
307 while i < set.len() {
308 let d = (x - set[i] as f64).abs();
309 if d < best_d { best = set[i]; best_d = d; }
310 i += 1;
311 }
312 best
313}
314
315const fn normalise_u32_pair(a: u32, b: u32) -> (u32, u32) {
316 if a <= b { (a, b) } else { (b, a) }
317}
318
319fn normalise_f64_pair(a: f64, b: f64) -> (f64, f64) {
320 match a.partial_cmp(&b) {
321 Some(Ordering::Greater) => (b, a),
322 _ => (a, b),
323 }
324}