Skip to content

feat: auto-derive the syncopated rhythm tag (#75) - #92

Merged
PhysShell merged 7 commits into
mainfrom
claude/modest-maxwell-0zecwn
Jun 20, 2026
Merged

feat: auto-derive the syncopated rhythm tag (#75)#92
PhysShell merged 7 commits into
mainfrom
claude/modest-maxwell-0zecwn

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Adds the last of #75's derive-style asks: syncopation::derive_syncopated, which tags SwancoreTag::Syncopated from note-onset placement.

What

  • Displacement metric: a beat is displaced when the off-beat eighth-note "and" just before it is struck while the beat tick itself is not (anticipation / sustain-over). Reuses the existing master-timeline grid math.
  • Tags when displaced_beats / total_beats >= SYNCOPATION_THRESHOLD (0.25, a documented constant). Steady eighth/sixteenth runs score zero (every beat is still struck), so busy-but-regular riffs aren't over-tagged — unlike a naive off-beat-onset ratio.
  • Wired into both fronts — CLI build_chunk_meta and web build_chunk_meta_record — merged additively after the technique + chord-quality tags. Web parity test included.
  • Presence-only and deterministic (pure fn + one constant, SPEC §6). Derives the rhythm tag Syncopated, not the style tag SyncopatedRiff (a passage-dominance call left to curation).

Deferred (first cut; see docs/decisions.log.md)

Accent-based syncopation, compound meters (numerator treated as the beat count), finer-than-eighth / triplet grids, and bars whose beat isn't an even tick count — under-tag, never mis-tag.

⚠️ Also carries the stranded #91 docs

#90 merged the pre-#91 head into main, so Codex's #91 GP-helper docs + the complement.rs mul_add change never actually reached main — they live only on this branch. I rebased onto the branch tip rather than force-push (which would have discarded them), so this PR's diff also includes those two commits (a77b7e0, and the #91 merge). Merging this PR lands them in main too. They're unrelated to syncopation but harmless and already reviewed on #91.

TDD

red (f517cb1) → green core (6d045f7) → both-fronts wiring (bb953e2) → docs (7be34a7).

Verification

  • 8 syncopation tests + full workspace suite green
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • web host tests (incl. the parity test) + wasm build green

🤖 Generated with Claude Code

https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Automatic syncopation detection: tracks are analyzed during chunk processing and now receive syncopation rhythm tags when displaced/anticipating onsets meet the documented threshold.
    • Syncopation tags are merged into chunk metadata alongside existing technique and harmony-derived tags.
  • Documentation

    • Expanded documentation for the Guitar Pro import pipeline and added a decision log describing the syncopation derivation approach and limits.
  • Tests

    • Added core and integration/unit tests covering multiple onset patterns, threshold behavior, determinism, and out-of-range/empty inputs.

PhysShell and others added 6 commits June 20, 2026 08:38
…t-guitar-pro-tapping-as-a-tapped

docs: follow up GP import doc coverage
Failing tests for a new `syncopation::derive_syncopated` deriver that sets
SwancoreTag::Syncopated from onset placement: a beat is "displaced" when the
off-beat "and" before it is struck but the beat itself is not (anticipation
/ sustain-over). Tags when displaced-beat share >= 0.25; steady runs score
zero. References `griff_core::syncopation`, which does not exist yet, so the
suite fails to compile until the green step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
…75)

`syncopation::derive_syncopated` reuses the master-timeline grid math to
find *displaced* beats: the off-beat eighth-note "and" before a beat is
struck while the beat tick itself is not (anticipation / sustain-over).
Tags SwancoreTag::Syncopated when the displaced-beat share meets the
documented `SYNCOPATION_THRESHOLD` (0.25); steady eighth/sixteenth runs
score zero by construction, so busy-but-regular riffs are not over-tagged.

Presence-only and deterministic — a pure function of the score plus one
constant (SPEC §6). Derives the rhythm tag `Syncopated`, not the style tag
`SyncopatedRiff` (a passage-dominance judgement left to curation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
`build_chunk_meta` (CLI) and `build_chunk_meta_record` (web capture) now
call `syncopation::derive_syncopated` and fold the result into the chunk's
tags additively, after the technique and chord-quality tags — so both
curation fronts emit the same rhythm tag for a given track. Adds a web
parity test that a displaced-onset pattern surfaces `Syncopated`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
Y-statement for the syncopated deriver: the displacement metric (off-beat
"and" anticipating an unstruck beat) and its prior art (Longuet-Higgins &
Lee), why it beats a raw off-beat ratio (steady runs score zero), the
documented `SYNCOPATION_THRESHOLD = 0.25` as a deliberate departure from the
threshold-free derivers (SPEC §6), Syncopated vs SyncopatedRiff, and the
first-cut deferrals (accents, compound meter, finer/triplet grids).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2aab77f7-edcb-4dbf-8f82-7f63420f6106

📥 Commits

Reviewing files that changed from the base of the PR and between 7be34a7 and 1a6a481.

📒 Files selected for processing (1)
  • cli/src/main.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/src/main.rs

📝 Walkthrough

Walkthrough

Adds a new core/src/syncopation.rs module that derives SwancoreTag::Syncopated by computing a displaced-beat ratio across all master bars and comparing it against a constant threshold. The tag is integrated into ChunkMeta tag merging in both the CLI and web/WASM chunk pipelines. Eight unit tests and integration tests in both pipelines cover the algorithm. Also includes mul_add usage in the Pearson correlator and added doc comments on GP import functions.

Changes

Syncopated tag derivation and pipeline wiring

Layer / File(s) Summary
syncopation module: algorithm and export
core/src/syncopation.rs, core/src/lib.rs
Implements derive_syncopated with SYNCOPATION_THRESHOLD = 0.25, track_onsets (aggregates all voice note-onset ticks), and displaced_beats (per-bar off-beat vs. beat-tick grid scan). Publicly exports the module.
syncopation unit tests
core/tests/syncopation.rs
Eight tests covering steady eighths, on-beat quarters, anticipated beats, threshold boundary (inclusive and exclusive), out-of-range track, empty master bars, and determinism.
CLI pipeline wiring and unit test
cli/src/main.rs
Imports syncopation, calls derive_syncopated in build_chunk_meta, merges the result additively into the ChunkMeta.tags chain, and adds a unit test asserting displaced-onset scores yield the Syncopated tag.
Web/WASM pipeline wiring and integration test
web/src/lib.rs
Imports syncopation, adds derive_syncopated call and additive merge in build_chunk_meta_record, and adds an integration test asserting displaced-onset scores yield the Syncopated tag in chunk metadata.
Decision log
docs/decisions.log.md
New 2026-06-20 entry documents the displacement metric, the threshold constant, and first-cut exclusions (finer grids, triplets, SyncopatedRiff distinction).

Incidental improvements

Layer / File(s) Summary
Pearson correlator mul_add
core/src/complement.rs
Switches the three accumulation statements in rotated_correlation from separate multiply-then-add to mul_add (fused multiply-add).
GP import pipeline docs
core/src/gp.rs
Adds doc comments to gp_song_to_score, build_gp_master_bars, build_gp_track, build_gp_voice, append_beat, and rest_group.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PhysShell/griff#69: Both PRs modify the same chunk-curation seams (cli/src/main.rs build_chunk_meta and web/src/lib.rs build_chunk_meta_record) to merge curator tags with derived tags, with the main PR extending the existing tag-merging chain to additionally derive and append syncopated tags.
  • PhysShell/griff#88: Both PRs modify the same build_chunk_meta/tag-merging flow in cli/src/main.rs and web/src/lib.rs to add another derived tag source—main PR adds syncopation::derive_syncopated while retrieved PR adds harmony::derive_harmony—so they overlap in how derived tags are accumulated into ChunkMeta.tags.

Poem

🐇 Hop, hop on the off-beat tick,
Between the beats I play my trick—
A displaced note, an "and" surprise,
The threshold met, the tag applies!
Syncopated blooms in the score's JSON stream,
One quarter-hop past what on-beat would seem. 🎵

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: auto-deriving the syncopated rhythm tag, which is the primary focus across multiple files (syncopation.rs, cli/main.rs, web/lib.rs, and tests).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/modest-maxwell-0zecwn

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7be34a7842

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread core/src/syncopation.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cli/src/main.rs (1)

1061-1085: ⚡ Quick win

Add a CLI-local regression test for syncopation tag wiring.

This merge path is correct, but a direct CLI test asserting ChunkMeta.tags includes SwancoreTag::Syncopated for displaced onsets would guard parity drift versus web.

Suggested test direction
+#[test]
+fn build_chunk_meta_auto_fills_syncopated_from_displaced_onsets() {
+    // Build a small score with displaced onsets on the selected track,
+    // call build_chunk_meta(...),
+    // assert meta.tags contains SwancoreTag::Syncopated.
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/main.rs` around lines 1061 - 1085, Add a CLI-local regression test
that verifies syncopation tag wiring in the ChunkMeta struct. The test should
create a test score or input with displaced onsets, process it through the same
code path that calls derive_syncopated and merges the tags (the
technique::merge_tags calls), and then assert that the resulting ChunkMeta.tags
collection includes SwancoreTag::Syncopated. This guards against future drift
from the web implementation's behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/src/main.rs`:
- Around line 1061-1085: Add a CLI-local regression test that verifies
syncopation tag wiring in the ChunkMeta struct. The test should create a test
score or input with displaced onsets, process it through the same code path that
calls derive_syncopated and merges the tags (the technique::merge_tags calls),
and then assert that the resulting ChunkMeta.tags collection includes
SwancoreTag::Syncopated. This guards against future drift from the web
implementation's behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 879e8366-8510-4da4-8dc0-6c2c32786c17

📥 Commits

Reviewing files that changed from the base of the PR and between be51d2f and 7be34a7.

📒 Files selected for processing (8)
  • cli/src/main.rs
  • core/src/complement.rs
  • core/src/gp.rs
  • core/src/lib.rs
  • core/src/syncopation.rs
  • core/tests/syncopation.rs
  • docs/decisions.log.md
  • web/src/lib.rs

A CLI-local regression test that `build_chunk_meta` auto-fills
SwancoreTag::Syncopated for a displaced-onset score, mirroring the web
parity test (CodeRabbit nitpick on #92). Characterization of existing
wiring — no new public API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
@PhysShell
PhysShell merged commit 62bfd5d into main Jun 20, 2026
2 checks passed
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