bpbel hört über das Mikrofon die Musik in deiner Umgebung und zeigt live:
- 🥁 BPM (Beats per Minute) — das Tempo des laufenden Tracks, erkannt über Kick-Onset-Detection.
- 🔊 Dezibel (dBFS) — die aktuelle Lautstärke, als animiertes Segment-Meter mit Peak-Hold.
Beides wird in einer Material 3 Expressive Oberfläche visualisiert: ein Orb mit dunklem Kern (damit die Zahl lesbar bleibt), der im Takt federt, bei jedem Beat Sonar-Ringe aussendet, von einem Glow-Arc umkreist wird und in lebendigen Verlaufsfarben schimmert.
Die fertige, signierte App gibt es in der Releases-Sektion:
👉 Neueste APK herunterladen — bpbel-vX.Y.Z.apk
APK auf dem Android-Gerät öffnen und installieren (ggf. „Installation aus unbekannten Quellen" für deinen Browser/Dateimanager erlauben). Alle Releases sind mit demselben Schlüssel signiert, sodass Updates direkt über eine bestehende Installation laufen.
Für Maintainer — neues Release veröffentlichen:
versionCode/versionNameinapp/build.gradle.ktserhöhen, committen, dann taggen:git tag v1.0.1 && git push origin v1.0.1Der Release-Workflow baut die signierte APK und hängt sie automatisch an einen neuen GitHub Release.
Der Detektor ist ein energie-basierter Onset-Detector mit IOI-Clustering (Inter-Onset-Interval) —
der klassische, praxiserprobte Ansatz für 4/4-Musik mit klarem Kick (typische Genauigkeit 85–95 %).
Die Kernlogik (BpmAnalyzer.kt) ist ein
1:1-Port eines kampferprobten TypeScript-Detektors und wird durch eine portierte Test-Suite plus
End-to-End-DSP-Tests (23 Tests) gegen das Original abgesichert.
Mikrofon (AudioRecord, 44.1 kHz mono PCM-16)
│
├──▶ Full-band RMS ─────────▶ dBFS ──▶ Lautstärke-Meter
│
└──▶ Kick-Bandpass (Highpass 30 Hz → Lowpass 100 Hz, kick-only)
│
├─▶ RMS-Energie pro 1024-Sample-Frame
├─▶ gleitender Mittelwert (Baseline-Gate)
├─▶ Onset, wenn Energie > Schwelle · Mittelwert
│ UND steigende Flanke (SuperFlux: Energie schlägt ihren
│ eigenen jüngsten Peak → echter neuer Anschlag, kein
│ erneutes Triggern auf gehaltenem Bass)
│ UND Lautstärke-Gate offen (echte Musik, keine Stille)
│ UND Refraktärzeit verstrichen
├─▶ Onsets → IOIs → Median → 60000 / Median = BPM
├─▶ Oktav-Korrektur (60–200 BPM) + Oktav-Snap (kein 120↔240-Flackern)
└─▶ 4-s-Fenster-Mittel = stabiler Anzeigewert (+ Stale-Reset bei Stille)
Die SuperFlux-Flankenerkennung (Böck & Widmer 2013) und das Lautstärke-Gate
sind direkt aus dem produktiven Detektor des disco-controllers
übernommen, der auf demselben BpmAnalyzer aufsetzt: Ersteres unterdrückt den
Geister-Beat direkt nach einem echten Bass auf langsamen, gehaltenen Tracks;
Letzteres verhindert, dass sich die BPM in einem leisen Raum auf Mikrofon-Rauschen
einrastet.
Konfidenz entsteht aus der IOI-Konsistenz (1 − stddev/median): ein gleichmäßiger
Viertel-Puls liefert ~0,9, Sprache/Rauschen ~0,1.
Warum kein FFT/Autokorrelation? Für „welcher Track läuft gerade — 120, 140 oder 170?" ist Onset-Detection deutlich günstiger (kein FFT) und völlig ausreichend.
| Element | Umsetzung |
|---|---|
| Theme | MaterialExpressiveTheme — alle M3-Komponenten erben das feder-basierte expressive MotionScheme. |
| Motion | Beat-Puls über Feder-Physik (spring, Overshoot), Sonar-Ringe pro Beat, umlaufender Glow-Arc, Liquid-Sweep-Gradient, BPM-Count-up. |
| Shapes | Morphing Circle ↔ Star via androidx.graphics:graphics-shapes (Morph, RoundedPolygon). |
| Color | Durchgängig dunkles, kuratiertes Theme (Violett → Magenta → Cyan) — club-style, garantiert lesbare Hell-auf-Dunkel-Readouts. |
| Typografie | Emphasized Type-Scale (Black/Bold) für die Hero-Numerik. |
Voraussetzungen: JDK 17, Android SDK (compileSdk 36), ein Gerät/Emulator mit Mikrofon.
# Debug-APK bauen
./gradlew :app:assembleDebug
# Auf verbundenem Gerät/Emulator installieren
./gradlew :app:installDebug
# oder:
adb install -r app/build/outputs/apk/debug/app-debug.apk
# Release (minified, R8)
./gradlew :app:assembleReleaseBeim ersten Start fragt die App die RECORD_AUDIO-Berechtigung an. Danach genügt es, das Gerät
in die Nähe einer Musikquelle zu halten — der BPM-Wert pendelt sich nach ~5 Sekunden ein.
./gradlew :app:testDebugUnitTestBpmAnalyzerTest synthetisiert Onset-Trains
bei bekannten Tempi (90/120/175 BPM, Half-/Double-Time) und prüft Lock-on, Oktav-Korrektur,
Refraktärzeit, Stale-Reset, Oktav-Snap und Konfidenz — als Beweis, dass der Kotlin-Port das
Verhalten des Referenz-Algorithmus exakt reproduziert.
AudioChainTest treibt die echte
KickBandpass → BpmAnalyzer-Kette mit realistischem Frame-Timing und prüft u. a., dass ein
Backbeat-Snare das Tempo nicht verdoppelt und ein langsamer, gehaltener Bass weiterhin
sauber lockt (die SuperFlux-Flanke unterdrückt Geister-Beats, ohne echte langsame Beats zu
schlucken). Auf Analyzer-Ebene sichern zwei Tests die neuen Gates ab: ein gehaltener Ton
feuert genau einen Onset (kein Re-Trigger) und ein geschlossenes Lautstärke-Gate
unterdrückt selbst laute, periodische Energie komplett.
app/src/main/java/io/celox/bpbel/
├── MainActivity.kt # Permission-Flow + Engine-Lifecycle
├── audio/
│ ├── BpmAnalyzer.kt # Onset/IOI-Tempo-Detektor (portiert)
│ ├── Biquad.kt # RBJ-Biquad + Kick-Bandpass (30→100 Hz, exakt wie inspector-rust)
│ └── AudioEngine.kt # AudioRecord → dBFS + BPM → StateFlow
└── ui/
├── BpmScreen.kt # Screen-Layout + Permission-States
├── BeatPulse.kt # Morphende Spring-Puls-Visualisierung
├── DecibelMeter.kt # Segment-Meter mit Attack/Release + Peak-Hold
└── theme/ # Material 3 Expressive Theme/Color/Type
- dBFS ≠ kalibrierte SPL — relativer digitaler Pegel (abhängig von Mikrofon-Gain/AGC), keine echte akustische Dezibel-Messung.
- Genauigkeit folgt dem Onset-Ansatz: stark auf 4/4-Musik mit klarem Kick, schwächer bei synkopiertem/ambientem Material.
- Hört nur im Vordergrund (kein Background-Service).
PRs willkommen. Bitte vor dem Push ./gradlew testDebugUnitTest laufen lassen.
MIT © pepperonas

