From 01df6e1e223ec4fc66c24a29db13371affa19b00 Mon Sep 17 00:00:00 2001 From: Stefan Keller Date: Fri, 20 Oct 2023 23:25:20 +0200 Subject: [PATCH] Player: optionally use album gain for loudness normalisation --- .../gianlu/librespot/audio/NormalizationData.java | 10 ++++++++-- .../gianlu/librespot/player/FileConfiguration.java | 1 + .../gianlu/librespot/player/PlayerConfiguration.java | 12 ++++++++++-- .../librespot/player/playback/PlayerQueueEntry.java | 2 +- player/src/main/resources/default.toml | 3 ++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/NormalizationData.java b/lib/src/main/java/xyz/gianlu/librespot/audio/NormalizationData.java index a2292f91..a18db032 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/NormalizationData.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/NormalizationData.java @@ -62,8 +62,10 @@ public static NormalizationData read(@NotNull InputStream in) throws IOException return new NormalizationData(buffer.getFloat(), buffer.getFloat(), buffer.getFloat(), buffer.getFloat()); } - public float getFactor(float normalisationPregain) { - float normalisationFactor = (float) Math.pow(10, (track_gain_db + normalisationPregain) / 20); + public float getFactor(float normalisationPregain, boolean useAlbumGain) { + float gain = useAlbumGain? album_gain_db : track_gain_db; + LOGGER.trace("Using gain: {}", gain); + float normalisationFactor = (float) Math.pow(10, (gain + normalisationPregain) / 20); if (normalisationFactor * track_peak > 1) { LOGGER.warn("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid."); normalisationFactor = 1 / track_peak; @@ -71,4 +73,8 @@ public float getFactor(float normalisationPregain) { return normalisationFactor; } + + public float getFactor(float normalisationPregain) { + return getFactor(normalisationPregain, false); + } } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java b/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java index 99c4a2f8..571721ac 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java @@ -439,6 +439,7 @@ public PlayerConfiguration toPlayer() { .setAutoplayEnabled(config.get("player.autoplayEnabled")) .setCrossfadeDuration(config.get("player.crossfadeDuration")) .setEnableNormalisation(config.get("player.enableNormalisation")) + .setUseAlbumGain(config.get("player.useAlbumGain")) .setInitialVolume(config.get("player.initialVolume")) .setLogAvailableMixers(config.get("player.logAvailableMixers")) .setMetadataPipe(metadataPipe()) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java b/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java index b8379379..197262db 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java @@ -29,6 +29,7 @@ public final class PlayerConfiguration { // Audio public final AudioQuality preferredQuality; public final boolean enableNormalisation; + public final boolean useAlbumGain; public final float normalisationPregain; public final boolean autoplayEnabled; public final int crossfadeDuration; @@ -52,11 +53,12 @@ public final class PlayerConfiguration { // Local files public final File localFilesPath; - private PlayerConfiguration(AudioQuality preferredQuality, boolean enableNormalisation, float normalisationPregain, boolean autoplayEnabled, int crossfadeDuration, boolean preloadEnabled, + private PlayerConfiguration(AudioQuality preferredQuality, boolean enableNormalisation, boolean useAlbumGain, float normalisationPregain, boolean autoplayEnabled, int crossfadeDuration, boolean preloadEnabled, AudioOutput output, String outputClass, Object[] outputClassParams, File outputPipe, File metadataPipe, String[] mixerSearchKeywords, boolean logAvailableMixers, int releaseLineDelay, int initialVolume, int volumeSteps, boolean bypassSinkVolume, File localFilesPath) { this.preferredQuality = preferredQuality; this.enableNormalisation = enableNormalisation; + this.useAlbumGain = useAlbumGain; this.normalisationPregain = normalisationPregain; this.autoplayEnabled = autoplayEnabled; this.crossfadeDuration = crossfadeDuration; @@ -83,6 +85,7 @@ public final static class Builder { // Audio private AudioQuality preferredQuality = AudioQuality.NORMAL; private boolean enableNormalisation = true; + private boolean useAlbumGain = false; private float normalisationPregain = 3.0f; private boolean autoplayEnabled = true; private int crossfadeDuration = 0; @@ -119,6 +122,11 @@ public Builder setEnableNormalisation(boolean enableNormalisation) { return this; } + public Builder setUseAlbumGain(boolean useAlbumGain) { + this.useAlbumGain = useAlbumGain; + return this; + } + public Builder setNormalisationPregain(float normalisationPregain) { this.normalisationPregain = normalisationPregain; return this; @@ -207,7 +215,7 @@ public Builder setLocalFilesPath(File localFilesPath) { @Contract(value = " -> new", pure = true) public @NotNull PlayerConfiguration build() { - return new PlayerConfiguration(preferredQuality, enableNormalisation, normalisationPregain, autoplayEnabled, crossfadeDuration, preloadEnabled, + return new PlayerConfiguration(preferredQuality, enableNormalisation, useAlbumGain, normalisationPregain, autoplayEnabled, crossfadeDuration, preloadEnabled, output, outputClass, outputClassParams, outputPipe, metadataPipe, mixerSearchKeywords, logAvailableMixers, releaseLineDelay, initialVolume, volumeSteps, bypassSinkVolume, localFilesPath); } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java index e6183302..e8af70ee 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java @@ -135,7 +135,7 @@ private void load(boolean preload) throws IOException, Decoder.DecoderException, float normalizationFactor; if (stream.normalizationData == null || !conf.enableNormalisation) normalizationFactor = 1; - else normalizationFactor = stream.normalizationData.getFactor(conf.normalisationPregain); + else normalizationFactor = stream.normalizationData.getFactor(conf.normalisationPregain, conf.useAlbumGain); Iterator iter = Decoders.initDecoder(stream.in.codec(), stream.in.stream(), normalizationFactor, metadata.duration()); while (iter.hasNext()) { diff --git a/player/src/main/resources/default.toml b/player/src/main/resources/default.toml index 7d5cb04b..f08ef8ab 100644 --- a/player/src/main/resources/default.toml +++ b/player/src/main/resources/default.toml @@ -37,6 +37,7 @@ manualCorrection = 0 # Manual time correction in millis autoplayEnabled = true # Autoplay similar songs when your music ends preferredAudioQuality = "NORMAL" # Preferred audio quality (NORMAL, HIGH, VERY_HIGH) enableNormalisation = true # Whether to apply the Spotify loudness normalisation +useAlbumGain = false # Whether to apply album gain (instead of track gain) for normalisation normalisationPregain = +3.0 # Normalisation pregain in decibels (loud at +6, normal at +3, quiet at -5) initialVolume = 65536 # Initial volume (0-65536) volumeSteps = 64 # Number of volume notches @@ -83,4 +84,4 @@ onPanicState = "" onConnectionDropped = "" onConnectionEstablished = "" onStartedLoading = "" -onFinishedLoading = "" \ No newline at end of file +onFinishedLoading = ""