mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
New filter coeff's and only have one quality setting.
This commit is contained in:
parent
e31293cc10
commit
3928d0775f
5 changed files with 1427 additions and 1345 deletions
|
@ -4,9 +4,7 @@ pub use crate::dither::{mk_ditherer, DithererBuilder, TriangularDitherer};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
convert::i24,
|
convert::i24,
|
||||||
filter_coefficients::{
|
filter_coefficients::{HZ48000_COEFFICIENTS, HZ88200_COEFFICIENTS, HZ96000_COEFFICIENTS},
|
||||||
HZ48000_HIGH, HZ48000_LOW, HZ88200_HIGH, HZ88200_LOW, HZ96000_HIGH, HZ96000_LOW,
|
|
||||||
},
|
|
||||||
RESAMPLER_INPUT_SIZE, SAMPLE_RATE,
|
RESAMPLER_INPUT_SIZE, SAMPLE_RATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,69 +31,6 @@ 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;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub enum InterpolationQuality {
|
|
||||||
Low,
|
|
||||||
#[default]
|
|
||||||
High,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for InterpolationQuality {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
use InterpolationQuality::*;
|
|
||||||
|
|
||||||
match s.to_lowercase().as_ref() {
|
|
||||||
"low" => Ok(Low),
|
|
||||||
"high" => Ok(High),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for InterpolationQuality {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
use InterpolationQuality::*;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Low => write!(f, "Low"),
|
|
||||||
High => write!(f, "High"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterpolationQuality {
|
|
||||||
pub fn get_interpolation_coefficients(
|
|
||||||
&self,
|
|
||||||
mut coefficients: Vec<f64>,
|
|
||||||
resample_factor_reciprocal: f64,
|
|
||||||
) -> Vec<f64> {
|
|
||||||
let mut coefficient_sum = 0.0;
|
|
||||||
|
|
||||||
for (index, coefficient) in coefficients.iter_mut().enumerate() {
|
|
||||||
*coefficient *= Self::sinc((index as f64 * resample_factor_reciprocal).fract());
|
|
||||||
|
|
||||||
coefficient_sum += *coefficient;
|
|
||||||
}
|
|
||||||
|
|
||||||
coefficients
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|coefficient| *coefficient /= coefficient_sum);
|
|
||||||
|
|
||||||
coefficients
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sinc(x: f64) -> f64 {
|
|
||||||
if x.abs() < f64::EPSILON {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
let pi_x = std::f64::consts::PI * x;
|
|
||||||
pi_x.sin() / pi_x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub enum SampleRate {
|
pub enum SampleRate {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -152,14 +87,6 @@ impl std::fmt::Display for SampleRate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
impl SampleRate {
|
||||||
pub fn as_u32(&self) -> u32 {
|
pub fn as_u32(&self) -> u32 {
|
||||||
use SampleRate::*;
|
use SampleRate::*;
|
||||||
|
@ -207,41 +134,73 @@ impl SampleRate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_resample_spec(&self) -> ResampleSpec {
|
pub fn get_resample_factor_reciprocal(&self) -> Option<f64> {
|
||||||
use SampleRate::*;
|
use SampleRate::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
// Dummy values to satisfy
|
Hz44100 => None,
|
||||||
// the match statement.
|
Hz48000 => Some(HZ48000_RESAMPLE_FACTOR_RECIPROCAL),
|
||||||
// 44.1kHz will be bypassed.
|
Hz88200 => Some(HZ88200_RESAMPLE_FACTOR_RECIPROCAL),
|
||||||
Hz44100 => {
|
Hz96000 => Some(HZ96000_RESAMPLE_FACTOR_RECIPROCAL),
|
||||||
warn!("Resampling 44.1kHz to 44.1kHz is just a really CPU intensive no-op, you should not be doing it");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ResampleSpec {
|
pub fn get_interpolation_output_size(&self) -> Option<usize> {
|
||||||
resample_factor_reciprocal: 1.0,
|
use SampleRate::*;
|
||||||
interpolation_output_size: RESAMPLER_INPUT_SIZE,
|
|
||||||
high_coefficients: vec![],
|
match self {
|
||||||
low_coefficients: vec![],
|
Hz44100 => None,
|
||||||
}
|
Hz48000 => Some(HZ48000_INTERPOLATION_OUTPUT_SIZE),
|
||||||
}
|
Hz88200 => Some(HZ88200_INTERPOLATION_OUTPUT_SIZE),
|
||||||
Hz48000 => ResampleSpec {
|
Hz96000 => Some(HZ96000_INTERPOLATION_OUTPUT_SIZE),
|
||||||
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(),
|
pub fn get_interpolation_coefficients(&self) -> Option<Vec<f64>> {
|
||||||
},
|
use SampleRate::*;
|
||||||
Hz88200 => ResampleSpec {
|
|
||||||
resample_factor_reciprocal: HZ88200_RESAMPLE_FACTOR_RECIPROCAL,
|
match self {
|
||||||
interpolation_output_size: HZ88200_INTERPOLATION_OUTPUT_SIZE,
|
Hz44100 => None,
|
||||||
high_coefficients: HZ88200_HIGH.to_vec(),
|
Hz48000 => Some(Self::calculate_interpolation_coefficients(
|
||||||
low_coefficients: HZ88200_LOW.to_vec(),
|
HZ48000_COEFFICIENTS.to_vec(),
|
||||||
},
|
HZ48000_RESAMPLE_FACTOR_RECIPROCAL,
|
||||||
Hz96000 => ResampleSpec {
|
)),
|
||||||
resample_factor_reciprocal: HZ96000_RESAMPLE_FACTOR_RECIPROCAL,
|
Hz88200 => Some(Self::calculate_interpolation_coefficients(
|
||||||
interpolation_output_size: HZ96000_INTERPOLATION_OUTPUT_SIZE,
|
HZ88200_COEFFICIENTS.to_vec(),
|
||||||
high_coefficients: HZ96000_HIGH.to_vec(),
|
HZ88200_RESAMPLE_FACTOR_RECIPROCAL,
|
||||||
low_coefficients: HZ96000_LOW.to_vec(),
|
)),
|
||||||
},
|
Hz96000 => Some(Self::calculate_interpolation_coefficients(
|
||||||
|
HZ96000_COEFFICIENTS.to_vec(),
|
||||||
|
HZ96000_RESAMPLE_FACTOR_RECIPROCAL,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_interpolation_coefficients(
|
||||||
|
mut coefficients: Vec<f64>,
|
||||||
|
resample_factor_reciprocal: f64,
|
||||||
|
) -> Vec<f64> {
|
||||||
|
let mut coefficient_sum = 0.0;
|
||||||
|
|
||||||
|
for (index, coefficient) in coefficients.iter_mut().enumerate() {
|
||||||
|
*coefficient *= Self::sinc((index as f64 * resample_factor_reciprocal).fract());
|
||||||
|
|
||||||
|
coefficient_sum += *coefficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
coefficients
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|coefficient| *coefficient /= coefficient_sum);
|
||||||
|
|
||||||
|
coefficients
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sinc(x: f64) -> f64 {
|
||||||
|
if x.abs() < f64::EPSILON {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
let pi_x = std::f64::consts::PI * x;
|
||||||
|
pi_x.sin() / pi_x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,7 +320,6 @@ pub struct PlayerConfig {
|
||||||
pub gapless: bool,
|
pub gapless: bool,
|
||||||
pub passthrough: bool,
|
pub passthrough: bool,
|
||||||
|
|
||||||
pub interpolation_quality: InterpolationQuality,
|
|
||||||
pub sample_rate: SampleRate,
|
pub sample_rate: SampleRate,
|
||||||
|
|
||||||
pub normalisation: bool,
|
pub normalisation: bool,
|
||||||
|
@ -384,7 +342,6 @@ impl Default for PlayerConfig {
|
||||||
bitrate: Bitrate::default(),
|
bitrate: Bitrate::default(),
|
||||||
gapless: true,
|
gapless: true,
|
||||||
normalisation: false,
|
normalisation: false,
|
||||||
interpolation_quality: InterpolationQuality::default(),
|
|
||||||
sample_rate: SampleRate::default(),
|
sample_rate: SampleRate::default(),
|
||||||
normalisation_type: NormalisationType::default(),
|
normalisation_type: NormalisationType::default(),
|
||||||
normalisation_method: NormalisationMethod::default(),
|
normalisation_method: NormalisationMethod::default(),
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,9 +7,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{InterpolationQuality, SampleRate},
|
config::SampleRate, player::PLAYER_COUNTER, RESAMPLER_INPUT_SIZE,
|
||||||
player::PLAYER_COUNTER,
|
SAMPLE_RATE as SOURCE_SAMPLE_RATE,
|
||||||
RESAMPLER_INPUT_SIZE, SAMPLE_RATE as SOURCE_SAMPLE_RATE,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DelayLine {
|
struct DelayLine {
|
||||||
|
@ -88,27 +87,27 @@ struct MonoSincResampler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonoSincResampler {
|
impl MonoSincResampler {
|
||||||
fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self {
|
fn new(sample_rate: SampleRate) -> Self {
|
||||||
let spec = sample_rate.get_resample_spec();
|
let coefficients = sample_rate
|
||||||
|
.get_interpolation_coefficients()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let coefficients = match interpolation_quality {
|
let resample_factor_reciprocal = sample_rate
|
||||||
InterpolationQuality::Low => spec.low_coefficients,
|
.get_resample_factor_reciprocal()
|
||||||
InterpolationQuality::High => spec.high_coefficients,
|
.unwrap_or_default();
|
||||||
};
|
|
||||||
|
|
||||||
let delay_line_latency =
|
let interpolation_output_size = sample_rate
|
||||||
(coefficients.len() as f64 * spec.resample_factor_reciprocal) as u64;
|
.get_interpolation_output_size()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let delay_line_latency = (coefficients.len() as f64 * resample_factor_reciprocal) as u64;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
interpolator: ConvolutionFilter::new(
|
interpolator: ConvolutionFilter::new(coefficients),
|
||||||
interpolation_quality
|
|
||||||
.get_interpolation_coefficients(coefficients, 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,
|
||||||
delay_line_latency,
|
delay_line_latency,
|
||||||
interpolation_output_size: spec.interpolation_output_size,
|
interpolation_output_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +280,7 @@ pub struct StereoInterleavedResampler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StereoInterleavedResampler {
|
impl StereoInterleavedResampler {
|
||||||
pub fn new(sample_rate: SampleRate, interpolation_quality: InterpolationQuality) -> Self {
|
pub fn new(sample_rate: SampleRate) -> Self {
|
||||||
debug!("Sample Rate: {sample_rate}");
|
debug!("Sample Rate: {sample_rate}");
|
||||||
|
|
||||||
let resampler = match sample_rate {
|
let resampler = match sample_rate {
|
||||||
|
@ -292,7 +291,7 @@ impl StereoInterleavedResampler {
|
||||||
Resampler::Bypass
|
Resampler::Bypass
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug!("Interpolation Quality: {interpolation_quality}");
|
debug!("Interpolation Type: Windowed Sinc");
|
||||||
|
|
||||||
// The player increments the player id when it gets it...
|
// The player increments the player id when it gets it...
|
||||||
let player_id = PLAYER_COUNTER.load(Ordering::SeqCst).saturating_sub(1);
|
let player_id = PLAYER_COUNTER.load(Ordering::SeqCst).saturating_sub(1);
|
||||||
|
@ -300,12 +299,15 @@ impl StereoInterleavedResampler {
|
||||||
let left_thread_name = format!("resampler:{player_id}:left");
|
let left_thread_name = format!("resampler:{player_id}:left");
|
||||||
let right_thread_name = format!("resampler:{player_id}:right");
|
let right_thread_name = format!("resampler:{player_id}:right");
|
||||||
|
|
||||||
let left = MonoSincResampler::new(sample_rate, interpolation_quality);
|
|
||||||
let right = MonoSincResampler::new(sample_rate, interpolation_quality);
|
|
||||||
|
|
||||||
Resampler::Worker {
|
Resampler::Worker {
|
||||||
left_resampler: ResampleWorker::new(left, left_thread_name),
|
left_resampler: ResampleWorker::new(
|
||||||
right_resampler: ResampleWorker::new(right, right_thread_name),
|
MonoSincResampler::new(sample_rate),
|
||||||
|
left_thread_name,
|
||||||
|
),
|
||||||
|
right_resampler: ResampleWorker::new(
|
||||||
|
MonoSincResampler::new(sample_rate),
|
||||||
|
right_thread_name,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,8 +23,7 @@ impl SamplePipeline {
|
||||||
sink: Box<dyn Sink>,
|
sink: Box<dyn Sink>,
|
||||||
volume_getter: Box<dyn VolumeGetter>,
|
volume_getter: Box<dyn VolumeGetter>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let resampler =
|
let resampler = StereoInterleavedResampler::new(config.sample_rate);
|
||||||
StereoInterleavedResampler::new(config.sample_rate, config.interpolation_quality);
|
|
||||||
|
|
||||||
let normaliser = Normaliser::new(config, volume_getter);
|
let normaliser = Normaliser::new(config, volume_getter);
|
||||||
let converter = Converter::new(config.ditherer);
|
let converter = Converter::new(config.ditherer);
|
||||||
|
|
37
src/main.rs
37
src/main.rs
|
@ -24,8 +24,8 @@ use librespot::{
|
||||||
playback::{
|
playback::{
|
||||||
audio_backend::{self, SinkBuilder, BACKENDS},
|
audio_backend::{self, SinkBuilder, BACKENDS},
|
||||||
config::{
|
config::{
|
||||||
AudioFormat, Bitrate, InterpolationQuality, NormalisationMethod, NormalisationType,
|
AudioFormat, Bitrate, NormalisationMethod, NormalisationType, PlayerConfig, SampleRate,
|
||||||
PlayerConfig, SampleRate, VolumeCtrl,
|
VolumeCtrl,
|
||||||
},
|
},
|
||||||
dither,
|
dither,
|
||||||
mixer::{self, MixerConfig, MixerFn},
|
mixer::{self, MixerConfig, MixerFn},
|
||||||
|
@ -240,7 +240,6 @@ fn get_setup() -> Setup {
|
||||||
const VOLUME_RANGE: &str = "volume-range";
|
const VOLUME_RANGE: &str = "volume-range";
|
||||||
const ZEROCONF_PORT: &str = "zeroconf-port";
|
const ZEROCONF_PORT: &str = "zeroconf-port";
|
||||||
const ZEROCONF_INTERFACE: &str = "zeroconf-interface";
|
const ZEROCONF_INTERFACE: &str = "zeroconf-interface";
|
||||||
const INTERPOLATION_QUALITY: &str = "interpolation-quality";
|
|
||||||
const SAMPLE_RATE: &str = "sample-rate";
|
const SAMPLE_RATE: &str = "sample-rate";
|
||||||
|
|
||||||
// Mostly arbitrary.
|
// Mostly arbitrary.
|
||||||
|
@ -579,11 +578,6 @@ fn get_setup() -> Setup {
|
||||||
ZEROCONF_INTERFACE,
|
ZEROCONF_INTERFACE,
|
||||||
"Comma-separated interface IP addresses on which zeroconf will bind. Defaults to all interfaces. Ignored by DNS-SD.",
|
"Comma-separated interface IP addresses on which zeroconf will bind. Defaults to all interfaces. Ignored by DNS-SD.",
|
||||||
"IP"
|
"IP"
|
||||||
).optopt(
|
|
||||||
"",
|
|
||||||
INTERPOLATION_QUALITY,
|
|
||||||
"Interpolation Quality to use if Resampling {Low|High}. Defaults to High.",
|
|
||||||
"QUALITY"
|
|
||||||
).optopt(
|
).optopt(
|
||||||
"",
|
"",
|
||||||
SAMPLE_RATE,
|
SAMPLE_RATE,
|
||||||
|
@ -800,32 +794,6 @@ fn get_setup() -> Setup {
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let interpolation_quality = opt_str(INTERPOLATION_QUALITY)
|
|
||||||
.as_deref()
|
|
||||||
.map(|interpolation_quality| match sample_rate {
|
|
||||||
SampleRate::Hz44100 => {
|
|
||||||
warn!(
|
|
||||||
"--{} has no effect with a sample rate of {sample_rate}.",
|
|
||||||
INTERPOLATION_QUALITY
|
|
||||||
);
|
|
||||||
|
|
||||||
InterpolationQuality::default()
|
|
||||||
}
|
|
||||||
_ => InterpolationQuality::from_str(interpolation_quality).unwrap_or_else(|_| {
|
|
||||||
let default_value = &format!("{}", InterpolationQuality::default());
|
|
||||||
invalid_error_msg(
|
|
||||||
INTERPOLATION_QUALITY,
|
|
||||||
"",
|
|
||||||
interpolation_quality,
|
|
||||||
"Low, High",
|
|
||||||
default_value,
|
|
||||||
);
|
|
||||||
|
|
||||||
exit(1);
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let format = opt_str(FORMAT)
|
let format = opt_str(FORMAT)
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|format| {
|
.map(|format| {
|
||||||
|
@ -1671,7 +1639,6 @@ fn get_setup() -> Setup {
|
||||||
bitrate,
|
bitrate,
|
||||||
gapless,
|
gapless,
|
||||||
passthrough,
|
passthrough,
|
||||||
interpolation_quality,
|
|
||||||
sample_rate,
|
sample_rate,
|
||||||
normalisation,
|
normalisation,
|
||||||
normalisation_type,
|
normalisation_type,
|
||||||
|
|
Loading…
Reference in a new issue