Skip to content

Commit

Permalink
feat: choose sample rate dynamically
Browse files Browse the repository at this point in the history
- We choose the highest sampling rate possible
  • Loading branch information
Goose97 committed Nov 3, 2023
1 parent e99d9a8 commit 4680e4d
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 33 deletions.
8 changes: 4 additions & 4 deletions src/bin/guitar_tuner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use terminal_guitar_tuner::guitar::Note;
use terminal_guitar_tuner::pitch_detector;
use terminal_guitar_tuner::recorder::Recorder;
use terminal_guitar_tuner::ui;
use terminal_guitar_tuner::{AppEvent, SAMPLE_RATE};
use terminal_guitar_tuner::AppEvent;

const FRAME_RATE_PER_SECOND: u64 = 2;

Expand All @@ -30,8 +30,8 @@ fn main() -> Result<()> {

let mut next_frame_deadline = Instant::now();
let buffer_size = 1 << 12;
let mut recorder = Recorder::new(SAMPLE_RATE, buffer_size);
recorder.record().unwrap();
let mut recorder = Recorder::new(buffer_size);
let sample_rate = recorder.record().unwrap();

// Open a file with append option
let mut debug_log_file = if debug {
Expand All @@ -51,7 +51,7 @@ fn main() -> Result<()> {
next_frame_deadline += Duration::from_millis(1000 / FRAME_RATE_PER_SECOND);

recorder.with_samples(|samples| {
let result = pitch_detector::detect_note(&samples, SAMPLE_RATE, &tuning_notes);
let result = pitch_detector::detect_note(&samples, sample_rate.0, &tuning_notes);

let event = match result {
Ok((note, frequency)) => AppEvent::PitchDetected(note, frequency),
Expand Down
10 changes: 6 additions & 4 deletions src/bin/raw_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::{Duration, Instant};
use terminal_guitar_tuner::guitar::{get_note_frequency, Note};
use terminal_guitar_tuner::pitch_detector;
use terminal_guitar_tuner::recorder::Recorder;
use terminal_guitar_tuner::{AppEvent, SAMPLE_RATE};
use terminal_guitar_tuner::AppEvent;

const FRAME_RATE_PER_SECOND: u64 = 2;

Expand All @@ -22,15 +22,17 @@ fn main() -> Result<()> {

let mut next_frame_deadline = Instant::now();
let buffer_size = 1 << 11;
let mut recorder = Recorder::new(SAMPLE_RATE, buffer_size);
recorder.record().unwrap();
let mut recorder = Recorder::new(buffer_size);
let sample_rate = recorder.record().unwrap();

println!("Raw {:?}", sample_rate);

// Loop until interrupted by user
loop {
next_frame_deadline += Duration::from_millis(1000 / FRAME_RATE_PER_SECOND);

recorder.with_samples(|samples| {
let result = pitch_detector::detect_note(&samples, SAMPLE_RATE, &tuning_notes);
let result = pitch_detector::detect_note(&samples, sample_rate.0, &tuning_notes);

let event = match result {
Ok((note, frequency)) => AppEvent::PitchDetected(note, frequency),
Expand Down
4 changes: 2 additions & 2 deletions src/bin/record_fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ use std::fs::File;
use std::io::Write;
use std::thread;
use std::time::Duration;
use terminal_guitar_tuner::FIXTURE_SAMPLE_RATE;

use terminal_guitar_tuner::recorder::Recorder;
use terminal_guitar_tuner::SAMPLE_RATE;

// Record into fixtures
fn main() -> Result<()> {
let mut recorder = Recorder::new(SAMPLE_RATE, SAMPLE_RATE * 2);
let mut recorder = Recorder::new(FIXTURE_SAMPLE_RATE as usize * 2);
recorder.record()?;

thread::sleep(Duration::from_millis(2500));
Expand Down
5 changes: 3 additions & 2 deletions src/bin/simulate_from_fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::time::Duration;
use terminal_guitar_tuner::guitar::Note;
use terminal_guitar_tuner::pitch_detector;
use terminal_guitar_tuner::ui;
use terminal_guitar_tuner::{AppEvent, SAMPLE_RATE};
use terminal_guitar_tuner::AppEvent;

// Simulate from fixture
fn main() -> Result<()> {
Expand All @@ -31,7 +31,8 @@ fn main() -> Result<()> {

thread::spawn(move || {
for mut chunk in samples.chunks(chunk_size).into_iter() {
let result = pitch_detector::detect_note(&mut chunk, SAMPLE_RATE, &tuning_notes);
// Typical sample rate 44,1kHz
let result = pitch_detector::detect_note(&mut chunk, 44100, &tuning_notes);
let event = match result {
Ok((note, frequency)) => AppEvent::PitchDetected(note, frequency),
Err(_) => AppEvent::NoPitchDetected,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ pub enum AppEvent {
Quit,
}

pub const SAMPLE_RATE: usize = 44100;
pub const FIXTURE_SAMPLE_RATE: u32 = 44100;
8 changes: 4 additions & 4 deletions src/pitch_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const MAX_FREQUENCY: f64 = 1325.0;
// 4. Infer the closest note from the peak
pub fn detect_note(
samples: &[f64],
sampling_rate: usize,
sampling_rate: u32,
tuning_notes: &[Note],
) -> Result<(Note, f64)> {
// Guitar notes have range of 75Hz - 1320Hz (accounted for overtones)
Expand Down Expand Up @@ -66,7 +66,7 @@ struct KeyMaxima {
right_neighbor: Option<f64>,
}

fn infer_fundamental_frequency(samples: &[f64], sampling_rate: usize) -> Result<f64> {
fn infer_fundamental_frequency(samples: &[f64], sampling_rate: u32) -> Result<f64> {
let maximas = key_local_maximas(&samples);
let best_maxima = pick_maxima(&maximas).ok_or(anyhow!("Can't the best local maxima"))?;
let interpolated_index = parabolic_interpolation(&best_maxima);
Expand Down Expand Up @@ -278,7 +278,7 @@ fn apply_filter(samples: &[f64], filter: &[f64]) -> Vec<f64> {
#[cfg(test)]
mod tests {
use super::*;
use crate::SAMPLE_RATE;
use crate::FIXTURE_SAMPLE_RATE;
use std::fs;

fn overlap_chunks(samples: &[f64], chunk_size: usize, move_index: usize) -> Vec<Vec<f64>> {
Expand Down Expand Up @@ -316,7 +316,7 @@ mod tests {
.into_iter()
.take(5)
.filter(|chunk| chunk.len() == chunk_size)
.map(|mut chunk| detect_note(&mut chunk, SAMPLE_RATE, &tuning_notes).unwrap())
.map(|mut chunk| detect_note(&mut chunk, FIXTURE_SAMPLE_RATE, &tuning_notes).unwrap())
.collect()
}

Expand Down
25 changes: 9 additions & 16 deletions src/recorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ use cpal::{Device, SampleFormat, SampleRate, Stream, SupportedStreamConfig};
use std::sync::{Arc, Mutex};

pub struct Recorder {
sample_rate: usize,
samples: Arc<Mutex<Vec<f64>>>,
// Maximum size of the samples vec
buffer_size: usize,
stream: Option<Stream>,
}

impl Recorder {
pub fn new(sample_rate: usize, buffer_size: usize) -> Self {
pub fn new(buffer_size: usize) -> Self {
Self {
sample_rate,
samples: Arc::new(Mutex::new(Vec::with_capacity(buffer_size))),
stream: None,
buffer_size,
Expand All @@ -25,7 +23,7 @@ impl Recorder {
// Start recording audio and pulse code modulating. Each sample is a number in the range of
// -1.0..1.0
// This function will fail if the recording device doesn't support the provided sample rate
pub fn record(&mut self) -> Result<()> {
pub fn record(&mut self) -> Result<SampleRate> {
#[cfg(any(
not(any(
target_os = "linux",
Expand All @@ -42,8 +40,7 @@ impl Recorder {
.default_input_device()
.ok_or(anyhow!("Can't find default input device"))?;

let config =
get_device_input_config(&device, SampleRate(self.sample_rate.try_into().unwrap()));
let config = get_device_input_config(&device);

let samples_clone = self.samples.clone();
let buffer_size = self.buffer_size;
Expand All @@ -53,7 +50,7 @@ impl Recorder {
};

let stream = device.build_input_stream(
&config.into(),
&config.clone().into(),
move |data: &[f32], _: &_| {
let mut buffer = samples_clone.lock().unwrap();

Expand All @@ -72,7 +69,7 @@ impl Recorder {
stream.play()?;
self.stream = Some(stream);

Ok(())
Ok(config.sample_rate())
}

// Invoke callback on collected samples. Only use the last `limit` samples
Expand All @@ -85,20 +82,16 @@ impl Recorder {
}
}

fn get_device_input_config(device: &Device, sample_rate: SampleRate) -> SupportedStreamConfig {
fn get_device_input_config(device: &Device) -> SupportedStreamConfig {
let configs = device.supported_input_configs().unwrap();

let config = configs
.into_iter()
.find(|config| {
config.channels() == 1
&& config.max_sample_rate() >= sample_rate
&& config.min_sample_rate() <= sample_rate
&& config.sample_format() == SampleFormat::F32
})
.filter(|config| config.channels() == 1 && config.sample_format() == SampleFormat::F32)
.max_by_key(|config| config.max_sample_rate())
.unwrap();

config.with_sample_rate(sample_rate)
config.with_max_sample_rate()
}

// Take N elements from tail. Avoid allocation by copying
Expand Down

0 comments on commit 4680e4d

Please sign in to comment.