feat(rhythm): 16th-grid shuffle detection in the swing measurement (PR-G5) - #214
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>
…racy PR-G5) compute_swing_detail probes the 16th grid when the 8th grid reads straight or absent: same long/short-split logic at half scale (straight center 0.25, thresholds 0.265/0.235). UKG/2-step shuffle lives on the 16ths and was previously invisible. 8th-swung material keeps priority and is byte-identical; straight material cannot fabricate a shuffle (no split at 0.25); sparse material still abstains. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…me-resolution gap (PR-G5) New swing:gridResolution harness check (expected.swingGrid) promotes shuffle detection to an active gate on shuffle16_130_62 — swingDetail now reads swung/16th/conf 0.992 where it read None. The ratio value stays a knownGap with its measured diagnosis: onset detection quantizes to 11.6ms hops with a one-frame-per-side inward bias, compressing 62% shuffle to a 55% read (verified not a fixture artifact: a 50ms closed hat renders byte-identical medians). Value correction without a gate would repeat the confidently-wrong pattern; it stays a future, separately gated onset-timing refinement. 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-G5 of the genre-generalization program (stacked on #213): the swing measurement learns to see the 16th-note grid — UKG/2-step shuffle — which was previously invisible (
compute_swing_detailkept only 8th-scale IOIs, 0.30–0.70 beats, and hardwiredgridResolution: "8th"; the shuffle corpus clip measuredswingDetail: None).Design
The 8th grid keeps absolute priority and its outputs are byte-identical: swung-8th material returns exactly what it did before, and so does straight material. Only when the 8th grid reads straight or absent is the 16th grid probed, with the same long/short-split logic at half scale (straight center 0.25 beats, split thresholds 0.265/0.235). A 16th result only ships when a genuine long/short alternation exists (≥2 of each) — straight 16ths cluster at 0.25 and cannot fabricate a shuffle; sparse material still abstains with
None.Measured results — an honest split between detection and value
On
shuffle16_130_62(four-on-floor kicks + 62%-shuffled 16th hats):swingDetailreads{gridResolution: "16th", direction: "swung", swingConfidence: 0.992}where it previously readNone. A newswing:gridResolutioncheck (additive harness capability,expected.swingGrid) promotes this to an active gate.Regression guard: the five existing 8th-grid swing clips (50–66%) and every other rhythm clip are unchanged — 7 unit tests in
test_swing_detail.pycover 16th recovery at 58/62/66 from clean IOIs (exact), 8th-priority, straight-16th no-fabrication, and the original 8th cases.Validation
unittest discover— 1361 tests, OK (3 skipped) (exit codes captured directly).npm run verify: 51 smoke passed (no UI-side changes in this PR —gridResolutionandswingPercentare existing fields).🤖 Generated with Claude Code