experimenting with high- and low-pass filters. not sure if they're right.

This commit is contained in:
Theron 2020-03-04 20:09:04 -06:00
parent e36bfd1821
commit dbccee8edd
1 changed files with 111 additions and 2 deletions

View File

@ -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[i1] + gamma * (x[i] x[i1])
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<Mutex<Vec<f32>>>,
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[i1] + α × (x[i] x[i1])
(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<Mutex<Vec<f32>>>)
};
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));
}
}
}