From bbeb7d3fd2d27d36ada725120845026fab28580e Mon Sep 17 00:00:00 2001 From: Ben Nicholson Date: Fri, 25 Sep 2026 23:29:40 +1000 Subject: [PATCH] Player: apply source.gain_db as a fixed trim on top of the volume It was in the config and UI but never applied. The gain now saturates at full scale (the trim boosts up to +12 dB). Checked on HLS: -12 dB lowers the peak by exactly 12 dB, +12 dB clips cleanly at 0 dBFS. Co-Authored-By: Claude Opus 5.5 --- main/player.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/main/player.c b/main/player.c index 459ae7d..e32402b 100644 --- a/main/player.c +++ b/main/player.c @@ -55,21 +55,30 @@ static volatile bool s_failover_on_pause; static int s_fade_out; // frames left in the fade-out before a switch static int s_fade_in; // frames left in the fade-in after a switch -// Player volume, applied after the ring so a change is heard at once (not a ring length later). +// Player volume and the fixed output trim (source.gain_db), applied after the ring so a change is +// heard at once (not a ring length later). static volatile float s_target_gain = 1.0f; static float s_gain = 1.0f; // TX task only; ramps to s_target_gain static volatile int s_volume_pct = 100; +static volatile float s_trim = 1.0f; // source.gain_db as amplitude -// 0..100 % -> amplitude: cubic curve (about -18 dB at 50 %, 0 % = mute). +// 0..100 % -> amplitude: cubic curve (about -18 dB at 50 %, 0 % = mute), times the trim. static void set_volume(int pct) { pct = pct < 0 ? 0 : pct > 100 ? 100 : pct; float r = pct / 100.0f; s_volume_pct = pct; - s_target_gain = r * r * r; + s_target_gain = r * r * r * s_trim; } -// Apply the volume, ramping linearly from the previous gain over this buffer (no zipper noise). +// Scale one sample, clipping at full scale (the trim can boost up to +12 dB). +static inline int32_t scale(int32_t x, float g) +{ + float v = x * g; + return v >= 2147483520.0f ? INT32_MAX : v <= -2147483648.0f ? INT32_MIN : (int32_t)v; +} + +// Apply the gain, ramping linearly from the previous gain over this buffer (no zipper noise). static void apply_gain(int32_t *buf, size_t frames) { float target = s_target_gain; @@ -79,8 +88,8 @@ static void apply_gain(int32_t *buf, size_t frames) float step = (target - s_gain) / frames; for (size_t i = 0; i < frames; i++) { s_gain += step; - buf[i * 2] = (int32_t)(buf[i * 2] * s_gain); - buf[i * 2 + 1] = (int32_t)(buf[i * 2 + 1] * s_gain); + buf[i * 2] = scale(buf[i * 2], s_gain); + buf[i * 2 + 1] = scale(buf[i * 2 + 1], s_gain); } s_gain = target; } @@ -215,6 +224,8 @@ void player_apply(const cJSON *source) { s_failover_delay_s = (int)cJSON_GetObjectItemCaseSensitive(source, "failover_delay_s")->valuedouble; s_failover_on_pause = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(source, "failover_on_pause")); + s_trim = powf(10.0f, (float)cJSON_GetObjectItemCaseSensitive(source, "gain_db")->valuedouble / 20.0f); + set_volume(s_volume_pct); s_cfg_mode = mode_of(cJSON_GetObjectItemCaseSensitive(source, "mode")->valuestring); strlcpy(s_cfg_url, cJSON_GetObjectItemCaseSensitive(source, "hls_url")->valuestring, sizeof(s_cfg_url)); apply_mode();