@@ -25,7 +25,7 @@ use crate::core::spotify_id::SpotifyId;
2525use crate :: core:: util:: SeqGenerator ;
2626use crate :: decoder:: { AudioDecoder , AudioPacket , DecoderError , PassthroughDecoder , VorbisDecoder } ;
2727use crate :: metadata:: { AudioItem , FileFormat } ;
28- use crate :: mixer:: AudioFilter ;
28+ use crate :: mixer:: VolumeGetter ;
2929
3030use crate :: { MS_PER_PAGE , NUM_CHANNELS , PAGES_PER_MS , SAMPLES_PER_SECOND } ;
3131
@@ -58,7 +58,7 @@ struct PlayerInternal {
5858 sink : Box < dyn Sink > ,
5959 sink_status : SinkStatus ,
6060 sink_event_callback : Option < SinkEventCallback > ,
61- audio_filter : Option < Box < dyn AudioFilter + Send > > ,
61+ volume_getter : Box < dyn VolumeGetter + Send > ,
6262 event_senders : Vec < mpsc:: UnboundedSender < PlayerEvent > > ,
6363 converter : Converter ,
6464
@@ -319,7 +319,7 @@ impl Player {
319319 pub fn new < F > (
320320 config : PlayerConfig ,
321321 session : Session ,
322- audio_filter : Option < Box < dyn AudioFilter + Send > > ,
322+ volume_getter : Box < dyn VolumeGetter + Send > ,
323323 sink_builder : F ,
324324 ) -> ( Player , PlayerEventChannel )
325325 where
@@ -369,7 +369,7 @@ impl Player {
369369 sink : sink_builder ( ) ,
370370 sink_status : SinkStatus :: Closed ,
371371 sink_event_callback : None ,
372- audio_filter ,
372+ volume_getter ,
373373 event_senders : [ event_sender] . to_vec ( ) ,
374374 converter,
375375
@@ -1314,109 +1314,110 @@ impl PlayerInternal {
13141314 Some ( mut packet) => {
13151315 if !packet. is_empty ( ) {
13161316 if let AudioPacket :: Samples ( ref mut data) = packet {
1317+ // Get the volume for the packet.
1318+ // In the case of hardware volume control this will
1319+ // always be 1.0 (no change).
1320+ let volume = self . volume_getter . attenuation_factor ( ) ;
1321+
13171322 // For the basic normalisation method, a normalisation factor of 1.0 indicates that
13181323 // there is nothing to normalise (all samples should pass unaltered). For the
13191324 // dynamic method, there may still be peaks that we want to shave off.
1320- if self . config . normalisation {
1321- if self . config . normalisation_method == NormalisationMethod :: Basic
1322- && normalisation_factor < 1.0
1323- {
1324- for sample in data. iter_mut ( ) {
1325- * sample *= normalisation_factor;
1326- }
1327- } else if self . config . normalisation_method
1328- == NormalisationMethod :: Dynamic
1329- {
1330- // zero-cost shorthands
1331- let threshold_db = self . config . normalisation_threshold_dbfs ;
1332- let knee_db = self . config . normalisation_knee_db ;
1333- let attack_cf = self . config . normalisation_attack_cf ;
1334- let release_cf = self . config . normalisation_release_cf ;
1335-
1336- for sample in data. iter_mut ( ) {
1337- * sample *= normalisation_factor;
1338-
1339- // Feedforward limiter in the log domain
1340- // After: Giannoulis, D., Massberg, M., & Reiss, J.D. (2012). Digital Dynamic
1341- // Range Compressor Design—A Tutorial and Analysis. Journal of The Audio
1342- // Engineering Society, 60, 399-408.
1343-
1344- // Some tracks have samples that are precisely 0.0. That's silence
1345- // and we know we don't need to limit that, in which we can spare
1346- // the CPU cycles.
1347- //
1348- // Also, calling `ratio_to_db(0.0)` returns `inf` and would get the
1349- // peak detector stuck. Also catch the unlikely case where a sample
1350- // is decoded as `NaN` or some other non-normal value.
1351- let limiter_db = if sample. is_normal ( ) {
1352- // step 1-4: half-wave rectification and conversion into dB
1353- // and gain computer with soft knee and subtractor
1354- let bias_db = ratio_to_db ( sample. abs ( ) ) - threshold_db;
1355- let knee_boundary_db = bias_db * 2.0 ;
1356-
1357- if knee_boundary_db < -knee_db {
1358- 0.0
1359- } else if knee_boundary_db. abs ( ) <= knee_db {
1360- // The textbook equation:
1361- // ratio_to_db(sample.abs()) - (ratio_to_db(sample.abs()) - (bias_db + knee_db / 2.0).powi(2) / (2.0 * knee_db))
1362- // Simplifies to:
1363- // ((2.0 * bias_db) + knee_db).powi(2) / (8.0 * knee_db)
1364- // Which in our case further simplifies to:
1365- // (knee_boundary_db + knee_db).powi(2) / (8.0 * knee_db)
1366- // because knee_boundary_db is 2.0 * bias_db.
1367- ( knee_boundary_db + knee_db) . powi ( 2 ) / ( 8.0 * knee_db)
1368- } else {
1369- // Textbook:
1370- // ratio_to_db(sample.abs()) - threshold_db, which is already our bias_db.
1371- bias_db
1372- }
1373- } else {
1325+ // No matter the case we apply volume attenuation last if there is any.
1326+ if !self . config . normalisation && volume < 1.0 {
1327+ for sample in data. iter_mut ( ) {
1328+ * sample *= volume;
1329+ }
1330+ } else if self . config . normalisation_method == NormalisationMethod :: Basic
1331+ && ( normalisation_factor < 1.0 || volume < 1.0 )
1332+ {
1333+ for sample in data. iter_mut ( ) {
1334+ * sample *= normalisation_factor * volume;
1335+ }
1336+ } else if self . config . normalisation_method == NormalisationMethod :: Dynamic {
1337+ // zero-cost shorthands
1338+ let threshold_db = self . config . normalisation_threshold_dbfs ;
1339+ let knee_db = self . config . normalisation_knee_db ;
1340+ let attack_cf = self . config . normalisation_attack_cf ;
1341+ let release_cf = self . config . normalisation_release_cf ;
1342+
1343+ for sample in data. iter_mut ( ) {
1344+ * sample *= normalisation_factor;
1345+
1346+ // Feedforward limiter in the log domain
1347+ // After: Giannoulis, D., Massberg, M., & Reiss, J.D. (2012). Digital Dynamic
1348+ // Range Compressor Design—A Tutorial and Analysis. Journal of The Audio
1349+ // Engineering Society, 60, 399-408.
1350+
1351+ // Some tracks have samples that are precisely 0.0. That's silence
1352+ // and we know we don't need to limit that, in which we can spare
1353+ // the CPU cycles.
1354+ //
1355+ // Also, calling `ratio_to_db(0.0)` returns `inf` and would get the
1356+ // peak detector stuck. Also catch the unlikely case where a sample
1357+ // is decoded as `NaN` or some other non-normal value.
1358+ let limiter_db = if sample. is_normal ( ) {
1359+ // step 1-4: half-wave rectification and conversion into dB
1360+ // and gain computer with soft knee and subtractor
1361+ let bias_db = ratio_to_db ( sample. abs ( ) ) - threshold_db;
1362+ let knee_boundary_db = bias_db * 2.0 ;
1363+
1364+ if knee_boundary_db < -knee_db {
13741365 0.0
1375- } ;
1376-
1377- // Spare the CPU unless (1) the limiter is engaged, (2) we
1378- // were in attack or (3) we were in release, and that attack/
1379- // release wasn't finished yet.
1380- if limiter_db > 0.0
1381- || self . normalisation_integrator > 0.0
1382- || self . normalisation_peak > 0.0
1383- {
1384- // step 5: smooth, decoupled peak detector
1385- // Textbook:
1386- // release_cf * self.normalisation_integrator + (1.0 - release_cf) * limiter_db
1366+ } else if knee_boundary_db. abs ( ) <= knee_db {
1367+ // The textbook equation:
1368+ // ratio_to_db(sample.abs()) - (ratio_to_db(sample.abs()) - (bias_db + knee_db / 2.0).powi(2) / (2.0 * knee_db))
13871369 // Simplifies to:
1388- // release_cf * self.normalisation_integrator - release_cf * limiter_db + limiter_db
1389- self . normalisation_integrator = f64:: max (
1390- limiter_db,
1391- release_cf * self . normalisation_integrator
1392- - release_cf * limiter_db
1393- + limiter_db,
1394- ) ;
1370+ // ((2.0 * bias_db) + knee_db).powi(2) / (8.0 * knee_db)
1371+ // Which in our case further simplifies to:
1372+ // (knee_boundary_db + knee_db).powi(2) / (8.0 * knee_db)
1373+ // because knee_boundary_db is 2.0 * bias_db.
1374+ ( knee_boundary_db + knee_db) . powi ( 2 ) / ( 8.0 * knee_db)
1375+ } else {
13951376 // Textbook:
1396- // attack_cf * self.normalisation_peak + (1.0 - attack_cf) * self.normalisation_integrator
1397- // Simplifies to:
1398- // attack_cf * self.normalisation_peak - attack_cf * self.normalisation_integrator + self.normalisation_integrator
1399- self . normalisation_peak = attack_cf
1400- * self . normalisation_peak
1401- - attack_cf * self . normalisation_integrator
1402- + self . normalisation_integrator ;
1403-
1404- // step 6: make-up gain applied later (volume attenuation)
1405- // Applying the standard normalisation factor here won't work,
1406- // because there are tracks with peaks as high as 6 dB above
1407- // the default threshold, so that would clip.
1408-
1409- // steps 7-8: conversion into level and multiplication into gain stage
1410- * sample *= db_to_ratio ( -self . normalisation_peak ) ;
1377+ // ratio_to_db(sample.abs()) - threshold_db, which is already our bias_db.
1378+ bias_db
14111379 }
1380+ } else {
1381+ 0.0
1382+ } ;
1383+
1384+ // Spare the CPU unless (1) the limiter is engaged, (2) we
1385+ // were in attack or (3) we were in release, and that attack/
1386+ // release wasn't finished yet.
1387+ if limiter_db > 0.0
1388+ || self . normalisation_integrator > 0.0
1389+ || self . normalisation_peak > 0.0
1390+ {
1391+ // step 5: smooth, decoupled peak detector
1392+ // Textbook:
1393+ // release_cf * self.normalisation_integrator + (1.0 - release_cf) * limiter_db
1394+ // Simplifies to:
1395+ // release_cf * self.normalisation_integrator - release_cf * limiter_db + limiter_db
1396+ self . normalisation_integrator = f64:: max (
1397+ limiter_db,
1398+ release_cf * self . normalisation_integrator
1399+ - release_cf * limiter_db
1400+ + limiter_db,
1401+ ) ;
1402+ // Textbook:
1403+ // attack_cf * self.normalisation_peak + (1.0 - attack_cf) * self.normalisation_integrator
1404+ // Simplifies to:
1405+ // attack_cf * self.normalisation_peak - attack_cf * self.normalisation_integrator + self.normalisation_integrator
1406+ self . normalisation_peak = attack_cf * self . normalisation_peak
1407+ - attack_cf * self . normalisation_integrator
1408+ + self . normalisation_integrator ;
1409+
1410+ // step 6: make-up gain applied later (volume attenuation)
1411+ // Applying the standard normalisation factor here won't work,
1412+ // because there are tracks with peaks as high as 6 dB above
1413+ // the default threshold, so that would clip.
1414+
1415+ // steps 7-8: conversion into level and multiplication into gain stage
1416+ * sample *= db_to_ratio ( -self . normalisation_peak ) ;
14121417 }
1413- }
1414- }
14151418
1416- // Apply volume attenuation last. TODO: make this so we can chain
1417- // the normaliser and mixer as a processing pipeline.
1418- if let Some ( ref editor) = self . audio_filter {
1419- editor. modify_stream ( data)
1419+ * sample *= volume;
1420+ }
14201421 }
14211422 }
14221423
0 commit comments