mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Anti-alias Linear Interpolation also.
This commit is contained in:
parent
cb8f6c954d
commit
33e2ce65d3
2 changed files with 150 additions and 32 deletions
|
@ -26,6 +26,8 @@ const HZ88200_INTERPOLATION_OUTPUT_SIZE: usize =
|
||||||
const HZ96000_INTERPOLATION_OUTPUT_SIZE: usize =
|
const HZ96000_INTERPOLATION_OUTPUT_SIZE: usize =
|
||||||
(RESAMPLER_INPUT_SIZE as f64 * (1.0 / HZ96000_RESAMPLE_FACTOR_RECIPROCAL)) as 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
|
// Blackman Window coefficients
|
||||||
const BLACKMAN_A0: f64 = 0.42;
|
const BLACKMAN_A0: f64 = 0.42;
|
||||||
const BLACKMAN_A1: f64 = 0.5;
|
const BLACKMAN_A1: f64 = 0.5;
|
||||||
|
@ -77,7 +79,9 @@ impl InterpolationQuality {
|
||||||
let mut coefficients = Vec::with_capacity(interpolation_coefficients_length);
|
let mut coefficients = Vec::with_capacity(interpolation_coefficients_length);
|
||||||
|
|
||||||
if interpolation_coefficients_length == 0 {
|
if interpolation_coefficients_length == 0 {
|
||||||
warn!("InterpolationQuality::Low::get_interpolation_coefficients always returns an empty Vec<f64> because Linear Interpolation does not use coefficients");
|
warn!("InterpolationQuality::Low::get_interpolation_coefficients always returns an empty Vec<f64>");
|
||||||
|
warn!("Linear Interpolation does not use coefficients");
|
||||||
|
|
||||||
return coefficients;
|
return coefficients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,30 +95,17 @@ impl InterpolationQuality {
|
||||||
|interpolation_coefficient_index| {
|
|interpolation_coefficient_index| {
|
||||||
let index_float = interpolation_coefficient_index as f64;
|
let index_float = interpolation_coefficient_index as f64;
|
||||||
let sample_index_fractional = (index_float * resample_factor_reciprocal).fract();
|
let sample_index_fractional = (index_float * resample_factor_reciprocal).fract();
|
||||||
let sinc_center_offset =
|
|
||||||
// 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.
|
|
||||||
(index_float - sinc_center) * resample_factor_reciprocal;
|
|
||||||
|
|
||||||
let sample_index_fractional_sinc_weight = Self::sinc(sample_index_fractional);
|
let sample_index_fractional_sinc_weight = Self::sinc(sample_index_fractional);
|
||||||
|
|
||||||
let sinc_value = Self::sinc(sinc_center_offset);
|
let fir_filter = Self::fir_filter(
|
||||||
// Calculate the Blackman window function for the given center offset
|
index_float,
|
||||||
// w(n) = A0 - A1*cos(2πn / (N-1)) + A2*cos(4πn / (N-1)),
|
last_index,
|
||||||
// where n is the center offset, N is the window size,
|
sinc_center,
|
||||||
// and A0, A1, A2 are precalculated coefficients
|
resample_factor_reciprocal,
|
||||||
|
);
|
||||||
|
|
||||||
let two_pi_n = TWO_TIMES_PI * index_float;
|
let coefficient = sample_index_fractional_sinc_weight * fir_filter;
|
||||||
let four_pi_n = FOUR_TIMES_PI * index_float;
|
|
||||||
|
|
||||||
let blackman_window_value = BLACKMAN_A0
|
|
||||||
- BLACKMAN_A1 * (two_pi_n / last_index).cos()
|
|
||||||
+ BLACKMAN_A2 * (four_pi_n / last_index).cos();
|
|
||||||
|
|
||||||
let sinc_window = sinc_value * blackman_window_value;
|
|
||||||
|
|
||||||
let coefficient = sinc_window * sample_index_fractional_sinc_weight;
|
|
||||||
|
|
||||||
coefficient_sum += coefficient;
|
coefficient_sum += coefficient;
|
||||||
|
|
||||||
|
@ -129,6 +120,44 @@ impl InterpolationQuality {
|
||||||
coefficients
|
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 {
|
pub fn get_interpolation_coefficients_length(&self) -> usize {
|
||||||
use InterpolationQuality::*;
|
use InterpolationQuality::*;
|
||||||
match self {
|
match self {
|
||||||
|
@ -146,6 +175,35 @@ impl InterpolationQuality {
|
||||||
pi_x.sin() / pi_x
|
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)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
|
|
@ -8,28 +8,28 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{InterpolationQuality, SampleRate},
|
config::{InterpolationQuality, SampleRate, NUM_FIR_FILTER_TAPS},
|
||||||
player::PLAYER_COUNTER,
|
player::PLAYER_COUNTER,
|
||||||
RESAMPLER_INPUT_SIZE, SAMPLE_RATE as SOURCE_SAMPLE_RATE,
|
RESAMPLER_INPUT_SIZE, SAMPLE_RATE as SOURCE_SAMPLE_RATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DelayLine {
|
struct DelayLine {
|
||||||
buffer: VecDeque<f64>,
|
buffer: VecDeque<f64>,
|
||||||
interpolation_coefficients_length: usize,
|
coefficients_length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DelayLine {
|
impl DelayLine {
|
||||||
fn new(interpolation_coefficients_length: usize) -> DelayLine {
|
fn new(coefficients_length: usize) -> DelayLine {
|
||||||
Self {
|
Self {
|
||||||
buffer: VecDeque::with_capacity(interpolation_coefficients_length),
|
buffer: VecDeque::with_capacity(coefficients_length),
|
||||||
interpolation_coefficients_length,
|
coefficients_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, sample: f64) {
|
fn push(&mut self, sample: f64) {
|
||||||
self.buffer.push_back(sample);
|
self.buffer.push_back(sample);
|
||||||
|
|
||||||
while self.buffer.len() > self.interpolation_coefficients_length {
|
while self.buffer.len() > self.coefficients_length {
|
||||||
self.buffer.pop_front();
|
self.buffer.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,21 @@ impl<'a> IntoIterator for &'a DelayLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait Convoluter {
|
||||||
|
fn new(interpolation_quality: InterpolationQuality, resample_factor_reciprocal: f64) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
fn convolute(&mut self, sample: f64) -> f64;
|
||||||
|
fn clear(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
struct WindowedSincInterpolator {
|
struct WindowedSincInterpolator {
|
||||||
interpolation_coefficients: Vec<f64>,
|
interpolation_coefficients: Vec<f64>,
|
||||||
delay_line: DelayLine,
|
delay_line: DelayLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowedSincInterpolator {
|
impl Convoluter for WindowedSincInterpolator {
|
||||||
fn new(interpolation_quality: InterpolationQuality, resample_factor_reciprocal: f64) -> Self {
|
fn new(interpolation_quality: InterpolationQuality, resample_factor_reciprocal: f64) -> Self {
|
||||||
let interpolation_coefficients =
|
let interpolation_coefficients =
|
||||||
interpolation_quality.get_interpolation_coefficients(resample_factor_reciprocal);
|
interpolation_quality.get_interpolation_coefficients(resample_factor_reciprocal);
|
||||||
|
@ -66,7 +75,7 @@ impl WindowedSincInterpolator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interpolate(&mut self, sample: f64) -> f64 {
|
fn convolute(&mut self, sample: f64) -> f64 {
|
||||||
// Since our interpolation coefficients are pre-calculated
|
// Since our interpolation coefficients are pre-calculated
|
||||||
// we can basically pretend like the Interpolator is a FIR filter.
|
// we can basically pretend like the Interpolator is a FIR filter.
|
||||||
self.delay_line.push(sample);
|
self.delay_line.push(sample);
|
||||||
|
@ -85,6 +94,41 @@ impl WindowedSincInterpolator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LinearFirFilter {
|
||||||
|
fir_filter_coefficients: Vec<f64>,
|
||||||
|
delay_line: DelayLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convoluter for LinearFirFilter {
|
||||||
|
fn new(interpolation_quality: InterpolationQuality, resample_factor_reciprocal: f64) -> Self {
|
||||||
|
let fir_filter_coefficients =
|
||||||
|
interpolation_quality.get_fir_filter_coefficients(resample_factor_reciprocal);
|
||||||
|
|
||||||
|
let delay_line = DelayLine::new(fir_filter_coefficients.len());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fir_filter_coefficients,
|
||||||
|
delay_line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convolute(&mut self, sample: f64) -> f64 {
|
||||||
|
self.delay_line.push(sample);
|
||||||
|
|
||||||
|
// Temporal convolution
|
||||||
|
self.fir_filter_coefficients
|
||||||
|
.iter()
|
||||||
|
.zip(&self.delay_line)
|
||||||
|
.fold(0.0, |acc, (coefficient, delay_line_sample)| {
|
||||||
|
acc + coefficient * delay_line_sample
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.delay_line.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait MonoResampler {
|
trait MonoResampler {
|
||||||
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self
|
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self
|
||||||
where
|
where
|
||||||
|
@ -156,7 +200,7 @@ impl MonoResampler for MonoSincResampler {
|
||||||
// out the other side is Sinc Windowed Interpolated samples.
|
// out the other side is Sinc Windowed Interpolated samples.
|
||||||
let sample_index = (ouput_index as f64 * self.resample_factor_reciprocal) as usize;
|
let sample_index = (ouput_index as f64 * self.resample_factor_reciprocal) as usize;
|
||||||
let sample = self.input_buffer[sample_index];
|
let sample = self.input_buffer[sample_index];
|
||||||
self.interpolator.interpolate(sample)
|
self.interpolator.convolute(sample)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.input_buffer.drain(..input_size);
|
self.input_buffer.drain(..input_size);
|
||||||
|
@ -166,27 +210,39 @@ impl MonoResampler for MonoSincResampler {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MonoLinearResampler {
|
struct MonoLinearResampler {
|
||||||
|
fir_filter: LinearFirFilter,
|
||||||
input_buffer: Vec<f64>,
|
input_buffer: Vec<f64>,
|
||||||
resample_factor_reciprocal: f64,
|
resample_factor_reciprocal: f64,
|
||||||
|
delay_line_latency: u64,
|
||||||
interpolation_output_size: usize,
|
interpolation_output_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonoResampler for MonoLinearResampler {
|
impl MonoResampler for MonoLinearResampler {
|
||||||
fn new(sample_rate: SampleRate, _: InterpolationQuality) -> Self {
|
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self {
|
||||||
let spec = sample_rate.get_resample_spec();
|
let spec = sample_rate.get_resample_spec();
|
||||||
|
|
||||||
|
let delay_line_latency =
|
||||||
|
(NUM_FIR_FILTER_TAPS as f64 * spec.resample_factor_reciprocal) as u64;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
fir_filter: LinearFirFilter::new(
|
||||||
|
interpolation_quality,
|
||||||
|
spec.resample_factor_reciprocal,
|
||||||
|
),
|
||||||
|
|
||||||
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE as usize),
|
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE as usize),
|
||||||
resample_factor_reciprocal: spec.resample_factor_reciprocal,
|
resample_factor_reciprocal: spec.resample_factor_reciprocal,
|
||||||
|
delay_line_latency,
|
||||||
interpolation_output_size: spec.interpolation_output_size,
|
interpolation_output_size: spec.interpolation_output_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_latency_pcm(&mut self) -> u64 {
|
fn get_latency_pcm(&mut self) -> u64 {
|
||||||
self.input_buffer.len() as u64
|
self.input_buffer.len() as u64 + self.delay_line_latency
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
|
self.fir_filter.clear();
|
||||||
self.input_buffer.clear();
|
self.input_buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +276,10 @@ impl MonoResampler for MonoLinearResampler {
|
||||||
// Remove the last garbage sample.
|
// Remove the last garbage sample.
|
||||||
output.pop();
|
output.pop();
|
||||||
|
|
||||||
|
output
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|sample| *sample = self.fir_filter.convolute(*sample));
|
||||||
|
|
||||||
self.input_buffer.drain(..input_size);
|
self.input_buffer.drain(..input_size);
|
||||||
|
|
||||||
Some(output)
|
Some(output)
|
||||||
|
|
Loading…
Reference in a new issue