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, up to ten of them, and drag the chips into the order you practise them.
- 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.
- English and German — picked in Settings or followed from the device, stored as Android's own per-app language. Chords can optionally use German note names (H and B).
- Opens audio shared from other apps — via the share sheet or "Open with".
- Session comforts — System/Light/Dark themes with a theme-aware waveform, follow-playhead auto-scroll, keep-screen-on, library backup & restore, an in-app quick-help sheet, and a short "what's new" note on the first launch after an update. 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!
GPL-3.0-only — version 3 of the GNU General Public License, and not "or any later version". The full text is in LICENSE, the copyright notice in COPYRIGHT.
The wordmark and the ring icon are licensed separately, under CC BY 4.0. COPYRIGHT lists the files.
The name is not licensed by either grant — give a fork its own.
-
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).
