Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
use crate::dither::{Ditherer, DithererBuilder};
|
2021-04-05 19:30:40 +00:00
|
|
|
use zerocopy::AsBytes;
|
|
|
|
|
|
|
|
#[derive(AsBytes, Copy, Clone, Debug)]
|
|
|
|
#[allow(non_camel_case_types)]
|
|
|
|
#[repr(transparent)]
|
|
|
|
pub struct i24([u8; 3]);
|
|
|
|
impl i24 {
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
fn from_s24(sample: i32) -> Self {
|
|
|
|
// trim the padding in the most significant byte
|
|
|
|
let [a, b, c, _d] = sample.to_le_bytes();
|
2021-04-05 19:30:40 +00:00
|
|
|
i24([a, b, c])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
pub struct Converter {
|
|
|
|
ditherer: Option<Box<dyn Ditherer>>,
|
2021-04-05 19:30:40 +00:00
|
|
|
}
|
|
|
|
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
impl Converter {
|
|
|
|
pub fn new(dither_config: Option<DithererBuilder>) -> Self {
|
|
|
|
if let Some(ref ditherer_builder) = dither_config {
|
|
|
|
let ditherer = (ditherer_builder)();
|
|
|
|
info!("Converting with ditherer: {}", ditherer.name());
|
|
|
|
Self {
|
|
|
|
ditherer: Some(ditherer),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Self { ditherer: None }
|
|
|
|
}
|
|
|
|
}
|
2021-04-05 19:30:40 +00:00
|
|
|
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
// Denormalize and dither
|
|
|
|
pub fn scale(&mut self, sample: f32, factor: i64) -> f32 {
|
|
|
|
// From the many float to int conversion methods available, match what
|
|
|
|
// the reference Vorbis implementation uses: sample * 32768 (for 16 bit)
|
|
|
|
let int_value = sample * factor as f32;
|
2021-04-05 19:30:40 +00:00
|
|
|
|
2021-05-27 21:44:45 +00:00
|
|
|
// https://doc.rust-lang.org/nomicon/casts.html: casting float to integer
|
|
|
|
// rounds towards zero, then saturates. Ideally halves should round to even to
|
|
|
|
// prevent any bias, but since it is extremely unlikely that a float has
|
|
|
|
// *exactly* .5 as fraction, this should be more than precise enough.
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
match self.ditherer {
|
|
|
|
Some(ref mut d) => int_value + d.noise(int_value),
|
|
|
|
None => int_value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special case for samples packed in a word of greater bit depth (e.g.
|
|
|
|
// S24): clamp between min and max to ensure that the most significant
|
|
|
|
// byte is zero. Otherwise, dithering may cause an overflow. This is not
|
|
|
|
// necessary for other formats, because casting to integer will saturate
|
|
|
|
// to the bounds of the primitive.
|
|
|
|
pub fn clamping_scale(&mut self, sample: f32, factor: i64) -> f32 {
|
|
|
|
let int_value = self.scale(sample, factor);
|
|
|
|
|
|
|
|
// In two's complement, there are more negative than positive values.
|
|
|
|
let min = -factor as f32;
|
|
|
|
let max = (factor - 1) as f32;
|
|
|
|
|
|
|
|
if int_value < min {
|
|
|
|
return min;
|
|
|
|
} else if int_value > max {
|
|
|
|
return max;
|
|
|
|
}
|
|
|
|
int_value
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn f32_to_s32(&mut self, samples: &[f32]) -> Vec<i32> {
|
|
|
|
samples
|
|
|
|
.iter()
|
|
|
|
.map(|sample| self.scale(*sample, 0x80000000) as i32)
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
// S24 is 24-bit PCM packed in an upper 32-bit word
|
|
|
|
pub fn f32_to_s24(&mut self, samples: &[f32]) -> Vec<i32> {
|
|
|
|
samples
|
|
|
|
.iter()
|
|
|
|
.map(|sample| self.clamping_scale(*sample, 0x800000) as i32)
|
|
|
|
.collect()
|
|
|
|
}
|
2021-04-05 19:30:40 +00:00
|
|
|
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
// S24_3 is 24-bit PCM in a 3-byte array
|
|
|
|
pub fn f32_to_s24_3(&mut self, samples: &[f32]) -> Vec<i24> {
|
|
|
|
samples
|
|
|
|
.iter()
|
|
|
|
.map(|sample| {
|
|
|
|
// Not as DRY as calling f32_to_s24 first, but this saves iterating
|
|
|
|
// over all samples twice.
|
|
|
|
let int_value = self.clamping_scale(*sample, 0x800000) as i32;
|
|
|
|
i24::from_s24(int_value)
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn f32_to_s16(&mut self, samples: &[f32]) -> Vec<i16> {
|
|
|
|
samples
|
|
|
|
.iter()
|
|
|
|
.map(|sample| self.scale(*sample, 0x8000) as i16)
|
|
|
|
.collect()
|
|
|
|
}
|
2021-04-05 19:30:40 +00:00
|
|
|
}
|