Methodology · v1.3.0 · 2026-07-27

Song Genome — Methodology

How a song becomes data, what each number is worth, and what we refuse to claim.

Product spec →

Version: 1.3.0 Last updated: 2026-07-27 Status: Living document. Blobs are stamped with the methodology version they were computed under; regeneration is always opt-in, never silent.

This document defines how a song becomes data in this product: what we measure, what we compute, what we let a model interpret, how confident we are in each of those, how disagreements between sources are resolved, and what we refuse to claim. If a number appears anywhere in the app, its provenance is described here.


1. Core principles

  1. Nothing is invented. A value that was not measured renders as not measured. We never substitute a plausible default, a genre average, or a model guess for a missing measurement.
  2. Every tag is traceable. Each attribute carries a source (provider or formula), a confidence, and a plain-English why. The UI exposes that on demand.
  3. The user can argue with us. Every tag is correctable. Corrections are logged with the original value, the provider, and the methodology version.
  4. Providers are replaceable, the method is not. Measurement vendors sit behind MusicDNAProvider. The schema, confidence model, prompt library, cross-checks, and correction log are ours.
  5. Disagreement is information. When two sources conflict, we show both rather than silently picking a winner.
  6. Interpretation is fenced in by measurement. Model output is fact-checked against the measured layer before a user ever sees it.

2. The three layers

Every attribute in a DNA blob belongs to exactly one layer. The layer determines its default confidence and its visual treatment.

LayerWhat it isDefault confidenceBorder
L1 MeasuredRead from the audio by a first-party analyzer1.0Solid
L2 DerivedComputed by our formulas over L10.9Dashed
L3 InterpretedSynthesized by a language model from L1 + L20.7Muted

Two further layers act on the DNA rather than describing it: L4 Creative (blueprints, prompts, variations, sketches) and L5 Adaptive (the per-user learned profile). They never write into L1–L3.

2.1 Layer 1 — Measured

AttributeSourceNotes
KeyFadrTonic pitch class + mode, with the provider's own confidence
BPMFadrInteger; subject to the half/double reconciliation in §4.1
Time signatureFadr4/4, 3/4, 6/8, …
Chord progressionFadrTimestamped chord symbols; Roman numerals resolved against the measured key
Section boundariesFadrStart/end times only — the labels are L3
Beat / bar gridDerived from tempoNo provider reports beats; see §2.1.a
Loudness (LUFS)Web Audio DSPIntegrated and short-term
StemsFadr, ElevenLabs fallbackvocals / drums / bass / other
Duration, sample rate, channelsFile decode

#### 2.1.a Chord spelling, minor-key Roman numerals, and the beat grid

Three conventions are fixed here because everything downstream — including the deferred Hooktheory normalization layer — depends on them being deterministic.

Raw preservation. The provider's untouched chord string (Fadr's D#:min) is kept on every chord event as raw, and the complete chord CSV is stored verbatim in overrides.fadr.raw_chord_csv. No normalization step is allowed to be lossy; anything we compute must be re-derivable from the raw payload.

Enharmonic spelling. Chord symbols are re-spelled to match the key signature: flat keys (F, B♭, E♭, A♭, D♭, G♭ major; D, G, C, F, B♭, E♭, A♭ minor) use flats, everything else uses sharps. In C minor, Fadr's D# is displayed as Eb. Spelling changes the label only, never the pitch class.

