diff --git a/src/audio.rs b/src/audio.rs index f7362a8..7867020 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,3 +1,20 @@ +/* +First order low-pass filter equation: H(s)=1/(τs+1). H(s) is output, + +low pass filter: +y = (1 - gamma) * y + gamma * x + +high pass filter: +y[i] := gamma * y[i−1] + gamma * (x[i] − x[i−1]) + +fc = 44100 = sample frequency +Ts = 1/44100 = sample period +fc = 14000 = cutoff frequency +gamma = 1 - (e ^ (-2pi * fc / fs)) +*/ + +use std::f32::consts::{E, PI}; + extern crate sdl2; use std::sync::{Arc, Mutex}; @@ -10,11 +27,62 @@ const SDL_SAMPLE_RATE: i32 = 44_100; // devices and then sleeping. So the audio device is set to play 44,100 samples per second, and grab them in 60 intervals over the course of that second. const SAMPLES_PER_FRAME: u16 = SDL_SAMPLE_RATE as u16/60; +// struct LowPass { +// cutoff_freq: f32, +// gamma: f32, +// previous_input: f32, +// previous_out: f32, +// } + +// struct HighPass { +// cutoff_freq: f32, +// gamma: f32, +// previous_input: f32, +// previous_out: f32, +// } + +// impl HighPass { +// fn filter(&self, sample: f32) -> f32 +// } + +fn get_gamma(cutoff_freq: f32) -> f32 { + 1. - (E.powf(-2. * PI * cutoff_freq / (SDL_SAMPLE_RATE as f32))) +} + pub struct ApuSampler { // This buffer receives all of the raw audio produced by the APU. // The callback will take what it needs when it needs it and truncate the buffer for smooth audio output. buffer: Arc>>, sample_ratio: f32, + + prev_input_90Hz: f32, + prev_output_90Hz: f32, + gamma_90Hz: f32, + + prev_input_440Hz: f32, + prev_output_440Hz: f32, + gamma_440Hz: f32, + + prev_input_14kHz: f32, + prev_output_14kHz: f32, + gamma_14kHz: f32, +} + +impl ApuSampler { + + fn high_pass_90Hz(&self, sample: f32) -> f32 { + // y[i] := α × y[i−1] + α × (x[i] − x[i−1]) + (self.gamma_90Hz * self.prev_output_90Hz) + (sample - self.prev_input_90Hz) + } + + fn high_pass_440Hz(&self, sample: f32) -> f32 { + (self.gamma_440Hz * self.prev_output_440Hz) + (sample - self.prev_input_440Hz) + } + + fn low_pass_14kHz(&self, sample: f32) -> f32 { + ((1. - self.gamma_14kHz) * self.prev_output_14kHz) + (self.gamma_14kHz * sample) + } + } impl AudioCallback for ApuSampler { @@ -28,7 +96,24 @@ impl AudioCallback for ApuSampler { for (i, x) in out.iter_mut().enumerate() { let sample_idx = ((i as f32) * self.sample_ratio) as usize; if sample_idx < b.len() { - *x = b[sample_idx]; + let sample = b[sample_idx]; + + let filtered_90Hz = self.high_pass_90Hz(sample); + self.prev_input_90Hz = sample; + self.prev_output_90Hz = filtered_90Hz; + // *x = filtered_90Hz; + + let filtered_440Hz = self.high_pass_440Hz(filtered_90Hz); + self.prev_input_440Hz = filtered_90Hz; + self.prev_output_440Hz = filtered_440Hz; + // *x = filtered_440Hz; + + let filtered_14kHz = self.low_pass_14kHz(filtered_440Hz); + self.prev_input_14kHz = filtered_440Hz; + self.prev_output_14kHz = filtered_14kHz; + *x = filtered_14kHz; + + // *x = sample; } } let l = b.len(); @@ -53,6 +138,30 @@ pub fn initialize(sdl_context: &Sdl, buffer: Arc>>) }; audio_subsystem.open_playback(None, &desired_spec, |_spec| { // println!("{:?}", _spec); - ApuSampler{buffer, sample_ratio: APU_SAMPLE_RATE / (SDL_SAMPLE_RATE as f32)} + ApuSampler{ + buffer, + sample_ratio: APU_SAMPLE_RATE / (SDL_SAMPLE_RATE as f32), + + prev_input_90Hz: 0., + prev_output_90Hz: 0., + gamma_90Hz: 1.-get_gamma(90.), + prev_input_440Hz: 0., + prev_output_440Hz: 0., + gamma_440Hz: 1.-get_gamma(440.), + prev_input_14kHz: 0., + prev_output_14kHz: 0., + gamma_14kHz: get_gamma(14_000.), + } }) } + +#[cfg(test)] +mod tests { + use super::get_gamma; + #[test] + fn show_gamma_values() { + for i in [0, 100, 1000, 10000, 100000].iter() { + println!("gamma for cutoff frequency {}: {}", i, get_gamma(*i as f32)); + } + } +}