Skip to content

feat(rhythm): real 8th-note swing measurement (accuracy PR-B2) - #205

Merged
slittycode merged 1 commit into
mainfrom
claude/asa-integration-producer-bp82ws
Jul 4, 2026
Merged

feat(rhythm): real 8th-note swing measurement (accuracy PR-B2)#205
slittycode merged 1 commit into
mainfrom
claude/asa-integration-producer-bp82ws

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Phase B of the accuracy program: turns swing from a scalar proxy into a real, Groove-Pool-ready number.

What

The old swing signal (grooveDetail.kickSwing/hihatSwing) is a tanh-normalized beat-loudness-interval variance — a "how loose" hint, not a swing percentage. This adds compute_swing_detail, which measures swing from the long/short alternation of 8th-note inter-onset intervals: swingPercent = long / (long + short) × 100, on Ableton's Groove Pool 50–75 scale (50 = straight, 66.7 = full triplet). Additive rhythmDetail.swingDetail {swingPercent, swingConfidence, gridResolution, direction, meanAbsOffsetMs, offbeatOnsetCount}, reusing the onset array already computed in analyze_rhythm_detail (no extra DSP pass), null when unmeasurable.

Why interval-ratio, not phase-vs-grid

I first built the obvious phase-against-beat-grid version. It failed on the corpus: the beat tracker, on sparse material, sometimes anchors "the beat" on the loud offbeat hat, which flips a phase reading (58% measured as 42%). The long/short interval structure is invariant to that choice — proven by a phase-shift-invariance test and by the corpus results: truth 50/54/58/62/66 → measured 50.0/54.8/57.1/61.9/65.9, all within ±1.3, well inside the ±3 gate.

Validation

swingPercent is now an active ±3.0 check on all 5 swing clips (the harness learns expected.swingPercent; the swing fixtures render a realistic swung hat pattern — straight on-beat 8th + delayed off-beat 8th — so the interval alternation exists). Full synthetic run: 54/54 active + 8 informational, allPassed.

User-facing

The Reconstruction Brief groove line now says "swings at about 62% — set the Groove Pool swing to match" (falls back to the proxy when swing is unmeasurable), and the MeasurementDashboard Swing panel shows the Groove-Pool percentage above the old kick/hat bars.

Contract mirrors (tripwire #4)

JSON_SCHEMA.md sub-fields · types/measurement.ts SwingDetail (rhythmDetail passes through wholesale — no reconstructor) · phase1FullPayload fixture · golden re-baselined (nested, structural).

Tests

test_swing_detail (ratio recovery, phase-flip invariance, degenerate inputs), brief swing-sentence, corpus expected-keys. Backend 1299 OK, fundamentals gate green, frontend verify green (unit + build + 51 smoke).

🤖 Generated with Claude Code

https://claude.ai/code/session_01T4wfz87k6kzJqkKLKZE7YL


Generated by Claude Code

Replaces the loudness-interval swing proxy (grooveDetail.kickSwing/
hihatSwing) with a real micro-timing measurement. compute_swing_detail
reads swing from the long/short alternation of 8th-note inter-onset
intervals: swingPercent = long/(long+short)*100 on Ableton's Groove Pool
50-75 scale (50 straight, 66.7 full triplet), so it drops straight into a
groove template. Additive rhythmDetail.swingDetail {swingPercent,
swingConfidence, gridResolution, direction, meanAbsOffsetMs,
offbeatOnsetCount}; reuses the onset array already computed in
analyze_rhythm_detail (no extra DSP pass); null when unmeasurable.

Interval-ratio rather than phase-vs-grid is deliberate: on sparse
material the beat tracker sometimes anchors 'the beat' on a loud offbeat
hat, which flips a phase reading (58% -> 42%). The long/short structure
is invariant to that choice — validated by a phase-shift-invariance test
and against the corpus swing clips (truth 50/54/58/62/66 -> measured
50.0/54.8/57.1/61.9/65.9, all within +-1.3, well inside the +-3 gate).

Corpus: swing clips now render a realistic swung hat pattern (straight
on-beat 8th + delayed off-beat 8th) so the interval alternation exists;
swingPercent promoted from manifest truth to an active +-3.0 check on all
5 swing clips (fundamentals_evaluation learns expected.swingPercent).

UI: reconstructionBrief groove sentence prefers the measured percentage
('swings at about 62% - set the Groove Pool swing to match'), falls back
to the proxy; MeasurementDashboard Swing panel shows the Groove-Pool
number.

Contract mirrors (tripwire #4): JSON_SCHEMA.md swingDetail sub-fields;
types/measurement.ts SwingDetail (rhythmDetail passes through wholesale,
no reconstructor); phase1FullPayload fixture; golden re-baselined
(nested, structural). Tests: test_swing_detail (ratios, phase-flip
invariance, degenerate), brief swing-sentence, corpus expected-keys.
Backend 1299 OK, fundamentals gate green, frontend verify green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4wfz87k6kzJqkKLKZE7YL
@slittycode
slittycode merged commit 0081037 into main Jul 4, 2026
5 checks passed
@slittycode
slittycode deleted the claude/asa-integration-producer-bp82ws branch July 4, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants