From baf51cf2a7a34736252b3b6adedddce9ea1a0c9e Mon Sep 17 00:00:00 2001 From: dasl- Date: Sun, 2 Jul 2023 09:27:53 -0400 Subject: [PATCH 1/4] Add logarithmic volume_control_profile mode --- common.c | 27 +++++++++++++++++++++++++++ common.h | 5 +++++ dbus-service.c | 7 +++++++ player.c | 3 +++ scripts/shairport-sync.conf | 1 + shairport.c | 6 ++++-- 6 files changed, 47 insertions(+), 2 deletions(-) diff --git a/common.c b/common.c index 2c902e3b..f9b83b8d 100644 --- a/common.c +++ b/common.c @@ -37,6 +37,7 @@ #include #include // PRIdPTR #include +#include #include #include #include @@ -1205,6 +1206,32 @@ double flat_vol2attn(double vol, long max_db, long min_db) { } return vol_setting; } + +double logarithmic_vol2attn(double vol, long max_db, long min_db) { + double vol_setting = min_db; // if all else fails, set this, for safety + + if ((vol <= 0.0) && (vol >= -30.0)) { + double vol_pct = 1 - (vol / -30.0); // This will be in the range [0, 1] + if (vol_pct <= 0) { + return min_db; + } + + vol_setting = 1000 * log10(vol_pct) / log10(2); // This will be in the range [-inf, 0] + if (vol_setting < min_db) { + return min_db; + } + if (vol_setting > max_db) { + return max_db; + } + return vol_setting; + } else if (vol != -144.0) { + debug(1, + "Logarithmic volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.", + vol); + } + return vol_setting; +} + // Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an // attenuation depending on the volume and the function's transfer function // See http://tangentsoft.net/audio/atten.html for data on good attenuators. diff --git a/common.h b/common.h index e152db68..ceea2206 100644 --- a/common.h +++ b/common.h @@ -73,6 +73,7 @@ typedef enum { typedef enum { VCP_standard = 0, VCP_flat, + VCP_logarithmic, } volume_control_profile_type; typedef enum { @@ -390,6 +391,10 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode); // dB), return an attenuation depending on a linear interpolation along along the range double flat_vol2attn(double vol, long max_db, long min_db); +// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 +// dB), return an attenuation depending on a logarithmic interpolation along along the range +double logarithmic_vol2attn(double vol, long max_db, long min_db); + // given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 // dB), return an attenuation depending on the transfer function double vol2attn(double vol, long max_db, long min_db); diff --git a/dbus-service.c b/dbus-service.c index 6a3b544f..4795896e 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -765,6 +765,8 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton, config.volume_control_profile = VCP_standard; else if (strcasecmp(th, "flat") == 0) config.volume_control_profile = VCP_flat; + else if (strcasecmp(th, "logarithmic") == 0) + config.volume_control_profile = VCP_logarithmic; else { warn("Unrecognised Volume Control Profile: \"%s\".", th); switch (config.volume_control_profile) { @@ -774,6 +776,9 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton, case VCP_flat: shairport_sync_set_volume_control_profile(skeleton, "flat"); break; + case VCP_logarithmic: + shairport_sync_set_volume_control_profile(skeleton, "logarithmic"); + break; default: debug(1, "This should never happen!"); shairport_sync_set_volume_control_profile(skeleton, "standard"); @@ -1065,6 +1070,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name if (config.volume_control_profile == VCP_standard) shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "standard"); + else if (config.volume_control_profile == VCP_logarithmic) + shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "logarithmic"); else shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat"); diff --git a/player.c b/player.c index c1739ee1..3aa29943 100644 --- a/player.c +++ b/player.c @@ -3432,6 +3432,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c else if (config.volume_control_profile == VCP_flat) scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + else if (config.volume_control_profile == VCP_logarithmic) + scaled_attenuation = + logarithmic_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "player_volume_without_notification: unrecognised volume control profile"); } diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 03cb4339..e2521985 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -42,6 +42,7 @@ general = // volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume. // "standard" makes the volume change more quickly at lower volumes and slower at higher volumes. // "flat" makes the volume change at the same rate at all volumes. +// "logarithmic" makes the volume change logarithmically. // volume_control_combined_hardware_priority = "no"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware mixer first, then the built-in software attenuator. // default_airplay_volume = -24.0; // this is the suggested volume after a reset or after the high_volume_threshold has been exceed and the high_volume_idle_timeout_in_minutes has passed diff --git a/shairport.c b/shairport.c index db5bf2c0..7d200313 100644 --- a/shairport.c +++ b/shairport.c @@ -905,9 +905,11 @@ int parse_options(int argc, char **argv) { config.volume_control_profile = VCP_standard; else if (strcasecmp(str, "flat") == 0) config.volume_control_profile = VCP_flat; + else if (strcasecmp(str, "logarithmic") == 0) + config.volume_control_profile = VCP_logarithmic; else - die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default) " - "or \"flat\"", + die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default), " + "\"logarithmic\", or \"flat\"", str); } From 3479d8d7d4aad9221d417246489221befcc52131 Mon Sep 17 00:00:00 2001 From: David Leibovic Date: Fri, 7 Jul 2023 20:19:34 -0400 Subject: [PATCH 2/4] make use of max_db --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index f9b83b8d..6e3e4ab0 100644 --- a/common.c +++ b/common.c @@ -1216,7 +1216,7 @@ double logarithmic_vol2attn(double vol, long max_db, long min_db) { return min_db; } - vol_setting = 1000 * log10(vol_pct) / log10(2); // This will be in the range [-inf, 0] + vol_setting = max_db + 1000 * log10(vol_pct) / log10(2); // This will be in the range [-inf, max_db] if (vol_setting < min_db) { return min_db; } From ebe1ca17f2eb0e3434c249958316c50624c20d4a Mon Sep 17 00:00:00 2001 From: dasl- Date: Thu, 13 Jul 2023 21:43:25 -0400 Subject: [PATCH 3/4] rename to dasl_tapered --- common.c | 4 ++-- common.h | 9 ++++++--- dbus-service.c | 12 ++++++------ player.c | 4 ++-- scripts/shairport-sync.conf | 4 +++- shairport.c | 6 +++--- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/common.c b/common.c index 6e3e4ab0..edd4351a 100644 --- a/common.c +++ b/common.c @@ -1207,7 +1207,7 @@ double flat_vol2attn(double vol, long max_db, long min_db) { return vol_setting; } -double logarithmic_vol2attn(double vol, long max_db, long min_db) { +double dasl_tapered_vol2attn(double vol, long max_db, long min_db) { double vol_setting = min_db; // if all else fails, set this, for safety if ((vol <= 0.0) && (vol >= -30.0)) { @@ -1226,7 +1226,7 @@ double logarithmic_vol2attn(double vol, long max_db, long min_db) { return vol_setting; } else if (vol != -144.0) { debug(1, - "Logarithmic volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.", + "dasl_tapered volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.", vol); } return vol_setting; diff --git a/common.h b/common.h index ceea2206..c65b4c1f 100644 --- a/common.h +++ b/common.h @@ -73,7 +73,7 @@ typedef enum { typedef enum { VCP_standard = 0, VCP_flat, - VCP_logarithmic, + VCP_dasl_tapered, } volume_control_profile_type; typedef enum { @@ -392,8 +392,11 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode); double flat_vol2attn(double vol, long max_db, long min_db); // given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 -// dB), return an attenuation depending on a logarithmic interpolation along along the range -double logarithmic_vol2attn(double vol, long max_db, long min_db); +// dB), return an attenuation depending on a logarithmic interpolation along along the range. +// The intention behind this attenuation function is that a given percentage change in volume +// should result in the same percentage change in perceived loudness. For instance, a doubling +// (100% increase) of volume level should result in a doubling of perceived loudness. +double dasl_tapered_vol2attn(double vol, long max_db, long min_db); // given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 // dB), return an attenuation depending on the transfer function diff --git a/dbus-service.c b/dbus-service.c index 4795896e..b4a9cc9e 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -765,8 +765,8 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton, config.volume_control_profile = VCP_standard; else if (strcasecmp(th, "flat") == 0) config.volume_control_profile = VCP_flat; - else if (strcasecmp(th, "logarithmic") == 0) - config.volume_control_profile = VCP_logarithmic; + else if (strcasecmp(th, "dasl_tapered") == 0) + config.volume_control_profile = VCP_dasl_tapered; else { warn("Unrecognised Volume Control Profile: \"%s\".", th); switch (config.volume_control_profile) { @@ -776,8 +776,8 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton, case VCP_flat: shairport_sync_set_volume_control_profile(skeleton, "flat"); break; - case VCP_logarithmic: - shairport_sync_set_volume_control_profile(skeleton, "logarithmic"); + case VCP_dasl_tapered: + shairport_sync_set_volume_control_profile(skeleton, "dasl_tapered"); break; default: debug(1, "This should never happen!"); @@ -1070,8 +1070,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name if (config.volume_control_profile == VCP_standard) shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "standard"); - else if (config.volume_control_profile == VCP_logarithmic) - shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "logarithmic"); + else if (config.volume_control_profile == VCP_dasl_tapered) + shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "dasl_tapered"); else shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat"); diff --git a/player.c b/player.c index 3aa29943..a5b0681b 100644 --- a/player.c +++ b/player.c @@ -3432,9 +3432,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c else if (config.volume_control_profile == VCP_flat) scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points - else if (config.volume_control_profile == VCP_logarithmic) + else if (config.volume_control_profile == VCP_dasl_tapered) scaled_attenuation = - logarithmic_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + dasl_tapered_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "player_volume_without_notification: unrecognised volume control profile"); } diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index e2521985..5528712d 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -42,7 +42,9 @@ general = // volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume. // "standard" makes the volume change more quickly at lower volumes and slower at higher volumes. // "flat" makes the volume change at the same rate at all volumes. -// "logarithmic" makes the volume change logarithmically. +// "dasl_tapered" is similar to "standard" - it makes the volume change more quickly at lower volumes and slower at higher volumes. +// The intention behind dasl_tapered is that a given percentage change in volume should result in the same percentage change in +// perceived loudness. For instance, a doubling (100% increase) of volume level should result in a doubling of perceived loudness. // volume_control_combined_hardware_priority = "no"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware mixer first, then the built-in software attenuator. // default_airplay_volume = -24.0; // this is the suggested volume after a reset or after the high_volume_threshold has been exceed and the high_volume_idle_timeout_in_minutes has passed diff --git a/shairport.c b/shairport.c index 7d200313..6d9fb959 100644 --- a/shairport.c +++ b/shairport.c @@ -905,11 +905,11 @@ int parse_options(int argc, char **argv) { config.volume_control_profile = VCP_standard; else if (strcasecmp(str, "flat") == 0) config.volume_control_profile = VCP_flat; - else if (strcasecmp(str, "logarithmic") == 0) - config.volume_control_profile = VCP_logarithmic; + else if (strcasecmp(str, "dasl_tapered") == 0) + config.volume_control_profile = VCP_dasl_tapered; else die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default), " - "\"logarithmic\", or \"flat\"", + "\"dasl_tapered\", or \"flat\"", str); } From 1a555377900592f41999b8397444a57bfea534e9 Mon Sep 17 00:00:00 2001 From: dasl- Date: Thu, 13 Jul 2023 21:49:29 -0400 Subject: [PATCH 4/4] fix typo --- common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index c65b4c1f..0c4d92ec 100644 --- a/common.h +++ b/common.h @@ -388,11 +388,11 @@ char *base64_enc(uint8_t *input, int length); uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode); // given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 -// dB), return an attenuation depending on a linear interpolation along along the range +// dB), return an attenuation depending on a linear interpolation along the range double flat_vol2attn(double vol, long max_db, long min_db); // given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 -// dB), return an attenuation depending on a logarithmic interpolation along along the range. +// dB), return an attenuation depending on a logarithmic interpolation along the range. // The intention behind this attenuation function is that a given percentage change in volume // should result in the same percentage change in perceived loudness. For instance, a doubling // (100% increase) of volume level should result in a doubling of perceived loudness.