mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Add and default to "auto" normalisation type (#844)
This commit is contained in:
parent
7401d6a96e
commit
949ca4fded
6 changed files with 82 additions and 25 deletions
|
@ -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] `alsamixer`: support for querying dB range from Alsa softvol
|
||||
- [playback] Add `--format F64` (supported by Alsa and GStreamer only)
|
||||
- [playback] Add `--normalisation-type auto` that switches between album and track automatically
|
||||
|
||||
### 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)
|
||||
|
@ -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] `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] 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
|
||||
- [connect] The `discovery` module was deprecated in favor of the `librespot-discovery` crate
|
||||
|
|
|
@ -266,7 +266,8 @@ impl AudioFileFetch {
|
|||
fn handle_file_data(&mut self, data: ReceivedData) -> ControlFlow {
|
||||
match data {
|
||||
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.
|
||||
while self.network_response_times.len() >= 3 {
|
||||
|
|
|
@ -902,7 +902,8 @@ impl SpircTask {
|
|||
self.context_fut = self.resolve_station(&context_uri);
|
||||
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
|
||||
// Note: This doesn't seem to reflect in the UI
|
||||
// the additional tracks in the frame don't show up as with station view
|
||||
|
@ -917,6 +918,11 @@ impl SpircTask {
|
|||
if tracks_len > 0 {
|
||||
self.state.set_playing_track_index(new_index);
|
||||
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 {
|
||||
info!("Not playing next track because there are no more tracks left in queue.");
|
||||
self.state.set_playing_track_index(0);
|
||||
|
@ -1084,6 +1090,9 @@ impl SpircTask {
|
|||
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_track(tracks.iter().cloned().collect());
|
||||
self.state.set_context_uri(context_uri);
|
||||
|
|
|
@ -76,10 +76,11 @@ impl AudioFormat {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum NormalisationType {
|
||||
Album,
|
||||
Track,
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl FromStr for NormalisationType {
|
||||
|
@ -88,6 +89,7 @@ impl FromStr for NormalisationType {
|
|||
match s.to_lowercase().as_ref() {
|
||||
"album" => Ok(Self::Album),
|
||||
"track" => Ok(Self::Track),
|
||||
"auto" => Ok(Self::Auto),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +97,7 @@ impl FromStr for NormalisationType {
|
|||
|
||||
impl Default for NormalisationType {
|
||||
fn default() -> Self {
|
||||
Self::Album
|
||||
Self::Auto
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,8 @@ struct PlayerInternal {
|
|||
limiter_peak_sample: f64,
|
||||
limiter_factor: f64,
|
||||
limiter_strength: f64,
|
||||
|
||||
auto_normalise_as_album: bool,
|
||||
}
|
||||
|
||||
enum PlayerCommand {
|
||||
|
@ -86,6 +88,7 @@ enum PlayerCommand {
|
|||
AddEventSender(mpsc::UnboundedSender<PlayerEvent>),
|
||||
SetSinkEventCallback(Option<SinkEventCallback>),
|
||||
EmitVolumeSetEvent(u16),
|
||||
SetAutoNormaliseAsAlbum(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -238,9 +241,10 @@ impl NormalisationData {
|
|||
return 1.0;
|
||||
}
|
||||
|
||||
let [gain_db, gain_peak] = match config.normalisation_type {
|
||||
NormalisationType::Album => [data.album_gain_db, data.album_peak],
|
||||
NormalisationType::Track => [data.track_gain_db, data.track_peak],
|
||||
let [gain_db, gain_peak] = if config.normalisation_type == NormalisationType::Album {
|
||||
[data.album_gain_db, data.album_peak]
|
||||
} else {
|
||||
[data.track_gain_db, data.track_peak]
|
||||
};
|
||||
|
||||
let normalisation_power = gain_db as f64 + config.normalisation_pregain;
|
||||
|
@ -264,7 +268,11 @@ impl NormalisationData {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -327,6 +335,8 @@ impl Player {
|
|||
limiter_peak_sample: 0.0,
|
||||
limiter_factor: 1.0,
|
||||
limiter_strength: 0.0,
|
||||
|
||||
auto_normalise_as_album: false,
|
||||
};
|
||||
|
||||
// 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) {
|
||||
self.command(PlayerCommand::EmitVolumeSetEvent(volume));
|
||||
}
|
||||
|
||||
pub fn set_auto_normalise_as_album(&self, setting: bool) {
|
||||
self.command(PlayerCommand::SetAutoNormaliseAsAlbum(setting));
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Player {
|
||||
|
@ -423,7 +437,7 @@ impl Drop for Player {
|
|||
|
||||
struct PlayerLoadedTrackData {
|
||||
decoder: Decoder,
|
||||
normalisation_factor: f64,
|
||||
normalisation_data: NormalisationData,
|
||||
stream_loader_controller: StreamLoaderController,
|
||||
bytes_per_second: usize,
|
||||
duration_ms: u32,
|
||||
|
@ -456,6 +470,7 @@ enum PlayerState {
|
|||
track_id: SpotifyId,
|
||||
play_request_id: u64,
|
||||
decoder: Decoder,
|
||||
normalisation_data: NormalisationData,
|
||||
normalisation_factor: f64,
|
||||
stream_loader_controller: StreamLoaderController,
|
||||
bytes_per_second: usize,
|
||||
|
@ -467,6 +482,7 @@ enum PlayerState {
|
|||
track_id: SpotifyId,
|
||||
play_request_id: u64,
|
||||
decoder: Decoder,
|
||||
normalisation_data: NormalisationData,
|
||||
normalisation_factor: f64,
|
||||
stream_loader_controller: StreamLoaderController,
|
||||
bytes_per_second: usize,
|
||||
|
@ -543,7 +559,7 @@ impl PlayerState {
|
|||
decoder,
|
||||
duration_ms,
|
||||
bytes_per_second,
|
||||
normalisation_factor,
|
||||
normalisation_data,
|
||||
stream_loader_controller,
|
||||
stream_position_pcm,
|
||||
..
|
||||
|
@ -553,7 +569,7 @@ impl PlayerState {
|
|||
play_request_id,
|
||||
loaded_track: PlayerLoadedTrackData {
|
||||
decoder,
|
||||
normalisation_factor,
|
||||
normalisation_data,
|
||||
stream_loader_controller,
|
||||
bytes_per_second,
|
||||
duration_ms,
|
||||
|
@ -572,6 +588,7 @@ impl PlayerState {
|
|||
track_id,
|
||||
play_request_id,
|
||||
decoder,
|
||||
normalisation_data,
|
||||
normalisation_factor,
|
||||
stream_loader_controller,
|
||||
duration_ms,
|
||||
|
@ -583,6 +600,7 @@ impl PlayerState {
|
|||
track_id,
|
||||
play_request_id,
|
||||
decoder,
|
||||
normalisation_data,
|
||||
normalisation_factor,
|
||||
stream_loader_controller,
|
||||
duration_ms,
|
||||
|
@ -603,6 +621,7 @@ impl PlayerState {
|
|||
track_id,
|
||||
play_request_id,
|
||||
decoder,
|
||||
normalisation_data,
|
||||
normalisation_factor,
|
||||
stream_loader_controller,
|
||||
duration_ms,
|
||||
|
@ -615,6 +634,7 @@ impl PlayerState {
|
|||
track_id,
|
||||
play_request_id,
|
||||
decoder,
|
||||
normalisation_data,
|
||||
normalisation_factor,
|
||||
stream_loader_controller,
|
||||
duration_ms,
|
||||
|
@ -775,14 +795,16 @@ impl PlayerTrackLoader {
|
|||
|
||||
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
||||
|
||||
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file)
|
||||
{
|
||||
Ok(normalisation_data) => {
|
||||
NormalisationData::get_factor(&self.config, normalisation_data)
|
||||
}
|
||||
let normalisation_data = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
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 {
|
||||
decoder,
|
||||
normalisation_factor,
|
||||
normalisation_data,
|
||||
stream_loader_controller,
|
||||
bytes_per_second,
|
||||
duration_ms,
|
||||
|
@ -1339,6 +1361,17 @@ impl PlayerInternal {
|
|||
) {
|
||||
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 {
|
||||
self.ensure_sink_running();
|
||||
|
||||
|
@ -1353,7 +1386,8 @@ impl PlayerInternal {
|
|||
track_id,
|
||||
play_request_id,
|
||||
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,
|
||||
duration_ms: loaded_track.duration_ms,
|
||||
bytes_per_second: loaded_track.bytes_per_second,
|
||||
|
@ -1370,7 +1404,8 @@ impl PlayerInternal {
|
|||
track_id,
|
||||
play_request_id,
|
||||
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,
|
||||
duration_ms: loaded_track.duration_ms,
|
||||
bytes_per_second: loaded_track.bytes_per_second,
|
||||
|
@ -1497,7 +1532,7 @@ impl PlayerInternal {
|
|||
stream_loader_controller,
|
||||
bytes_per_second,
|
||||
duration_ms,
|
||||
normalisation_factor,
|
||||
normalisation_data,
|
||||
..
|
||||
}
|
||||
| PlayerState::Paused {
|
||||
|
@ -1506,13 +1541,13 @@ impl PlayerInternal {
|
|||
stream_loader_controller,
|
||||
bytes_per_second,
|
||||
duration_ms,
|
||||
normalisation_factor,
|
||||
normalisation_data,
|
||||
..
|
||||
} = old_state
|
||||
{
|
||||
let loaded_track = PlayerLoadedTrackData {
|
||||
decoder,
|
||||
normalisation_factor,
|
||||
normalisation_data,
|
||||
stream_loader_controller,
|
||||
bytes_per_second,
|
||||
duration_ms,
|
||||
|
@ -1750,6 +1785,10 @@ impl PlayerInternal {
|
|||
PlayerCommand::EmitVolumeSetEvent(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) => {
|
||||
f.debug_tuple("VolumeSet").field(&volume).finish()
|
||||
}
|
||||
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => f
|
||||
.debug_tuple("SetAutoNormaliseAsAlbum")
|
||||
.field(&setting)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -359,7 +359,7 @@ fn get_setup(args: &[String]) -> Setup {
|
|||
.optopt(
|
||||
"",
|
||||
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",
|
||||
)
|
||||
.optopt(
|
||||
|
|
Loading…
Reference in a new issue