dtmf/
lib.rs

1// allow std in tests
2#![cfg_attr(not(test), no_std)]
3
4pub mod decoder;
5pub mod enums;
6
7#[cfg(test)]
8mod tests {
9	use crate::decoder::Decoder;
10	use crate::enums::{State, Tone};
11	use rodio::{Decoder as RDecoder, Source};
12	use std::fs::File;
13	use std::io::BufReader;
14
15	// 12398754932387ABCD*#
16	#[test]
17	fn decoder_works() {
18		let expected = vec![
19			(Tone::One, State::On),
20			(Tone::One, State::Off),
21			(Tone::Two, State::On),
22			(Tone::Two, State::Off),
23			(Tone::Three, State::On),
24			(Tone::Three, State::Off),
25			(Tone::Nine, State::On),
26			(Tone::Nine, State::Off),
27			(Tone::Eight, State::On),
28			(Tone::Eight, State::Off),
29			(Tone::Seven, State::On),
30			(Tone::Seven, State::Off),
31			(Tone::Five, State::On),
32			(Tone::Five, State::Off),
33			(Tone::Four, State::On),
34			(Tone::Four, State::Off),
35			(Tone::Nine, State::On),
36			(Tone::Nine, State::Off),
37			(Tone::Three, State::On),
38			(Tone::Three, State::Off),
39			(Tone::Two, State::On),
40			(Tone::Two, State::Off),
41			(Tone::Three, State::On),
42			(Tone::Three, State::Off),
43			(Tone::Eight, State::On),
44			(Tone::Eight, State::Off),
45			(Tone::Seven, State::On),
46			(Tone::Seven, State::Off),
47			(Tone::A, State::On),
48			(Tone::A, State::Off),
49			(Tone::B, State::On),
50			(Tone::B, State::Off),
51			(Tone::C, State::On),
52			(Tone::C, State::Off),
53			(Tone::D, State::On),
54			(Tone::D, State::Off),
55			(Tone::Asterisk, State::On),
56			(Tone::Asterisk, State::Off),
57			(Tone::Pound, State::On),
58		];
59
60		let file = BufReader::new(File::open("data/dtmf_test.wav").unwrap());
61		let source = RDecoder::new(file).unwrap();
62		let samples = source.convert_samples();
63		let sample_rate = samples.sample_rate();
64		let data: Vec<f32> = samples.collect();
65
66		// first extreme - very few samples
67		{
68			let mut actual = vec![];
69			let mut decoder = Decoder::new(sample_rate, |tone, state| {
70				actual.push((tone, state));
71			});
72
73			for s in data.chunks(1) {
74				decoder.process(s);
75			}
76
77			assert_eq!(expected, actual);
78		}
79
80		// second extreme - many samples
81		{
82			let mut actual = vec![];
83			let mut decoder = Decoder::new(sample_rate, |tone, state| {
84				actual.push((tone, state));
85			});
86
87			for s in data.chunks(40_000) {
88				decoder.process(s);
89			}
90
91			assert_eq!(expected, actual);
92		}
93	}
94
95	#[test]
96	fn decoder_works_noisy() {
97		let file = BufReader::new(File::open("data/noisy_dtmf.mp3").unwrap());
98		let source = RDecoder::new(file).unwrap();
99		let samples = source.convert_samples();
100		let sample_rate = samples.sample_rate();
101		let data: Vec<f32> = samples.collect();
102
103		// first extreme - very few samples
104		let c1;
105		{
106			let mut actual = vec![];
107			let mut decoder = Decoder::new(sample_rate, |tone, state| {
108				actual.push((tone, state));
109			});
110
111			for s in data.chunks(1) {
112				decoder.process(s);
113			}
114
115			c1 = actual.len();
116			log_sanity_check(actual);
117		}
118
119		// second extreme - many samples
120		let c2;
121		{
122			let mut actual = vec![];
123			let mut decoder = Decoder::new(sample_rate, |tone, state| {
124				actual.push((tone, state));
125			});
126
127			for s in data.chunks(40_000) {
128				decoder.process(s);
129			}
130
131			c2 = actual.len();
132			log_sanity_check(actual);
133		}
134
135		assert_eq!(c1, c2);
136		assert_eq!(c1, 8047); // true value is 8063
137	}
138
139	#[test]
140	fn no_false_positives() {
141		// Sound taken from https://www.youtube.com/watch?v=YzG4RZNLuUk
142
143		let file = BufReader::new(File::open("data/radio_chatter.mp3").unwrap());
144		let source = RDecoder::new(file).unwrap();
145		let samples = source.convert_samples();
146		let sample_rate = samples.sample_rate();
147		let data: Vec<f32> = samples.collect();
148
149		// first extreme - very few samples
150		let c1;
151		{
152			let mut actual = vec![];
153			let mut decoder = Decoder::new(sample_rate, |tone, state| {
154				actual.push((tone, state));
155			});
156
157			for s in data.chunks(1) {
158				decoder.process(s);
159			}
160
161			c1 = actual.len();
162			log_sanity_check(actual);
163		}
164
165		// second extreme - many samples
166		let c2;
167		{
168			let mut actual = vec![];
169			let mut decoder = Decoder::new(sample_rate, |tone, state| {
170				actual.push((tone, state));
171			});
172
173			for s in data.chunks(40_000) {
174				decoder.process(s);
175			}
176
177			c2 = actual.len();
178			log_sanity_check(actual);
179		}
180
181		assert_eq!(c1, c2);
182		assert_eq!(c1, 34); // ideally we want 0
183	}
184
185	// See issue #2
186	#[test]
187	fn dont_on_when_on_dont_off_when_off() {
188		let expected = vec![(Tone::One, State::On), (Tone::One, State::Off)];
189
190		let file = BufReader::new(File::open("data/dtmf_edge.wav").unwrap());
191		let source = RDecoder::new(file).unwrap();
192		let samples = source.convert_samples();
193		let sample_rate = samples.sample_rate();
194		let data: Vec<f32> = samples.collect();
195
196		// first extreme - very few samples
197		{
198			let mut actual = vec![];
199			let mut decoder = Decoder::new(sample_rate, |tone, state| {
200				actual.push((tone, state));
201			});
202
203			for s in data.chunks(1) {
204				decoder.process(s);
205			}
206
207			assert_eq!(expected, actual);
208		}
209
210		// second extreme - many samples
211		{
212			let mut actual = vec![];
213			let mut decoder = Decoder::new(sample_rate, |tone, state| {
214				actual.push((tone, state));
215			});
216
217			for s in data.chunks(40_000) {
218				decoder.process(s);
219			}
220
221			assert_eq!(expected, actual);
222		}
223	}
224
225	fn log_sanity_check(mut log: Vec<(Tone, State)>) {
226		// pad w/ off if last element is on
227		if log.last().unwrap().1 == State::On {
228			log.push((log.last().unwrap().0, State::Off));
229		}
230
231		// every on needs an off
232		for c in log.chunks(2) {
233			assert_eq!(c[0].0, c[1].0);
234			assert_eq!(State::On, c[0].1);
235			assert_eq!(State::Off, c[1].1);
236		}
237	}
238}