1use clap::Parser;
5use qzn3t_pitch_detection::note_detection_result::NoteDetectionResult;
6use qzn3t_pitch_detection::note_detection_result::NoteName;
7use qzn3t_pitch_detection::runner::{Detector, DetectorCfg, pitch_detection_run};
8use std::io::Write;
9use std::sync::{Arc, atomic::AtomicBool, atomic::Ordering, mpsc};
10use std::thread::spawn;
11use std::{
12 io::{self},
13 thread::JoinHandle,
14};
15
16#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
17pub enum TunerNote {
18 A,
19 ASharp,
20 B,
21 C,
22 CSharp,
23 D,
24 DSharp,
25 E,
26 F,
27 FSharp,
28 G,
29 GSharp,
30}
31
32impl From<NoteName> for TunerNote {
33 fn from(n: NoteName) -> Self {
34 match n {
35 NoteName::A => TunerNote::A,
36 NoteName::ASharp => TunerNote::ASharp,
37 NoteName::B => TunerNote::B,
38 NoteName::C => TunerNote::C,
39 NoteName::CSharp => TunerNote::CSharp,
40 NoteName::D => TunerNote::D,
41 NoteName::DSharp => TunerNote::DSharp,
42 NoteName::E => TunerNote::E,
43 NoteName::F => TunerNote::F,
44 NoteName::FSharp => TunerNote::FSharp,
45 NoteName::G => TunerNote::G,
46 NoteName::GSharp => TunerNote::GSharp,
47 }
48 }
49}
50#[derive(Debug)]
52pub struct TunerData {
53 pub note: TunerNote,
54 pub octave: i32,
55 pub cents_offset: f32,
56}
57
58#[derive(Parser, Debug)]
60#[command(version, about, long_about = None)]
61pub struct TunerArgs {
62 #[arg(short = 'p', long)]
63 pub connect_port: Option<String>, }
65
66pub fn get_results(
67 args: &TunerArgs,
68 sender: mpsc::Sender<TunerData>,
69 kill_switch: Arc<AtomicBool>,
70) -> JoinHandle<()> {
71 let port = match &args.connect_port {
72 Some(p) => p.clone(),
73 None => "system:capture_1".to_string(),
74 };
75 let (tx, rx) = mpsc::channel::<NoteDetectionResult>();
77 let (tx_f32, rx_f32) = mpsc::channel::<f32>();
79
80 let audio_dst_client = match qzn3t_pitch_detection::runner::start_jack(tx_f32, &port) {
82 Ok(ac) => ac,
83 Err(err) => panic!(
84 "Error pitch_detectiopn tester: Cannot create Jack clent to receive audio: {err}"
85 ),
86 };
87 let detector_cfg = DetectorCfg {
88 sample_rate: audio_dst_client
89 .as_client()
90 .sample_rate()
91 .try_into()
92 .unwrap(),
93 size: 16384,
94 padding: 1024,
95 power_threshold: 0.1,
96 clarity_threshold: 0.5,
97 detector: Detector::McLeod,
98 };
99
100 let pd_handle = pitch_detection_run(tx, rx_f32, &detector_cfg, Some(kill_switch.clone()));
101 spawn(move || {
102 let _audio_dst_client = audio_dst_client;
104 let sender = sender.clone();
105 loop {
106 if kill_switch.load(Ordering::SeqCst) {
107 _ = pd_handle.join();
109 break;
110 }
111
112 match rx.recv() {
113 Ok(ndr) => {
114 let td = TunerData {
115 octave: ndr.octave,
116 note: ndr.note_name.into(),
117 cents_offset: ndr.cents,
118 };
119 if let Err(err) = sender.send(td) {
120 eprintln!(
121 "Error qzn3t/tuner: Note detection loop failed sending results: {err}"
122 );
123 break;
124 }
125 }
126 Err(err) => {
127 eprintln!(
128 "Error qzn3t/tuner: Note detection loop failed receiving results: {err}"
129 );
130 break;
131 }
132 }
133 }
134 })
135}
136
137pub fn inner_main(args: &TunerArgs) {
138 let kill_switch = Arc::new(AtomicBool::new(false));
139 let (sender, receiver) = mpsc::channel::<TunerData>();
140 _ = get_results(args, sender, kill_switch.clone());
141 loop {
142 let ndr = match receiver.recv() {
143 Ok(r) => r,
144 Err(err) => {
145 eprintln!("DBG tuner: get_results send error: {err}");
146 break;
147 }
148 };
149 let report = format!(
150 "Tuner> {:?}/{} {: >-6.2}\n",
151 ndr.note, ndr.octave, ndr.cents_offset
152 );
153 if let Err(err) = my_write(report) {
154 eprintln!("Error tuner: inner main. {err}");
155 }
156 }
157}
158
159fn my_write(report: String) -> io::Result<()> {
160 let mut v = io::stdout().lock();
161 v.write_all(report.as_bytes())?;
162 v.flush()?;
163 Ok(())
164}