Skip to content

Fix #17793: Apply swing to chord symbol playback#32942

Open
dpad46 wants to merge 2 commits intomusescore:masterfrom
dpad46:17793-swing-chord-symbol-playback
Open

Fix #17793: Apply swing to chord symbol playback#32942
dpad46 wants to merge 2 commits intomusescore:masterfrom
dpad46:17793-swing-chord-symbol-playback

Conversation

@dpad46
Copy link
Copy Markdown

@dpad46 dpad46 commented Apr 8, 2026

Resolves: #17793, where when swing is enabled, chord symbols placed on offbeat eighth notes are played back straight, causing them to be played out of sync with the notes they are attached to (notes are correctly swung but chord symbols are not).

Fixed by updating PlaybackEventsRenderer::renderChordSymbol to call a new helper applySwingIfNeed, which adjusts the event timestamp and duration using logic based on the existing NoteRenderer::applySwingIfNeed.

Fixed playback:

8mb.video-v5m-ohvLGyaz.mp4

Before (master):

8mb.video-VNH-Cp8vVe6R.mp4
  • I signed the CLA
  • The title of the PR describes the problem it addresses
  • Each commit's message describes its purpose and effects, and references the issue it resolves
  • If changes are extensive, there is a sequence of easily reviewable commits
  • The code in the PR follows the coding rules
  • There are no unnecessary changes
  • The code compiles and runs on my machine, preferably after each commit individually
  • I created a unit test or vtest to verify the changes I made (if applicable)

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

The playback chord-symbol event generation in playbackeventsrenderer.cpp is updated to conditionally apply swing timing adjustments. Two helper functions are introduced: one locates the Chord element associated with a Harmony by navigating the parent Segment, and another applies swing timing modifications to the event timestamp and duration when swing is enabled on the staff. The renderChordSymbol() function now calls the swing helper after initial timestamp and duration calculation but before retrieving the playback event list, ensuring chord events emit at swung timings. Swing adjustments are omitted when no corresponding chord exists or the chord contains a tuplet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the fix: applying swing timing to chord symbol playback events, which directly addresses the main change in the PR.
Description check ✅ Passed The description is comprehensive, includes the issue reference, explains the problem and solution, provides before/after demonstrations, and mostly completes the template checklist.
Linked Issues check ✅ Passed The PR directly addresses issue #17793 by implementing swing timing for chord symbols. The code changes add applySwingIfNeed to adjust event timestamps and durations to match swung note playback, resolving the synchronization issue described.
Out of Scope Changes check ✅ Passed All changes are focused on fixing chord symbol swing timing. The modifications to playbackeventsrenderer.cpp add only the necessary helper functions and integrate them into the chord symbol rendering logic without unnecessary additions.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/engraving/playback/playbackeventsrenderer.cpp`:
- Around line 77-83: The code adjusts eventTimestamp using
Swing::applySwing(...).remainingDurationMultiplier but ignores
Swing::ChordDurationAdjustment::durationMultiplier, so on-beat swung chords
never lengthen and later events can desync; update the block that computes
swingOffset and modifies eventTimestamp/duration (referencing
Swing::ChordDurationAdjustment, swingDurationAdjustment,
remainingDurationMultiplier, durationMultiplier, nominalDuration,
eventTimestamp, duration, timestampFromTicks, positionTickWithOffset,
chord->actualTicks().ticks()) to both shift the eventTimestamp by swingOffset
and also apply the durationMultiplier to the event's duration (e.g., scale or
add the extra portion derived from nominalDuration * (durationMultiplier - 1))
so the event lengthens when durationMultiplier > 1.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 97b38878-f10f-4b2c-b741-118ebfb3424f

📥 Commits

Reviewing files that changed from the base of the PR and between 4e25547 and ffe6191.

📒 Files selected for processing (1)
  • src/engraving/playback/playbackeventsrenderer.cpp

Comment thread src/engraving/playback/playbackeventsrenderer.cpp
@RomanPudashkin RomanPudashkin self-requested a review April 8, 2026 07:09
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/engraving/playback/playbackeventsrenderer.cpp`:
- Around line 61-84: Add regression tests covering the swing timing code paths
exercised by applySwingIfNeed: (1) an offbeat swung-eighth case that verifies
eventTimestamp shifts for offbeat alignment, (2) an on-beat case that verifies
duration uses Swing::ChordDurationAdjustment.durationMultiplier (and
remainingDurationMultiplier behavior), and (3) early-return cases when
findChordAtHarmony returns null and when chord->tuplet() is true. Use fixtures
to create a Score/Harmony/Chord and staff swing parameters (staff()->swing(...))
and assert both timestamp and duration outputs match expected values; include
equivalent vtests for the same scenarios so the timing behavior is covered after
the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9a246a06-b62f-43fb-a701-421a6542b18f

📥 Commits

Reviewing files that changed from the base of the PR and between ffe6191 and 5b0be27.

📒 Files selected for processing (1)
  • src/engraving/playback/playbackeventsrenderer.cpp

Comment on lines +61 to +84
static void applySwingIfNeed(const Harmony* chordSymbol,
const Score* score,
int positionTickWithOffset,
timestamp_t& eventTimestamp,
duration_t& duration)
{
const SwingParameters swing = chordSymbol->staff()->swing(chordSymbol->tick());
if (!swing.isOn()) {
return;
}

const Chord* chord = findChordAtHarmony(chordSymbol);
if (!chord || chord->tuplet()) {
return;
}

const Swing::ChordDurationAdjustment swingDurationAdjustment = Swing::applySwing(chord, swing);
const duration_t nominalDuration = timestampFromTicks(score, positionTickWithOffset + chord->actualTicks().ticks()) - eventTimestamp;
const duration_t additionalDuration = duration - nominalDuration;
const timestamp_t swingOffset = static_cast<timestamp_t>(nominalDuration * swingDurationAdjustment.remainingDurationMultiplier);

eventTimestamp += swingOffset;
duration = static_cast<duration_t>(nominalDuration * swingDurationAdjustment.durationMultiplier) + additionalDuration;
}
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 | 🔵 Trivial

Please add regression coverage for swing chord-symbol timing paths.

This is a timing-sensitive change; add tests/vtests for: offbeat swung eighth alignment, on-beat duration-multiplier behavior, and tuplet/no-chord early-return paths.

I can draft a compact test matrix and expected timestamp/duration assertions if helpful.

Also applies to: 188-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engraving/playback/playbackeventsrenderer.cpp` around lines 61 - 84, Add
regression tests covering the swing timing code paths exercised by
applySwingIfNeed: (1) an offbeat swung-eighth case that verifies eventTimestamp
shifts for offbeat alignment, (2) an on-beat case that verifies duration uses
Swing::ChordDurationAdjustment.durationMultiplier (and
remainingDurationMultiplier behavior), and (3) early-return cases when
findChordAtHarmony returns null and when chord->tuplet() is true. Use fixtures
to create a Score/Harmony/Chord and staff swing parameters (staff()->swing(...))
and assert both timestamp and duration outputs match expected values; include
equivalent vtests for the same scenarios so the timing behavior is covered after
the change.

@zacjansheski
Copy link
Copy Markdown
Contributor

Would it be possible for chords that are not attached to notes to swing as well?

I'm also experiencing some inconsistencies with the swing text switching to straight at the start, will keep investigating

2026-04-14.13-23-25.mp4

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.

Playback of chord symbols does not adapt to swing setting

4 participants