1#![cfg_attr(not(feature = "std"), no_std)]
2use core::cmp::Ordering;
40use core::fmt::Display;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum DtmfKey {
45 K1,
46 K2,
47 K3,
48 A,
49 K4,
50 K5,
51 K6,
52 B,
53 K7,
54 K8,
55 K9,
56 C,
57 Star,
58 K0,
59 Hash,
60 D,
61}
62
63impl Display for DtmfKey {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 if f.alternate() {
66 write!(f, "DtmfKey::{:?}", self)
68 } else {
69 write!(f, "{}", self.to_char())
71 }
72 }
73}
74
75impl DtmfKey {
76 pub const fn from_char(c: char) -> Option<Self> {
78 match c {
79 '1' => Some(Self::K1),
80 '2' => Some(Self::K2),
81 '3' => Some(Self::K3),
82 'A' => Some(Self::A),
83 '4' => Some(Self::K4),
84 '5' => Some(Self::K5),
85 '6' => Some(Self::K6),
86 'B' => Some(Self::B),
87 '7' => Some(Self::K7),
88 '8' => Some(Self::K8),
89 '9' => Some(Self::K9),
90 'C' => Some(Self::C),
91 '*' => Some(Self::Star),
92 '0' => Some(Self::K0),
93 '#' => Some(Self::Hash),
94 'D' => Some(Self::D),
95 _ => None,
96 }
97 }
98
99 pub const fn from_char_or_panic(c: char) -> Self {
101 match Self::from_char(c) {
102 Some(k) => k,
103 None => panic!("invalid DTMF char"),
104 }
105 }
106
107 pub const fn to_char(self) -> char {
109 match self {
110 Self::K1 => '1',
111 Self::K2 => '2',
112 Self::K3 => '3',
113 Self::A => 'A',
114 Self::K4 => '4',
115 Self::K5 => '5',
116 Self::K6 => '6',
117 Self::B => 'B',
118 Self::K7 => '7',
119 Self::K8 => '8',
120 Self::K9 => '9',
121 Self::C => 'C',
122 Self::Star => '*',
123 Self::K0 => '0',
124 Self::Hash => '#',
125 Self::D => 'D',
126 }
127 }
128
129 pub const fn freqs(self) -> (u16, u16) {
131 match self {
132 Self::K1 => (697, 1209),
133 Self::K2 => (697, 1336),
134 Self::K3 => (697, 1477),
135 Self::A => (697, 1633),
136
137 Self::K4 => (770, 1209),
138 Self::K5 => (770, 1336),
139 Self::K6 => (770, 1477),
140 Self::B => (770, 1633),
141
142 Self::K7 => (852, 1209),
143 Self::K8 => (852, 1336),
144 Self::K9 => (852, 1477),
145 Self::C => (852, 1633),
146
147 Self::Star => (941, 1209),
148 Self::K0 => (941, 1336),
149 Self::Hash => (941, 1477),
150 Self::D => (941, 1633),
151 }
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub struct DtmfTone {
158 pub key: DtmfKey,
159 pub low_hz: u16,
160 pub high_hz: u16,
161}
162
163impl Display for DtmfTone {
164 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165 if f.alternate() {
166 write!(
168 f,
169 "DtmfTone {{ key: {}, low: {} Hz, high: {} Hz }}",
170 self.key, self.low_hz, self.high_hz
171 )
172 } else {
173 write!(f, "{}: ({} Hz, {} Hz)", self.key, self.low_hz, self.high_hz)
175 }
176 }
177}
178
179pub struct DtmfTable;
181
182impl Default for DtmfTable {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188impl DtmfTable {
189 pub const LOWS: [u16; 4] = [697, 770, 852, 941];
191 pub const HIGHS: [u16; 4] = [1209, 1336, 1477, 1633];
192
193 pub const ALL_KEYS: [DtmfKey; 16] = [
195 DtmfKey::K1,
196 DtmfKey::K2,
197 DtmfKey::K3,
198 DtmfKey::A,
199 DtmfKey::K4,
200 DtmfKey::K5,
201 DtmfKey::K6,
202 DtmfKey::B,
203 DtmfKey::K7,
204 DtmfKey::K8,
205 DtmfKey::K9,
206 DtmfKey::C,
207 DtmfKey::Star,
208 DtmfKey::K0,
209 DtmfKey::Hash,
210 DtmfKey::D,
211 ];
212
213 pub const ALL_TONES: [DtmfTone; 16] = [
215 DtmfTone {
216 key: DtmfKey::K1,
217 low_hz: 697,
218 high_hz: 1209,
219 },
220 DtmfTone {
221 key: DtmfKey::K2,
222 low_hz: 697,
223 high_hz: 1336,
224 },
225 DtmfTone {
226 key: DtmfKey::K3,
227 low_hz: 697,
228 high_hz: 1477,
229 },
230 DtmfTone {
231 key: DtmfKey::A,
232 low_hz: 697,
233 high_hz: 1633,
234 },
235 DtmfTone {
236 key: DtmfKey::K4,
237 low_hz: 770,
238 high_hz: 1209,
239 },
240 DtmfTone {
241 key: DtmfKey::K5,
242 low_hz: 770,
243 high_hz: 1336,
244 },
245 DtmfTone {
246 key: DtmfKey::K6,
247 low_hz: 770,
248 high_hz: 1477,
249 },
250 DtmfTone {
251 key: DtmfKey::B,
252 low_hz: 770,
253 high_hz: 1633,
254 },
255 DtmfTone {
256 key: DtmfKey::K7,
257 low_hz: 852,
258 high_hz: 1209,
259 },
260 DtmfTone {
261 key: DtmfKey::K8,
262 low_hz: 852,
263 high_hz: 1336,
264 },
265 DtmfTone {
266 key: DtmfKey::K9,
267 low_hz: 852,
268 high_hz: 1477,
269 },
270 DtmfTone {
271 key: DtmfKey::C,
272 low_hz: 852,
273 high_hz: 1633,
274 },
275 DtmfTone {
276 key: DtmfKey::Star,
277 low_hz: 941,
278 high_hz: 1209,
279 },
280 DtmfTone {
281 key: DtmfKey::K0,
282 low_hz: 941,
283 high_hz: 1336,
284 },
285 DtmfTone {
286 key: DtmfKey::Hash,
287 low_hz: 941,
288 high_hz: 1477,
289 },
290 DtmfTone {
291 key: DtmfKey::D,
292 low_hz: 941,
293 high_hz: 1633,
294 },
295 ];
296
297 pub const fn new() -> Self {
299 DtmfTable
300 }
301
302 pub const fn lookup_key(key: DtmfKey) -> (u16, u16) {
306 key.freqs()
307 }
308
309 pub const fn from_pair_exact(low: u16, high: u16) -> Option<DtmfKey> {
311 match (low, high) {
312 (697, 1209) => Some(DtmfKey::K1),
313 (697, 1336) => Some(DtmfKey::K2),
314 (697, 1477) => Some(DtmfKey::K3),
315 (697, 1633) => Some(DtmfKey::A),
316 (770, 1209) => Some(DtmfKey::K4),
317 (770, 1336) => Some(DtmfKey::K5),
318 (770, 1477) => Some(DtmfKey::K6),
319 (770, 1633) => Some(DtmfKey::B),
320 (852, 1209) => Some(DtmfKey::K7),
321 (852, 1336) => Some(DtmfKey::K8),
322 (852, 1477) => Some(DtmfKey::K9),
323 (852, 1633) => Some(DtmfKey::C),
324 (941, 1209) => Some(DtmfKey::Star),
325 (941, 1336) => Some(DtmfKey::K0),
326 (941, 1477) => Some(DtmfKey::Hash),
327 (941, 1633) => Some(DtmfKey::D),
328 _ => None,
329 }
330 }
331
332 pub const fn from_pair_normalised(a: u16, b: u16) -> Option<DtmfKey> {
334 let (low, high) = if a <= b { (a, b) } else { (b, a) };
335 Self::from_pair_exact(low, high)
336 }
337
338 pub fn iter_keys(&self) -> core::slice::Iter<'static, DtmfKey> {
342 Self::ALL_KEYS.iter()
343 }
344
345 pub fn iter_tones(&self) -> core::slice::Iter<'static, DtmfTone> {
347 Self::ALL_TONES.iter()
348 }
349
350 pub fn from_pair_tol_u32(&self, low: u32, high: u32, tol_hz: u32) -> Option<DtmfKey> {
353 let (lo, hi) = normalise_u32_pair(low, high);
354 for t in Self::ALL_TONES {
355 if abs_diff_u32(lo, t.low_hz as u32) <= tol_hz
356 && abs_diff_u32(hi, t.high_hz as u32) <= tol_hz
357 {
358 return Some(t.key);
359 }
360 }
361 None
362 }
363
364 pub fn from_pair_tol_f64(&self, low: f64, high: f64, tol_hz: f64) -> Option<DtmfKey> {
366 let (lo, hi) = normalise_f64_pair(low, high);
367 for t in Self::ALL_TONES {
368 if (lo - t.low_hz as f64).abs() <= tol_hz && (hi - t.high_hz as f64).abs() <= tol_hz {
369 return Some(t.key);
370 }
371 }
372 None
373 }
374
375 pub fn nearest_u32(&self, low: u32, high: u32) -> (DtmfKey, u16, u16) {
378 let (lo, hi) = normalise_u32_pair(low, high);
379 let nearest_low = nearest_in_set_u32(lo, &Self::LOWS);
380 let nearest_high = nearest_in_set_u32(hi, &Self::HIGHS);
381 let key = Self::from_pair_exact(nearest_low, nearest_high)
382 .expect("canonical pair must map to a key");
383 (key, nearest_low, nearest_high)
384 }
385
386 pub fn nearest_f64(&self, low: f64, high: f64) -> (DtmfKey, u16, u16) {
388 let (lo, hi) = normalise_f64_pair(low, high);
389 let nearest_low = nearest_in_set_f64(lo, &Self::LOWS);
390 let nearest_high = nearest_in_set_f64(hi, &Self::HIGHS);
391 let key = Self::from_pair_exact(nearest_low, nearest_high)
392 .expect("canonical pair must map to a key");
393 (key, nearest_low, nearest_high)
394 }
395}
396
397impl Display for DtmfTable {
398 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
399 if f.alternate() {
400 writeln!(f, "DTMF Keypad Layout:")?;
402 writeln!(f, " 1209 Hz 1336 Hz 1477 Hz 1633 Hz")?;
403 writeln!(f, "697 Hz: 1 2 3 A")?;
404 writeln!(f, "770 Hz: 4 5 6 B")?;
405 writeln!(f, "852 Hz: 7 8 9 C")?;
406 write!(f, "941 Hz: * 0 # D")
407 } else {
408 writeln!(f, "DTMF Table:")?;
410 for tone in Self::ALL_TONES.iter() {
411 writeln!(f, " {}", tone)?;
412 }
413 Ok(())
414 }
415 }
416}
417
418const fn abs_diff_u32(a: u32, b: u32) -> u32 {
421 a.abs_diff(b)
422}
423
424fn nearest_in_set_u32(x: u32, set: &[u16]) -> u16 {
425 let mut best = set[0];
426 let mut best_d = abs_diff_u32(x, best as u32);
427 let mut i = 1;
428 while i < set.len() {
429 let d = abs_diff_u32(x, set[i] as u32);
430 if d < best_d {
431 best = set[i];
432 best_d = d;
433 }
434 i += 1;
435 }
436 best
437}
438
439fn nearest_in_set_f64(x: f64, set: &[u16]) -> u16 {
440 let mut best = set[0];
441 let mut best_d = (x - best as f64).abs();
442 let mut i = 1;
443 while i < set.len() {
444 let d = (x - set[i] as f64).abs();
445 if d < best_d {
446 best = set[i];
447 best_d = d;
448 }
449 i += 1;
450 }
451 best
452}
453
454const fn normalise_u32_pair(a: u32, b: u32) -> (u32, u32) {
455 if a <= b { (a, b) } else { (b, a) }
456}
457
458fn normalise_f64_pair(a: f64, b: f64) -> (f64, f64) {
459 match a.partial_cmp(&b) {
460 Some(Ordering::Greater) => (b, a),
461 _ => (a, b),
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[cfg(feature = "std")]
471 mod std_tests {
472 use super::*;
473 extern crate std;
474 use std::format;
475
476 #[test]
477 fn test_dtmf_key_normal_display() {
478 assert_eq!(format!("{}", DtmfKey::K5), "5");
479 assert_eq!(format!("{}", DtmfKey::Star), "*");
480 assert_eq!(format!("{}", DtmfKey::Hash), "#");
481 assert_eq!(format!("{}", DtmfKey::A), "A");
482 }
483
484 #[test]
485 fn test_dtmf_key_alternate_display() {
486 assert_eq!(format!("{:#}", DtmfKey::K5), "DtmfKey::K5");
487 assert_eq!(format!("{:#}", DtmfKey::Star), "DtmfKey::Star");
488 assert_eq!(format!("{:#}", DtmfKey::Hash), "DtmfKey::Hash");
489 assert_eq!(format!("{:#}", DtmfKey::A), "DtmfKey::A");
490 }
491
492 #[test]
493 fn test_dtmf_tone_normal_display() {
494 let tone = DtmfTone {
495 key: DtmfKey::K5,
496 low_hz: 770,
497 high_hz: 1336,
498 };
499 assert_eq!(format!("{}", tone), "5: (770 Hz, 1336 Hz)");
500 }
501
502 #[test]
503 fn test_dtmf_tone_alternate_display() {
504 let tone = DtmfTone {
505 key: DtmfKey::K5,
506 low_hz: 770,
507 high_hz: 1336,
508 };
509 assert_eq!(
510 format!("{:#}", tone),
511 "DtmfTone { key: 5, low: 770 Hz, high: 1336 Hz }"
512 );
513 }
514
515 #[test]
516 fn test_dtmf_table_normal_display() {
517 let table = DtmfTable::new();
518 let output = format!("{}", table);
519 assert!(output.contains("DTMF Table:"));
520 assert!(output.contains("1: (697 Hz, 1209 Hz)"));
521 assert!(output.contains("5: (770 Hz, 1336 Hz)"));
522 assert!(output.contains("D: (941 Hz, 1633 Hz)"));
523 }
524
525 #[test]
526 fn test_dtmf_table_alternate_display() {
527 let table = DtmfTable::new();
528 let output = format!("{:#}", table);
529 assert!(output.contains("DTMF Keypad Layout:"));
530 assert!(output.contains("1209 Hz"));
531 assert!(output.contains("697 Hz:"));
532 assert!(output.contains("941 Hz:"));
533 assert!(output.contains("1"));
535 assert!(output.contains("5"));
536 assert!(output.contains("*"));
537 assert!(output.contains("#"));
538 }
539
540 #[test]
541 fn test_all_keys_have_alternate_format() {
542 for key in DtmfTable::ALL_KEYS.iter() {
544 let normal = format!("{}", key);
545 let alternate = format!("{:#}", key);
546
547 assert_eq!(normal.len(), 1);
549
550 assert!(alternate.starts_with("DtmfKey::"));
552
553 assert_ne!(normal, alternate);
555 }
556 }
557
558 #[test]
559 fn test_all_tones_have_alternate_format() {
560 for tone in DtmfTable::ALL_TONES.iter() {
562 let normal = format!("{}", tone);
563 let alternate = format!("{:#}", tone);
564
565 assert!(normal.contains("Hz"));
567
568 assert!(alternate.contains("DtmfTone"));
570
571 assert_ne!(normal, alternate);
573 }
574 }
575 }
576}