feat(meter): loudness-accent stream for meter + downbeats on non-4/4 (PR-G4) - #213
Merged
Conversation
…acy PR-G3) Adds bpmOctaveEvidence (full-mode only): simple-ratio candidates (1:2, 2:3, 1:1, 3:2, 2:1 of the shipped bpm, clamped 40-220) scored against two inter-onset streams — low-band kick articulation (integer multiples, fit divided by the median multiple) plus the full-band hat/snare grid (admits the 8th-note 0.5 multiple), which pins the notated tempo when the kick only marks bars. Never overrides bpm (invariant #1); abstains (null) on sparse/beatless material. Measured on the corpus: recovers all three broken octave cases (174 -> 173.8, 190 -> 189.8, halftime-174 -> 175.5 via 3:2) with zero false alarms on the 10 correctly-shipped rhythm clips; the beatless ambient clip abstains. Promoted as an active tempoOctave:preferredBpm gate on 14 rhythm clips (13 pass; grid_7_8_140 stays informational — its bpm is meter-smeared, not octave-wrong: PR-G4 territory). Both sides landed: JSON_SCHEMA.md + full-only docstring, measurement.ts types, backendPhase1Client reconstructor, phase1FullPayload fixture; golden deliberately re-baselined (bpmOctaveEvidence key + one rounding-step lufsShortTermMax nudge within designed tolerance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o-fixture key set Two omissions from PR-G3 that CI caught on the rebased branch: - server_phase1._build_phase1 did not forward bpmOctaveEvidence to the frontend (test_phase1_golden.BuildPhase1KeySupersetTests caught it) - EXPECTED_TOP_LEVEL_KEYS in test_audio_fixture.py did not include it (AudioFixtureSmokeTest.test_output_contains_all_expected_keys caught it) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NCtxwnhn8nzHBjzeehPtf8
…(accuracy PR-G4) The onset-count accent stream is loudness-blind: a kick on every beat yields identical counts at every bar position regardless of which beat is accented — measured on the odd-meter corpus clips as 3/4 edging 4/4 by 0.8%, shipping 4/4 at confidence 0.03. analyze_time_signature now folds the per-beat low-band loudness (beat_data, hoisted above the meter call) at each candidate bar length as a second accent stream: max-over-phase dominance, clamped at 10, neutral under 4 bars, harmonic folds collapsed by echo PROMINENCE (0.262 true-3/4 vs 0.000 true-6/8 measured; raw echo/peak is ~0.72 for both). Decision runs on the count x loudness product with a 15% override margin when loudness contributed; count-only callers (including --fast) keep the 20% margin byte-identical. Burn-down: 3/4, 6/8, 7/8 meter AND downbeats (F1 0.875 each) flip from knownGaps to gating checks; all ten 4/4 rhythm clips (incl. broken-kick 2-step/halftime/breakbeat) unchanged. Remaining gaps are all tempo-family. timeSignatureCandidates entries gain an additive loudnessDominance key, landed on both sides; golden passes without re-baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
PR-G4 of the genre-generalization program (stacked on #212): meter and downbeat detection learn to hear loudness accents, closing the measured odd-meter gaps. The onset-count accent stream that
analyze_time_signatureran on is loudness-blind — a kick on every beat yields identical counts at every bar position no matter which beat is accented, which is exactly how the corpus odd-meter clips (and most real four-on-the-floor material in 3/4, 6/8, 7/8) are shaped. Measured before this PR: 3/4's best candidate edged 4/4 by 0.8% (dominance 1.067 vs 1.059, position means all ~1.0), nowhere near the 20% override margin, shipping 4/4 at confidence 0.03.Design
beat_data— the same signal the downbeat phase heuristic already uses — folded at each candidate bar length, dominance maximized over bar phase, clamped to 10 (near-silent off-beat positions explode the ratio without carrying more information), neutral (1.0) when the fold has fewer than 4 bars or the caller has nobeat_data.count-dominance × loudness-dominanceproduct, overriding 4/4 at a 15% margin when loudness contributed (calibrated: 6/8's true combined margin is ~17%) and the original 20% count-only margin otherwise — legacy callers and--fast(which pass nobeat_data) are byte-identical.beat_dataextraction is hoisted above the meter call inanalyze.py(one extraction, same object reused by rhythm detail/groove/sidechain as before).timeSignatureCandidatesentries gain an additiveloudnessDominancekey, landed on both sides (schema doc, TS type, client parser — which would otherwise have silently dropped it, tripwire Preserve signal monitor playback during analysis and harden run flow #3 — and the full-payload fixture).Measured burn-down (the PR's gate)
_KNOWN_GAPS_BY_IDshrinks from 5 entries / 11 checks to 4 entries / 9 checks; every remaining entry is tempo-family (octave halving at 174/190, halftime-174's 2:3 error, 7/8's smear) owned by the PR-G3 promotion gate, plus the PR-G5 shuffle gap.Behavior change disclosure
This PR changes the shipped
timeSignature(and therefore downbeats) on material with loudness-accented non-4/4 bars — that is its purpose. The pre-registered gate is the synthetic corpus: all 38 clips green, 113 active checks passed / 0 failed / 10 informational. What it does NOT change: any caller withoutbeat_data(including--fast), the golden (passes without re-baseline — the pipeline reorder shifted no curated value), or 4/4 material with flat or broken-kick accents (measured: all ten 4/4 rhythm clips unchanged). Honest caveat: the 15% accent margin is calibrated on synthetic accents; real-audio meter accuracy has no staged corpus yet (GTZAN-Rhythm is beat/downbeat-annotated and can serve the downbeat half via the existing beat-eval harness when the beat_this gate work resumes).Validation
unittest discover— 1357 tests, OK (3 skipped) (exit code captured directly, not piped).npm run verify: 51 smoke passed (one deep-equal unit test updated for the new candidate key).test_time_signature_accent.py(dominance math, neutrality guards, cap, harmonic collapse both directions, no-beat_data fallback).🤖 Generated with Claude Code