librespot-org / librespot-java

The most up-to-date open source Spotify client
Apache License 2.0
384 stars 94 forks source link

Logarithmic Volume Control #373

Open jcadduono opened 3 years ago

jcadduono commented 3 years ago

Is your feature request related to a problem? Please describe. Controlling the volume is a struggle because there's almost no difference between 25% and 100% and most of the normal listening range is from 0-15% on my system.

Describe the solution you'd like Add a volume control config option between linear and logarithmic.

Describe alternatives you've considered Sending audio to a mixer with a max volume set at 25%, but then I wouldn't be able to blast music when wanted.

Additional context I dunno maybe there's a better way to do this?

manfreddz commented 2 years ago

Why was this closed? I can't find any option for logarithmic volume control..

manfreddz commented 2 years ago

I think this would be a really nice addition. I've experimented with some manual code changes and adding the following row in AudioSink.setVolume() works well for me:

volumeNorm = (float) Math.pow(volumeNorm, 4) / 2;

I will run my custom build until this gets added (if ever). Could we reopen this issue? @jcadduono @devgianlu

jcadduono commented 2 years ago

sorry, the moment i found the rust version of librespot (with cubic volume control that works really well with my AVR) i flocked to that and closed this to pretend it never happened lol

manfreddz commented 2 years ago

No worries! I've run librespot for a few years, but it's just too buggy. This is the feature I'm missing in the Java version.

tofublock commented 1 year ago

I have the same problem, and this improves it greatly. Power of 4 moves it too much in the other direction for me (why did you choose that value, @manfreddz?) but pow(volumeNorm, 2) is very nice. I'm not so sure about it mathematically, but if it works it works. :)

Would be great if this easy change could be integrated here!

I think this would be a really nice addition. I've experimented with some manual code changes and adding the following row in AudioSink.setVolume() works well for me:

volumeNorm = (float) Math.pow(volumeNorm, 4) / 2;

I will run my custom build until this gets added (if ever). Could we reopen this issue? @jcadduono @devgianlu

dsheets commented 11 months ago

I migrated from librespot-org/librespot for the local API and immediately had this very loud problem. My Java is approximately 16 years out-of-date but this is the patch I'm running now to get something very similar to librespot-org/librespot's default logarithmic volume control. I used DB_RANGE = 40 here instead of 60 because 60 still seemed too loud/too much range but I think they default to 60.

--- a/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java
+++ b/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java
@@ -137,9 +137,22 @@ public final class AudioSink implements Runnable, Closeable {
         if (volume < 0 || volume > Player.VOLUME_MAX)
             throw new IllegalArgumentException("Invalid volume: " + volume);

-        float volumeNorm = ((float) volume) / Player.VOLUME_MAX;
-        if (output.setVolume(volumeNorm)) mixing.setGlobalGain(1);
-        else mixing.setGlobalGain(volumeNorm);
+        double volumeNorm;
+        if (volume == 0) { // set to avoid fp/rounding error
+            volumeNorm = 0.0;
+        } else if (volume == Player.VOLUME_MAX) { // set to avoid fp/rounding error
+            volumeNorm = 1.0;
+        } else {
+            volumeNorm = ((double) volume) / Player.VOLUME_MAX;
+            double DB_RANGE = 40.0;
+            double DB_VOLTAGE_RATIO = 20.0;
+            double db_ratio = Math.pow(10.0, DB_RANGE / DB_VOLTAGE_RATIO);
+            double ideal_factor = Math.log(db_ratio);
+            volumeNorm = Math.exp(ideal_factor * volumeNorm) / db_ratio;
+        }
+
+        if (output.setVolume((float) volumeNorm)) mixing.setGlobalGain(1);
+        else mixing.setGlobalGain((float) volumeNorm);
     }

     @Override
manfreddz commented 11 months ago

@tofublock, if I remember correctly I found some research paper saying the value should be somewhere between 3 and 4. From that I just tested some values and liked 4 the most. I added the division just because it matched well with my clients and their speakers.