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.
This commit is contained in:
austinried
2026-10-16 13:07:11 +00:00
parent 0b4e1d18bc
commit 31859b66b6
4 changed files with 57 additions and 7 deletions
@@ -17,6 +17,13 @@ internal val REPLAY_GAIN_PREAMP_STEPS =
// storing it. // storing it.
internal fun snapReplayGainPreampDb(value: Float): Float = (value / REPLAY_GAIN_PREAMP_STEP_DB).roundToInt() * REPLAY_GAIN_PREAMP_STEP_DB 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( internal fun replayGainModeLabel(
resources: Resources, resources: Resources,
mode: ReplayGainMode, mode: ReplayGainMode,
@@ -28,6 +28,7 @@ import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Favorite import androidx.compose.material.icons.rounded.Favorite
import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Language import androidx.compose.material.icons.rounded.Language
import androidx.compose.material.icons.rounded.Remove
import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -779,13 +780,44 @@ private fun ReplayGainPreampDialog(
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
) )
Slider( Row(verticalAlignment = Alignment.CenterVertically) {
value = draft, IconButton(
onValueChange = { draft = snapReplayGainPreampDb(it) }, onClick = { draft = stepReplayGainPreampDb(draft, -1) },
valueRange = MIN_REPLAY_GAIN_PREAMP_DB..MAX_REPLAY_GAIN_PREAMP_DB, enabled = draft > MIN_REPLAY_GAIN_PREAMP_DB,
steps = REPLAY_GAIN_PREAMP_STEPS, ) {
) Icon(
Text(replayGainPreampLabel(resources, draft)) 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 = { confirmButton = {
+3
View File
@@ -77,7 +77,10 @@
<string name="settings_replay_gain_mode_album">Album gain</string> <string name="settings_replay_gain_mode_album">Album gain</string>
<string name="settings_replay_gain_mode_off">Off</string> <string name="settings_replay_gain_mode_off">Off</string>
<string name="settings_replay_gain_mode_track">Track gain</string> <string name="settings_replay_gain_mode_track">Track gain</string>
<string name="settings_replay_gain_preamp_decrease">Lower preamp</string>
<string name="settings_replay_gain_preamp_increase">Raise preamp</string>
<string name="settings_replay_gain_preamp_prompt">Adjust level on top of the replay gain</string> <string name="settings_replay_gain_preamp_prompt">Adjust level on top of the replay gain</string>
<string name="settings_replay_gain_preamp_reset">Reset</string>
<string name="settings_replay_gain_preamp_title">Preamp</string> <string name="settings_replay_gain_preamp_title">Preamp</string>
<string name="settings_replay_gain_preamp_value">%1$s dB</string> <string name="settings_replay_gain_preamp_value">%1$s dB</string>
<string name="settings_replay_gain_prompt">Normalise loudness using the gain from the server</string> <string name="settings_replay_gain_prompt">Normalise loudness using the gain from the server</string>
@@ -34,6 +34,14 @@ class ReplayGainControlsTest {
assertEquals(59, REPLAY_GAIN_PREAMP_STEPS) 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 @Test
fun thePreampSnapsBackOntoTheHalfDecibelGrid() { fun thePreampSnapsBackOntoTheHalfDecibelGrid() {
assertEquals(0f, snapReplayGainPreampDb(-0f), 0f) assertEquals(0f, snapReplayGainPreampDb(-0f), 0f)