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