Skip to content

Commit c6e97a7

Browse files
authored
Save some more CPU cycles in the limiter (#939)
Optimise limiter CPU usage
1 parent 72af0d2 commit c6e97a7

3 files changed

Lines changed: 64 additions & 58 deletions

File tree

playback/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub struct PlayerConfig {
130130
pub normalisation: bool,
131131
pub normalisation_type: NormalisationType,
132132
pub normalisation_method: NormalisationMethod,
133-
pub normalisation_pregain_db: f32,
133+
pub normalisation_pregain_db: f64,
134134
pub normalisation_threshold_dbfs: f64,
135135
pub normalisation_attack_cf: f64,
136136
pub normalisation_release_cf: f64,

playback/src/player.rs

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -214,21 +214,21 @@ pub fn coefficient_to_duration(coefficient: f64) -> Duration {
214214

215215
#[derive(Clone, Copy, Debug)]
216216
pub struct NormalisationData {
217-
track_gain_db: f32,
218-
track_peak: f32,
219-
album_gain_db: f32,
220-
album_peak: f32,
217+
track_gain_db: f64,
218+
track_peak: f64,
219+
album_gain_db: f64,
220+
album_peak: f64,
221221
}
222222

223223
impl NormalisationData {
224224
fn parse_from_file<T: Read + Seek>(mut file: T) -> io::Result<NormalisationData> {
225225
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
226226
file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?;
227227

228-
let track_gain_db = file.read_f32::<LittleEndian>()?;
229-
let track_peak = file.read_f32::<LittleEndian>()?;
230-
let album_gain_db = file.read_f32::<LittleEndian>()?;
231-
let album_peak = file.read_f32::<LittleEndian>()?;
228+
let track_gain_db = file.read_f32::<LittleEndian>()? as f64;
229+
let track_peak = file.read_f32::<LittleEndian>()? as f64;
230+
let album_gain_db = file.read_f32::<LittleEndian>()? as f64;
231+
let album_peak = file.read_f32::<LittleEndian>()? as f64;
232232

233233
let r = NormalisationData {
234234
track_gain_db,
@@ -246,17 +246,17 @@ impl NormalisationData {
246246
}
247247

248248
let (gain_db, gain_peak) = if config.normalisation_type == NormalisationType::Album {
249-
(data.album_gain_db as f64, data.album_peak as f64)
249+
(data.album_gain_db, data.album_peak)
250250
} else {
251-
(data.track_gain_db as f64, data.track_peak as f64)
251+
(data.track_gain_db, data.track_peak)
252252
};
253253

254-
let normalisation_power = gain_db + config.normalisation_pregain_db as f64;
254+
let normalisation_power = gain_db + config.normalisation_pregain_db;
255255
let mut normalisation_factor = db_to_ratio(normalisation_power);
256256

257257
if normalisation_power + ratio_to_db(gain_peak) > config.normalisation_threshold_dbfs {
258258
let limited_normalisation_factor =
259-
db_to_ratio(config.normalisation_threshold_dbfs as f64) / gain_peak;
259+
db_to_ratio(config.normalisation_threshold_dbfs) / gain_peak;
260260
let limited_normalisation_power = ratio_to_db(limited_normalisation_factor);
261261

262262
if config.normalisation_method == NormalisationMethod::Basic {
@@ -279,7 +279,7 @@ impl NormalisationData {
279279
normalisation_factor * 100.0
280280
);
281281

282-
normalisation_factor as f64
282+
normalisation_factor
283283
}
284284
}
285285

@@ -1305,54 +1305,60 @@ impl PlayerInternal {
13051305
// Engineering Society, 60, 399-408.
13061306
if self.config.normalisation_method == NormalisationMethod::Dynamic
13071307
{
1308-
// steps 1 + 2: half-wave rectification and conversion into dB
1309-
let abs_sample_db = ratio_to_db(sample.abs());
1310-
1311-
// Some tracks have samples that are precisely 0.0, but ratio_to_db(0.0)
1312-
// returns -inf and gets the peak detector stuck.
1313-
if !abs_sample_db.is_normal() {
1314-
continue;
1315-
}
1316-
1317-
// step 3: gain computer with soft knee
1318-
let biased_sample = abs_sample_db - threshold_db;
1319-
let limited_sample = if 2.0 * biased_sample < -knee_db {
1320-
abs_sample_db
1321-
} else if 2.0 * biased_sample.abs() <= knee_db {
1322-
abs_sample_db
1323-
- (biased_sample + knee_db / 2.0).powi(2)
1324-
/ (2.0 * knee_db)
1308+
// Some tracks have samples that are precisely 0.0. That's silence
1309+
// and we know we don't need to limit that, in which we can spare
1310+
// the CPU cycles.
1311+
//
1312+
// Also, calling `ratio_to_db(0.0)` returns `inf` and would get the
1313+
// peak detector stuck. Also catch the unlikely case where a sample
1314+
// is decoded as `NaN` or some other non-normal value.
1315+
let limiter_db = if sample.is_normal() {
1316+
// step 1-2: half-wave rectification and conversion into dB
1317+
let abs_sample_db = ratio_to_db(sample.abs());
1318+
1319+
// step 3-4: gain computer with soft knee and subtractor
1320+
let bias_db = abs_sample_db - threshold_db;
1321+
let knee_boundary_db = bias_db * 2.0;
1322+
1323+
if knee_boundary_db < -knee_db {
1324+
0.0
1325+
} else if knee_boundary_db.abs() <= knee_db {
1326+
abs_sample_db
1327+
- (abs_sample_db
1328+
- (bias_db + knee_db / 2.0).powi(2)
1329+
/ (2.0 * knee_db))
1330+
} else {
1331+
abs_sample_db - threshold_db
1332+
}
13251333
} else {
1326-
threshold_db as f64
1334+
0.0
13271335
};
13281336

1329-
// step 4: subtractor
1330-
let limiter_input = abs_sample_db - limited_sample;
1331-
1332-
// Spare the CPU unless the limiter is active or we are riding a peak.
1333-
if !(limiter_input > 0.0
1337+
// Spare the CPU unless (1) the limiter is engaged, (2) we
1338+
// were in attack or (3) we were in release, and that attack/
1339+
// release wasn't finished yet.
1340+
if limiter_db > 0.0
13341341
|| self.normalisation_integrator > 0.0
1335-
|| self.normalisation_peak > 0.0)
1342+
|| self.normalisation_peak > 0.0
13361343
{
1337-
continue;
1344+
// step 5: smooth, decoupled peak detector
1345+
self.normalisation_integrator = f64::max(
1346+
limiter_db,
1347+
release_cf * self.normalisation_integrator
1348+
+ (1.0 - release_cf) * limiter_db,
1349+
);
1350+
self.normalisation_peak = attack_cf
1351+
* self.normalisation_peak
1352+
+ (1.0 - attack_cf) * self.normalisation_integrator;
1353+
1354+
// step 6: make-up gain applied later (volume attenuation)
1355+
// Applying the standard normalisation factor here won't work,
1356+
// because there are tracks with peaks as high as 6 dB above
1357+
// the default threshold, so that would clip.
1358+
1359+
// steps 7-8: conversion into level and multiplication into gain stage
1360+
*sample *= db_to_ratio(-self.normalisation_peak);
13381361
}
1339-
1340-
// step 5: smooth, decoupled peak detector
1341-
self.normalisation_integrator = f64::max(
1342-
limiter_input,
1343-
release_cf * self.normalisation_integrator
1344-
+ (1.0 - release_cf) * limiter_input,
1345-
);
1346-
self.normalisation_peak = attack_cf * self.normalisation_peak
1347-
+ (1.0 - attack_cf) * self.normalisation_integrator;
1348-
1349-
// step 6: make-up gain applied later (volume attenuation)
1350-
// Applying the standard normalisation factor here won't work,
1351-
// because there are tracks with peaks as high as 6 dB above
1352-
// the default threshold, so that would clip.
1353-
1354-
// steps 7-8: conversion into level and multiplication into gain stage
1355-
*sample *= db_to_ratio(-self.normalisation_peak);
13561362
}
13571363
}
13581364
}

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ fn get_setup() -> Setup {
187187
const VALID_INITIAL_VOLUME_RANGE: RangeInclusive<u16> = 0..=100;
188188
const VALID_VOLUME_RANGE: RangeInclusive<f64> = 0.0..=100.0;
189189
const VALID_NORMALISATION_KNEE_RANGE: RangeInclusive<f64> = 0.0..=10.0;
190-
const VALID_NORMALISATION_PREGAIN_RANGE: RangeInclusive<f32> = -10.0..=10.0;
190+
const VALID_NORMALISATION_PREGAIN_RANGE: RangeInclusive<f64> = -10.0..=10.0;
191191
const VALID_NORMALISATION_THRESHOLD_RANGE: RangeInclusive<f64> = -10.0..=0.0;
192192
const VALID_NORMALISATION_ATTACK_RANGE: RangeInclusive<u64> = 1..=500;
193193
const VALID_NORMALISATION_RELEASE_RANGE: RangeInclusive<u64> = 1..=1000;
@@ -1339,7 +1339,7 @@ fn get_setup() -> Setup {
13391339
.unwrap_or(player_default_config.normalisation_type);
13401340

13411341
normalisation_pregain_db = opt_str(NORMALISATION_PREGAIN)
1342-
.map(|pregain| match pregain.parse::<f32>() {
1342+
.map(|pregain| match pregain.parse::<f64>() {
13431343
Ok(value) if (VALID_NORMALISATION_PREGAIN_RANGE).contains(&value) => value,
13441344
_ => {
13451345
let valid_values = &format!(

0 commit comments

Comments
 (0)