Remove the volume sample iteration (#986)

Move volume calculations out of their own separate samples iteration and into the normalisation iteration
This commit is contained in:
Jason Gray 2022-05-19 15:23:14 -05:00 committed by GitHub
parent 70de5752dc
commit 7efc62b9ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 125 deletions

View file

@ -6,6 +6,7 @@ use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId; use librespot::core::spotify_id::SpotifyId;
use librespot::playback::audio_backend; use librespot::playback::audio_backend;
use librespot::playback::config::{AudioFormat, PlayerConfig}; use librespot::playback::config::{AudioFormat, PlayerConfig};
use librespot::playback::mixer::NoOpVolume;
use librespot::playback::player::Player; use librespot::playback::player::Player;
#[tokio::main] #[tokio::main]
@ -30,7 +31,7 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
let (mut player, _) = Player::new(player_config, session, None, move || { let (mut player, _) = Player::new(player_config, session, Box::new(NoOpVolume), move || {
backend(None, audio_format) backend(None, audio_format)
}); });

View file

@ -3,6 +3,8 @@ use crate::config::VolumeCtrl;
pub mod mappings; pub mod mappings;
use self::mappings::MappedCtrl; use self::mappings::MappedCtrl;
pub struct NoOpVolume;
pub trait Mixer: Send { pub trait Mixer: Send {
fn open(config: MixerConfig) -> Self fn open(config: MixerConfig) -> Self
where where
@ -11,13 +13,19 @@ pub trait Mixer: Send {
fn set_volume(&self, volume: u16); fn set_volume(&self, volume: u16);
fn volume(&self) -> u16; fn volume(&self) -> u16;
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> { fn get_soft_volume(&self) -> Box<dyn VolumeGetter + Send> {
None Box::new(NoOpVolume)
} }
} }
pub trait AudioFilter { pub trait VolumeGetter {
fn modify_stream(&self, data: &mut [f64]); fn attenuation_factor(&self) -> f64;
}
impl VolumeGetter for NoOpVolume {
fn attenuation_factor(&self) -> f64 {
1.0
}
} }
pub mod softmixer; pub mod softmixer;

View file

@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
use super::AudioFilter; use super::VolumeGetter;
use super::{MappedCtrl, VolumeCtrl}; use super::{MappedCtrl, VolumeCtrl};
use super::{Mixer, MixerConfig}; use super::{Mixer, MixerConfig};
@ -35,10 +35,8 @@ impl Mixer for SoftMixer {
.store(mapped_volume.to_bits(), Ordering::Relaxed) .store(mapped_volume.to_bits(), Ordering::Relaxed)
} }
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> { fn get_soft_volume(&self) -> Box<dyn VolumeGetter + Send> {
Some(Box::new(SoftVolumeApplier { Box::new(SoftVolume(self.volume.clone()))
volume: self.volume.clone(),
}))
} }
} }
@ -46,17 +44,10 @@ impl SoftMixer {
pub const NAME: &'static str = "softvol"; pub const NAME: &'static str = "softvol";
} }
struct SoftVolumeApplier { struct SoftVolume(Arc<AtomicU64>);
volume: Arc<AtomicU64>,
}
impl AudioFilter for SoftVolumeApplier { impl VolumeGetter for SoftVolume {
fn modify_stream(&self, data: &mut [f64]) { fn attenuation_factor(&self) -> f64 {
let volume = f64::from_bits(self.volume.load(Ordering::Relaxed)); f64::from_bits(self.0.load(Ordering::Relaxed))
if volume < 1.0 {
for x in data.iter_mut() {
*x *= volume;
}
}
} }
} }

View file

