Add and default to "auto" normalisation type (#844)

This commit is contained in:
Roderick van Domburg 2021-09-20 19:22:02 +02:00 committed by GitHub
parent 7401d6a96e
commit 949ca4fded
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 25 deletions

View file

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [playback] Add `--volume-range` option to set dB range and control `log` and `cubic` volume control curves - [playback] Add `--volume-range` option to set dB range and control `log` and `cubic` volume control curves
- [playback] `alsamixer`: support for querying dB range from Alsa softvol - [playback] `alsamixer`: support for querying dB range from Alsa softvol
- [playback] Add `--format F64` (supported by Alsa and GStreamer only) - [playback] Add `--format F64` (supported by Alsa and GStreamer only)
- [playback] Add `--normalisation-type auto` that switches between album and track automatically
### Changed ### Changed
- [audio, playback] Moved `VorbisDecoder`, `VorbisError`, `AudioPacket`, `PassthroughDecoder`, `PassthroughError`, `AudioError`, `AudioDecoder` and the `convert` module from `librespot-audio` to `librespot-playback`. The underlying crates `vorbis`, `librespot-tremor`, `lewton` and `ogg` should be used directly. (breaking) - [audio, playback] Moved `VorbisDecoder`, `VorbisError`, `AudioPacket`, `PassthroughDecoder`, `PassthroughError`, `AudioError`, `AudioDecoder` and the `convert` module from `librespot-audio` to `librespot-playback`. The underlying crates `vorbis`, `librespot-tremor`, `lewton` and `ogg` should be used directly. (breaking)
@ -26,7 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [playback] `alsamixer`: use `--device` name for `--mixer-card` unless specified otherwise - [playback] `alsamixer`: use `--device` name for `--mixer-card` unless specified otherwise
- [playback] `player`: consider errors in `sink.start`, `sink.stop` and `sink.write` fatal and `exit(1)` (breaking) - [playback] `player`: consider errors in `sink.start`, `sink.stop` and `sink.write` fatal and `exit(1)` (breaking)
- [playback] `player`: make `convert` and `decoder` public so you can implement your own `Sink` - [playback] `player`: make `convert` and `decoder` public so you can implement your own `Sink`
- [playback] Updated default normalisation threshold to -2 dBFS - [playback] `player`: update default normalisation threshold to -2 dBFS
- [playback] `player`: default normalisation type is now `auto`
### Deprecated ### Deprecated
- [connect] The `discovery` module was deprecated in favor of the `librespot-discovery` crate - [connect] The `discovery` module was deprecated in favor of the `librespot-discovery` crate

View file

@ -266,7 +266,8 @@ impl AudioFileFetch {
fn handle_file_data(&mut self, data: ReceivedData) -> ControlFlow { fn handle_file_data(&mut self, data: ReceivedData) -> ControlFlow {
match data { match data {
ReceivedData::ResponseTime(response_time) => { ReceivedData::ResponseTime(response_time) => {
trace!("Ping time estimated as: {}ms", response_time.as_millis()); // chatty
// trace!("Ping time estimated as: {}ms", response_time.as_millis());
// prune old response times. Keep at most two so we can push a third. // prune old response times. Keep at most two so we can push a third.
while self.network_response_times.len() >= 3 { while self.network_response_times.len() >= 3 {

View file

@ -902,7 +902,8 @@ impl SpircTask {
self.context_fut = self.resolve_station(&context_uri); self.context_fut = self.resolve_station(&context_uri);
self.update_tracks_from_context(); self.update_tracks_from_context();
} }
if self.config.autoplay && new_index == tracks_len - 1 { let last_track = new_index == tracks_len - 1;
if self.config.autoplay && last_track {
// Extend the playlist // Extend the playlist
// Note: This doesn't seem to reflect in the UI // Note: This doesn't seem to reflect in the UI
// the additional tracks in the frame don't show up as with station view // the additional tracks in the frame don't show up as with station view
@ -917,6 +918,11 @@ impl SpircTask {
if tracks_len > 0 { if tracks_len > 0 {
self.state.set_playing_track_index(new_index); self.state.set_playing_track_index(new_index);
self.load_track(continue_playing, 0); self.load_track(continue_playing, 0);
if self.config.autoplay && last_track {
// If we're now playing the last track of an album, then
// switch to track normalisation mode for the autoplay to come.
self.player.set_auto_normalise_as_album(false);
}
} else { } else {
info!("Not playing next track because there are no more tracks left in queue."); info!("Not playing next track because there are no more tracks left in queue.");
self.state.set_playing_track_index(0); self.state.set_playing_track_index(0);
@ -1084,6 +1090,9 @@ impl SpircTask {
self.autoplay_fut = self.resolve_autoplay_uri(&context_uri); self.autoplay_fut = self.resolve_autoplay_uri(&context_uri);
} }
self.player
.set_auto_normalise_as_album(context_uri.starts_with("spotify:album:"));
self.state.set_playing_track_index(index); self.state.set_playing_track_index(index);
self.state.set_track(tracks.iter().cloned().collect()); self.state.set_track(tracks.iter().cloned().collect());
self.state.set_context_uri(context_uri); self.state.set_context_uri(context_uri);

View file

@ -76,10 +76,11 @@ impl AudioFormat {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub enum NormalisationType { pub enum NormalisationType {
Album, Album,
Track, Track,
Auto,
} }
impl FromStr for NormalisationType { impl FromStr for NormalisationType {
@ -88,6 +89,7 @@ impl FromStr for NormalisationType {
match s.to_lowercase().as_ref() { match s.to_lowercase().as_ref() {
"album" => Ok(Self::Album), "album" => Ok(Self::Album),
"track" => Ok(Self::Track), "track" => Ok(Self::Track),
"auto" => Ok(Self::Auto),
_ => Err(()), _ => Err(()),
} }
} }
@ -95,7 +97,7 @@ impl FromStr for NormalisationType {
impl Default for NormalisationType { impl Default for NormalisationType {
fn default() -> Self { fn default() -> Self {
Self::Album Self::Auto
} }
} }

View file

@ -67,6 +67,8 @@ struct PlayerInternal {
limiter_peak_sample: f64, limiter_peak_sample: f64,
limiter_factor: f64, limiter_factor: f64,
limiter_strength: f64, limiter_strength: f64,
auto_normalise_as_album: bool,
} }
enum PlayerCommand { enum PlayerCommand {
@ -86,6 +88,7 @@ enum PlayerCommand {
AddEventSender(mpsc::UnboundedSender<PlayerEvent>), AddEventSender(mpsc::UnboundedSender<PlayerEvent>),
SetSinkEventCallback(Option<SinkEventCallback>), SetSinkEventCallback(Option<SinkEventCallback>),
EmitVolumeSetEvent(u16), EmitVolumeSetEvent(u16),
SetAutoNormaliseAsAlbum(bool),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -238,9 +241,10 @@ impl NormalisationData {
return 1.0; return 1.0;
} }
let [gain_db, gain_peak] = match config.normalisation_type { let [gain_db, gain_peak] = if config.normalisation_type == NormalisationType::Album {
NormalisationType::Album => [data.album_gain_db, data.album_peak], [data.album_gain_db, data.album_peak]
NormalisationType::Track => [data.track_gain_db, data.track_peak], } else {
[data.track_gain_db, data.track_peak]
}; };
let normalisation_power = gain_db as f64 + config.normalisation_pregain; let normalisation_power = gain_db as f64 + config.normalisation_pregain;
@ -264,7 +268,11 @@ impl NormalisationData {
} }
debug!("Normalisation Data: {:?}", data); debug!("Normalisation Data: {:?}", data);
debug!("Normalisation Factor: {:.2}%", normalisation_factor * 100.0); debug!(
"Calculated Normalisation Factor for {:?}: {:.2}%",
config.normalisation_type,
normalisation_factor * 100.0
);
normalisation_factor as f64 normalisation_factor as f64
} }
@ -327,6 +335,8 @@ impl Player {
limiter_peak_sample: 0.0, limiter_peak_sample: 0.0,
limiter_factor: 1.0, limiter_factor: 1.0,
limiter_strength: 0.0, limiter_strength: 0.0,
auto_normalise_as_album: false,
}; };
// While PlayerInternal is written as a future, it still contains blocking code. // While PlayerInternal is written as a future, it still contains blocking code.
@ -406,6 +416,10 @@ impl Player {
pub fn emit_volume_set_event(&self, volume: u16) { pub fn emit_volume_set_event(&self, volume: u16) {
self.command(PlayerCommand::EmitVolumeSetEvent(volume)); self.command(PlayerCommand::EmitVolumeSetEvent(volume));
} }
pub fn set_auto_normalise_as_album(&self, setting: bool) {
self.command(PlayerCommand::SetAutoNormaliseAsAlbum(setting));
}
} }
impl Drop for Player { impl Drop for Player {
@ -423,7 +437,7 @@ impl Drop for Player {
struct PlayerLoadedTrackData { struct PlayerLoadedTrackData {
decoder: Decoder, decoder: Decoder,
normalisation_factor: f64, normalisation_data: NormalisationData,
stream_loader_controller: StreamLoaderController, stream_loader_controller: StreamLoaderController,
bytes_per_second: usize, bytes_per_second: usize,
duration_ms: u32, duration_ms: u32,
@ -456,6 +470,7 @@ enum PlayerState {
track_id: SpotifyId, track_id: SpotifyId,
play_request_id: u64, play_request_id: u64,
decoder: Decoder, decoder: Decoder,
normalisation_data: NormalisationData,
normalisation_factor: f64, normalisation_factor: f64,
stream_loader_controller: StreamLoaderController, stream_loader_controller: StreamLoaderController,
bytes_per_second: usize, bytes_per_second: usize,
@ -467,6 +482,7 @@ enum PlayerState {
track_id: SpotifyId, track_id: SpotifyId,
play_request_id: u64, play_request_id: u64,
decoder: Decoder, decoder: Decoder,
normalisation_data: NormalisationData,
normalisation_factor: f64, normalisation_factor: f64,
stream_loader_controller: StreamLoaderController, stream_loader_controller: StreamLoaderController,
bytes_per_second: usize, bytes_per_second: usize,
@ -543,7 +559,7 @@ impl PlayerState {
decoder, decoder,
duration_ms, duration_ms,
bytes_per_second, bytes_per_second,
normalisation_factor, normalisation_data,
stream_loader_controller, stream_loader_controller,
stream_position_pcm, stream_position_pcm,
.. ..
@ -553,7 +569,7 @@ impl PlayerState {
play_request_id, play_request_id,
loaded_track: PlayerLoadedTrackData { loaded_track: PlayerLoadedTrackData {
decoder, decoder,
normalisation_factor, normalisation_data,
stream_loader_controller, stream_loader_controller,
bytes_per_second, bytes_per_second,
duration_ms, duration_ms,
@ -572,6 +588,7 @@ impl PlayerState {
track_id, track_id,
play_request_id, play_request_id,
decoder, decoder,
normalisation_data,
normalisation_factor, normalisation_factor,
stream_loader_controller, stream_loader_controller,
duration_ms, duration_ms,
@ -583,6 +600,7 @@ impl PlayerState {
track_id, track_id,
play_request_id, play_request_id,
decoder, decoder,
normalisation_data,
normalisation_factor, normalisation_factor,
stream_loader_controller, stream_loader_controller,
duration_ms, duration_ms,
@ -603,6 +621,7 @@ impl PlayerState {
track_id, track_id,
play_request_id, play_request_id,
decoder, decoder,
normalisation_data,
normalisation_factor, normalisation_factor,
stream_loader_controller, stream_loader_controller,
duration_ms, duration_ms,
@ -615,6 +634,7 @@ impl PlayerState {
track_id, track_id,
play_request_id, play_request_id,
decoder, decoder,
normalisation_data,
normalisation_factor, normalisation_factor,
stream_loader_controller, stream_loader_controller,
duration_ms, duration_ms,
@ -775,14 +795,16 @@ impl PlayerTrackLoader {
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file); let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) let normalisation_data = match NormalisationData::parse_from_file(&mut decrypted_file) {
{ Ok(data) => data,
Ok(normalisation_data) => {
NormalisationData::get_factor(&self.config, normalisation_data)
}
Err(_) => { Err(_) => {
warn!("Unable to extract normalisation data, using default value."); warn!("Unable to extract normalisation data, using default value.");
1.0 NormalisationData {
track_gain_db: 0.0,
track_peak: 1.0,
album_gain_db: 0.0,
album_peak: 1.0,
}
} }
}; };
@ -838,7 +860,7 @@ impl PlayerTrackLoader {
return Some(PlayerLoadedTrackData { return Some(PlayerLoadedTrackData {
decoder, decoder,
normalisation_factor, normalisation_data,
stream_loader_controller, stream_loader_controller,
bytes_per_second, bytes_per_second,
duration_ms, duration_ms,
@ -1339,6 +1361,17 @@ impl PlayerInternal {
) { ) {
let position_ms = Self::position_pcm_to_ms(loaded_track.stream_position_pcm); let position_ms = Self::position_pcm_to_ms(loaded_track.stream_position_pcm);
let mut config = self.config.clone();
if config.normalisation_type == NormalisationType::Auto {
if self.auto_normalise_as_album {
config.normalisation_type = NormalisationType::Album;
} else {
config.normalisation_type = NormalisationType::Track;
}
};
let normalisation_factor =
NormalisationData::get_factor(&config, loaded_track.normalisation_data);
if start_playback { if start_playback {
self.ensure_sink_running(); self.ensure_sink_running();
@ -1353,7 +1386,8 @@ impl PlayerInternal {
track_id, track_id,
play_request_id, play_request_id,
decoder: loaded_track.decoder, decoder: loaded_track.decoder,
normalisation_factor: loaded_track.normalisation_factor, normalisation_data: loaded_track.normalisation_data,
normalisation_factor,
stream_loader_controller: loaded_track.stream_loader_controller, stream_loader_controller: loaded_track.stream_loader_controller,
duration_ms: loaded_track.duration_ms, duration_ms: loaded_track.duration_ms,
bytes_per_second: loaded_track.bytes_per_second, bytes_per_second: loaded_track.bytes_per_second,
@ -1370,7 +1404,8 @@ impl PlayerInternal {
track_id, track_id,
play_request_id, play_request_id,
decoder: loaded_track.decoder, decoder: loaded_track.decoder,
normalisation_factor: loaded_track.normalisation_factor, normalisation_data: loaded_track.normalisation_data,
normalisation_factor,
stream_loader_controller: loaded_track.stream_loader_controller, stream_loader_controller: loaded_track.stream_loader_controller,
duration_ms: loaded_track.duration_ms, duration_ms: loaded_track.duration_ms,
bytes_per_second: loaded_track.bytes_per_second, bytes_per_second: loaded_track.bytes_per_second,
@ -1497,7 +1532,7 @@ impl PlayerInternal {
stream_loader_controller, stream_loader_controller,
bytes_per_second, bytes_per_second,
duration_ms, duration_ms,
normalisation_factor, normalisation_data,
.. ..
} }
| PlayerState::Paused { | PlayerState::Paused {
@ -1506,13 +1541,13 @@ impl PlayerInternal {
stream_loader_controller, stream_loader_controller,
bytes_per_second, bytes_per_second,
duration_ms, duration_ms,
normalisation_factor, normalisation_data,
.. ..
} = old_state } = old_state
{ {
let loaded_track = PlayerLoadedTrackData { let loaded_track = PlayerLoadedTrackData {
decoder, decoder,
normalisation_factor, normalisation_data,
stream_loader_controller, stream_loader_controller,
bytes_per_second, bytes_per_second,
duration_ms, duration_ms,
@ -1750,6 +1785,10 @@ impl PlayerInternal {
PlayerCommand::EmitVolumeSetEvent(volume) => { PlayerCommand::EmitVolumeSetEvent(volume) => {
self.send_event(PlayerEvent::VolumeSet { volume }) self.send_event(PlayerEvent::VolumeSet { volume })
} }
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => {
self.auto_normalise_as_album = setting
}
} }
} }
@ -1855,6 +1894,10 @@ impl ::std::fmt::Debug for PlayerCommand {
PlayerCommand::EmitVolumeSetEvent(volume) => { PlayerCommand::EmitVolumeSetEvent(volume) => {
f.debug_tuple("VolumeSet").field(&volume).finish() f.debug_tuple("VolumeSet").field(&volume).finish()
} }
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => f
.debug_tuple("SetAutoNormaliseAsAlbum")
.field(&setting)
.finish(),
} }
} }
} }

View file

@ -359,7 +359,7 @@ fn get_setup(args: &[String]) -> Setup {
.optopt( .optopt(
"", "",
NORMALISATION_GAIN_TYPE, NORMALISATION_GAIN_TYPE,
"Specify the normalisation gain type to use {track|album}. Defaults to album.", "Specify the normalisation gain type to use {track|album|auto}. Defaults to auto.",
"TYPE", "TYPE",
) )
.optopt( .optopt(