Minor-key convention. Natural minor is the diatonic baseline: i, ii°, III, iv, v, VI, VII — so the ♭3, ♭6 and ♭7 chords of a minor key are written III, VI and VII with no flat sign, because they are already in the scale. A flat or sharp in a minor-key numeral therefore always means genuinely chromatic (♭II is the Neapolitan, #VII is the raised leading-tone region). The raised 7th is treated as functionally diatonic for complexity scoring, so a major V and a vii° in minor do not count as borrowed. Numeral case follows the chord's actual quality, not the scale default.

Beat grid. No provider currently reports beats or downbeats, and an LLM is never permitted to invent them. The grid is therefore derived: a constant- tempo grid at the reconciled BPM, phase-anchored to the first chord change, tagged origin: "derived-from-tempo" with confidence capped at 0.7. It is labelled derived, not measured, everywhere it appears. If a provider later returns a real beat array, the same field carries origin: "provider-reported" at full confidence with no consumer changes.

L1 values are never overwritten by a later layer. Enrichment from a catalog provider is merged into a separate namespace (overrides.musicatlas) so the measured reading stays intact and both are visible.

2.2 Layer 2 — Derived

All L2 math is pure TypeScript — browser Web Audio or a server function. No Python worker is in the 1.0 path.

AttributeFormulaBands
Energy curveSmoothed RMS of the full mix, 50 ms frames, 1 s moving average, min-max normalizedcontinuous 0–1
Section loudnessWindowed RMS per measured sectioncontinuous LUFS
Harmonic complexitynon_diatonic_unique / unique_chords, weighted by unique/total< 0.10 simple · < 0.30 moderate · ≥ 0.30 complex
Vocal presenceShare of frames where the vocal stem exceeds −30 LUFS short-term0–100 %
Stereo widthMean 1 − correlation(L, R) over 500 ms windowscontinuous 0–1
Chord movement classRoman-numeral analysis plus a common-name lookup (I–V–vi–IV → "Pop-Punk Progression", i–VII–VI–V → "Andalusian Cadence")categorical
Section energy / contrastMean and max of the energy curve inside each measured section; contrast is the signed difference against the previous sectioncontinuous 0–1
Section harmonic tensionShare of chords inside a section whose Roman numeral is chromatically altered (borrowed)0–100 %
Chorus liftPaired before/after deltas across one section boundary over loudness, energy, tension and harmonic rhythm; the largest normalized delta is named the driversigned deltas
Harmonic rhythmMean beats per chord change, derived from chord timestamps and BPMcontinuous

Harmonic rhythm exists mainly to serve the tempo reconciliation in §4.1 — it is the cheapest independent witness we have to whether a reported tempo is the one a human would tap.

Roadmap, deliberately not in 1.0: rhythmic density (transients per beat on the drum stem), groove signature (kick/snare offset mean and variance), MERT embeddings, Essentia mood. Each needs a Python runtime we do not run at the edge.

2.3 Layer 3 — Interpreted

Produced by the Lovable AI Gateway (default google/gemini-2.5-flash) from a structured payload of L1 + L2 values.

AttributeShape
Genre / sub-genre1 primary, up to 2 secondary
Mood3–5 adjectives
Era feelDecade-anchored phrase
Sounds-like2–4 artists or tracks, never presented as a licensing clearance
Section labelsVerse / Pre / Chorus / Bridge / Outro mapped onto measured boundaries
Narrative summaryA short paragraph of prose
Why-this-tagGenerated on demand, per tag, in plain English
Mechanisms2–4 evidence-backed explanations of why the song works

The model receives measured values as facts and is instructed never to restate them as its own findings. It cannot introduce a key, a tempo, or a chord.

#### Mechanisms ("Why it works")

A mechanism is the smallest unit of understanding the product ships: a named musical device, one sentence of plain English, and the measured evidence behind it.

``text { title: "The chorus lift", explanation: "The chorus arrives 4 LUFS louder with the vocal doubled, so the entry reads as release.", evidence: ["measured.sections", "measured.loudnessLufs", "derived.vocalPresencePct"] } ``

Grounding rule, enforced after generation and before the blob is written:

  • Any BPM, key, chord symbol, or section named inside a mechanism is checked against the measured layer exactly as narrative prose is (§7). A mechanism carrying an unsupported fact is dropped, not softened.
  • Every evidence entry is a dotted tag path resolved against the stored blob. Paths that do not resolve are removed; a mechanism left with no evidence is pruned.
  • Maximum four mechanisms. Mechanisms inherit the interpreted-layer confidence, so an ungrounded run displays them at reduced confidence or not at all.

The panel renders in the L3 treatment (dashed border) and shows "No grounded mechanisms found." rather than filling space with unsupported claims.

#### 2.3.b Signature (interpreted)

AttributeShapeCountGrounding rule
signature{ title, explanation, evidence: string[] }[]exactly 2–3 (capped at 3)Same guard as mechanisms

Signature answers a different question than mechanisms. Mechanisms explain why the song works; signature names what makes this specific recording itself rather than its genre neighbors. Each claim must point at one concrete trait — a groove habit, a harmonic move, a vocal characteristic, an arrangement decision, or a production fingerprint — and cite at least one L1/L2 evidence path.

Generic claims are forbidden by the interpret prompt: mood adjectives, genre restatement, and praise like "catchy chorus" or "great energy" are rejected. Required specificity looks like "the iv chord borrowed from minor in every chorus" or "vocal presence above 90% with near-zero stereo width — a dry, close vocal."

Grounding is identical to §2.3: named BPM, key, chord, or section values are checked against the measured layer, unresolvable evidence paths are removed, and a claim left with no evidence is pruned. The panel renders directly below "Why it works" in the same dashed L3 card pattern, and shows "No grounded signature claims found." when the run produces nothing defensible.

2.4 Coverage model (v1.3)

Every track carries a coverage strip: the set of expected genome fields and whether each is present, attempted-and-empty, or missing. There are no silent blanks.

StatusMeaningVisual
presentA provider reported an informative valueSolid bar
emptyA provider ran but returned no usable valueDashed bar
missingNo provider has attempted this field yetEmpty bar

A backfill worker re-queues any done track that is missing a provider row, and auto-queues any track still at idle. MusicAtlas enrichment is never skipped silently: if the artist is unknown, the pipeline resolves it via filename pattern, MusicBrainz, and a final LLM pass before giving up.


3. Confidence

Confidence is stored per tag, not per track. It is a property of the value, not of the analysis run.

RangeTreatment
1.0Solid border, primary text
0.85–0.99Solid border, primary text
0.70–0.84Dashed border, muted primary
0.50–0.69Dashed border, muted foreground, low-confidence tooltip
< 0.50Hidden unless low-confidence tags are shown explicitly

Confidence moves for three reasons, and only three:

  • Provider confidence. A provider that reports its own certainty passes it through.
  • Cross-check penalty. A value contradicted by an independent witness is demoted (§4).
  • User correction. A corrected value is 1.0 for that track, permanently.

Confidence is expressed as border weight, never as a colored badge and never as a bare percentage on the tag itself.


4. Cross-checking

Two providers plus our own derivations means three witnesses. Where they agree we say so quietly; where they disagree we show the split.

4.1 Tempo reconciliation

Beat trackers systematically report a song at half or double the tempo a musician would tap. Grid alignment alone cannot distinguish 62.5 from 125 — both fit the grid perfectly. So we add a harmonic-rhythm prior:

  1. Take the reported BPM and its half and double.
  2. For each candidate, compute beats per chord change from the measured chord timestamps.
  3. A plausible hold is 1.5–6 beats per chord. Candidates outside that window are penalized.
  4. If a candidate scores materially better than the reported value, the reading is flagged half-time or double-time, the suggested tempo is shown alongside the provider's, and provider confidence is multiplied by a penalty factor.
  5. Otherwise the verdict is agrees and nothing is demoted.

The guard is deliberately conservative: a true 125 BPM track must not be demoted. Verified against real tracks and synthetic half-time material.

4.2 Disagreement surfacing

Key, tempo, and loudness are compared across Fadr, MusicAtlas, and derivation. A conflict is rendered as a conflict — both readings, both sources, no arbitration — on the track's cross-checks panel. Enharmonic spellings (D♯ vs E♭) are normalized before comparison so we do not report a disagreement that is only orthographic.

4.3 L3 grounding guard

Interpreted prose is fact-checked against the measured layer before display:

  • Numbers. Any BPM or key named in the prose must match the measured value.
  • Chords. Chords are extracted both as "X chord" and as progression notation (Bmaj7–Cdim, F to B♭) and must exist in the measured progression.
  • Sections. Labels must map onto measured boundaries; invented sections are cleared.
  • Sounds-like. The list is trimmed to the allowed maximum.

Violations prune the offending sentences and leave the grounded ones. Blanking a whole summary because of one bad clause destroys more information than it protects. The pass/fail result and the violation list are persisted on the blob, so a grounding regression is auditable after the fact.


5. Provider strategy

All reads go through MusicDNAProvider. Feature code never names a vendor.

ProviderCostRole
AMusicAtlassubscriptionCatalog enrichment by artist + title — genre, influences, catalog key/BPM/loudness. Live, merged as overrides.
BSelf-built — Fadr L1 + Web Audio L2 + Gemini L3per callDefault. Works on any uploaded file, catalog or not.
CCyanitesubscriptionInterface only. Reserved for a commercial licensing customer who requires it.

Enrichment needs an artist name. A track uploaded without one skips enrichment, writes an explicit skipped event rather than failing quietly, and requeues automatically the moment an artist is set.


6. The analysis pipeline

Stages are forward-only, idempotent, and individually retryable:

``text fadr_submit → fadr_poll → derive → interpret → enrich → panel → done ``

StageCeilingOn timeout
Separation / L120 minpark in failed, offer retry
Derivation5 minpark in failed, offer retry
Interpretation3 minpark in failed, offer retry
Enrichment2 minpark in failed, offer retry
Panel5 minpark in failed, offer retry

A scheduled tick advances in-flight jobs every minute, so closing the tab does not cancel analysis. Every transition writes a row to analysis_events — stage, status, attempt, duration, error code — and the track page renders that ledger verbatim. User-facing errors are plain language; raw provider detail is admin-only.

Because every stage is idempotent and wall-clock bounded, and every paid provider sits behind a per-user daily cap, a duplicate tick can only move a job its owner already started.

6.1 Coverage backfill and failure recovery

Coverage is not best-effort. The same tick that advances live jobs also repairs gaps:

  1. Any track sitting at idle is queued, so no upload is left un-analyzed.
  2. For finished tracks, the canonical genome's provider set is inspected. A missing measurement, enrichment, interpretation, or panel row re-queues only that stage — not the whole pipeline.
  3. A parked failure older than 15 minutes is given one automatic second life at the stage where it stopped, with attempts reset. Most parked failures are transient — model output-shape drift or a provider hiccup — and should not require the user to find a Retry button.

Interpretation output is parsed tolerantly by design. When a model returns the right claim in the wrong shape (a bare string, { mechanism, description }, evidence as a single string), the claim is repaired into the canonical { title, explanation, evidence } form and only discarded when it contains no usable text. A schema mismatch must never cost a whole analysis run; grounding, not shape, is what disqualifies a claim.


7. Correction log and learned profile

Every correction writes:

``text track_id, user_id, tag_path, original_value, corrected_value, provider, methodology_version, created_at ``

Corrections surface as overrides on subsequent reads, so the user's truth wins for that track from then on. In aggregate they drive threshold retuning between methodology versions — a single user's correction never silently changes the method for everyone.

Corrections and 👍/👎 feedback on generated artifacts aggregate into user_preferences, which steers later interpretation and creative prompts. That profile is a bias on generation, never a rewrite of measurement.


8. What we will not claim

  • Not a licensing clearance. Sounds-like references are creative signposts. They are not a rights opinion and must not be used as one.
  • No lyric transcription in 1.0. No lyrics pipeline exists, so nothing prosodic is measured — the Lyricist agent reasons from structure and rhythm, not from transcribed words.
  • No emotional truth claims. Mood adjectives describe the recording's affect as a model reads it, not the writer's intent.
  • No cross-user inference. Similarity search is inert until the corpus is large enough to be meaningful; we would rather ship nothing than ship a ranked list of three.

9. Versioning

ChangeRequires
New L1 attribute or provider swapMinor bump, blobs re-runnable
Threshold or band change in L2Minor bump, old blobs keep their old stamp
L3 prompt library or grounding rule changeMinor bump
Schema change to the DNA objectMajor bump, explicit migration
Copy-only edit to this documentNo bump

Old blobs are never rewritten in place by a version bump. Re-analysis is a user action.


10. Comparison mode (designed, not shipped)

Two tracks are compared as the Euclidean distance between their normalized L2 vectors:

``text d = √Σ (aᵢ − bᵢ)² for i in {energy_mean, harmonic_complexity, vocal_presence, stereo_width} ``

The model then narrates the gap, weighted toward the axes with the highest per-axis variance. Radar above, prose below. Documented here so it does not get redesigned from scratch later.

The aggregate distance d is computed internally and never displayed to the user; comparison is always presented as named dimensions (energy, harmonic complexity, vocal presence, stereo width) with the narration weighted toward the axes of highest variance. A single global similarity score or percentage must not appear anywhere in the product.

Section-level comparison (comparing chorus to chorus, verse to verse, using the measured section boundaries) is the known deepening of this design and is deferred, not forgotten.


Section-derived lift metrics arrived in methodology 0.5.0. They add no provider call: every value is arithmetic over the L1 section list and the L2 energy curve already stored in the blob. Where the section list degrades to a single whole-track span, the lift analyzer refuses to draw rather than compare a section with itself. Vocal presence and stereo width remain track-level measurements and are labelled as context, never split across sections they were never measured on.

Methodology version is stamped on every stored DNA blob. When this document changes materially, bump `METHODOLOGY_DOC_VERSION` and note the change in the release notes.