@ -25,7 +25,7 @@ use crate::core::spotify_id::SpotifyId;
use crate::core::util::SeqGenerator; use crate::core::util::SeqGenerator;
use crate::decoder::{AudioDecoder, AudioPacket, DecoderError, PassthroughDecoder, VorbisDecoder}; use crate::decoder::{AudioDecoder, AudioPacket, DecoderError, PassthroughDecoder, VorbisDecoder};
use crate::metadata::{AudioItem, FileFormat}; use crate::metadata::{AudioItem, FileFormat};
use crate::mixer::AudioFilter; use crate::mixer::VolumeGetter;
use crate::{MS_PER_PAGE, NUM_CHANNELS, PAGES_PER_MS, SAMPLES_PER_SECOND}; use crate::{MS_PER_PAGE, NUM_CHANNELS, PAGES_PER_MS, SAMPLES_PER_SECOND};
@ -58,7 +58,7 @@ struct PlayerInternal {
sink: Box<dyn Sink>, sink: Box<dyn Sink>,
sink_status: SinkStatus, sink_status: SinkStatus,
sink_event_callback: Option<SinkEventCallback>, sink_event_callback: Option<SinkEventCallback>,
audio_filter: Option<Box<dyn AudioFilter + Send>>, volume_getter: Box<dyn VolumeGetter + Send>,
event_senders: Vec<mpsc::UnboundedSender<PlayerEvent>>, event_senders: Vec<mpsc::UnboundedSender<PlayerEvent>>,
converter: Converter, converter: Converter,
@ -319,7 +319,7 @@ impl Player {
pub fn new<F>( pub fn new<F>(
config: PlayerConfig, config: PlayerConfig,
session: Session, session: Session,
audio_filter: Option<Box<dyn AudioFilter + Send>>, volume_getter: Box<dyn VolumeGetter + Send>,
sink_builder: F, sink_builder: F,
) -> (Player, PlayerEventChannel) ) -> (Player, PlayerEventChannel)
where where
@ -369,7 +369,7 @@ impl Player {
sink: sink_builder(), sink: sink_builder(),
sink_status: SinkStatus::Closed, sink_status: SinkStatus::Closed,
sink_event_callback: None, sink_event_callback: None,
audio_filter, volume_getter,
event_senders: [event_sender].to_vec(), event_senders: [event_sender].to_vec(),
converter, converter,
@ -1314,19 +1314,26 @@ impl PlayerInternal {
Some(mut packet) => { Some(mut packet) => {
if !packet.is_empty() { if !packet.is_empty() {
if let AudioPacket::Samples(ref mut data) = packet { if let AudioPacket::Samples(ref mut data) = packet {
// Get the volume for the packet.
// In the case of hardware volume control this will
// always be 1.0 (no change).
let volume = self.volume_getter.attenuation_factor();
// For the basic normalisation method, a normalisation factor of 1.0 indicates that // For the basic normalisation method, a normalisation factor of 1.0 indicates that
// there is nothing to normalise (all samples should pass unaltered). For the // there is nothing to normalise (all samples should pass unaltered). For the
// dynamic method, there may still be peaks that we want to shave off. // dynamic method, there may still be peaks that we want to shave off.
if self.config.normalisation { // No matter the case we apply volume attenuation last if there is any.
if self.config.normalisation_method == NormalisationMethod::Basic if !self.config.normalisation && volume < 1.0 {
&& normalisation_factor < 1.0 for sample in data.iter_mut() {
*sample *= volume;
}
} else if self.config.normalisation_method == NormalisationMethod::Basic
&& (normalisation_factor < 1.0 || volume < 1.0)
{ {
for sample in data.iter_mut() { for sample in data.iter_mut() {
*sample *= normalisation_factor; *sample *= normalisation_factor * volume;
} }
} else if self.config.normalisation_method } else if self.config.normalisation_method == NormalisationMethod::Dynamic {
== NormalisationMethod::Dynamic
{
// zero-cost shorthands // zero-cost shorthands
let threshold_db = self.config.normalisation_threshold_dbfs; let threshold_db = self.config.normalisation_threshold_dbfs;
let knee_db = self.config.normalisation_knee_db; let knee_db = self.config.normalisation_knee_db;
@ -1396,8 +1403,7 @@ impl PlayerInternal {
// attack_cf * self.normalisation_peak + (1.0 - attack_cf) * self.normalisation_integrator // attack_cf * self.normalisation_peak + (1.0 - attack_cf) * self.normalisation_integrator
// Simplifies to: // Simplifies to:
// attack_cf * self.normalisation_peak - attack_cf * self.normalisation_integrator + self.normalisation_integrator // attack_cf * self.normalisation_peak - attack_cf * self.normalisation_integrator + self.normalisation_integrator
self.normalisation_peak = attack_cf self.normalisation_peak = attack_cf * self.normalisation_peak
* self.normalisation_peak
- attack_cf * self.normalisation_integrator - attack_cf * self.normalisation_integrator
+ self.normalisation_integrator; + self.normalisation_integrator;
@ -1409,14 +1415,9 @@ impl PlayerInternal {
// steps 7-8: conversion into level and multiplication into gain stage // steps 7-8: conversion into level and multiplication into gain stage
*sample *= db_to_ratio(-self.normalisation_peak); *sample *= db_to_ratio(-self.normalisation_peak);
} }
}
}
}
// Apply volume attenuation last. TODO: make this so we can chain *sample *= volume;
// the normaliser and mixer as a processing pipeline. }
if let Some(ref editor) = self.audio_filter {
editor.modify_stream(data)
} }
} }

View file

@ -1648,12 +1648,12 @@ async fn main() {
let player_config = setup.player_config.clone(); let player_config = setup.player_config.clone();
let connect_config = setup.connect_config.clone(); let connect_config = setup.connect_config.clone();
let audio_filter = mixer.get_audio_filter(); let soft_volume = mixer.get_soft_volume();
let format = setup.format; let format = setup.format;
let backend = setup.backend; let backend = setup.backend;
let device = setup.device.clone(); let device = setup.device.clone();
let (player, event_channel) = let (player, event_channel) =
Player::new(player_config, session.clone(), audio_filter, move || { Player::new(player_config, session.clone(), soft_volume, move || {
(backend)(device, format) (backend)(device, format)
}); });