More touch ups

This commit is contained in:
JasonLG1979 2023-09-03 17:32:55 -05:00
parent a331729f0e
commit 8aaab0a210
5 changed files with 1498 additions and 1464 deletions

View file

@ -8,10 +8,14 @@ use crate::{
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;
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;
const HZ48000_RESAMPLE_FACTOR: f64 = 48_000.0 / (SAMPLE_RATE as f64);
const HZ88200_RESAMPLE_FACTOR: f64 = 88_200.0 / (SAMPLE_RATE as f64);
const HZ96000_RESAMPLE_FACTOR: f64 = 96_000.0 / (SAMPLE_RATE as f64);
// Reciprocals allow us to multiply instead of divide during normal interpolation.
const HZ48000_RESAMPLE_FACTOR_RECIPROCAL: f64 = 1.0 / HZ48000_RESAMPLE_FACTOR;
const HZ88200_RESAMPLE_FACTOR_RECIPROCAL: f64 = 1.0 / HZ88200_RESAMPLE_FACTOR;
const HZ96000_RESAMPLE_FACTOR_RECIPROCAL: f64 = 1.0 / HZ96000_RESAMPLE_FACTOR;
// sample rate * channels
const HZ44100_SAMPLES_PER_SECOND: f64 = 44_100.0 * 2.0;
@ -23,13 +27,13 @@ const HZ96000_SAMPLES_PER_SECOND: f64 = 96_000.0 * 2.0;
// 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;
(RESAMPLER_INPUT_SIZE as f64 * HZ48000_RESAMPLE_FACTOR) as usize;
const HZ88200_INTERPOLATION_OUTPUT_SIZE: usize =
(RESAMPLER_INPUT_SIZE as f64 * (1.0 / HZ88200_RESAMPLE_FACTOR_RECIPROCAL)) as usize;
(RESAMPLER_INPUT_SIZE as f64 * HZ88200_RESAMPLE_FACTOR) as 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 * HZ96000_RESAMPLE_FACTOR) as usize;
#[derive(Clone, Copy, Debug, Default)]
pub enum SampleRate {
@ -134,6 +138,17 @@ impl SampleRate {
}
}
pub fn get_resample_factor(&self) -> Option<f64> {
use SampleRate::*;
match self {
Hz44100 => None,
Hz48000 => Some(HZ48000_RESAMPLE_FACTOR),
Hz88200 => Some(HZ88200_RESAMPLE_FACTOR),
Hz96000 => Some(HZ96000_RESAMPLE_FACTOR),
}
}
pub fn get_resample_factor_reciprocal(&self) -> Option<f64> {
use SampleRate::*;

File diff suppressed because it is too large Load diff

View file

@ -7,32 +7,6 @@ use crate::{
ratio_to_db, PCM_AT_0DBFS,
};
struct NoNormalisation;
impl NoNormalisation {
fn normalise(mut samples: Vec<f64>, volume: f64) -> Vec<f64> {
if volume < 1.0 {
samples.iter_mut().for_each(|sample| *sample *= volume);
}
samples
}
}
struct BasicNormalisation;
impl BasicNormalisation {
fn normalise(mut samples: Vec<f64>, volume: f64, factor: f64) -> Vec<f64> {
if volume < 1.0 || factor < 1.0 {
samples
.iter_mut()
.for_each(|sample| *sample *= factor * volume);
}
samples
}
}
#[derive(PartialEq)]
struct DynamicNormalisation {
threshold_db: f64,
@ -194,12 +168,35 @@ impl Normalisation {
}
}
fn normalise(&mut self, samples: Vec<f64>, volume: f64, factor: f64) -> Vec<f64> {
fn normalise(&mut self, mut samples: Vec<f64>, volume: f64, factor: f64) -> Vec<f64> {
use Normalisation::*;
match self {
None => NoNormalisation::normalise(samples, volume),
Basic => BasicNormalisation::normalise(samples, volume, factor),
None => {
// We only care about volume.
// We don't care about factor.
// volume: 0.0 - 1.0
if volume < 1.0 {
// for each sample: sample = sample * volume
samples.iter_mut().for_each(|sample| *sample *= volume);
}
samples
}
Basic => {
// We care about both volume and factor.
// volume: 0.0 - 1.0
// factor: 0.0 - 1.0
if volume < 1.0 || factor < 1.0 {
// for each sample: sample = sample * volume * factor
samples
.iter_mut()
.for_each(|sample| *sample *= volume * factor);
}
samples
}
// We don't care about anything, DynamicNormalisation does that for us.
Dynamic(ref mut d) => d.normalise(samples, volume, factor),
}
}
@ -222,7 +219,7 @@ impl Normaliser {
normalisation_type: config.normalisation_type,
pregain_db: config.normalisation_pregain_db,
threshold_dbfs: config.normalisation_threshold_dbfs,
factor: 1.0,
factor: 0.0,
}
}
@ -233,6 +230,7 @@ impl Normaliser {
}
pub fn stop(&mut self) {
self.factor = 0.0;
self.normalisation.stop();
}
@ -241,6 +239,8 @@ impl Normaliser {
auto_normalise_as_album: bool,
data: NormalisationData,
) {
// Normalisation::None doesn't use the factor,
// so there is no need to waste the time calculating it.
if self.normalisation != Normalisation::None {
self.factor = self.get_factor(auto_normalise_as_album, data);
}
@ -326,6 +326,7 @@ impl Normaliser {
};
debug!("Normalisation Data: {:?}", data);
debug!("Normalisation Type: {:?}", self.normalisation_type);
debug!(
"Calculated Normalisation Factor for {:?}: {:.2}%",
norm_type,

View file

@ -1,76 +1,60 @@
use std::{
collections::{vec_deque, VecDeque},
process::exit,
sync::atomic::Ordering,
sync::mpsc,
thread,
collections::VecDeque, process::exit, sync::atomic::Ordering::SeqCst, sync::mpsc, thread,
};
use crate::{
config::SampleRate, player::PLAYER_COUNTER, RESAMPLER_INPUT_SIZE,
SAMPLE_RATE as SOURCE_SAMPLE_RATE,
};
struct DelayLine {
buffer: VecDeque<f64>,
coefficients_length: usize,
}
impl DelayLine {
fn new(coefficients_length: usize) -> DelayLine {
Self {
buffer: VecDeque::with_capacity(coefficients_length),
coefficients_length,
}
}
fn push(&mut self, sample: f64) {
self.buffer.push_back(sample);
while self.buffer.len() > self.coefficients_length {
self.buffer.pop_front();
}
}
fn clear(&mut self) {
self.buffer.clear();
}
}
impl<'a> IntoIterator for &'a DelayLine {
type Item = &'a f64;
type IntoIter = vec_deque::Iter<'a, f64>;
fn into_iter(self) -> Self::IntoIter {
self.buffer.iter()
}
}
use crate::{config::SampleRate, player::PLAYER_COUNTER, RESAMPLER_INPUT_SIZE};
struct ConvolutionFilter {
coefficients: Vec<f64>,
delay_line: DelayLine,
coefficients_length: usize,
delay_line: VecDeque<f64>,
}
impl ConvolutionFilter {
fn new(coefficients: Vec<f64>) -> Self {
let delay_line = DelayLine::new(coefficients.len());
let coefficients_length = coefficients.len();
let delay_line = VecDeque::with_capacity(coefficients_length);
Self {
coefficients,
coefficients_length,
delay_line,
}
}
fn convolute(&mut self, sample: f64) -> f64 {
self.delay_line.push(sample);
// Temporal convolution
self.coefficients
fn get_convoluted_sample(&mut self) -> f64 {
let output_sample = self
.coefficients
.iter()
.zip(&self.delay_line)
.fold(0.0, |acc, (coefficient, delay_line_sample)| {
acc + coefficient * delay_line_sample
})
});
self.delay_line.pop_front();
output_sample
}
fn convolute(&mut self, sample: f64) -> f64 {
self.delay_line.push_back(sample);
if self.delay_line.len() == self.coefficients_length {
self.get_convoluted_sample()
} else {
0.0
}
}
fn drain(&mut self) -> Vec<f64> {
let delay_line_len = self.delay_line.len();
let mut output = Vec::with_capacity(delay_line_len);
for _ in 0..delay_line_len {
output.push(self.get_convoluted_sample());
}
output
}
fn clear(&mut self) {
@ -81,6 +65,7 @@ impl ConvolutionFilter {
struct MonoSincResampler {
interpolator: ConvolutionFilter,
input_buffer: Vec<f64>,
resample_factor: f64,
resample_factor_reciprocal: f64,
delay_line_latency: u64,
interpolation_output_size: usize,
@ -92,6 +77,8 @@ impl MonoSincResampler {
.get_interpolation_coefficients()
.unwrap_or_default();
let resample_factor = sample_rate.get_resample_factor().unwrap_or_default();
let resample_factor_reciprocal = sample_rate
.get_resample_factor_reciprocal()
.unwrap_or_default();
@ -104,7 +91,8 @@ impl MonoSincResampler {
Self {
interpolator: ConvolutionFilter::new(coefficients),
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE as usize),
input_buffer: Vec::with_capacity(RESAMPLER_INPUT_SIZE),
resample_factor,
resample_factor_reciprocal,
delay_line_latency,
interpolation_output_size,
@ -120,40 +108,76 @@ impl MonoSincResampler {
self.input_buffer.clear();
}
fn resample(&mut self, samples: &[f64]) -> Option<Vec<f64>> {
fn drain(&mut self) -> (Option<Vec<f64>>, u64) {
// On drain the interpolation isn't perfect for a couple reasons:
// 1. buffer len * resample_factor more than likely isn't an integer.
// 2. As you drain the delay line there are less and less samples to use for interpolation.
let output_len = (self.input_buffer.len() as f64 * self.resample_factor) as usize;
let mut output = Vec::with_capacity(output_len);
output.extend((0..output_len).map(|ouput_index| {
self.interpolator.convolute(
*self
.input_buffer
.get((ouput_index as f64 * self.resample_factor_reciprocal) as usize)
.unwrap_or(&0.0),
)
}));
let interpolator_drainage = self.interpolator.drain();
output.reserve_exact(interpolator_drainage.len());
output.extend(interpolator_drainage.iter());
let output_len = output.len() as f64;
// Do a simple linear fade out of the drainage (about 5ms) to hide/prevent audible artifacts.
for (index, sample) in output.iter_mut().enumerate() {
let fade_factor = 1.0 - (index as f64) / output_len;
*sample *= fade_factor;
}
(Some(output), 0)
}
fn resample(&mut self, samples: &[f64]) -> (Option<Vec<f64>>, u64) {
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;
return (None, self.get_latency_pcm());
}
let input_size = num_buffer_chunks * RESAMPLER_INPUT_SIZE;
// The size of the output after interpolation.
let output_size = num_buffer_chunks * self.interpolation_output_size;
let mut output = Vec::with_capacity(output_size);
output.extend((0..output_size).map(|ouput_index| {
// The factional weights are already calculated and factored
// into our interpolation coefficients so all we have to
// do is pretend we're doing nearest-neighbor interpolation
// and push samples though the Interpolator and what comes
// out the other side is Sinc Windowed Interpolated samples.
let sample_index = (ouput_index as f64 * self.resample_factor_reciprocal) as usize;
let sample = self.input_buffer[sample_index];
self.interpolator.convolute(sample)
// Since the interpolation coefficients are pre-calculated we can pretend like
// we're doing nearest neighbor interpolation and then push the samples though
// the interpolator as if it were a simple FIR filter (which it actually also is).
// What comes out the other side is anti-aliased windowed sinc interpolated samples.
self.interpolator.convolute(
*self
.input_buffer
.get((ouput_index as f64 * self.resample_factor_reciprocal) as usize)
.unwrap_or(&0.0),
)
}));
self.input_buffer.drain(..input_size);
Some(output)
(Some(output), self.get_latency_pcm())
}
}
enum ResampleTask {
Stop,
Drain,
Terminate,
Resample(Vec<f64>),
}
@ -165,12 +189,14 @@ struct ResampleWorker {
}
impl ResampleWorker {
fn new(mut resampler: MonoSincResampler, name: String) -> Self {
fn new(sample_rate: SampleRate, name: String) -> Self {
let (task_sender, task_receiver) = mpsc::channel();
let (result_sender, result_receiver) = mpsc::channel();
let builder = thread::Builder::new().name(name.clone());
let mut resampler = MonoSincResampler::new(sample_rate);
let handle = match builder.spawn(move || loop {
match task_receiver.recv() {
Err(e) => {
@ -183,11 +209,11 @@ impl ResampleWorker {
}
Ok(task) => match task {
ResampleTask::Stop => resampler.stop(),
ResampleTask::Drain => {
result_sender.send(resampler.drain()).ok();
}
ResampleTask::Resample(samples) => {
let resampled = resampler.resample(&samples);
let latency = resampler.get_latency_pcm();
result_sender.send((resampled, latency)).ok();
result_sender.send(resampler.resample(&samples)).ok();
}
ResampleTask::Terminate => {
loop {
@ -231,6 +257,12 @@ impl ResampleWorker {
.and_then(|sender| sender.send(ResampleTask::Stop).ok());
}
fn drain(&mut self) {
self.task_sender
.as_mut()
.and_then(|sender| sender.send(ResampleTask::Drain).ok());
}
fn resample(&mut self, samples: Vec<f64>) {
self.task_sender
.as_mut()
@ -294,16 +326,16 @@ impl StereoInterleavedResampler {
debug!("Interpolation Type: Windowed Sinc");
// 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(SeqCst).saturating_sub(1);
Resampler::Worker {
left_resampler: ResampleWorker::new(
MonoSincResampler::new(sample_rate),
format!("resampler:{player_id}:left"),
sample_rate,
format!("resampler:L:{player_id}"),
),
right_resampler: ResampleWorker::new(
MonoSincResampler::new(sample_rate),
format!("resampler:{player_id}:right"),
sample_rate,
format!("resampler:R:{player_id}"),
),
}
}
@ -319,6 +351,26 @@ impl StereoInterleavedResampler {
self.latency_pcm
}
pub fn drain(&mut self) -> Option<Vec<f64>> {
match &mut self.resampler {
// Bypass is basically a no-op.
Resampler::Bypass => None,
Resampler::Worker {
left_resampler,
right_resampler,
} => {
left_resampler.drain();
right_resampler.drain();
let (resampled, latency_pcm) = Self::get_resampled(left_resampler, right_resampler);
self.latency_pcm = latency_pcm;
resampled
}
}
}
pub fn resample(&mut self, input_samples: Vec<f64>) -> Option<Vec<f64>> {
match &mut self.resampler {
// Bypass is basically a no-op.
@ -332,17 +384,11 @@ impl StereoInterleavedResampler {
left_resampler.resample(left_samples);
right_resampler.resample(right_samples);
let (left_resampled, left_latency_pcm) = left_resampler.get_resampled();
let (right_resampled, right_latency_pcm) = right_resampler.get_resampled();
let (resampled, latency_pcm) = Self::get_resampled(left_resampler, right_resampler);
// They should always be equal
self.latency_pcm = left_latency_pcm.max(right_latency_pcm);
self.latency_pcm = latency_pcm;
left_resampled.and_then(|left_samples| {
right_resampled.map(|right_samples| {
Self::interleave_samples(&left_samples, &right_samples)
})
})
resampled
}
}
}
@ -364,6 +410,24 @@ impl StereoInterleavedResampler {
}
}
fn get_resampled(
left_resampler: &mut ResampleWorker,
right_resampler: &mut ResampleWorker,
) -> (Option<Vec<f64>>, u64) {
let (left_resampled, left_latency_pcm) = left_resampler.get_resampled();
let (right_resampled, right_latency_pcm) = right_resampler.get_resampled();
let resampled = left_resampled.and_then(|left_samples| {
right_resampled
.map(|right_samples| Self::interleave_samples(&left_samples, &right_samples))
});
// They should always be equal
let latency_pcm = left_latency_pcm.max(right_latency_pcm);
(resampled, latency_pcm)
}
fn interleave_samples(left_samples: &[f64], right_samples: &[f64]) -> Vec<f64> {
// Re-interleave the resampled channels.
let mut output = Vec::with_capacity(left_samples.len() + right_samples.len());

View file

@ -10,15 +10,120 @@ use crate::{
MS_PER_PAGE,
};
pub struct SamplePipeline {
pub enum SamplePipeline {
PassThrough(Bypass),
Process(Pipeline),
}
impl SamplePipeline {
pub fn new(
config: &PlayerConfig,
sink: Box<dyn Sink>,
volume_getter: Box<dyn VolumeGetter>,
) -> Self {
if config.passthrough {
SamplePipeline::PassThrough(Bypass::new(config, sink))
} else {
SamplePipeline::Process(Pipeline::new(config, sink, volume_getter))
}
}
pub fn get_latency_ms(&mut self) -> u32 {
use SamplePipeline::*;
match self {
PassThrough(_) => 0,
Process(ref mut p) => p.get_latency_ms(),
}
}
pub fn start(&mut self) -> SinkResult<()> {
use SamplePipeline::*;
match self {
PassThrough(ref mut p) => p.start()?,
Process(ref mut p) => p.start()?,
}
Ok(())
}
pub fn stop(&mut self) -> SinkResult<()> {
use SamplePipeline::*;
match self {
PassThrough(ref mut p) => p.stop()?,
Process(ref mut p) => p.stop()?,
}
Ok(())
}
pub fn update_normalisation_data(
&mut self,
auto_normalise_as_album: bool,
data: NormalisationData,
) {
use SamplePipeline::*;
match self {
PassThrough(_) => (),
Process(ref mut p) => p.update_normalisation_data(auto_normalise_as_album, data),
}
}
pub fn write(&mut self, packet: AudioPacket) -> SinkResult<()> {
use SamplePipeline::*;
match self {
PassThrough(ref mut p) => p.write(packet)?,
Process(ref mut p) => p.write(packet)?,
}
Ok(())
}
}
pub struct Bypass {
converter: Converter,
sink: Box<dyn Sink>,
}
impl Bypass {
fn new(config: &PlayerConfig, sink: Box<dyn Sink>) -> Self {
let converter = Converter::new(config.ditherer);
Self { converter, sink }
}
fn start(&mut self) -> SinkResult<()> {
self.sink.start()?;
Ok(())
}
fn stop(&mut self) -> SinkResult<()> {
self.sink.stop()?;
Ok(())
}
fn write(&mut self, packet: AudioPacket) -> SinkResult<()> {
self.sink.write(packet, &mut self.converter)?;
Ok(())
}
}
pub struct Pipeline {
resampler: StereoInterleavedResampler,
normaliser: Normaliser,
converter: Converter,
sink: Box<dyn Sink>,
}
impl SamplePipeline {
pub fn new(
impl Pipeline {
fn new(
config: &PlayerConfig,
sink: Box<dyn Sink>,
volume_getter: Box<dyn VolumeGetter>,
@ -36,27 +141,34 @@ impl SamplePipeline {
}
}
pub fn get_latency_ms(&mut self) -> u32 {
fn get_latency_ms(&mut self) -> u32 {
let total_latency_pcm = self.sink.get_latency_pcm() + self.resampler.get_latency_pcm();
(total_latency_pcm as f64 * MS_PER_PAGE) as u32
}
pub fn start(&mut self) -> SinkResult<()> {
fn start(&mut self) -> SinkResult<()> {
self.sink.start()?;
Ok(())
}
pub fn stop(&mut self) -> SinkResult<()> {
fn stop(&mut self) -> SinkResult<()> {
self.resampler
.drain()
.map(|processed_samples| self.normaliser.normalise(processed_samples))
.map(|new_packet| self.sink.write(new_packet, &mut self.converter))
.transpose()?;
self.resampler.stop();
self.normaliser.stop();
self.sink.stop()?;
Ok(())
}
pub fn update_normalisation_data(
fn update_normalisation_data(
&mut self,
auto_normalise_as_album: bool,
data: NormalisationData,
@ -65,15 +177,13 @@ impl SamplePipeline {
.update_normalisation_data(auto_normalise_as_album, data);
}
pub fn write(&mut self, packet: AudioPacket) -> SinkResult<()> {
fn write(&mut self, packet: AudioPacket) -> SinkResult<()> {
if let AudioPacket::Samples(samples) = packet {
self.resampler
.resample(samples)
.map(|processed_samples| self.normaliser.normalise(processed_samples))
.map(|new_packet| self.sink.write(new_packet, &mut self.converter))
.transpose()?;
} else {
self.sink.write(packet, &mut self.converter)?;
}
Ok(())