mirror of
https://github.com/librespot-org/librespot.git
synced 2025-01-27 17:44:04 +00:00
Better Filters
All Windows calculated with pyfda (Python Filter Design Analysis Tool) https://github.com/chipmuenk/pyfda Window = Kaiser beta = 8.6 (Similar to a Blackman Window) fc = 22.5kHz -86dB by 23kHz This also gets rid of Linear Interpolation which leaves only Low and High both being Windowed Sinc.
This commit is contained in:
parent
ac68d2431e
commit
e31293cc10
5 changed files with 1234 additions and 246 deletions
|
@ -1,7 +1,14 @@
|
|||
use std::{mem, str::FromStr, time::Duration};
|
||||
|
||||
pub use crate::dither::{mk_ditherer, DithererBuilder, TriangularDitherer};
|
||||
use crate::{convert::i24, RESAMPLER_INPUT_SIZE, SAMPLE_RATE};
|
||||
|
||||
use crate::{
|
||||
convert::i24,
|
||||
filter_coefficients::{
|
||||
HZ48000_HIGH, HZ48000_LOW, HZ88200_HIGH, HZ88200_LOW, HZ96000_HIGH, HZ96000_LOW,
|
||||
},
|
||||
RESAMPLER_INPUT_SIZE, SAMPLE_RATE,
|
||||
};
|
||||
|
||||
// Reciprocals allow us to multiply instead of divide during interpolation.
|
||||
const HZ48000_RESAMPLE_FACTOR_RECIPROCAL: f64 = SAMPLE_RATE as f64 / 48_000.0;
|
||||
|
@ -26,21 +33,9 @@ const HZ88200_INTERPOLATION_OUTPUT_SIZE: usize =
|
|||
const HZ96000_INTERPOLATION_OUTPUT_SIZE: usize =
|
||||
(RESAMPLER_INPUT_SIZE as f64 * (1.0 / HZ96000_RESAMPLE_FACTOR_RECIPROCAL)) as usize;
|
||||
|
||||
pub const NUM_FIR_FILTER_TAPS: usize = 5;
|
||||
|
||||
// Blackman Window coefficients
|
||||
const BLACKMAN_A0: f64 = 0.42;
|
||||
const BLACKMAN_A1: f64 = 0.5;
|
||||
const BLACKMAN_A2: f64 = 0.08;
|
||||
|
||||
// Constants for calculations
|
||||
const TWO_TIMES_PI: f64 = 2.0 * std::f64::consts::PI;
|
||||
const FOUR_TIMES_PI: f64 = 4.0 * std::f64::consts::PI;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum InterpolationQuality {
|
||||
Low,
|
||||
Medium,
|
||||
#[default]
|
||||
High,
|
||||
}
|
||||
|
@ -53,7 +48,6 @@ impl FromStr for InterpolationQuality {
|
|||
|
||||
match s.to_lowercase().as_ref() {
|
||||
"low" => Ok(Low),
|
||||
"medium" => Ok(Medium),
|
||||
"high" => Ok(High),
|
||||
_ => Err(()),
|
||||
}
|
||||
|
@ -66,52 +60,24 @@ impl std::fmt::Display for InterpolationQuality {
|
|||
|
||||
match self {
|
||||
Low => write!(f, "Low"),
|
||||
Medium => write!(f, "Medium"),
|
||||
High => write!(f, "High"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InterpolationQuality {
|
||||
pub fn get_interpolation_coefficients(&self, resample_factor_reciprocal: f64) -> Vec<f64> {
|
||||
let interpolation_coefficients_length = self.get_interpolation_coefficients_length();
|
||||
|
||||
let mut coefficients = Vec::with_capacity(interpolation_coefficients_length);
|
||||
|
||||
if interpolation_coefficients_length == 0 {
|
||||
warn!("InterpolationQuality::Low::get_interpolation_coefficients always returns an empty Vec<f64>");
|
||||
warn!("Linear Interpolation does not use coefficients");
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
let last_index = interpolation_coefficients_length as f64 - 1.0;
|
||||
|
||||
let sinc_center = last_index * 0.5;
|
||||
|
||||
pub fn get_interpolation_coefficients(
|
||||
&self,
|
||||
mut coefficients: Vec<f64>,
|
||||
resample_factor_reciprocal: f64,
|
||||
) -> Vec<f64> {
|
||||
let mut coefficient_sum = 0.0;
|
||||
|
||||
coefficients.extend((0..interpolation_coefficients_length).map(
|
||||
|interpolation_coefficient_index| {
|
||||
let index_float = interpolation_coefficient_index as f64;
|
||||
let sample_index_fractional = (index_float * resample_factor_reciprocal).fract();
|
||||
for (index, coefficient) in coefficients.iter_mut().enumerate() {
|
||||
*coefficient *= Self::sinc((index as f64 * resample_factor_reciprocal).fract());
|
||||
|
||||
let sample_index_fractional_sinc_weight = Self::sinc(sample_index_fractional);
|
||||
|
||||
let fir_filter = Self::fir_filter(
|
||||
index_float,
|
||||
last_index,
|
||||
sinc_center,
|
||||
resample_factor_reciprocal,
|
||||
);
|
||||
|
||||
let coefficient = sample_index_fractional_sinc_weight * fir_filter;
|
||||
|
||||
coefficient_sum += coefficient;
|
||||
|
||||
coefficient
|
||||
},
|
||||
));
|
||||
coefficient_sum += *coefficient;
|
||||
}
|
||||
|
||||
coefficients
|
||||
.iter_mut()
|
||||
|
@ -120,53 +86,6 @@ impl InterpolationQuality {
|
|||
coefficients
|
||||
}
|
||||
|
||||
pub fn get_fir_filter_coefficients(&self, resample_factor_reciprocal: f64) -> Vec<f64> {
|
||||
let mut coefficients = Vec::with_capacity(NUM_FIR_FILTER_TAPS);
|
||||
|
||||
if self.get_interpolation_coefficients_length() != 0 {
|
||||
warn!("InterpolationQuality::Medium/High::get_fir_filter_coefficients always returns an empty Vec<f64>");
|
||||
warn!("The FIR Filter coefficients are a part of the Windowed Sinc Interpolation coefficients");
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
let last_index = NUM_FIR_FILTER_TAPS as f64 - 1.0;
|
||||
|
||||
let sinc_center = last_index * 0.5;
|
||||
|
||||
let mut coefficient_sum = 0.0;
|
||||
|
||||
coefficients.extend(
|
||||
(0..NUM_FIR_FILTER_TAPS).map(|fir_filter_coefficient_index| {
|
||||
let coefficient = Self::fir_filter(
|
||||
fir_filter_coefficient_index as f64,
|
||||
last_index,
|
||||
sinc_center,
|
||||
resample_factor_reciprocal,
|
||||
);
|
||||
|
||||
coefficient_sum += coefficient;
|
||||
|
||||
coefficient
|
||||
}),
|
||||
);
|
||||
|
||||
coefficients
|
||||
.iter_mut()
|
||||
.for_each(|coefficient| *coefficient /= coefficient_sum);
|
||||
|
||||
coefficients
|
||||
}
|
||||
|
||||
pub fn get_interpolation_coefficients_length(&self) -> usize {
|
||||
use InterpolationQuality::*;
|
||||
match self {
|
||||
Low => 0,
|
||||
Medium => 129,
|
||||
High => 257,
|
||||
}
|
||||
}
|
||||
|
||||
fn sinc(x: f64) -> f64 {
|
||||
if x.abs() < f64::EPSILON {
|
||||
1.0
|
||||
|
@ -175,35 +94,6 @@ impl InterpolationQuality {
|
|||
pi_x.sin() / pi_x
|
||||
}
|
||||
}
|
||||
|
||||
fn blackman(index: f64, last_index: f64) -> f64 {
|
||||
// Calculate the Blackman window function for the given center offset
|
||||
// w(n) = A0 - A1*cos(2πn / (N-1)) + A2*cos(4πn / (N-1)),
|
||||
// where n is the center offset, N is the window size,
|
||||
// and A0, A1, A2 are precalculated coefficients
|
||||
let two_pi_n = TWO_TIMES_PI * index;
|
||||
let four_pi_n = FOUR_TIMES_PI * index;
|
||||
|
||||
BLACKMAN_A0 - BLACKMAN_A1 * (two_pi_n / last_index).cos()
|
||||
+ BLACKMAN_A2 * (four_pi_n / last_index).cos()
|
||||
}
|
||||
|
||||
fn fir_filter(
|
||||
index: f64,
|
||||
last_index: f64,
|
||||
sinc_center: f64,
|
||||
resample_factor_reciprocal: f64,
|
||||
) -> f64 {
|
||||
// The resample_factor_reciprocal also happens to be our
|
||||
// anti-alias cutoff. In this case it represents the minimum
|
||||
// output bandwidth needed to fully represent the input.
|
||||
let adjusted_sinc_center_offset = (index - sinc_center) * resample_factor_reciprocal;
|
||||
|
||||
let sinc_value = Self::sinc(adjusted_sinc_center_offset);
|
||||
let blackman_window_value = Self::blackman(index, last_index);
|
||||
|
||||
sinc_value * blackman_window_value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
|
@ -262,10 +152,12 @@ impl std::fmt::Display for SampleRate {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ResampleSpec {
|
||||
pub resample_factor_reciprocal: f64,
|
||||
pub interpolation_output_size: usize,
|
||||
pub high_coefficients: Vec<f64>,
|
||||
pub low_coefficients: Vec<f64>,
|
||||
}
|
||||
|
||||
impl SampleRate {
|
||||
|
@ -328,19 +220,27 @@ impl SampleRate {
|
|||
ResampleSpec {
|
||||
resample_factor_reciprocal: 1.0,
|
||||
interpolation_output_size: RESAMPLER_INPUT_SIZE,
|
||||
high_coefficients: vec![],
|
||||
low_coefficients: vec![],
|
||||
}
|
||||
}
|
||||
Hz48000 => ResampleSpec {
|
||||
resample_factor_reciprocal: HZ48000_RESAMPLE_FACTOR_RECIPROCAL,
|
||||
interpolation_output_size: HZ48000_INTERPOLATION_OUTPUT_SIZE,
|
||||
high_coefficients: HZ48000_HIGH.to_vec(),
|
||||
low_coefficients: HZ48000_LOW.to_vec(),
|
||||
},
|
||||
Hz88200 => ResampleSpec {
|
||||
resample_factor_reciprocal: HZ88200_RESAMPLE_FACTOR_RECIPROCAL,
|
||||
interpolation_output_size: HZ88200_INTERPOLATION_OUTPUT_SIZE,
|
||||
high_coefficients: HZ88200_HIGH.to_vec(),
|
||||
low_coefficients: HZ88200_LOW.to_vec(),
|
||||
},
|
||||
Hz96000 => ResampleSpec {
|
||||
resample_factor_reciprocal: HZ96000_RESAMPLE_FACTOR_RECIPROCAL,
|
||||
interpolation_output_size: HZ96000_INTERPOLATION_OUTPUT_SIZE,
|
||||
high_coefficients: HZ96000_HIGH.to_vec(),
|
||||
low_coefficients: HZ96000_LOW.to_vec(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
1187
playback/src/filter_coefficients.rs
Normal file
1187
playback/src/filter_coefficients.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,7 @@ pub mod config;
|
|||
pub mod convert;
|
||||
pub mod decoder;
|
||||
pub mod dither;
|
||||
pub mod filter_coefficients;
|
||||
pub mod mixer;
|
||||
pub mod normaliser;
|
||||
pub mod player;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{
|
||||
collections::{vec_deque, VecDeque},
|
||||
marker::Send,
|
||||
process::exit,
|
||||
sync::atomic::Ordering,
|
||||
sync::mpsc,
|
||||
|
@ -8,7 +7,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
config::{InterpolationQuality, SampleRate, NUM_FIR_FILTER_TAPS},
|
||||
config::{InterpolationQuality, SampleRate},
|
||||
player::PLAYER_COUNTER,
|
||||
RESAMPLER_INPUT_SIZE, SAMPLE_RATE as SOURCE_SAMPLE_RATE,
|
||||
};
|
||||
|
@ -80,16 +79,6 @@ impl ConvolutionFilter {
|
|||
}
|
||||
}
|
||||
|
||||
trait MonoResampler {
|
||||
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn stop(&mut self);
|
||||
fn get_latency_pcm(&mut self) -> u64;
|
||||
fn resample(&mut self, samples: &[f64]) -> Option<Vec<f64>>;
|
||||
}
|
||||
|
||||
struct MonoSincResampler {
|
||||
interpolator: ConvolutionFilter,
|
||||
input_buffer: Vec<f64>,
|
||||
|
@ -98,18 +87,22 @@ struct MonoSincResampler {
|
|||
interpolation_output_size: usize,
|
||||
}
|
||||
|
||||
impl MonoResampler for MonoSincResampler {
|
||||
impl MonoSincResampler {
|
||||
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self {
|
||||
let spec = sample_rate.get_resample_spec();
|
||||
|
||||
let delay_line_latency = (interpolation_quality.get_interpolation_coefficients_length()
|
||||
as f64
|
||||
* spec.resample_factor_reciprocal) as u64;
|
||||
let coefficients = match interpolation_quality {
|
||||
InterpolationQuality::Low => spec.low_coefficients,
|
||||
InterpolationQuality::High => spec.high_coefficients,
|
||||
};
|
||||
|
||||
let delay_line_latency =
|
||||
(coefficients.len() as f64 * spec.resample_factor_reciprocal) as u64;
|
||||
|
||||
Self {
|
||||
interpolator: ConvolutionFilter::new(
|
||||
interpolation_quality
|
||||
.get_interpolation_coefficients(spec.resample_factor_reciprocal),
|
||||
.get_interpolation_coefficients(coefficients, spec.resample_factor_reciprocal),
|
||||
),
|
||||
|
||||
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE as usize),
|
||||
|
@ -160,82 +153,6 @@ impl MonoResampler for MonoSincResampler {
|
|||
}
|
||||
}
|
||||
|
||||
struct MonoLinearResampler {
|
||||
fir_filter: ConvolutionFilter,
|
||||
input_buffer: Vec<f64>,
|
||||
resample_factor_reciprocal: f64,
|
||||
delay_line_latency: u64,
|
||||
interpolation_output_size: usize,
|
||||
}
|
||||
|
||||
impl MonoResampler for MonoLinearResampler {
|
||||
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self {
|
||||
let spec = sample_rate.get_resample_spec();
|
||||
|
||||
let delay_line_latency =
|
||||
(NUM_FIR_FILTER_TAPS as f64 * spec.resample_factor_reciprocal) as u64;
|
||||
|
||||
Self {
|
||||
fir_filter: ConvolutionFilter::new(
|
||||
interpolation_quality.get_fir_filter_coefficients(spec.resample_factor_reciprocal),
|
||||
),
|
||||
|
||||
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE as usize),
|
||||
resample_factor_reciprocal: spec.resample_factor_reciprocal,
|
||||
delay_line_latency,
|
||||
interpolation_output_size: spec.interpolation_output_size,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_latency_pcm(&mut self) -> u64 {
|
||||
self.input_buffer.len() as u64 + self.delay_line_latency
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
self.fir_filter.clear();
|
||||
self.input_buffer.clear();
|
||||
}
|
||||
|
||||
fn resample(&mut self, samples: &[f64]) -> Option<Vec<f64>> {
|
||||
self.input_buffer.extend_from_slice(samples);
|
||||
|
||||
let num_buffer_chunks = self.input_buffer.len().saturating_div(RESAMPLER_INPUT_SIZE);
|
||||
|
||||
if num_buffer_chunks == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let input_size = num_buffer_chunks * RESAMPLER_INPUT_SIZE;
|
||||
// The size of the output after interpolation.
|
||||
// We have to account for the fact that to do effective linear
|
||||
// interpolation we need an extra sample to be able to throw away later.
|
||||
let output_size = num_buffer_chunks * self.interpolation_output_size + 1;
|
||||
|
||||
let mut output = Vec::with_capacity(output_size);
|
||||
|
||||
output.extend((0..output_size).map(|output_index| {
|
||||
let sample_index = output_index as f64 * self.resample_factor_reciprocal;
|
||||
let sample_index_fractional = sample_index.fract();
|
||||
let sample_index = sample_index as usize;
|
||||
let sample = *self.input_buffer.get(sample_index).unwrap_or(&0.0);
|
||||
let next_sample = *self.input_buffer.get(sample_index + 1).unwrap_or(&0.0);
|
||||
let sample_index_fractional_complementary = 1.0 - sample_index_fractional;
|
||||
sample * sample_index_fractional_complementary + next_sample * sample_index_fractional
|
||||
}));
|
||||
|
||||
// Remove the last garbage sample.
|
||||
output.pop();
|
||||
|
||||
output
|
||||
.iter_mut()
|
||||
.for_each(|sample| *sample = self.fir_filter.convolute(*sample));
|
||||
|
||||
self.input_buffer.drain(..input_size);
|
||||
|
||||
Some(output)
|
||||
}
|
||||
}
|
||||
|
||||
enum ResampleTask {
|
||||
Stop,
|
||||
Terminate,
|
||||
|
@ -249,7 +166,7 @@ struct ResampleWorker {
|
|||
}
|
||||
|
||||
impl ResampleWorker {
|
||||
fn new(mut resampler: impl MonoResampler + Send + 'static, name: String) -> Self {
|
||||
fn new(mut resampler: MonoSincResampler, name: String) -> Self {
|
||||
let (task_sender, task_receiver) = mpsc::channel();
|
||||
let (result_sender, result_receiver) = mpsc::channel();
|
||||
|
||||
|
@ -383,29 +300,12 @@ impl StereoInterleavedResampler {
|
|||
let left_thread_name = format!("resampler:{player_id}:left");
|
||||
let right_thread_name = format!("resampler:{player_id}:right");
|
||||
|
||||
match interpolation_quality {
|
||||
InterpolationQuality::Low => {
|
||||
debug!("Interpolation Type: Linear");
|
||||
let left = MonoSincResampler::new(sample_rate, interpolation_quality);
|
||||
let right = MonoSincResampler::new(sample_rate, interpolation_quality);
|
||||
|
||||
let left = MonoLinearResampler::new(sample_rate, interpolation_quality);
|
||||
let right = MonoLinearResampler::new(sample_rate, interpolation_quality);
|
||||
|
||||
Resampler::Worker {
|
||||
left_resampler: ResampleWorker::new(left, left_thread_name),
|
||||
right_resampler: ResampleWorker::new(right, right_thread_name),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("Interpolation Type: Windowed Sinc");
|
||||
|
||||
let left = MonoSincResampler::new(sample_rate, interpolation_quality);
|
||||
let right = MonoSincResampler::new(sample_rate, interpolation_quality);
|
||||
|
||||
Resampler::Worker {
|
||||
left_resampler: ResampleWorker::new(left, left_thread_name),
|
||||
right_resampler: ResampleWorker::new(right, right_thread_name),
|
||||
}
|
||||
}
|
||||
Resampler::Worker {
|
||||
left_resampler: ResampleWorker::new(left, left_thread_name),
|
||||
right_resampler: ResampleWorker::new(right, right_thread_name),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -582,7 +582,7 @@ fn get_setup() -> Setup {
|
|||
).optopt(
|
||||
"",
|
||||
INTERPOLATION_QUALITY,
|
||||
"Interpolation Quality to use if Resampling {Low|Medium|High}. Defaults to High.",
|
||||
"Interpolation Quality to use if Resampling {Low|High}. Defaults to High.",
|
||||
"QUALITY"
|
||||
).optopt(
|
||||
"",
|
||||
|
@ -817,7 +817,7 @@ fn get_setup() -> Setup {
|
|||
INTERPOLATION_QUALITY,
|
||||
"",
|
||||
interpolation_quality,
|
||||
"Low, Medium, High",
|
||||
"Low, High",
|
||||
default_value,
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue