Add InterpolationQuality and SampleRate enums

This commit is contained in:
JasonLG1979 2023-06-21 19:51:14 -05:00
parent 7dae33c4f0
commit 71660a2351
2 changed files with 243 additions and 1 deletions

View file

@ -1,7 +1,248 @@
use std::{mem, str::FromStr, time::Duration}; use std::{mem, str::FromStr, time::Duration};
pub use crate::dither::{mk_ditherer, DithererBuilder, TriangularDitherer}; pub use crate::dither::{mk_ditherer, DithererBuilder, TriangularDitherer};
use crate::{convert::i24, player::duration_to_coefficient}; use crate::{SAMPLE_RATE, RESAMPLER_INPUT_SIZE, convert::i24, player::duration_to_coefficient};
// Reciprocals allow us to multiply instead of divide during interpolation.
const HZ48000_RESAMPLE_FACTOR_RECIPROCAL: f64 = SAMPLE_RATE as f64 / 48_000.0;
const HZ88200_RESAMPLE_FACTOR_RECIPROCAL: f64 = SAMPLE_RATE as f64 / 88_200.0;
const HZ96000_RESAMPLE_FACTOR_RECIPROCAL: f64 = SAMPLE_RATE as f64 / 96_000.0;
// sample rate * channels
const HZ44100_SAMPLES_PER_SECOND: f64 = 44_100.0 * 2.0;
const HZ48000_SAMPLES_PER_SECOND: f64 = 48_000.0 * 2.0;
const HZ88200_SAMPLES_PER_SECOND: f64 = 88_200.0 * 2.0;
const HZ96000_SAMPLES_PER_SECOND: f64 = 96_000.0 * 2.0;
// Given a RESAMPLER_INPUT_SIZE of 147 all of our output sizes work out
// to be integers, which is a very good thing. That means no fractional samples
// which translates to much better interpolation.
const HZ48000_INTERPOLATION_OUTPUT_SIZE: usize =
(RESAMPLER_INPUT_SIZE as f64 * (1.0 / HZ48000_RESAMPLE_FACTOR_RECIPROCAL)) as usize;
const HZ88200_INTERPOLATION_OUTPUT_SIZE: usize =
(RESAMPLER_INPUT_SIZE as f64 * (1.0 / HZ88200_RESAMPLE_FACTOR_RECIPROCAL)) as usize;
const HZ96000_INTERPOLATION_OUTPUT_SIZE: usize =
(RESAMPLER_INPUT_SIZE as f64 * (1.0 / HZ96000_RESAMPLE_FACTOR_RECIPROCAL)) as usize;
// 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 {
#[default]
Low,
Medium,
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),
"medium" => Ok(Medium),
"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"),
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);
let last_index = interpolation_coefficients_length as f64 - 1.0;
let sinc_center = last_index * 0.5;
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();
let sinc_center_offset = index_float - sinc_center;
let sample_index_fractional_sinc_weight = Self::sinc(sample_index_fractional);
let sinc_value = Self::sinc(sinc_center_offset);
// 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_float;
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
},
));
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
} else {
let pi_x = std::f64::consts::PI * x;
pi_x.sin() / pi_x
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum SampleRate {
#[default]
Hz44100,
Hz48000,
Hz88200,
Hz96000,
}
impl FromStr for SampleRate {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
use SampleRate::*;
// Match against both the actual
// stringified value and how most
// humans would write a sample rate.
match s.to_uppercase().as_ref() {
"hz44100" | "44100hz" | "44100" | "44.1khz" => Ok(Hz44100),
"hz48000" | "48000hz" | "48000" | "48khz" => Ok(Hz48000),
"hz88200" | "88200hz" | "88200" | "88.2khz" => Ok(Hz88200),
"hz96000" | "96000hz" | "96000" | "96khz" => Ok(Hz96000),
_ => Err(()),
}
}
}
impl std::fmt::Display for SampleRate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use SampleRate::*;
match self {
// Let's make these more human readable.
// "Hz44100" is just awkward.
Hz44100 => write!(f, "44.1kHz"),
Hz48000 => write!(f, "48kHz"),
Hz88200 => write!(f, "88.2kHz"),
Hz96000 => write!(f, "96kHz"),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ResampleSpec {
resample_factor_reciprocal: f64,
interpolation_output_size: usize,
}
impl SampleRate {
pub fn as_u32(&self) -> u32 {
use SampleRate::*;
match self {
Hz44100 => 44100,
Hz48000 => 48000,
Hz88200 => 88200,
Hz96000 => 96000,
}
}
pub fn duration_to_normalisation_coefficient(&self, duration: Duration) -> f64 {
(-1.0 / (duration.as_secs_f64() * self.samples_per_second())).exp()
}
pub fn normalisation_coefficient_to_duration(&self, coefficient: f64) -> Duration {
Duration::from_secs_f64(-1.0 / coefficient.ln() / self.samples_per_second())
}
fn samples_per_second(&self) -> f64 {
use SampleRate::*;
match self {
Hz44100 => HZ44100_SAMPLES_PER_SECOND,
Hz48000 => HZ48000_SAMPLES_PER_SECOND,
Hz88200 => HZ88200_SAMPLES_PER_SECOND,
Hz96000 => HZ96000_SAMPLES_PER_SECOND,
}
}
pub fn get_resample_spec(&self) -> ResampleSpec {
use SampleRate::*;
match self {
// Dummy values to satisfy
// the match statement.
// 44.1kHz will be bypassed.
Hz44100 => ResampleSpec {
resample_factor_reciprocal: 1.0,
interpolation_output_size: RESAMPLER_INPUT_SIZE,
},
Hz48000 => ResampleSpec {
resample_factor_reciprocal: HZ48000_RESAMPLE_FACTOR_RECIPROCAL,
interpolation_output_size: HZ48000_INTERPOLATION_OUTPUT_SIZE,
},
Hz88200 => ResampleSpec {
resample_factor_reciprocal: HZ88200_RESAMPLE_FACTOR_RECIPROCAL,
interpolation_output_size: HZ88200_INTERPOLATION_OUTPUT_SIZE,
},
Hz96000 => ResampleSpec {
resample_factor_reciprocal: HZ96000_RESAMPLE_FACTOR_RECIPROCAL,
interpolation_output_size: HZ96000_INTERPOLATION_OUTPUT_SIZE,
},
}
}
}
#[derive(Clone, Copy, Debug, Default, Hash, PartialOrd, Ord, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub enum Bitrate { pub enum Bitrate {

View file

@ -13,6 +13,7 @@ pub mod dither;
pub mod mixer; pub mod mixer;
pub mod player; pub mod player;
pub const RESAMPLER_INPUT_SIZE: usize = 147;
pub const SAMPLE_RATE: u32 = 44100; pub const SAMPLE_RATE: u32 = 44100;
pub const NUM_CHANNELS: u8 = 2; pub const NUM_CHANNELS: u8 = 2;
pub const SAMPLES_PER_SECOND: u32 = SAMPLE_RATE * NUM_CHANNELS as u32; pub const SAMPLES_PER_SECOND: u32 = SAMPLE_RATE * NUM_CHANNELS as u32;