Created
February 29, 2024 07:28
-
-
Save Synergyst/301bbc0a5890dfa2e0e5aad40651f981 to your computer and use it in GitHub Desktop.
dtmf_test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate rustfft; | |
extern crate num_complex; | |
use std::f32; | |
use std::{thread, time::Duration}; | |
use std::collections::HashMap; | |
use std::process::Command; | |
use jack::{AsyncClient, Client, ClosureProcessHandler, ProcessScope}; | |
fn main() { | |
println!("Starting.."); | |
// Initialize JACK client | |
let (client, _status) = Client::new("dtmf_decoder", jack::ClientOptions::NO_START_SERVER).expect("Unable to create JACK client"); | |
println!("Initialized JACK client.."); | |
// Register ports | |
let spec = jack::AudioIn::default(); | |
let in_port = client.register_port("in", spec).expect("Failed to register input port"); | |
println!("Registered JACK port.."); | |
// Define the processing callback | |
let process_callback = ClosureProcessHandler::new(move |_client: &Client, scope: &ProcessScope| -> jack::Control { | |
// Access audio samples from in_port | |
let in_buffer = in_port.as_slice(scope) as &[f32]; | |
let sample_rate = 48000 as f32; | |
let dtmf_freqs: HashMap<char, (f32, f32)> = { | |
let mut map = HashMap::new(); | |
map.insert('1', (697.0, 1209.0)); | |
map.insert('2', (697.0, 1336.0)); | |
map.insert('3', (697.0, 1477.0)); | |
map.insert('A', (697.0, 1633.0)); | |
map.insert('4', (770.0, 1209.0)); | |
map.insert('5', (770.0, 1336.0)); | |
map.insert('6', (770.0, 1477.0)); | |
map.insert('B', (770.0, 1633.0)); | |
map.insert('7', (852.0, 1209.0)); | |
map.insert('8', (852.0, 1336.0)); | |
map.insert('9', (852.0, 1477.0)); | |
map.insert('C', (852.0, 1633.0)); | |
map.insert('*', (941.0, 1209.0)); | |
map.insert('0', (941.0, 1336.0)); | |
map.insert('#', (941.0, 1477.0)); | |
map.insert('D', (941.0, 1633.0)); | |
map | |
}; | |
let mut highest1: f32 = 0.0; | |
let mut highest2: f32 = 0.0; | |
let mut char1: char = ' '; | |
let mut char2: char = ' '; | |
for (key, (freq1, freq2)) in &dtmf_freqs { | |
let val1 = goertzel_threshold(in_buffer, sample_rate, *freq1, 10.0); | |
let val2 = goertzel_threshold(in_buffer, sample_rate, *freq2, 10.0); | |
//println!("magnitude: {:?} / {:?}", val1, val2); | |
if val1 < 15.0 || val2 < 15.0 { | |
continue; | |
} | |
if val1 > highest1 { | |
highest1 = val1; | |
char1 = *key; | |
} | |
if val2 > highest2 { | |
highest2 = val2; | |
char2 = *key; | |
} | |
} | |
if char1 != ' ' && char2 != ' ' { | |
//println!("{:?} / {:?}", char1, char2); | |
println!("First tone: {} (magnitude: {})", char1, highest1); | |
println!("Second tone: {} (magnitude: {})", char2, highest2); | |
} | |
jack::Control::Continue | |
},); | |
let _active_client = AsyncClient::new(client, (), process_callback).expect("Failed to create async client"); // Activate the client | |
println!("Press Enter to quit..."); | |
let command1 = "jack_connect"; | |
let command2 = "killall"; | |
let args1 = ["system:capture_1", "dtmf_decoder:in"]; | |
let args2 = ["-9", command1]; | |
let _child_process2 = Command::new(command2).args(&args2).spawn().expect("Failed to spawn command2"); | |
thread::sleep(Duration::from_millis(1000)); | |
let _child_process1 = Command::new(command1).args(&args1).spawn().expect("Failed to spawn command1"); | |
let mut user_input = String::new(); | |
std::io::stdin().read_line(&mut user_input).unwrap(); | |
} | |
pub fn goertzel(input: &[f32], sample_rate: f32, target_freq: f32) -> f32 { | |
let num_samples = input.len() as f32; | |
let target_k = (target_freq * num_samples / sample_rate).round() as usize; | |
let mut s_prev = 0.0; | |
let mut s_prev2 = 0.0; | |
let coeff = 2.0 * f32::cos(2.0 * f32::consts::PI * target_k as f32 / num_samples); | |
for &sample in input { | |
let s = sample + coeff * s_prev - s_prev2; | |
s_prev2 = s_prev; | |
s_prev = s; | |
} | |
let magnitude = s_prev2.powi(2) + s_prev.powi(2) - coeff * s_prev * s_prev2; | |
magnitude.sqrt() | |
} | |
pub fn goertzel_threshold(input: &[f32], sample_rate: f32, target_freq: f32, threshold: f32) -> f32 { | |
let num_samples = input.len() as f32; | |
let target_k = (target_freq * num_samples / sample_rate).round() as usize; | |
let mut s_prev = 0.0; | |
let mut s_prev2 = 0.0; | |
let coeff = 2.0 * f32::cos(2.0 * f32::consts::PI * target_k as f32 / num_samples); | |
for &sample in input { | |
let s = sample + coeff * s_prev - s_prev2; | |
s_prev2 = s_prev; | |
s_prev = s; | |
} | |
let magnitude = s_prev2.powi(2) + s_prev.powi(2) - coeff * s_prev * s_prev2; | |
// Calculate frequencies within the threshold range | |
let lower_freq = (target_freq - threshold).max(0.0); | |
let upper_freq = target_freq + threshold; | |
if target_freq - threshold <= 0.0 { | |
return magnitude.sqrt(); // Return magnitude as is if lower bound is 0 Hz | |
} | |
// Calculate corresponding k values for the threshold frequencies | |
let lower_k = (lower_freq * num_samples / sample_rate).round() as usize; | |
let upper_k = (upper_freq * num_samples / sample_rate).round() as usize; | |
// Sum magnitudes within the threshold range | |
let mut threshold_magnitude = 0.0; | |
for k in lower_k..=upper_k { | |
let coeff = 2.0 * f32::cos(2.0 * f32::consts::PI * k as f32 / num_samples); | |
threshold_magnitude += 2.0 * coeff * s_prev * s_prev2; | |
} | |
(magnitude + threshold_magnitude).sqrt() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment