From 468657c374129daf2ba2a2bf4b434602a015a1f6 Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 10 Mar 2026 02:24:50 +0000 Subject: [PATCH] audio: Use logarithmic dB curve for volume slider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A square curve still compresses too much at the top half of the range. Map the slider linearly to -40 dB … 0 dB, then convert to amplitude with 10^(dB/20). This makes each equal step of the slider produce an equal perceived loudness change, so the full 0–100% range feels usable. --- .../main/kotlin/net/jeena/pacer/AudioEngine.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/net/jeena/pacer/AudioEngine.kt b/app/src/main/kotlin/net/jeena/pacer/AudioEngine.kt index 58f7b9e..fefc153 100644 --- a/app/src/main/kotlin/net/jeena/pacer/AudioEngine.kt +++ b/app/src/main/kotlin/net/jeena/pacer/AudioEngine.kt @@ -1,6 +1,7 @@ package net.jeena.pacer import android.media.AudioAttributes +import kotlin.math.pow import android.media.AudioFormat import android.media.AudioTrack import android.os.Handler @@ -23,10 +24,16 @@ class AudioEngine { private const val BEEP_DURATION_MS = 40 private const val SILENCE_CHUNK_MS = 10 - // Apply a square curve so the slider feels linear to human hearing. - // AudioTrack.setVolume() takes a linear amplitude; our 0-1 slider - // value is perceived as "all the volume at the bottom" without this. - private fun toAmplitude(linear: Float): Float = linear * linear + // Map the linear 0-1 slider to a perceptually even loudness curve. + // AudioTrack.setVolume() takes a linear amplitude, but human hearing + // is logarithmic: equal dB steps sound like equal volume steps. + // We map the slider to a -40 dB … 0 dB range, so each 25% of travel + // changes perceived loudness by the same amount. + private fun toAmplitude(linear: Float): Float { + if (linear <= 0f) return 0f + val db = -40f * (1f - linear) // 0.0 → -40 dB, 1.0 → 0 dB + return 10f.pow(db / 20f) + } } private val mainHandler = Handler(Looper.getMainLooper())