How 20,000 scattered audio files became a searchable, playable catalog
After 15+ years of making music, audio files end up everywhere. OneDrive, iCloud, external drives, Ableton project folders, sample packs from 2008. You know you have that perfect vocal chop somewhere. You just can't find it.
Every producer hits this wall. The library grows faster than any organizational system can keep up with. Drag-and-drop DAW workflows encourage scattering. Cloud sync creates duplicates. Drive migrations leave orphans.
scan, stats, search,
doctor. First run indexed 1,948 files.
ThreadingHTTPServer. Three tabs: Catalog (browse +
search + filter), Sources (scan roots with stats), Duplicates
(exact + near-match detection). Audio player bar for auditioning
files.
pystray. Runs in the system
tray, opens browser on click. Persistent access without a terminal
window.
<audio> element can't reliably play
local files through an HTTP server. Range request issues, codec
limitations, seeking bugs. Web audio is not the answer for a tool
that needs to play 20,000+ files across every format.
miniaudio, a native C
library), the catalog database, and scanning. The UI is a webview
window that sends commands through a JS bridge. Users can
customize or replace the UI entirely.
ctk.audio.play(), ctk.catalog.search(),
ctk.sources.scan(). The UI never touches the database
directly. It sends commands, receives data. Same pattern as
WaveLoop's tape engine — clear separation between behavior
and presentation.
The toolkit follows a strict separation: core behaviors handle data and audio natively, while the UI layer is a webview that communicates through a JS bridge. This means the audio engine runs at the OS level (not in a browser sandbox), and the interface can be swapped, themed, or rebuilt without touching the core.
Built with Python, SQLite (WAL mode), miniaudio (native C audio), and pywebview (native OS windows). No Electron. No Rust. Just the simplest stack that solves the problem.
Read the full build guide: Build Your Own Audio Asset Dashboard with Claude