Looped is a local-first desktop audio app MVP for importing tracks, playing full songs, creating reusable clips, and replaying assigned tracks or clips from a digital Soundboard grid.
This first version is intentionally simple:
- Python is the main application layer.
- PySide6 provides the desktop UI and playback backend.
- SQLite stores track and clip metadata.
- Audio backend interfaces are separated so native C++ or platform-specific routing backends can be added later.
UI (PySide6)- Desktop window for importing folders, browsing tracks, playback controls, and clip management.
Service layer (Python)LibraryServicescans folders, reads metadata, and manages track records.ClipServicecreates and loads clip records.PlaylistServicemanages shared playlists that can hold tracks and clips.SoundboardServicestores the 3x3 Soundboard slot assignments.
Persistence layer (Python + SQLite)- Repositories isolate SQL details from the rest of the app.
Audio backend abstractionAudioBackenddefines playback behavior.QtAudioBackendis the current implementation for fast MVP playback.- Future macOS/Windows capture and routing backends are stubbed behind separate interfaces.
Native audio core (future C++)- Planned for low-latency playback, waveform generation, routing, and advanced audio features.
- The Python layer is structured so a future
pybind11module can replace or augment the MVP backend.
Looped/
├── README.md
├── pyproject.toml
├── docs/
│ └── architecture.md
├── src/
│ └── looped/
│ ├── app.py
│ ├── audio/
│ │ └── backends/
│ │ ├── base.py
│ │ ├── qt_backend.py
│ │ └── routing_stubs.py
│ ├── db/
│ │ └── schema.sql
│ ├── domain/
│ │ └── models.py
│ ├── persistence/
│ │ ├── database.py
│ │ └── repositories.py
│ ├── services/
│ │ ├── clip_service.py
│ │ └── library_service.py
│ └── ui/
│ └── main_window.py
idfilepathtitleartistalbumduration_msimported_at
idsource_track_idtitlestart_msend_mstagscreated_at
The MVP schema includes working tables for:
tracksclipsplaylistsplaylist_itemssettings
Current MVP capabilities:
- Scan/import a folder of
mp3,wav,flac,m4a,aac,ogg - Drag audio files or folders onto the track list to import
- Read track metadata and persist it in SQLite
- List imported tracks
- Create separate playlists for tracks and clips
- Switch between
Library,Clips, andSoundboardviews - Keep one shared playlist list across Library and Clips
- Assign imported tracks or clips to a 3x3 Soundboard grid and play them locally with one click
- Add selected tracks or clips to playlists
- Play/pause/stop selected tracks
- Resume playback with
Playwhen the current track is paused - Repeat the currently playing full track
- Seek selected tracks from the library playback slider
- Delete track records without deleting the underlying file
- Open a dedicated clip editor for a specific track
- Load waveform data lazily only when the clip editor opens
- Create clips from a selected track using start/end timestamps
- Adjust clip start/end visually from waveform handles in the clip editor
- Preview and loop the current clip region in the clip editor
- Replay saved clips
- Edit saved clips in the clip editor
- Delete saved clips
- Show clip hotkeys as a read-only column in Clips view
- Edit track title, artist, and album in SQLite without modifying source files
- Filter tracks and clips with simple text search
The project includes stubs and TODO markers for:
- waveform preview
- hotkeys
- system-audio rolling buffer
- virtual audio output / call routing
- playlists UI
- BlackHole integration on macOS
- WASAPI loopback on Windows
- Python 3.11+
- macOS is the current target
python3 -m venv .venv
source .venv/bin/activate
pip install -e .python -m looped.app- The SQLite database is created at
looped.dbin the project root. - Playback uses Qt Multimedia in the MVP.
- Clip replay seeks into the source file and stops automatically at the clip end.
- If metadata tags are missing, the file stem is used as the track title.
- Deleting a track uses SQLite foreign-key cascades to also remove dependent clips and playlist mappings. The source audio file on disk is kept.
- Importing into a selected track playlist still imports into the master track library, then adds those imported tracks to the selected playlist.
- Waveform preview uses
audioreadto decode a lightweight downsampled envelope for local files and is only computed when the clip editor opens for a track. - The
clipstable includes a nullablehotkeycolumn for a future global-hotkey milestone, but hotkey binding is not implemented yet.
- Keep the first shipping path fully in Python so iteration stays fast.
- Use repository and backend abstractions from day one so replacing internals is low-risk.
- Add waveform generation and clip trimming UX after the basic import/play/save/replay loop is solid.
- Introduce a C++ audio engine only when latency, routing, or decoding performance actually requires it.