dtmf/
decoder.rs

1use crate::enums::{State, Tone};
2
3use core::iter::Iterator;
4use goertzel_nostd::Parameters;
5
6const TONE_TABLE: [[Tone; 4]; 4] = [
7	[Tone::One, Tone::Two, Tone::Three, Tone::A],
8	[Tone::Four, Tone::Five, Tone::Six, Tone::B],
9	[Tone::Seven, Tone::Eight, Tone::Nine, Tone::C],
10	[Tone::Asterisk, Tone::Zero, Tone::Pound, Tone::D],
11];
12
13const SAMPLE_RATE: u32 = 8000;
14const NUM_SAMPLES: usize = 200;
15// how long we need to remain in a state for it to count
16const STATE_DURATION: u32 = 2;
17
18const LOW_FREQS: [f32; 4] = [697.0, 770.0, 852.0, 941.0];
19const HIGH_FREQS: [f32; 4] = [1209.0, 1336.0, 1477.0, 1633.0];
20
21pub struct Decoder<F: FnMut(Tone, State)> {
22	lows: [Parameters; 4],
23	highs: [Parameters; 4],
24	low_harms: [Parameters; 4],
25	high_harms: [Parameters; 4],
26
27	sample_rate: u32,
28	tone_change: F,
29	cur_tone: Option<Tone>,
30	old_tone: Option<Tone>,
31	// last tone that we sent through the closure
32	old_old_tone: Option<Tone>,
33
34	// how long we've been in the current state
35	state_duration: u32,
36
37	// holds samples that haven't been processed yet
38	samples: [f32; NUM_SAMPLES],
39	sample_len: usize,
40	// keeps track of our phase when downsampling
41	downsample_phase: f32,
42}
43
44impl<F: FnMut(Tone, State)> Decoder<F> {
45	pub fn new(sample_rate: u32, tone_change: F) -> Self {
46		let lows = [
47			Parameters::new(LOW_FREQS[0], SAMPLE_RATE, NUM_SAMPLES),
48			Parameters::new(LOW_FREQS[1], SAMPLE_RATE, NUM_SAMPLES),
49			Parameters::new(LOW_FREQS[2], SAMPLE_RATE, NUM_SAMPLES),
50			Parameters::new(LOW_FREQS[3], SAMPLE_RATE, NUM_SAMPLES),
51		];
52
53		let highs = [
54			Parameters::new(HIGH_FREQS[0], SAMPLE_RATE, NUM_SAMPLES),
55			Parameters::new(HIGH_FREQS[1], SAMPLE_RATE, NUM_SAMPLES),
56			Parameters::new(HIGH_FREQS[2], SAMPLE_RATE, NUM_SAMPLES),
57			Parameters::new(HIGH_FREQS[3], SAMPLE_RATE, NUM_SAMPLES),
58		];
59
60		let low_harms = [
61			Parameters::new(LOW_FREQS[0] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
62			Parameters::new(LOW_FREQS[1] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
63			Parameters::new(LOW_FREQS[2] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
64			Parameters::new(LOW_FREQS[3] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
65		];
66
67		let high_harms = [
68			Parameters::new(HIGH_FREQS[0] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
69			Parameters::new(HIGH_FREQS[1] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
70			Parameters::new(HIGH_FREQS[2] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
71			Parameters::new(HIGH_FREQS[3] * 2.0, SAMPLE_RATE, NUM_SAMPLES),
72		];
73
74		Self {
75			lows,
76			highs,
77
78			low_harms,
79			high_harms,
80
81			sample_rate,
82			tone_change,
83
84			cur_tone: None,
85			old_tone: None,
86			old_old_tone: None,
87
88			state_duration: STATE_DURATION,
89
90			samples: [0.0; NUM_SAMPLES],
91			sample_len: 0,
92
93			downsample_phase: 0.0,
94		}
95	}
96
97	/// Expects samples to be between -1.0 and 1.0
98	/// Batch together as many (or as few) samples when processing
99	pub fn process(&mut self, samples: &[f32]) {
100		self.downsample(samples, |decoder, sample| {
101			decoder.samples[decoder.sample_len] = sample;
102			decoder.sample_len += 1;
103			if decoder.sample_len == NUM_SAMPLES {
104				decoder.process_chunk();
105				decoder.sample_len = 0;
106			}
107		});
108	}
109
110	fn process_chunk(&mut self) {
111		let mut low_powers = [0.0; 4];
112		for (i, lp) in low_powers.iter_mut().enumerate() {
113			*lp = self.lows[i].mag_squared(&self.samples);
114		}
115
116		let mut high_powers = [0.0; 4];
117		for (i, hp) in high_powers.iter_mut().enumerate() {
118			*hp = self.highs[i].mag_squared(&self.samples);
119		}
120
121		let low = main_freq(&low_powers);
122		let high = main_freq(&high_powers);
123
124		match (low, high) {
125			(Some(l), Some(h)) => {
126				// ensure we don't have any strong harmonics of either of the freqs
127				// if we do, it's probably a false positive
128				let l2 = self.low_harms[l].mag_squared(&self.samples);
129
130				// harmonic must be less than half
131				if l2 > (low_powers[l] / 8.0) {
132					self.no_tone();
133				} else {
134					let h2 = self.high_harms[h].mag_squared(&self.samples);
135
136					if h2 > (high_powers[h] / 8.0) {
137						self.no_tone();
138					} else {
139						self.tone(l, h);
140					}
141				}
142			}
143			_ => {
144				self.no_tone();
145			}
146		}
147	}
148
149	fn tone(&mut self, low: usize, high: usize) {
150		let tone = TONE_TABLE[low][high];
151
152		if self.cur_tone != Some(tone) {
153			self.old_tone = self.cur_tone;
154			self.cur_tone = Some(tone);
155			self.state_duration = 0;
156		}
157
158		if self.state_duration < STATE_DURATION {
159			self.state_duration += 1;
160
161			if self.state_duration == STATE_DURATION && self.cur_tone != self.old_old_tone {
162				if let Some(old) = self.old_tone {
163					(self.tone_change)(old, State::Off);
164				}
165
166				(self.tone_change)(tone, State::On);
167				self.old_old_tone = self.cur_tone;
168			}
169		}
170	}
171
172	fn no_tone(&mut self) {
173		if let Some(tone) = self.cur_tone {
174			self.old_tone = Some(tone);
175			self.cur_tone = None;
176			self.state_duration = 0;
177		}
178
179		if self.state_duration < STATE_DURATION {
180			self.state_duration += 1;
181
182			if self.state_duration == STATE_DURATION && self.cur_tone != self.old_old_tone {
183				// old tone cannot be none because it was set when we first updated our tone to none
184				(self.tone_change)(self.old_tone.unwrap(), State::Off);
185				self.old_old_tone = self.cur_tone;
186			}
187		}
188	}
189
190	// gets a single downsampled sample
191	// also returns how many input samples were used up
192	fn downsample<T: Copy, C: Fn(&mut Self, T)>(&mut self, samples: &[T], func: C) {
193		let scale = self.sample_rate as f32 / SAMPLE_RATE as f32;
194
195		let len = samples.len();
196		let mut i = 0;
197		while i < len {
198			if self.downsample_phase > scale {
199				self.downsample_phase -= scale;
200
201				func(self, samples[i]);
202			}
203
204			let dist = ((scale - self.downsample_phase) as usize)
205				.max(1)
206				.min(len - i);
207
208			self.downsample_phase += dist as f32;
209			i += dist;
210		}
211	}
212}
213
214// gets the dominant frequency in the array, if there is one and it's strong enough
215fn main_freq(freqs: &[f32]) -> Option<usize> {
216	let mut idx = 0;
217	for i in 1..freqs.len() {
218		if freqs[i] > freqs[idx] {
219			idx = i;
220		}
221	}
222
223	let mut sum_others = 0.0;
224	for (i, v) in freqs.iter().enumerate() {
225		if i != idx {
226			sum_others += v;
227		}
228	}
229
230	// main freq must be 8x the sum of the others
231	if sum_others * 8.0 < freqs[idx] {
232		Some(idx)
233	} else {
234		None
235	}
236}