Skip to content

Multimeasure rest refactor#32900

Merged
miiizen merged 26 commits into
musescore:mainfrom
miiizen:31586-breathMMR
Jun 10, 2026
Merged

Multimeasure rest refactor#32900
miiizen merged 26 commits into
musescore:mainfrom
miiizen:31586-breathMMR

Conversation

@miiizen

@miiizen miiizen commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Resolves: #31586
Resolves: #32845
Resolves: #27880
Resolves: #30890
Resolves: #32663

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Made Measure::showMeasureNumberInAutoMode() null-safe by requiring system() before calling isFirstInSystem(). In measure layout, removed ElementType::BREATH from break and conditional-break classifications and added ElementType::FERMATA to break types. Tightened multimeasure-rest validation to reject end-of-measure annotations except PLAY_COUNT_TEXT and FERMATA, and moved tracks computation outside inner blocks. createMMRest() now synchronizes breath segments with underlying measures (enable state, clone or relayout per-track breaths, or remove mmrest breath). breakMultiMeasureRest() uses prevMeas consistently and treats previous-measure breath or fermata segments as breaks; breath segments no longer count as non-empty stops.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description only lists GitHub issue numbers without providing a substantive description of the changes, their motivation, or the problems being addressed. Expand the description to include a brief explanation of what the changes accomplish, why they are needed, and which template sections should be filled out (motivation, CLA confirmation, etc.).
Title check ❓ Inconclusive The title 'Multimeasure rest refactor' is vague and generic, failing to convey the key objective of breaking multimeasure rests on fermatas and breaths. Consider a more specific title such as 'Break multimeasure rests on fermatas and breaths' or 'Fix visibility of fermatas and breaths in multimeasure rests' to clearly communicate the main objective.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements all core requirements: making fermatas on barlines/caesuras break multimeasure rests (#31586), fixing the null-safety assertion failure during undo (#32845), and making breath marks break multimeasure rests while synchronizing them in the generated rest (#27880).
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives. Updates to measure.cpp fix the assertion failure; changes to measurelayout.cpp implement fermata/breath break logic and synchronization without introducing unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/engraving/rendering/score/measurelayout.cpp (1)

755-773: ⚠️ Potential issue | 🟡 Minor

Only break for previous-measure fermatas that can actually be shown.

This branch bypasses the existing staff/system visibility gate. A fermata on a hidden staff will still split the multimeasure rest here even though nothing is rendered, which creates an extra visible break with no visible marking.

🛠️ Proposed fix
                 if (breakForAnnotation(e)) {
                     return true;
                 }
                 // break for fermatas on the end barline in previous measure
-                if (e->isFermata()) {
+                const bool fermataOnVisibleContext = e->systemFlag() || (e->staff() && e->staff()->show());
+                if (e->isFermata() && fermataOnVisibleContext) {
                     return true;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engraving/rendering/score/measurelayout.cpp` around lines 755 - 773, Only
break for fermatas in the previous measure if the fermata is actually rendered:
inside the loop over prevMeas segments and annotations (the code iterating
Segment* s = prevMeas->first() and EngravingItem* e), after confirming
e->visible() and mid-measure, add the same staff/system visibility guard used
elsewhere in this file (the one used by breakForAnnotation or earlier visibility
checks) before treating e->isFermata() as a split condition; i.e., only return
true for e->isFermata() when the fermata’s staff/system is visible/rendered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/engraving/rendering/score/measurelayout.cpp`:
- Around line 755-773: Only break for fermatas in the previous measure if the
fermata is actually rendered: inside the loop over prevMeas segments and
annotations (the code iterating Segment* s = prevMeas->first() and
EngravingItem* e), after confirming e->visible() and mid-measure, add the same
staff/system visibility guard used elsewhere in this file (the one used by
breakForAnnotation or earlier visibility checks) before treating e->isFermata()
as a split condition; i.e., only return true for e->isFermata() when the
fermata’s staff/system is visible/rendered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9dc94cb4-2ece-4078-b5df-5510008a62d0

📥 Commits

Reviewing files that changed from the base of the PR and between 33acbbb and 63a0963.

📒 Files selected for processing (3)
  • src/engraving/dom/measure.cpp
  • src/engraving/rendering/score/measurelayout.cpp
  • vtest/scores/mmrest-breath.mscz

if (!muse::contains(BREAK_TYPES, e->type())) {
const bool breakType = muse::contains(BREAK_TYPES, e->type());
const bool fermata = e->isFermata();
if (!breakType && !fermata) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't this the same as just inserting ElementType::FERMATA in BREAK_TYPES?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah yes actually, it can be treated in the same way as play count text

// break for fermatas on the end barline in previous measure
if (e->isFermata()) {
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, if ElementType::FERMATA is in the BREAK_TYPES then breakForAnnotation should already do the job?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, the CONDITIONAL_BREAK_TYPES and ALWAYS_BREAK_TYPES sets are used to check if the current measure contains an element which should cause a break and start an MMRest. We don't want the measure containing the fermata to start an MMRest, we want it to end an MMRest. We should only check if the previous measure contains a barline with a fermata, like we do for end repeat barlines. Otherwise, we get results like this:
Screenshot 2026-04-02 at 14 49 54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/engraving/rendering/score/measurelayout.cpp (1)

753-768: ⚠️ Potential issue | 🟠 Major

Mirror the visible-staff guard in the previous-measure fermata break.

This branch now breaks on any visible fermata in prevMeas, including fermatas attached to hidden staves. validMMRestMeasure() explicitly ignores hidden staves on Lines 595-596, so visible multimeasure rests can still be fragmented by symbols that are not rendered. If barline fermatas are the only intended case here, scoping this to the end-barline segment would also match the copy path on Lines 245-274.

🔧 Proposed fix
-                // break for fermatas on the end barline in previous measure
-                if (e->isFermata()) {
+                // break for fermatas on a visible staff in the previous measure
+                if (e->isFermata()
+                    && e->staffIdx() < ctx.dom().nstaves()
+                    && e->staff()
+                    && e->staff()->show()) {
                     return true;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engraving/rendering/score/measurelayout.cpp` around lines 753 - 768, The
fermata check currently breaks on any visible fermata in prevMeas (including
those on hidden staves); change the fermata branch so it only triggers for
fermatas attached to the end-barline segment: inside the loop over Segment* s
(the same s used for s->annotations()), restrict the e->isFermata() return to
when the segment is the measure's end-barline segment (e.g. compare s to the
prevMeas end-barline segment accessor or use a segment predicate like
s->isEndBarline()/s->isBarline()), keeping the existing visible()/rtick() guards
and existing call to breakForAnnotation(e). This will mirror the copy-path
behavior and avoid breaking on fermatas on non-end-barline/hidden-staff
annotations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/engraving/rendering/score/measurelayout.cpp`:
- Around line 753-768: The fermata check currently breaks on any visible fermata
in prevMeas (including those on hidden staves); change the fermata branch so it
only triggers for fermatas attached to the end-barline segment: inside the loop
over Segment* s (the same s used for s->annotations()), restrict the
e->isFermata() return to when the segment is the measure's end-barline segment
(e.g. compare s to the prevMeas end-barline segment accessor or use a segment
predicate like s->isEndBarline()/s->isBarline()), keeping the existing
visible()/rtick() guards and existing call to breakForAnnotation(e). This will
mirror the copy-path behavior and avoid breaking on fermatas on
non-end-barline/hidden-staff annotations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b65173f3-0d26-4fa0-91ac-a372f177476c

📥 Commits

Reviewing files that changed from the base of the PR and between 63a0963 and 972cdc1.

📒 Files selected for processing (1)
  • src/engraving/rendering/score/measurelayout.cpp

// 1) This is the first measure of the system OR
// 2) The previous measure in the system is the first, and is excluded from numbering.
return isFirstInSystem()
return (system() && isFirstInSystem())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like that resolves #32845

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do wonder if it makes more sense to remove that assertion. If I am querying isFirstInSystem, it's a bit weird to be asked to check first if it does have a system. Feels like the case of not having a system is part of the query itself, i.e. just a legitimate expected return false.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/dom/measure.cpp`:
- Around line 587-588: The second branch calls prevMeasure->isFirstInSystem()
without ensuring prevMeasure->system() is non-null; update the conditional to
also guard that prevMeasure->system() is valid. Change the expression to require
prevMeasure && prevMeasure->system() && prevMeasure->excludeFromNumbering() &&
prevMeasure->isFirstInSystem(), so calls to prevMeasure->isFirstInSystem() only
occur when prevMeasure->system() is present (referencing prevMeasure, system(),
excludeFromNumbering(), and isFirstInSystem()).

In `@src/engraving/rendering/score/measurelayout.cpp`:
- Around line 789-792: Update the inline comment above the fermata check to
accurately describe the condition: it breaks for any fermata with rtick > 0
(which covers mid-measure fermatas as well as fermatas on the end barline of the
previous measure). Locate the block where e->isFermata() is used (in the measure
layout logic handling rtick > 0) and change the comment to something like "break
for fermatas mid-measure or on the end barline in previous measure" so it
matches the implemented behavior.
🪄 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: 4ea8a3de-a535-4061-9a3a-0132af1e84c2

📥 Commits

Reviewing files that changed from the base of the PR and between 972cdc1 and ed1768e.

📒 Files selected for processing (2)
  • src/engraving/dom/measure.cpp
  • src/engraving/rendering/score/measurelayout.cpp

Comment thread src/engraving/dom/measure.cpp Outdated
Comment thread src/engraving/rendering/score/measurelayout.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/rendering/score/measurelayout.cpp`:
- Around line 442-470: When underlyingBreathSeg exists but for a given track
there is no underlyingBreath, the code currently leaves an existing mmrBreath in
place; update the loop that iterates staffIdx/track (in the block that
references underlyingBreathSeg, mmrBreathSeg, Breath, toBreath, and
ctx.mutDom()) so that when toBreath(underlyingBreathSeg->element(track)) returns
null and toBreath(mmrBreathSeg->element(track)) returns non-null you remove the
stale mmrBreath by calling ctx.mutDom().doUndoRemoveElement(mmrBreath) (and
avoid further layout/clone logic for that track); this ensures per-track mmr
breaths are deleted when the underlying track no longer has a breath.
🪄 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: 2f5e813e-3921-47e4-9cc3-7df98c2dd1c7

📥 Commits

Reviewing files that changed from the base of the PR and between ed1768e and 9ca8816.

📒 Files selected for processing (3)
  • src/engraving/dom/measure.cpp
  • src/engraving/rendering/score/measurelayout.cpp
  • vtest/scores/mmrest-breath.mscz

Comment on lines +442 to +470
//
// check for end of measure breaths
//
Segment* underlyingBreathSeg = lastMeasure->findSegmentR(SegmentType::Breath, lastMeasure->ticks());
Segment* mmrBreathSeg = mmrMeasure->findSegmentR(SegmentType::Breath, mmrMeasure->ticks());
if (underlyingBreathSeg) {
if (mmrBreathSeg == 0) {
mmrBreathSeg = mmrMeasure->undoGetSegmentR(SegmentType::Breath, mmrMeasure->ticks());
}
mmrBreathSeg->setEnabled(underlyingBreathSeg->enabled());
for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
track_idx_t track = staffIdx * VOICES;
Breath* underlyingBreath = toBreath(underlyingBreathSeg->element(track));
if (underlyingBreath) {
Breath* mmrBreath = toBreath(mmrBreathSeg->element(track));
if (!mmrBreath) {
mmrBreath = underlyingBreath->generated() ? underlyingBreath->clone() : toBreath(
underlyingBreath->linkedClone());
mmrBreath->setParent(mmrBreathSeg);
ctx.mutDom().doUndoAddElement(mmrBreath);
} else {
TLayout::layoutBreath(mmrBreath, mmrBreath->mutldata(), ctx.conf());
}
}
}
} else if (mmrBreathSeg) {
// TODO: remove elements from mmrSeg?
ctx.mutDom().doUndoRemoveElement(mmrBreathSeg);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove stale per-track mmrest breaths when only some tracks lose breaths.

In Line 452-Line 466, if underlyingBreathSeg exists but a specific track has no underlyingBreath, the corresponding existing mmrBreath is never removed. That leaves stale breath marks in the mmrest after edits on individual staves.

Proposed fix
         for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
             track_idx_t track = staffIdx * VOICES;
             Breath* underlyingBreath = toBreath(underlyingBreathSeg->element(track));
             if (underlyingBreath) {
                 Breath* mmrBreath = toBreath(mmrBreathSeg->element(track));
                 if (!mmrBreath) {
                     mmrBreath = underlyingBreath->generated() ? underlyingBreath->clone() : toBreath(
                         underlyingBreath->linkedClone());
                     mmrBreath->setParent(mmrBreathSeg);
                     ctx.mutDom().doUndoAddElement(mmrBreath);
                 } else {
                     TLayout::layoutBreath(mmrBreath, mmrBreath->mutldata(), ctx.conf());
                 }
+            } else {
+                if (Breath* mmrBreath = toBreath(mmrBreathSeg->element(track))) {
+                    ctx.mutDom().doUndoRemoveElement(mmrBreath);
+                }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engraving/rendering/score/measurelayout.cpp` around lines 442 - 470, When
underlyingBreathSeg exists but for a given track there is no underlyingBreath,
the code currently leaves an existing mmrBreath in place; update the loop that
iterates staffIdx/track (in the block that references underlyingBreathSeg,
mmrBreathSeg, Breath, toBreath, and ctx.mutDom()) so that when
toBreath(underlyingBreathSeg->element(track)) returns null and
toBreath(mmrBreathSeg->element(track)) returns non-null you remove the stale
mmrBreath by calling ctx.mutDom().doUndoRemoveElement(mmrBreath) (and avoid
further layout/clone logic for that track); this ensures per-track mmr breaths
are deleted when the underlying track no longer has a breath.

@davidstephengrant davidstephengrant added the vtests This PR produces approved changes to vtest results label Apr 10, 2026
@davidstephengrant

Copy link
Copy Markdown
Contributor

@miiizen When breath marks are splitting an mmrest, edits to mmrest appearance properties or turning mmrests on/off (Ctrl+Shift+M) can cause duplicates to appear.

Screencast.from.2026-04-13.11-44-56.webm

@miiizen

miiizen commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@davidstephengrant Fixed!

@davidstephengrant

Copy link
Copy Markdown
Contributor

@miiizen Replacing breath marks on mmrests is behaving erratically. You can reach an odd state where different breath marks are drawn depending on if mmrests are enabled or disabled.

Screencast.from.2026-04-14.09-34-01.webm

@davidstephengrant

Copy link
Copy Markdown
Contributor

In a new score with mmrests off:

  1. Add a breath mark after e.g. bar 3.
  2. Add a another (different) breath mark before the first one, e.g. after b.2.
  3. Toggle mmrests on (Ctrl+Shift+M).
    RESULT: Notation as expected.
  4. Toggle mmrests off, then delete the breath mark after b.2.
  5. Toggle mmrests on and off (appears to be a required step).
  6. Repeat step 2-3.
    RESULT: Incorrect breath mark is drawn after b.2.

This persists saving and reloading the score - but - toggling mmrests off, saving, reloading then toggling mmrests back on causes both the breath marks to draw correctly.

Screencast.from.2026-04-15.11-22-41.webm

@davidstephengrant

Copy link
Copy Markdown
Contributor

In a new score with mmrests off:

  1. Add a breath mark after e.g. bar 3.
  2. Add a another (different) breath mark before the first one, e.g. after b.2.
  3. Open the instrumental part.
    RESULT: Notation as expected.
  4. Switch back to the score, then delete and recreate the breath mark after b.2.
  5. Switch to the part.
    RESULT: The mmrest is not broken correctly - both breath marks draw after b.3.
  6. (Still in the part) Toggle mmrests off.
    RESULT: The breath mark after b.2 does not draw at all, even though it still draws as expected in the score.
Screencast.from.2026-04-15.11-43-22.webm

@miiizen miiizen changed the title Break multimeasure rests on fermatas and breaths Multimeasure rest refactor Apr 23, 2026
@igorkorsukov igorkorsukov changed the base branch from master to main May 14, 2026 14:25
@miiizen miiizen force-pushed the 31586-breathMMR branch from d3962a8 to 0fc1345 Compare May 18, 2026 08:13
@davidstephengrant

Copy link
Copy Markdown
Contributor

@miiizen Please rebase

@miiizen miiizen force-pushed the 31586-breathMMR branch from 0fc1345 to fd980a0 Compare May 21, 2026 09:04
@davidstephengrant

Copy link
Copy Markdown
Contributor

@miiizen For now I'm adding some findings here. However, as MMRests on the whole are much improved, it might be worth merging this PR as-is and logging outstanding issues seperately.

@davidstephengrant

Copy link
Copy Markdown
Contributor

Layout objects are not added/removed correctly at the end of multimeasure rests

Adding a layout object to a bar where an identical object already exists will usually remove the existing object (effectively working like a toggle). At the end of multimeasure rests multiple layout object icons are instead added. Saving and reloading the score causes a single icon to be drawn, but the toggling behaviour is nonetheless broken.

Screencast.from.2026-05-25.10-12-17.webm

@miiizen miiizen force-pushed the 31586-breathMMR branch from 3405963 to b47ce00 Compare June 9, 2026 14:17
@miiizen miiizen force-pushed the 31586-breathMMR branch from b47ce00 to 4282512 Compare June 9, 2026 15:05

@mike-spa mike-spa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Absolutely outstanding work, I am SO HAPPY that we've finally cleaned this up

@miiizen miiizen merged commit ccbae64 into musescore:main Jun 10, 2026
13 of 14 checks passed
<right-margin>0</right-margin>
</system-margins>
<system-distance>87.26</system-distance>
<system-distance>-40</system-distance>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is bad. There must never be negative values. (This is what this test was for originally.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out - fixed in #33837

Jojo-Schmitz added a commit to Jojo-Schmitz/MuseScore that referenced this pull request Jun 10, 2026
Backport of musescore#32900, commit 1

Co-Authored-By: James Mizen <26510874+miiizen@users.noreply.github.com>
Jojo-Schmitz added a commit to Jojo-Schmitz/MuseScore that referenced this pull request Jun 10, 2026
Backport of musescore#32900, commit 4

Co-Authored-By: James Mizen <26510874+miiizen@users.noreply.github.com>
Jojo-Schmitz added a commit to Jojo-Schmitz/MuseScore that referenced this pull request Jun 11, 2026
Backport of musescore#32900, commit 1

Co-Authored-By: James Mizen <26510874+miiizen@users.noreply.github.com>
Jojo-Schmitz added a commit to Jojo-Schmitz/MuseScore that referenced this pull request Jun 11, 2026
Backport of musescore#32900, commit 4

Co-Authored-By: James Mizen <26510874+miiizen@users.noreply.github.com>
Jojo-Schmitz added a commit to Jojo-Schmitz/MuseScore that referenced this pull request Jun 11, 2026
Backport of musescore#32900, commit 1

Co-Authored-By: James Mizen <26510874+miiizen@users.noreply.github.com>
Jojo-Schmitz added a commit to Jojo-Schmitz/MuseScore that referenced this pull request Jun 11, 2026
Backport of musescore#32900, commit 4

Co-Authored-By: James Mizen <26510874+miiizen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vtests This PR produces approved changes to vtest results

Projects

None yet

5 participants