Tap tempo by tapping the BPM readout (v0.15.0)

Tapping the "### BPM" label sets the tempo from the average interval of recent
taps (up to 8, refining as you go); a pause over 2 s starts a fresh count. The
−/+ nudge buttons are unchanged. Purely UI-side — a small remembered list of tap
timestamps, no model/engine changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Reactorcoremeltdown
2026-07-21 09:07:06 +02:00
parent 8c44ed5151
commit 79a1844512
2 changed files with 29 additions and 4 deletions

View File

@@ -22,8 +22,8 @@ android {
// (android.media.midi) and AAudio low-latency audio we rely on. // (android.media.midi) and AAudio low-latency audio we rely on.
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 38 versionCode = 39
versionName = "0.14.0" versionName = "0.15.0"
// We provide our own instrumentation runner if/when tests are added. // We provide our own instrumentation runner if/when tests are added.
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -4,6 +4,7 @@
package space.rcmd.android.sizzle.ui.tracker package space.rcmd.android.sizzle.ui.tracker
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -19,8 +20,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -147,14 +150,31 @@ private fun TransportButtons(vm: AppViewModel) {
// Fixed width so the button doesn't resize as the label toggles. // Fixed width so the button doesn't resize as the label toggles.
RetroButton(if (isPlaying) "❚❚ PAUSE" else "▶ PLAY", RetroButton(if (isPlaying) "❚❚ PAUSE" else "▶ PLAY",
active = isPlaying, width = 112.dp, onClick = vm::playPause) active = isPlaying, width = 112.dp, onClick = vm::playPause)
// Tempo nudger: tap -/+ around a fixed-width readout. // Tempo nudger: tap -/+ around a fixed-width readout. Tapping the readout itself
// is TAP TEMPO — two or more taps in time set the tempo from the average interval.
val tapTimes = remember { ArrayList<Long>() }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
RepeatButton("-", onStep = { vm.setTempo(project.tempoBpm - 1) }) RepeatButton("-", onStep = { vm.setTempo(project.tempoBpm - 1) })
Text( Text(
"${project.tempoBpm.toInt()} BPM", "${project.tempoBpm.toInt()} BPM",
color = c.text, fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = c.text, fontFamily = FontFamily.Monospace, fontSize = 12.sp,
textAlign = TextAlign.Center, maxLines = 1, textAlign = TextAlign.Center, maxLines = 1,
modifier = Modifier.width(72.dp).padding(vertical = 6.dp), modifier = Modifier
.width(72.dp)
.pointerInput(Unit) {
detectTapGestures {
val now = System.currentTimeMillis()
// A long pause starts a fresh count rather than averaging across it.
if (tapTimes.isNotEmpty() && now - tapTimes.last() > TAP_RESET_MS) tapTimes.clear()
tapTimes.add(now)
while (tapTimes.size > TAP_MAX) tapTimes.removeAt(0)
if (tapTimes.size >= 2) {
val span = tapTimes.last() - tapTimes.first()
if (span > 0) vm.setTempo(60_000f * (tapTimes.size - 1) / span)
}
}
}
.padding(vertical = 6.dp),
) )
RepeatButton("+", onStep = { vm.setTempo(project.tempoBpm + 1) }) RepeatButton("+", onStep = { vm.setTempo(project.tempoBpm + 1) })
} }
@@ -224,3 +244,8 @@ private fun EditButtons(vm: AppViewModel) {
// Swap the bottom half between the arranger and the punch-in keyboard. // Swap the bottom half between the arranger and the punch-in keyboard.
RetroButton("⌨ KBD", active = vm.punchInMode, onClick = vm::togglePunchIn) RetroButton("⌨ KBD", active = vm.punchInMode, onClick = vm::togglePunchIn)
} }
// Tap-tempo tuning: a gap longer than this starts a fresh count; keep at most this
// many recent taps in the averaging window.
private const val TAP_RESET_MS = 2000L
private const val TAP_MAX = 8