From 31859b66b657b3d10ecef138d527f60f40d82cd9 Mon Sep 17 00:00:00 2001
From: austinried <4966622+austinried@users.noreply.github.com>
Date: Fri, 16 Oct 2026 05:29:22 +0000
Subject: [PATCH] settings: make the preamp easier to set exactly
The slider alone is awkward for the two things people actually do with a
preamp: zero it, and nudge it by a known amount.
- Minus and plus buttons flank the slider, stepping one grid notch (0.5
dB) and disabling at the ends.
- A Reset button sits under it, beside the current value, disabled when
the value is already zero.
- Both go through the same snapping the slider does, so a button cannot
land off the grid.
---
.../ui/settings/ReplayGainControls.kt | 7 +++
.../subtracks/ui/settings/SettingsScreen.kt | 46 ++++++++++++++++---
app/src/main/res/values/strings.xml | 3 ++
.../ui/settings/ReplayGainControlsTest.kt | 8 ++++
4 files changed, 57 insertions(+), 7 deletions(-)
diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt b/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt
index 833fef72..534ae664 100644
--- a/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt
+++ b/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt
@@ -17,6 +17,13 @@ internal val REPLAY_GAIN_PREAMP_STEPS =
// storing it.
internal fun snapReplayGainPreampDb(value: Float): Float = (value / REPLAY_GAIN_PREAMP_STEP_DB).roundToInt() * REPLAY_GAIN_PREAMP_STEP_DB
+internal fun stepReplayGainPreampDb(
+ value: Float,
+ steps: Int,
+): Float =
+ snapReplayGainPreampDb(value + steps * REPLAY_GAIN_PREAMP_STEP_DB)
+ .coerceIn(MIN_REPLAY_GAIN_PREAMP_DB, MAX_REPLAY_GAIN_PREAMP_DB)
+
internal fun replayGainModeLabel(
resources: Resources,
mode: ReplayGainMode,
diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt
index 7df3d06b..b82ab683 100644
--- a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt
+++ b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt
@@ -28,6 +28,7 @@ import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Favorite
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Language
+import androidx.compose.material.icons.rounded.Remove
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -779,13 +780,44 @@ private fun ReplayGainPreampDialog(
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
)
- Slider(
- value = draft,
- onValueChange = { draft = snapReplayGainPreampDb(it) },
- valueRange = MIN_REPLAY_GAIN_PREAMP_DB..MAX_REPLAY_GAIN_PREAMP_DB,
- steps = REPLAY_GAIN_PREAMP_STEPS,
- )
- Text(replayGainPreampLabel(resources, draft))
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ IconButton(
+ onClick = { draft = stepReplayGainPreampDb(draft, -1) },
+ enabled = draft > MIN_REPLAY_GAIN_PREAMP_DB,
+ ) {
+ Icon(
+ Icons.Rounded.Remove,
+ contentDescription = stringResource(R.string.settings_replay_gain_preamp_decrease),
+ )
+ }
+ Slider(
+ value = draft,
+ onValueChange = { draft = snapReplayGainPreampDb(it) },
+ valueRange = MIN_REPLAY_GAIN_PREAMP_DB..MAX_REPLAY_GAIN_PREAMP_DB,
+ steps = REPLAY_GAIN_PREAMP_STEPS,
+ modifier = Modifier.weight(1f),
+ )
+ IconButton(
+ onClick = { draft = stepReplayGainPreampDb(draft, 1) },
+ enabled = draft < MAX_REPLAY_GAIN_PREAMP_DB,
+ ) {
+ Icon(
+ Icons.Rounded.Add,
+ contentDescription = stringResource(R.string.settings_replay_gain_preamp_increase),
+ )
+ }
+ }
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Text(replayGainPreampLabel(resources, draft))
+ Spacer(Modifier.weight(1f))
+ TextButton(
+ onClick = { draft = 0f },
+ enabled = draft != 0f,
+ ) { Text(stringResource(R.string.settings_replay_gain_preamp_reset)) }
+ }
}
},
confirmButton = {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b266bccf..439d8c73 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -77,7 +77,10 @@
Album gain
Off
Track gain
+ Lower preamp
+ Raise preamp
Adjust level on top of the replay gain
+ Reset
Preamp
%1$s dB
Normalise loudness using the gain from the server
diff --git a/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt b/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt
index b6a5032d..5b08e816 100644
--- a/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt
+++ b/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt
@@ -34,6 +34,14 @@ class ReplayGainControlsTest {
assertEquals(59, REPLAY_GAIN_PREAMP_STEPS)
}
+ @Test
+ fun thePreampStepsAlongTheGridAndStopsAtTheEnds() {
+ assertEquals(-1.5f, stepReplayGainPreampDb(-1f, -1), 0f)
+ assertEquals(-0.5f, stepReplayGainPreampDb(-1f, 1), 0f)
+ assertEquals(MIN_REPLAY_GAIN_PREAMP_DB, stepReplayGainPreampDb(MIN_REPLAY_GAIN_PREAMP_DB, -1), 0f)
+ assertEquals(MAX_REPLAY_GAIN_PREAMP_DB, stepReplayGainPreampDb(MAX_REPLAY_GAIN_PREAMP_DB, 1), 0f)
+ }
+
@Test
fun thePreampSnapsBackOntoTheHalfDecibelGrid() {
assertEquals(0f, snapReplayGainPreampDb(-0f), 0f)