/library/decisions/dj-agent
ADR 0002 — CLAP embeddings from the start
Vibe similarity search requires audio embeddings. The original scaffold shipped an engineered 28-d vector (BPM, key, energy, MFCCs packed by hand) as a stopgap, deferring CLAP (Contrastive Language…
mirrored from ~/dev/dj-agent/docs/adr/0002-clap-from-the-start.md · commit 11c7cf2 · synced 2026.07.09Accepted (supersedes the original "engineered embedding first" plan)
Status: Accepted (supersedes the original "engineered embedding first" plan) Date: 2026-06-01
Context
Vibe similarity search requires audio embeddings. The original scaffold shipped an engineered 28-d vector (BPM, key, energy, MFCCs packed by hand) as a stopgap, deferring CLAP (Contrastive Language-Audio Pretraining — 512-d learned audio+text embeddings) to a later "Phase 5."
The engineered vector had two structural problems:
- No semantics. It encoded signal statistics, not "dreamy / nocturnal / driving." Mood prompts couldn't work.
- Tangled concerns. It crammed BPM and key into the embedding, mixing the hard mixing constraints with the soft vibe signal.
Decision
Use CLAP from the start (laion/larger_clap_music, 512-d). Remove the
engineered embed.py. Store two representations per track: the CLAP vector
for semantics, and plain structured columns for the hard mixing constraints.
Rationale
- Semantic + text→audio search on day one. CLAP embeds audio and text into
one space, so
nearest_to_text("dreamy nocturnal")works as soon as the library is ingested — the original blocker that justified CLAP. - Cleaner separation. Semantics live in the vector; BPM/key/energy live in SQL columns the Selector filters on. No more packing tempo into the embedding.
- Cost is acceptable and one-time. CLAP runs locally on Apple Silicon (MPS), ~1–3 s/track. That's an ingestion cost; queries stay sub-millisecond. The user explicitly accepted the ingestion hit to build the real thing once.
- No throwaway work. Building the engineered vector first would mean writing code we'd delete. Going straight to CLAP avoids that.
Trade-offs accepted
- Heavier deps:
torch+transformers(~hundreds of MB) and a one-time ~1.5 GB model download. - Slower ingestion than the engineered vector (~0.1 s/track) — but only at ingestion, and the user is fine with it.
- Tests for CLAP are slow (model download/inference) → marked
slowand deselected from the defaultpytestrun.
Consequences
VIBE_DIM = 512;schema.sqlusesvector(512).vibe/clap.pyis the semantic core;audio/analyze.pyis slimmed to structured features only (no MFCC/spectral).- A metadata layer (
metadata.py,mutagen) provides a cheap keyword-tag filter that complements CLAP.