From 79a18445122231eed57da99b87bb3145c8441a22 Mon Sep 17 00:00:00 2001 From: Reactorcoremeltdown Date: Tue, 21 Jul 2026 09:07:06 +0200 Subject: [PATCH] Tap tempo by tapping the BPM readout (v0.15.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/build.gradle.kts | 4 +-- .../sizzle/ui/tracker/TrackerScreen.kt | 29 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b54104b..708ec71 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,8 +22,8 @@ android { // (android.media.midi) and AAudio low-latency audio we rely on. minSdk = 26 targetSdk = 34 - versionCode = 38 - versionName = "0.14.0" + versionCode = 39 + versionName = "0.15.0" // We provide our own instrumentation runner if/when tests are added. testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/space/rcmd/android/sizzle/ui/tracker/TrackerScreen.kt b/app/src/main/java/space/rcmd/android/sizzle/ui/tracker/TrackerScreen.kt index 9fbf013..f56523d 100644 --- a/app/src/main/java/space/rcmd/android/sizzle/ui/tracker/TrackerScreen.kt +++ b/app/src/main/java/space/rcmd/android/sizzle/ui/tracker/TrackerScreen.kt @@ -4,6 +4,7 @@ package space.rcmd.android.sizzle.ui.tracker import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -19,8 +20,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign 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. RetroButton(if (isPlaying) "❚❚ PAUSE" else "▶ PLAY", 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() } Row(verticalAlignment = Alignment.CenterVertically) { RepeatButton("-", onStep = { vm.setTempo(project.tempoBpm - 1) }) Text( "${project.tempoBpm.toInt()} BPM", color = c.text, fontFamily = FontFamily.Monospace, fontSize = 12.sp, 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) }) } @@ -224,3 +244,8 @@ private fun EditButtons(vm: AppViewModel) { // Swap the bottom half between the arranger and the punch-in keyboard. 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