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 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 23:29:40 +10:00
parent c9e06da47c
commit bbeb7d3fd2
+17 -6
View File
@@ -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();