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.

Notation is stored side by side, never converted in place. Every chord event carries originalSymbol (exactly what the provider returned), rootPitchClass, keyAwareDisplaySymbol, canonicalRoman, and an externalNotation slot reserved for the later Hooktheory mapping. A symbol whose root is unparsable (N.C.) gets no numeral rather than a fabricated one.

Quality is parsed, never assumed (v0.8.1). Fadr returns Harte notation with a colon-delimited quality token (D#:min, G#:hdim7, A#:sus4). These are read explicitly: min → minor, dim/hdim7 → diminished, aug → augmented, bare or numeric → major. Anything unrecognised is marked other with an unsupported_quality ambiguity flag and reduced normalization confidence — it is never silently treated as major.

Diatonic classification is quality-aware (v0.8.1). Membership in the key requires both the root and the triad quality to match the degree. In C minor E♭ major is III and diatonic; E♭ minor is iii and chromatic. Root-only classification previously reported a diatonic ratio of 1.00 for progressions containing a chromatic-quality chord.

Raw payload durability. The Fadr chord CSV is copied into private Storage the moment it is fetched, before Fadr's signed URL expires, with storage path, checksum, asset id, capture timestamp and parser version recorded in raw_analysis_payloads. Derivations must stay reproducible after the provider link dies.

Chord instability. The measured chord sequence is never edited. A parallel analytical pass flags ultra-short events and rapid parallel-major/minor flipping (F → Fm → F inside a fraction of a beat), preserves all alternatives, lowers harmonic confidence by a recorded multiplier, and names the rule and methodology version that produced the flag. Recogniser wobble is never silently promoted to "modal mixture".

Key corrections re-derive, they do not relabel. A verified key or mode correction re-runs spelling, numerals, complexity and progression matching from the preserved original symbols, re-runs the grounding gate over existing interpretations, archives the previous derivation for audit, and records the rejected key so later provider runs cannot reinstate it.

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.1.b Deterministic harmonic normalization (Pass 2B, service version 2B.1)

A pure-TypeScript, versioned service converts preserved chord events into three representations that sit side by side. It contains no LLM call, no heuristic guessing, and no writes to the canonical genome record: results land in the staging tables harmonic_normalizations and progression_evidence with is_canonical = false.

Versioning. Three independent stamps travel with every row and every event: normalizationVersion (2B.1), hooktheoryMappingVersion, and cleanupVersion, alongside methodologyVersion. Changing any one of them produces a new row rather than mutating an old one, so a stored result can always be attributed to the rules that produced it.

Canonical representation. Each normalized event carries the original provider symbol, root pitch class, parsed quality from a closed vocabulary (maj, min, dim, hdim, aug, dom7, maj7, min7, sus, unsupported), extension and alterations only if present in the source, key-aware display spelling, mode-relative scale degree with accidental, canonical Roman numeral, diatonic flag, start/end/duration, source provenance, normalization confidence, ambiguity flags, and correction status. Bar number, beat position, section id and inversion are explicit nulls until a real beat grid exists — they are never inferred.

Key-relative rules. Degrees are computed against the detected mode's own scale, with natural minor as the diatonic baseline (§2.1.a). A contested or low-confidence key marks every event contested_key and lowers confidence rather than silently picking a winner. There is no default key: a track with no usable tonic or mode produces a run with status blocked and a recorded reason, never a normalization built on an assumed C major. A key correction produces a new immutable run (below).

Immutable runs. A normalization run is identified by a hash of everything that could change its output — the source chord checksum, the assumed tonic, mode and key provenance, the applied correction, and the three ruleset versions. Re-running with different inputs inserts a new row and marks the previous run superseded; every progression evidence row attached to the superseded run is marked invalidated in the same operation. Nothing is overwritten and no evidence can outlive the key it was derived from.

Hooktheory mapping. Hooktheory Trends indexes minor keys against their relative major — verified live, not assumed — so a C-minor tonic is 6, III is 1, VI is 4, VII is 5. The parallel-minor borrowed set is b1=i, b2=ii°, b3=♭III, b4=iv, b5=v, b6=♭VI, b7=♭VII, with ♭II as the separate token Y2; borrowed tokens are emitted in major keys only, because there is no probe evidence for chromatic encoding inside a minor-key song. Song Genome does not infer applied dominants: a major triad on a diatonically-minor degree returns no token and the reason applied_dominant_unverified or function_requires_context, since the chord symbol alone cannot distinguish tonicization from mixture or chromatic colour. Every emitted token is drawn from a registry of tokens observed in live API responses, dated and cited. Mapping never modifies the canonical Roman numeral, and a single unresolved token blocks only the windows containing it.

Progression windows. Because bars and sections are not yet reliable, windows are chord-count based: the whole-song deduplicated sequence plus rolling windows of two to six chords, deduplicated by child path with an occurrence count. The Trends API answers paths of at most four chords, so longer windows and the whole-song path are retained for internal analysis but are marked unqueryable with an explicit reason rather than being sent and silently failing. A window is queryable only when every chord has a verified token, the first token can start a path, and fewer than half its events are unstable.

Analytical cleanup. A separate cleaned sequence collapses consecutive identical chords and merges sub-beat parallel-quality flaps, keeping every displaced event in a cleanup record with the rule name, the removed alternatives and a confidence multiplier. The raw sequence is immutable, no merge crosses a gap longer than two seconds, and no extension or modulation is ever inferred.

Evidence classification. A Trends figure is P(next chord | preceding chord context) in a user-transcribed corpus, and nothing else — it is not the frequency of the progression, not a share of songs, and not a rarity or originality measure. Classifications are therefore named literally: high (≥ 0.10), medium (≥ 0.03) and low (> 0) conditional probability, plus no probability evidence and unsupported query. The words common, uncommon, rare and novel are prohibited at this layer. Thresholds are provisional and uncalibrated — absolute cut points that ignore branching factor and context length — and each stored classification carries that caveat, the sibling count, the context length, and the corpus caveat alongside it. This layer produces no prose, no affect language, and no whole-song verdict.

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.
  • Borrowed chords. Language claiming borrowed chords or modal mixture requires an actually non-diatonic chord in the measured progression, judged against the detected mode's own scale. Because natural minor is the minor baseline, a minor-key III/VI/VII no longer licenses a "borrowed" reading, and such claims are removed rather than displayed.

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. A claim that fails grounding never appears as an accepted Song Genome conclusion: it is stored with an explicit status — rejected for factual conflicts (BPM, key, invented chord, unsupported borrowing), disputed for invented structure, low_confidence_pending_review for shape violations. The pass/fail result, violation list and rejected-claim ledger 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.