An Android tool for practicing song parts. Pick an audio file, mark a start/stop region on its waveform, and play it back in a seamless, gapless loop — slowed down without changing pitch, and snapped to an editable beat grid. Chain loops into practice arrangements to drill transitions.
Early-stage release — expect rough edges. Feedback and bug reports welcome via Issues.
- Import-and-own library — picked files are copied into app-private storage, so loops survive the original being moved or deleted. The library doubles as the home screen, with recents and custom, renameable track titles.
- Waveform loop editing — drag start/end handles to mark a section; it decodes to PCM and loops via
AudioTrackloop points, so the boundary wraps with zero audible gap or click. - Editable beat grid — a dependency-free estimator finds BPM and a downbeat, and handles snap to the grid. Refine it by hand at any time: tap the tempo, nudge ±1 BPM, or halve/double it.
- Pitch-preserving speed — slow down or speed up from 0.5× to 1.5× with a WSOLA time-stretch that keeps the original pitch.
- Saved loops — capture a region + grid per track and recall it later.
- Practice arrangements — chain saved loops into an ordered sequence, each repeated a set number of times and optionally looping the whole set — for drilling A–B–A transitions.
- Session comforts — System/Light/Dark themes with a theme-aware waveform, follow-playhead auto-scroll, keep-screen-on, library backup & restore, and an in-app quick-help sheet. Preferences persist across launches.
- Language: Kotlin (Java 17 target)
- UI: Jetpack Compose (Canvas for the waveform, grid, and handles)
- Build: Android Gradle Plugin 9.2.1 + Gradle 9.5.1 (via wrapper)
- SDK:
minSdk26 ·compile/targetSdk36 - Decode:
MediaExtractor+MediaCodec→ PCM - Playback: low-level
AudioTrackwith sample-accurate loop points - Beat detection & time-stretch: custom (WSOLA), no native/NDK or GPL dependencies
- Async: Kotlin Coroutines + Flow
Requires a JDK (17+) and the Android SDK. Point the build at your SDK by creating a local.properties file in the project root (this file is git-ignored):
sdk.dir=/path/to/Android/SdkThen build a debug APK:
./gradlew assembleDebugThe APK lands at app/build/outputs/apk/debug/app-debug.apk; sideload it to a device to run it.
app/src/main/java/de/singular/looper/
MainActivity.kt Compose entry point
LooperViewModel.kt app state, orchestrates decode / detect / play
audio/
AudioDecoder.kt file URI -> PCM + metadata
LoopPlayer.kt AudioTrack wrapper: loop points, play/pause/seek
BeatDetector.kt tempo + downbeat estimation
TimeStretch.kt pitch-preserving WSOLA speed change
DecodedAudio.kt / WaveformData.kt PCM + envelope models
library/
LibraryRepository.kt import-and-own storage + metadata index
LibraryTrack.kt / SavedLoop.kt / ArrangementStep.kt
ui/
LoopWaveform.kt waveform + grid + handle rendering
If Rubber Ring is useful to you, you can support its development on Ko-fi. Thank you!
-
This app is built around my own musical style, particularly the chords and tempi I use. While looping will work regardless of the imported audio, chord and beat detection may not work perfectly for Prog-Polka-Fusion in 7/8.
-
This project was developed with the assistance of Claude under my direction and functional review. The code has been analysed with Codacy (detekt).
