From 1cd62b36d744158a9ebb8fc0dfe042b81928ae4a Mon Sep 17 00:00:00 2001 From: Shiina Mashiro <21091736+0xMashiro@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:07:02 +0800 Subject: [PATCH 1/4] Restore historical TAB duration-grid layout without losing rhythm The beam-mode edit alone replaced duration signs with bare stems because connection lengths were no longer calculated after horizontal spacing. Restore that late layout step and include duration symbols in chord shapes. Connect equal undotted values within a measure and tuplet using the font's beam levels. Preserve duration signs for isolated, unequal, dotted and zero-level cases. Keep group ends, rests and measure boundaries intact. Replace the mixed half/quarter example with a visual regression score for two quarters and four eighths in separate groups. Cover real EditBeam transactions, undo/redo/reset, save/reopen, group boundaries and scaled French-font layout; retain ordinary chord and subdivision restrictions. All 21 enabled Engraving_BeamTests pass with ASan; the same tests fail in four historical TAB cases on the previous edit-only revision. Native app builds and GUI editing, undo/redo and save/reopen were checked. Upstream Uncrustify checks pass. Refs #34839 --- src/engraving/dom/chordrest.cpp | 5 +- src/engraving/rendering/score/chordlayout.cpp | 4 + .../rendering/score/systemlayout.cpp | 4 + src/engraving/rendering/score/tlayout.cpp | 51 +++-- src/engraving/rendering/score/tlayout.h | 1 + src/engraving/tests/beam_tests.cpp | 184 ++++++++++++++++++ vtest/scores/historical-tab-grids.mscz | Bin 0 -> 1346 bytes 7 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 vtest/scores/historical-tab-grids.mscz diff --git a/src/engraving/dom/chordrest.cpp b/src/engraving/dom/chordrest.cpp index 7a68bc7df61e9..2b90ed7c02638 100644 --- a/src/engraving/dom/chordrest.cpp +++ b/src/engraving/dom/chordrest.cpp @@ -683,7 +683,10 @@ void ChordRest::undoChangeProperty(Pid id, const PropertyValue& newValue, Proper { if (id == Pid::BEAM_MODE) { if (isChord() && m_durationType.hooks() == 0) { - return; + const StaffType* tab = staffType(); + if (!tab || !tab->isTabStaff() || !tab->genDurations()) { + return; + } } BeamMode newBeamMode = newValue.value(); if ((newBeamMode == BeamMode::BEGIN16 && m_durationType.hooks() < 2) diff --git a/src/engraving/rendering/score/chordlayout.cpp b/src/engraving/rendering/score/chordlayout.cpp index 476b6b11f8b21..855e0104b8fd2 100644 --- a/src/engraving/rendering/score/chordlayout.cpp +++ b/src/engraving/rendering/score/chordlayout.cpp @@ -3461,6 +3461,10 @@ void ChordLayout::fillShape(const Chord* item, ChordRest::LayoutData* ldata) shape.add(note->shape().translate(note->pos())); } + if (item->tabDur() && item->tabDur()->addToSkyline()) { + shape.add(item->tabDur()->shape().translate(item->tabDur()->pos())); + } + for (const NoteParenthesisInfo* parenInfo : item->noteParentheses()) { Parenthesis* leftParen = parenInfo->leftParen(); Parenthesis* rightParen = parenInfo->rightParen(); diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index dd0403d6caa53..2db24bf0d96ac 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -1259,6 +1259,10 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx) GraceNotesGroup& graceAfter = chord->graceNotesAfter(); TLayout::layoutGraceNotesGroup2(&graceBefore, graceBefore.mutldata()); TLayout::layoutGraceNotesGroup2(&graceAfter, graceAfter.mutldata()); + if (TabDurationSymbol* symbol = chord->tabDur()) { + TLayout::layoutTabDurationSymbol2(symbol, symbol->mutldata()); + ChordLayout::fillShape(chord, chord->mutldata()); + } } for (ChordRest* cr : elementsToLayout.chordRests) { diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index 74ca225fce31b..f1d9e24b7d550 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -5683,11 +5683,28 @@ void TLayout::layoutTabDurationSymbol(const TabDurationSymbol* item, TabDuration double xpos, ypos; // position coords ldata->beamGrid = TabBeamGrid::NONE; + ldata->beamLength = 0.0; + ldata->beamLevel = 0; Chord* chord = item->ownershipParent() && item->ownershipParent()->isChord() ? toChord(item->ownershipParent()) : nullptr; + // A grid replaces the individual duration signs. Only connect equal, undotted + // values; otherwise retain the signs rather than lose their rhythm information. + auto connects = [](const ChordRest* left, const ChordRest* right) { + return left && right && left->isChord() && right->isChord() + && !left->isGrace() && !right->isGrace() && !toChord(left)->noStem() && !toChord(right)->noStem() + && left->measure() == right->measure() && left->staffType() == right->staffType() + && left->durationType() == right->durationType() && left->tuplet() == right->tuplet() + && !left->dots() && !right->dots() + && (left->beamMode() == BeamMode::BEGIN || left->beamMode() == BeamMode::MID) + && (right->beamMode() == BeamMode::MID || right->beamMode() == BeamMode::END); + }; + const int beamLevel = chord ? static_cast(chord->durationType().type()) + - static_cast(item->tab()->tabDurationFont().zeroBeamLevel) : 0; + const bool joinsPrevious = beamLevel > 0 && (chord->beamMode() == BeamMode::MID || chord->beamMode() == BeamMode::END) + && connects(Navigation::prevChordRest(chord), chord); + const bool joinsNext = beamLevel > 0 && (chord->beamMode() == BeamMode::BEGIN || chord->beamMode() == BeamMode::MID) + && connects(chord, Navigation::nextChordRest(chord)); // if no chord (shouldn't happens...) or not a special beam mode, layout regular symbol - if (!chord || !chord->isChord() - || (chord->beamMode() != BeamMode::BEGIN && chord->beamMode() != BeamMode::MID - && chord->beamMode() != BeamMode::END)) { + if (beamLevel < 1 || (!joinsPrevious && !joinsNext)) { FontMetrics fm(item->tab()->durationFont()); hbb = item->tab()->durationBoxH(); wbb = fm.width(item->text()); @@ -5710,15 +5727,8 @@ void TLayout::layoutTabDurationSymbol(const TabDurationSymbol* item, TabDuration ybb = -hbb; // bbox top is at top of stem height xpos = 0.75 * spatium; // conventional centring of stem on fret marks ypos = item->tab()->durationGridYOffset(); // stem start is at bottom - if (chord->beamMode() == BeamMode::BEGIN) { - ldata->beamGrid = TabBeamGrid::INITIAL; - ldata->beamLength = 0.0; - } else if (chord->beamMode() == BeamMode::MID || chord->beamMode() == BeamMode::END) { - ldata->beamLevel = (static_cast(chord->durationType().type()) - static_cast(font.zeroBeamLevel)); - ldata->beamGrid = (ldata->beamLevel < 1 ? TabBeamGrid::INITIAL : TabBeamGrid::MEDIALFINAL); - // _beamLength and bbox x and width will be set in layout2(), - // once horiz. positions of chords are known - } + ldata->beamLevel = beamLevel; + ldata->beamGrid = joinsPrevious ? TabBeamGrid::MEDIALFINAL : TabBeamGrid::INITIAL; } // set this' mag from parent chord mag (include staff mag) double mag = chord != nullptr ? chord->mag() : 1.0; @@ -5729,6 +5739,23 @@ void TLayout::layoutTabDurationSymbol(const TabDurationSymbol* item, TabDuration ldata->setPos(xpos * mag, ypos * mag); } +void TLayout::layoutTabDurationSymbol2(const TabDurationSymbol* item, TabDurationSymbol::LayoutData* ldata) +{ + if (ldata->beamGrid != TabBeamGrid::MEDIALFINAL) { + return; + } + const ChordRest* previous = Navigation::prevChordRest(toChord(item->ownershipParent())); + if (!previous || !previous->tabDur()) { + return; + } + // Horizontal spacing is now final. Drawing applies the symbol magnification again. + const double length = previous->tabDur()->pagePos().x() - item->pagePos().x(); + ldata->beamLength = length / item->magS(); + RectF bbox = ldata->bbox(); + bbox.setLeft(std::min(bbox.left(), length)); + ldata->setBbox(bbox); +} + void TLayout::layoutTapping(Tapping* item, Tapping::LayoutData* ldata, LayoutContext& ctx) { TappingLayout::layoutTapping(item, ldata, ctx); diff --git a/src/engraving/rendering/score/tlayout.h b/src/engraving/rendering/score/tlayout.h index f48119f2f72f6..7e3703e7b7e81 100644 --- a/src/engraving/rendering/score/tlayout.h +++ b/src/engraving/rendering/score/tlayout.h @@ -342,6 +342,7 @@ class TLayout static void layoutSystemText(const SystemText* item, SystemText::LayoutData* ldata); static void layoutTabDurationSymbol(const TabDurationSymbol* item, TabDurationSymbol::LayoutData* ldata); + static void layoutTabDurationSymbol2(const TabDurationSymbol* item, TabDurationSymbol::LayoutData* ldata); static void layoutTapping(Tapping* item, Tapping::LayoutData* ldata, LayoutContext& ctx); static void layoutTappingHalfSlur(TappingHalfSlur* item); static void layoutTempoText(const TempoText* item, TempoText::LayoutData* ldata); diff --git a/src/engraving/tests/beam_tests.cpp b/src/engraving/tests/beam_tests.cpp index d08991bf8f752..50bbd8b416c2b 100644 --- a/src/engraving/tests/beam_tests.cpp +++ b/src/engraving/tests/beam_tests.cpp @@ -21,6 +21,7 @@ */ #include +#include #include "engraving/dom/beam.h" #include "engraving/dom/chord.h" @@ -28,9 +29,14 @@ #include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" #include "engraving/dom/note.h" +#include "engraving/dom/staff.h" +#include "engraving/dom/tabdurationsymbol.h" #include "engraving/dom/tremolotwochord.h" #include "engraving/editing/flip.h" +#include "engraving/editing/editbeam.h" +#include "engraving/editing/editdata.h" +#include "engraving/editing/navigation.h" #include "engraving/editing/transaction/transaction.h" #include "utils/scorerw.h" @@ -61,6 +67,184 @@ void Engraving_BeamTests::beam(const char* path) delete score; } +TEST_F(Engraving_BeamTests, historicalTabGridModes) +{ + std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/historical-tab-grids.mscz")); + ASSERT_TRUE(score); + ChordRest* first = score->firstMeasure()->firstChordRest(0); + ASSERT_TRUE(first); + ChordRest* second = Navigation::nextChordRest(first); + ASSERT_TRUE(second); + ASSERT_EQ(first->durationType().type(), DurationType::V_QUARTER); + ASSERT_EQ(second->durationType().type(), DurationType::V_QUARTER); + for (ChordRest* cr = first; cr; cr = Navigation::nextChordRest(cr)) { + cr->setBeamMode(BeamMode::AUTO); + } + + auto setMode = [&](ChordRest* cr, BeamMode mode) { + score->select(cr, SelectType::SINGLE); + score->transactionManager()->transaction(TranslatableString::untranslatable("TAB grid mode"), [&](Transaction& tx) { + EditBeam::setBeamMode(tx, score.get(), mode); + }); + }; + setMode(first, BeamMode::BEGIN); + ASSERT_EQ(first->beamMode(), BeamMode::BEGIN); + EXPECT_EQ(first->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); + setMode(second, BeamMode::MID); + ASSERT_EQ(second->beamMode(), BeamMode::MID); + ASSERT_TRUE(second->tabDur()); + EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::MEDIALFINAL); + EXPECT_LT(second->tabDur()->ldata()->beamLength, 0.0); + + EditData ed; + score->undoRedo(true, &ed); + EXPECT_EQ(second->beamMode(), BeamMode::AUTO); + EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); + EXPECT_EQ(first->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); + score->undoRedo(true, &ed); + EXPECT_EQ(first->beamMode(), BeamMode::AUTO); + score->undoRedo(false, &ed); + score->undoRedo(false, &ed); + EXPECT_EQ(first->beamMode(), BeamMode::BEGIN); + EXPECT_EQ(second->beamMode(), BeamMode::MID); + EXPECT_LT(second->tabDur()->ldata()->beamLength, 0.0); + + ASSERT_TRUE(ScoreRW::saveScore(score.get(), u"historicalTabGridModes.mscx")); + std::unique_ptr reopened(ScoreRW::readScore(u"historicalTabGridModes.mscx", true)); + ASSERT_TRUE(reopened); + ChordRest* restored = Navigation::nextChordRest(reopened->firstMeasure()->firstChordRest(0)); + ASSERT_TRUE(restored && restored->tabDur()); + EXPECT_EQ(restored->beamMode(), BeamMode::MID); + EXPECT_EQ(restored->tabDur()->ldata()->beamLevel, 2); + EXPECT_LT(restored->tabDur()->ldata()->beamLength, 0.0); + + // These modes subdivide modern beams and still require actual hooks. + setMode(second, BeamMode::BEGIN16); + EXPECT_EQ(second->beamMode(), BeamMode::MID); + setMode(second, BeamMode::BEGIN32); + EXPECT_EQ(second->beamMode(), BeamMode::MID); + + score->deselectAll(); + score->transactionManager()->transaction(TranslatableString::untranslatable("Reset TAB grid"), [&](Transaction& tx) { + EditBeam::resetBeamMode(tx, score.get()); + }); + EXPECT_EQ(first->beamMode(), BeamMode::AUTO); + EXPECT_EQ(second->beamMode(), BeamMode::AUTO); + EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); + EXPECT_EQ(second->tabDur()->ldata()->beamLength, 0.0); + score->undoRedo(true, &ed); + EXPECT_EQ(first->beamMode(), BeamMode::BEGIN); + EXPECT_EQ(second->beamMode(), BeamMode::MID); + score->undoRedo(false, &ed); + EXPECT_EQ(first->beamMode(), BeamMode::AUTO); + EXPECT_EQ(second->beamMode(), BeamMode::AUTO); + + score.reset(ScoreRW::readScore(u"selectionfilter_data/selectionfilter1.mscx")); + ASSERT_TRUE(score); + ChordRest* quarter = score->firstMeasure()->firstChordRest(0); + ASSERT_TRUE(quarter); + ASSERT_EQ(quarter->durationType().type(), DurationType::V_QUARTER); + setMode(quarter, BeamMode::BEGIN); + EXPECT_EQ(quarter->beamMode(), BeamMode::AUTO); +} + +TEST_F(Engraving_BeamTests, historicalTabGridLayout) +{ + std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/historical-tab-grids.mscz")); + ASSERT_TRUE(score); + score->doLayout(); + ChordRest* cr = score->firstMeasure()->firstChordRest(0); + for (int i = 0; i < 6; ++i) { + ASSERT_TRUE(cr); + ASSERT_TRUE(cr->isChord()); + const TabDurationSymbol* symbol = toChord(cr)->tabDur(); + ASSERT_TRUE(symbol); + if (i == 0 || i == 2) { + EXPECT_EQ(symbol->ldata()->beamGrid, TabBeamGrid::INITIAL); + } else { + EXPECT_EQ(symbol->ldata()->beamGrid, TabBeamGrid::MEDIALFINAL); + EXPECT_EQ(symbol->ldata()->beamLevel, i < 2 ? 2 : 3); + EXPECT_LT(symbol->ldata()->beamLength, 0.0); + const ChordRest* previous = Navigation::prevChordRest(cr); + const double expectedLength = previous->tabDur()->pagePos().x() - symbol->pagePos().x(); + EXPECT_NEAR(symbol->ldata()->beamLength * symbol->magS(), expectedLength, 0.000001); + EXPECT_LE(symbol->ldata()->bbox().left(), expectedLength + 0.000001); + } + cr = Navigation::nextChordRest(cr); + } +} + +TEST_F(Engraving_BeamTests, historicalTabGridPreservesUnequalDurations) +{ + std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/tabfont-14.mscz")); + ASSERT_TRUE(score); + ChordRest* first = score->firstMeasure()->firstChordRest(0); + ChordRest* second = Navigation::nextChordRest(first); + ASSERT_EQ(first->durationType().type(), DurationType::V_HALF); + ASSERT_EQ(second->durationType().type(), DurationType::V_QUARTER); + first->setBeamMode(BeamMode::BEGIN); + second->setBeamMode(BeamMode::MID); + score->doLayout(); + ASSERT_TRUE(first->tabDur() && second->tabDur()); + EXPECT_EQ(first->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); + EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); + EXPECT_NE(first->tabDur()->text(), second->tabDur()->text()); +} + +TEST_F(Engraving_BeamTests, historicalTabGridBoundaries) +{ + std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/historical-tab-grids.mscz")); + ASSERT_TRUE(score); + const std::vector > expected { + { TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL, TabBeamGrid::INITIAL, + TabBeamGrid::MEDIALFINAL, TabBeamGrid::MEDIALFINAL, TabBeamGrid::MEDIALFINAL }, + // END closes the group; the following MID has no partner. + { TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL, TabBeamGrid::NONE, TabBeamGrid::NONE }, + // Dotted values retain their individual duration signs. + { TabBeamGrid::NONE, TabBeamGrid::NONE, TabBeamGrid::NONE }, + { TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL }, + // An Italian whole note has no grid beam level. + { TabBeamGrid::NONE }, + // A rest interrupts a group; a new BEGIN can start another. + { TabBeamGrid::NONE, TabBeamGrid::NONE, TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL }, + // MID does not connect to the previous measure's quarter-note group. + { TabBeamGrid::NONE, TabBeamGrid::NONE, TabBeamGrid::NONE }, + }; + ChordRest* cr = score->firstMeasure()->firstChordRest(0); + for (const auto& measure : expected) { + for (TabBeamGrid grid : measure) { + ASSERT_TRUE(cr); + if (cr->isChord()) { + ASSERT_TRUE(cr->tabDur()); + EXPECT_EQ(cr->tabDur()->ldata()->beamGrid, grid) << cr->tick().ticks(); + } + cr = Navigation::nextChordRest(cr); + } + } + EXPECT_FALSE(cr); + + // The French font has no quarter-note grid beams, and one for eighth notes. + // Use a small staff to check that connector lengths are magnified only once. + StaffType* tab = score->staff(0)->staffType(Fraction(0, 1)); + tab->setDurationFontName(u"MuseScore Tab French"); + tab->setUserMag(0.7); + score->setLayoutAll(); + score->doLayout(); + cr = score->firstMeasure()->firstChordRest(0); + for (int i = 0; i < 6; ++i) { + ASSERT_TRUE(cr && cr->tabDur()); + const TabDurationSymbol* symbol = cr->tabDur(); + if (i < 2) { + EXPECT_EQ(symbol->ldata()->beamGrid, TabBeamGrid::NONE); + } else if (i > 2) { + EXPECT_EQ(symbol->ldata()->beamLevel, 1); + const double length = Navigation::prevChordRest(cr)->tabDur()->pagePos().x() - symbol->pagePos().x(); + EXPECT_NEAR(symbol->ldata()->beamLength * symbol->magS(), length, 0.000001); + } + cr = Navigation::nextChordRest(cr); + } +} + TEST_F(Engraving_BeamTests, beamA) { beam("Beam-A.mscx"); diff --git a/vtest/scores/historical-tab-grids.mscz b/vtest/scores/historical-tab-grids.mscz new file mode 100644 index 0000000000000000000000000000000000000000..4388766ce499baf2d7ebe1482deee66ff0575f99 GIT binary patch literal 1346 zcmWIWW@Zs#U|`^2$lIIgCtNiBeS?9zbG>~F-Nx~F-bSQC^My4 zFSj_k;%$^~{%s3^z2Cz*_N7!uoG$w#>JT>ZnueK!Fw4en*ZwUg(>lDCoZ?r1xn5s0 zQpnZ%me|{6zKbSX&N+VZ#YbNEO>g(^^cGHM?@9I&Q$PIg!4XEG3j&if3YCt2d;jyK z^#7^Tz4%r)7_Hc_*R$T>#n1IJ$4|>vEji}B=kTrf{m+@tuU&I!r_lG4ax2#GxPH** zbA5CE_u`q)uWjr6!CoN3U2pNm|2)^3x@9aMc{p`~>u=2W^?%M4>?d-)q_?~3dIu+a zd-uH!;cMeLx7?i;mbw1Daeb4i0TRbOB;Hfh8dMM=6@H_atF59;3&Ycb_3{$T! zIS^v3Hig?m_`x0dRYy*5pT`6 z-5=JuTo*Rma_apW2f4G`OKP+p%RZa={)<)ErjDfEtQ%?uM_HD;-pY#RIn5IINin?7 zY0BX?qok^8=9q%orMEU5Jv{x(v?;GNdzd>W79BQgG^m(20`h|g}uJ^tneDD{&u;q1A$?C2(S=Vw!)|NZ&NHAgz5-D#e zd(nMFWtrR2s-~4PMX~o89A^~AcAt~tNIl1{dEjUEE0z!=zV)FiLmyZ*+&Y%6)AZw( z{QPILcWvik*>(H;*(sN@UK<>%R6h1|qQvuF2{oqh@@m0fkxI?y|9T`f&UkJX^K9a= zE0z0Pm}E8P{a#$CSRNf!tKKKMaH#ZZ zNG=w1Ub)%1v&A*vIctx#5b$@6QUw5$8x8Hb(^_S#x~PE|o~ zrsg`?qhaY0t1s7GwRu~bWwGni)xM|m+1EY2w>8J5sJ|uXNPt+B;5{ZU{Y;L3Y)Wga zoKnpbpE}N){zQD`JJE>RJ+oVP#TU4?nl3GOd~J3AR8q=sSFtC)ZuPh9)?WWK!>-`n zR6~np3TNBq_TCHZ?k+0}((OL};Mtm~vm?397vDd6mS01?zNg#z;h(Ckc??f247pd9^;^PksPOslaIdoHZO+8k7LB7!doqhB)ea`nl;R=jW9qX6B_9=~d+B_?|K3 zYf#{ExVT!P%%S|qW`#v2E3a(1T2bSE=ijHgh|O2mRd$@a)Q!b z#_lgN-=r_bdA`ok>JW1F%P?L0E#bzIxs&1_Cx5Vi^ZPS%fHxzP2s7>?3g{j%XkY|U nI14OvW6=EsF@%Akfl-VFmk$HHS=m79n1HYwNG}AcWMBXQ?ch|( literal 0 HcmV?d00001 From 8c40986f4fe7c4e345ecdfd425216dc122df96ab Mon Sep 17 00:00:00 2001 From: Shiina Mashiro <21091736+0xMashiro@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:27:04 +0800 Subject: [PATCH 2/4] Include Navigation directly in TAB duration layout The standalone CI build cannot resolve Navigation in tlayout.cpp. Local unity compilation masked the missing direct include. Include the owning header so both build modes compile. Validated tlayout.cpp, systemlayout.cpp and chordlayout.cpp as standalone translation units, rebuilt engraving_tests, and passed all 21 enabled Beam tests. Upstream Uncrustify passes. Refs #34839. --- src/engraving/rendering/score/tlayout.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index f1d9e24b7d550..2cd131b7e6efd 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -160,6 +160,7 @@ #include "dom/factory.h" #include "editing/editchord.h" +#include "editing/navigation.h" #include "accidentalslayout.h" #include "arpeggiolayout.h" From c3dcbea2b758355ec5c63311dd9f62eb23615ef2 Mon Sep 17 00:00:00 2001 From: Shiina Mashiro <21091736+0xMashiro@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:32:08 +0800 Subject: [PATCH 3/4] Move historical TAB layout coverage to visual tests Separate duration-grid connection predicates for review and preserve the existing rendering behavior. Keep edit/undo coverage in Beam tests with a two-note fixture, and move appearance, boundary and French-font scaling coverage into three vtest scores. Reuse the suite's transaction label. All 18 enabled Beam tests pass. Restoring the original hook guard makes the same edit regression fail, with 17 passes. All three visual scores have identical draw data and PNG pixels before/after the predicate refactor; the earlier edit-only implementation produces different draw data. Standalone layout compilation and official formatting checks pass. Refs: #34839 --- src/engraving/rendering/score/tlayout.cpp | 18 ++- .../tests/beam_data/historical-tab-grid.mscx | 90 +++++++++++++ src/engraving/tests/beam_tests.cpp | 124 +----------------- .../historical-tab-grid-boundaries.mscz | Bin 0 -> 1306 bytes .../historical-tab-grid-french-small.mscz | Bin 0 -> 1372 bytes vtest/scores/historical-tab-grids.mscz | Bin 1346 -> 1210 bytes 6 files changed, 107 insertions(+), 125 deletions(-) create mode 100644 src/engraving/tests/beam_data/historical-tab-grid.mscx create mode 100644 vtest/scores/historical-tab-grid-boundaries.mscz create mode 100644 vtest/scores/historical-tab-grid-french-small.mscz diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index 2cd131b7e6efd..3579f9917f385 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -5690,13 +5690,17 @@ void TLayout::layoutTabDurationSymbol(const TabDurationSymbol* item, TabDuration // A grid replaces the individual duration signs. Only connect equal, undotted // values; otherwise retain the signs rather than lose their rhythm information. auto connects = [](const ChordRest* left, const ChordRest* right) { - return left && right && left->isChord() && right->isChord() - && !left->isGrace() && !right->isGrace() && !toChord(left)->noStem() && !toChord(right)->noStem() - && left->measure() == right->measure() && left->staffType() == right->staffType() - && left->durationType() == right->durationType() && left->tuplet() == right->tuplet() - && !left->dots() && !right->dots() - && (left->beamMode() == BeamMode::BEGIN || left->beamMode() == BeamMode::MID) - && (right->beamMode() == BeamMode::MID || right->beamMode() == BeamMode::END); + if (!left || !right || !left->isChord() || !right->isChord()) { + return false; + } + const bool regularChords = !left->isGrace() && !right->isGrace() + && !toChord(left)->noStem() && !toChord(right)->noStem(); + const bool sameContext = left->measure() == right->measure() && left->staffType() == right->staffType() + && left->tuplet() == right->tuplet(); + const bool equalUndottedDurations = left->durationType() == right->durationType() && !left->dots() && !right->dots(); + const bool leftContinues = left->beamMode() == BeamMode::BEGIN || left->beamMode() == BeamMode::MID; + const bool rightConnects = right->beamMode() == BeamMode::MID || right->beamMode() == BeamMode::END; + return regularChords && sameContext && equalUndottedDurations && leftContinues && rightConnects; }; const int beamLevel = chord ? static_cast(chord->durationType().type()) - static_cast(item->tab()->tabDurationFont().zeroBeamLevel) : 0; diff --git a/src/engraving/tests/beam_data/historical-tab-grid.mscx b/src/engraving/tests/beam_data/historical-tab-grid.mscx new file mode 100644 index 0000000000000..feb1566bb280d --- /dev/null +++ b/src/engraving/tests/beam_data/historical-tab-grid.mscx @@ -0,0 +1,90 @@ + + + + 480 + + + + + 6 + 1.5 + 1 + 0 + 1 + MuseScore Tab Italian + 15 + 0 + 0 + 0 + 9 + 0 + 0 + 1 + 1 + 0 + 1 + 0 + 0 + 1 + 1 + 3 + + + + Renaissance Tenor Lute (6 course) + 43 + 79 + 43 + 79 + pluck.guitar + + 24 + 43 + 48 + 53 + 57 + 62 + 67 + + + + Fluid + + + + + + + + 2 + 4 + + + auto + quarter + + 67 + 15 + 0 + 0 + + + + auto + quarter + + 69 + 17 + 2 + 0 + + + + + + + \ No newline at end of file diff --git a/src/engraving/tests/beam_tests.cpp b/src/engraving/tests/beam_tests.cpp index 50bbd8b416c2b..5a47a6cc5dcf6 100644 --- a/src/engraving/tests/beam_tests.cpp +++ b/src/engraving/tests/beam_tests.cpp @@ -30,17 +30,16 @@ #include "engraving/dom/measure.h" #include "engraving/dom/note.h" #include "engraving/dom/staff.h" -#include "engraving/dom/tabdurationsymbol.h" #include "engraving/dom/tremolotwochord.h" -#include "engraving/editing/flip.h" #include "engraving/editing/editbeam.h" #include "engraving/editing/editdata.h" +#include "engraving/editing/flip.h" #include "engraving/editing/navigation.h" #include "engraving/editing/transaction/transaction.h" -#include "utils/scorerw.h" #include "utils/scorecomp.h" +#include "utils/scorerw.h" using namespace mu::engraving; @@ -69,7 +68,7 @@ void Engraving_BeamTests::beam(const char* path) TEST_F(Engraving_BeamTests, historicalTabGridModes) { - std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/historical-tab-grids.mscz")); + std::unique_ptr score(ScoreRW::readScore(BEAM_DATA_DIR + u"historical-tab-grid.mscx")); ASSERT_TRUE(score); ChordRest* first = score->firstMeasure()->firstChordRest(0); ASSERT_TRUE(first); @@ -77,46 +76,34 @@ TEST_F(Engraving_BeamTests, historicalTabGridModes) ASSERT_TRUE(second); ASSERT_EQ(first->durationType().type(), DurationType::V_QUARTER); ASSERT_EQ(second->durationType().type(), DurationType::V_QUARTER); - for (ChordRest* cr = first; cr; cr = Navigation::nextChordRest(cr)) { - cr->setBeamMode(BeamMode::AUTO); - } auto setMode = [&](ChordRest* cr, BeamMode mode) { score->select(cr, SelectType::SINGLE); - score->transactionManager()->transaction(TranslatableString::untranslatable("TAB grid mode"), [&](Transaction& tx) { + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving beam tests"), [&](Transaction& tx) { EditBeam::setBeamMode(tx, score.get(), mode); }); }; setMode(first, BeamMode::BEGIN); ASSERT_EQ(first->beamMode(), BeamMode::BEGIN); - EXPECT_EQ(first->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); setMode(second, BeamMode::MID); ASSERT_EQ(second->beamMode(), BeamMode::MID); - ASSERT_TRUE(second->tabDur()); - EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::MEDIALFINAL); - EXPECT_LT(second->tabDur()->ldata()->beamLength, 0.0); EditData ed; score->undoRedo(true, &ed); EXPECT_EQ(second->beamMode(), BeamMode::AUTO); - EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); - EXPECT_EQ(first->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); score->undoRedo(true, &ed); EXPECT_EQ(first->beamMode(), BeamMode::AUTO); score->undoRedo(false, &ed); score->undoRedo(false, &ed); EXPECT_EQ(first->beamMode(), BeamMode::BEGIN); EXPECT_EQ(second->beamMode(), BeamMode::MID); - EXPECT_LT(second->tabDur()->ldata()->beamLength, 0.0); ASSERT_TRUE(ScoreRW::saveScore(score.get(), u"historicalTabGridModes.mscx")); std::unique_ptr reopened(ScoreRW::readScore(u"historicalTabGridModes.mscx", true)); ASSERT_TRUE(reopened); ChordRest* restored = Navigation::nextChordRest(reopened->firstMeasure()->firstChordRest(0)); - ASSERT_TRUE(restored && restored->tabDur()); + ASSERT_TRUE(restored); EXPECT_EQ(restored->beamMode(), BeamMode::MID); - EXPECT_EQ(restored->tabDur()->ldata()->beamLevel, 2); - EXPECT_LT(restored->tabDur()->ldata()->beamLength, 0.0); // These modes subdivide modern beams and still require actual hooks. setMode(second, BeamMode::BEGIN16); @@ -125,13 +112,11 @@ TEST_F(Engraving_BeamTests, historicalTabGridModes) EXPECT_EQ(second->beamMode(), BeamMode::MID); score->deselectAll(); - score->transactionManager()->transaction(TranslatableString::untranslatable("Reset TAB grid"), [&](Transaction& tx) { + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving beam tests"), [&](Transaction& tx) { EditBeam::resetBeamMode(tx, score.get()); }); EXPECT_EQ(first->beamMode(), BeamMode::AUTO); EXPECT_EQ(second->beamMode(), BeamMode::AUTO); - EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); - EXPECT_EQ(second->tabDur()->ldata()->beamLength, 0.0); score->undoRedo(true, &ed); EXPECT_EQ(first->beamMode(), BeamMode::BEGIN); EXPECT_EQ(second->beamMode(), BeamMode::MID); @@ -148,103 +133,6 @@ TEST_F(Engraving_BeamTests, historicalTabGridModes) EXPECT_EQ(quarter->beamMode(), BeamMode::AUTO); } -TEST_F(Engraving_BeamTests, historicalTabGridLayout) -{ - std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/historical-tab-grids.mscz")); - ASSERT_TRUE(score); - score->doLayout(); - ChordRest* cr = score->firstMeasure()->firstChordRest(0); - for (int i = 0; i < 6; ++i) { - ASSERT_TRUE(cr); - ASSERT_TRUE(cr->isChord()); - const TabDurationSymbol* symbol = toChord(cr)->tabDur(); - ASSERT_TRUE(symbol); - if (i == 0 || i == 2) { - EXPECT_EQ(symbol->ldata()->beamGrid, TabBeamGrid::INITIAL); - } else { - EXPECT_EQ(symbol->ldata()->beamGrid, TabBeamGrid::MEDIALFINAL); - EXPECT_EQ(symbol->ldata()->beamLevel, i < 2 ? 2 : 3); - EXPECT_LT(symbol->ldata()->beamLength, 0.0); - const ChordRest* previous = Navigation::prevChordRest(cr); - const double expectedLength = previous->tabDur()->pagePos().x() - symbol->pagePos().x(); - EXPECT_NEAR(symbol->ldata()->beamLength * symbol->magS(), expectedLength, 0.000001); - EXPECT_LE(symbol->ldata()->bbox().left(), expectedLength + 0.000001); - } - cr = Navigation::nextChordRest(cr); - } -} - -TEST_F(Engraving_BeamTests, historicalTabGridPreservesUnequalDurations) -{ - std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/tabfont-14.mscz")); - ASSERT_TRUE(score); - ChordRest* first = score->firstMeasure()->firstChordRest(0); - ChordRest* second = Navigation::nextChordRest(first); - ASSERT_EQ(first->durationType().type(), DurationType::V_HALF); - ASSERT_EQ(second->durationType().type(), DurationType::V_QUARTER); - first->setBeamMode(BeamMode::BEGIN); - second->setBeamMode(BeamMode::MID); - score->doLayout(); - ASSERT_TRUE(first->tabDur() && second->tabDur()); - EXPECT_EQ(first->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); - EXPECT_EQ(second->tabDur()->ldata()->beamGrid, TabBeamGrid::NONE); - EXPECT_NE(first->tabDur()->text(), second->tabDur()->text()); -} - -TEST_F(Engraving_BeamTests, historicalTabGridBoundaries) -{ - std::unique_ptr score(ScoreRW::readScore(u"../../../vtest/scores/historical-tab-grids.mscz")); - ASSERT_TRUE(score); - const std::vector > expected { - { TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL, TabBeamGrid::INITIAL, - TabBeamGrid::MEDIALFINAL, TabBeamGrid::MEDIALFINAL, TabBeamGrid::MEDIALFINAL }, - // END closes the group; the following MID has no partner. - { TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL, TabBeamGrid::NONE, TabBeamGrid::NONE }, - // Dotted values retain their individual duration signs. - { TabBeamGrid::NONE, TabBeamGrid::NONE, TabBeamGrid::NONE }, - { TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL }, - // An Italian whole note has no grid beam level. - { TabBeamGrid::NONE }, - // A rest interrupts a group; a new BEGIN can start another. - { TabBeamGrid::NONE, TabBeamGrid::NONE, TabBeamGrid::INITIAL, TabBeamGrid::MEDIALFINAL }, - // MID does not connect to the previous measure's quarter-note group. - { TabBeamGrid::NONE, TabBeamGrid::NONE, TabBeamGrid::NONE }, - }; - ChordRest* cr = score->firstMeasure()->firstChordRest(0); - for (const auto& measure : expected) { - for (TabBeamGrid grid : measure) { - ASSERT_TRUE(cr); - if (cr->isChord()) { - ASSERT_TRUE(cr->tabDur()); - EXPECT_EQ(cr->tabDur()->ldata()->beamGrid, grid) << cr->tick().ticks(); - } - cr = Navigation::nextChordRest(cr); - } - } - EXPECT_FALSE(cr); - - // The French font has no quarter-note grid beams, and one for eighth notes. - // Use a small staff to check that connector lengths are magnified only once. - StaffType* tab = score->staff(0)->staffType(Fraction(0, 1)); - tab->setDurationFontName(u"MuseScore Tab French"); - tab->setUserMag(0.7); - score->setLayoutAll(); - score->doLayout(); - cr = score->firstMeasure()->firstChordRest(0); - for (int i = 0; i < 6; ++i) { - ASSERT_TRUE(cr && cr->tabDur()); - const TabDurationSymbol* symbol = cr->tabDur(); - if (i < 2) { - EXPECT_EQ(symbol->ldata()->beamGrid, TabBeamGrid::NONE); - } else if (i > 2) { - EXPECT_EQ(symbol->ldata()->beamLevel, 1); - const double length = Navigation::prevChordRest(cr)->tabDur()->pagePos().x() - symbol->pagePos().x(); - EXPECT_NEAR(symbol->ldata()->beamLength * symbol->magS(), length, 0.000001); - } - cr = Navigation::nextChordRest(cr); - } -} - TEST_F(Engraving_BeamTests, beamA) { beam("Beam-A.mscx"); diff --git a/vtest/scores/historical-tab-grid-boundaries.mscz b/vtest/scores/historical-tab-grid-boundaries.mscz new file mode 100644 index 0000000000000000000000000000000000000000..83ee01b67782c16284406b8ccc2c411ea0c32bc9 GIT binary patch literal 1306 zcmWIWW@Zs#U|`^2cpsz{`{liK*;-}>h9r3g24$dVMrLtIeoV}~z#GMJ=s zE`0gN<>XoU&6D|G_=adOo}N*dv9bANfr-=tb4P(tKI2P|S#JtGc7MJ(P-I)!+n7_w zYuK-Zbq4>KtzzW7`Qb}fk+N;QKZHGId78-aFMpqXCT|&Ir;zs|E)%&ppYv%_mwm3B z=ULnCwtdFw29?hTD=w^y_u6yQBrJKooHs|%xe!Zvk3Hu$hRCqVdOa?3a$Jxpa*g@^ zRt2XdspfOpLCLoyrL|N`mAHhbY)KauOZQ@3zkkZf0&FFDnG z2SdqSxo2GKo3%TOn=-fWI%+GL;#v11*Y}R-)hEH)`(}N)%PGC>)W+NdM-9zaTfT0L zn8us*#e1ntZ$?|6kx^MayA#*vCFedIRTg_Q&+OI8UfYhTo04xkgmO)C*cx|O=QJbd zj=-Z)+ZjH)oc<*IzvFSfHk0Sn8M)R6jwhY{WUayIU9zlk+g;I=8(}HYn->OexhN_+ zX*E+u>>a~V>flJ>{s)Zrblx8*)V(e1+Uj@{z^>u>p9JAs5e)_tZB>cy*Vg_doN!GzHi*>Cj5JM zk6r4Fpc8X7RMRz&yKXqTv+3u95+e>L{}~}dUo68_3|E=7eU;Hq5h?qW{Z6L%{Fd^0 zVXNw&$1sa^-F|e-=+Rvs?YnQM=WRD%Iq?hIOX1aa-($MxWYuxw6Pb(}vIm7>c z`j=Z^J&(;%v?~mQIU)s=q>Z^Cg)11%0 z+n@M->VDZ-@da^PUTb`D{&N5EnwkH0pW3|hW%l0s(mzj6zKqZRHT_lk9{U1wc_t8x0kN-Zh@-BjpPPPieqKpp zW?pKMUPW$>@0ruQ4GKIA7eaZ;Qy5dGf10c|b=EeOzW0Io@4qBH%xu`~*>xyKcIC_C zYxR}tn|lxQh9n7+{Bz5z1-sDinmd>^KM%R)V>ers5|-YaMZTP+6t>Zu5*|<2(xSyca_h~ z)DfKOF;)Ed%j^0pwN23nI-{TJl6w4_{E9l#}?$aH6}Rhe1F zuCN*0E;a#`zD7Ql$xb^PESS#SUFfw(Vm3o#@&k(k(b-2Yl>DBw@@~*1wiP|W6TH2S z@OB;CtH)p5yzQE5FuPn>xA%=?!)rgfZ(UuoPibyV&QbTdwY7&@-br-STjo4a*mOAP z#p0dMnxffcj!doBxNy})IO^H<6BE=w>&nP?R~)Zg#=e(xs#a6L;;e0)J%$Ghr){$m zJ#6GSWsiUBVu7O@)Ose|yU!>dW53d9)~$!yRsLKbQ;rE9@z|L*+hJzG8|Phd2YFVr zJt|OsymlAk7h&GjGk;h=slAo(;qnom-E1`-+f8;gvrH&BFM451c8QYe|9!g@zbw!S z*}dk~F^xJv+IAw)wQl8X7~J^uVW=E^7B@iW1fW`zhb-JiAi=UQ@Q)&HGH#XUG~1D^ZB9X`#oo^Gh?l1 zZmnFtIP87r)+piQyG#D@2`UO)U8K^(#&h(VoU72Zy%(3H-{d_i)Oh!_M^gOhQ0ZgE zQi`&>C&gdvF8Qxny)Y$R_X3xW?H>|O1r^SAm*)YEmL zx|3>^rYN-;?H7C~yWq#HUk~yW4;#i)HP5(AITqiXH3P z*IzU?2VT|}(tm6BEC2HHuTgjIyPu1ZY!2qSlw-2_qWY}|i{{RaIQ>X-N92|wo8Fx6b|!!6uHO^fE^Pd5`)1##?aqHykMX~~wQ;uMuJeV* ze7;9MoxSsA*52yUnr~0`th+A#_;;-7nYz+PH=gDP-}%oFfKphnA2>YQ4_HWK1F;wo z`?`iW>U#RQ=_lvsl_X~7r55Q`x^DxOIH#-7^IXzw4G5i7##9>9d~W#cr~8bIFFrGp{#%YMBxI$Mxgy z_yBK4CJ|=b1s2d5V9>w_qKGQK(2Yd*7Q{dXh6YAm7F?bT@MdKLsbd1d=|FltP$dHc E0E7-`u>b%7 literal 0 HcmV?d00001 diff --git a/vtest/scores/historical-tab-grids.mscz b/vtest/scores/historical-tab-grids.mscz index 4388766ce499baf2d7ebe1482deee66ff0575f99..82d8bb2e3dae88d170bbc8d7035364383c4938a6 100644 GIT binary patch delta 1088 zcmV-G1i$;j3c3j!P)h>@6aWAK2mpLiDqXNy%Q{0+b|5i z_g5IXbUQ3g(!2^d8rlWL3baAdWEgf-Y}OWLORgSiyI()aj%~?F4Gh==8+M8)exxY! zqv!5+@Q3hyCe!iP`T| zAdzE9RECEA+4O9#G1W$Srf(e@++i*~5-obeUTwdY_~D_FDtYsggBNqA@ntapOnz$Cl=4%#TO!)4G4SnDs zKMB~tvn;En(Fc2NgF6la`T|QhcfW9mZKydXs2c#EC&UvJK1# zC0QA^E{i4EAVzP03g&1;wRD(~u%~+vQkat(p-mpKWKXnWPbnNgtdJeq2C7_;2nu@* zvm^&3V5PFHege#imedaX5{F+m1fn7^TXuji*_NgEkOP+JOapDXX-L2B$hv2Jr)h(0 zDkN7K(6YHhyu*)*(1ckX8%VBWD0qDj6cZs#FM1oGjC(hKN`m)h>V-^k0dIAy)!@c~ zF5?}MVRDDYXGKx>l7+y}`@NZFS|c6$xNv-29Px4C_^=VAzU_5{@>GRi`>`St^T2mh z_LIi-d%naHA242^(YU00n_9-Ch;qL?rk!_bC!MyFS(kP?>eL>wHJmuSW$+=vltSuw z$vKNT&b%FeP8BSAqe;*6Tg0T2psQ4o$gt7~);TSgf#1-tnmK09)H6>rwLNog0Sm=# za_tytO@@tr?bq&WZ$cyS3h9a!zXz)2UbS>Q`A1yo#4sMUJX`|KZdn9q3o+3Xp_W}( z^^g}W`Ka_=fj7^)-Kcwm6_c$kQw788qLk&Tt(SR!SPU8S+twMrR?qZ-a?gyxF$S4z zw4*T@6aWAK2mpLiDqZ_5 zn-@p~0001AlPv@tVzWuk&nrpH%u6k@vnk5YFGT}oE0px@Z1j;d>m$qr08mQ-0u%!j000080DMv^U9efp zIc5U@0NoCgy97BPd{Qc1`z)ImNB{r;UjP6A761SM0000000000fB~HYlN1Fg1{nkZ G0002Wt?NAi delta 1239 zcmdnRd5B9Qz?+#xgn@y9gCTFbMr?$tgzJ4~28L})6D1VtqkQvkTL|p^9?r2Zr8?qt z*&k7du!+|+%p8PSHg>!AZ!wwH;kD!xzxvDd`kIkKuGY82-Y)Z9G}&^_@ry4$^15$& zyLYFza5{TWvX_|p;eQW~FbZ7|n4D3lboAT%pC_gNPo3_?x4OY-#fH6}^#(6~u9rD} zTDEG*G4DNxZ@uq-&V0Up?V3Y7g}$GZTd{`6^@Bd2>znhx7teftZCmFL_5u;^dW$#y z=ef?*Eo1q}!>JQoe`CI{|8uTjKauMtz1>yUJ2=_fyYFoXUmMT4T0Dcl~y5AMjXI#PdnBd34` zYoBub#z;Qja*dh0qP;#bY%S#ZG*76oHRyx>>a#J6dy5qVngmydhwo@_TJs^iGD+I<+`xhmQ(N7ILMvdUQ(m= zSoYb>_g}2SHgzQRX5COTILflz^;TANJRWrvF)GocX;ppM% zU#3lYrP;&WF|p{dS)}DlP+a>p)ECS?vH4Znz&?+&qsWAGw$&> zH`&?9GvR~3=!Gq>n@U!9t;xEUE3&rSaYup?Ymi8JL)nY&BPz?>j#f3Tlqrh6&)_(t zIJUm~oD@guId;thKeJ!4gc$Ly4_z7hz^dWav22~DAGhS^KbyU4I}gjQ+vm?txs>(V z;8>;dv7Zwqp7%k1t1<8Q;zGsp=%^~s zc~9#q&RFMv_KUjimzI0)<%+A-DQmACc^F&uKFNhcrM_1~aq*O?=gAlXL9^wQ(9x?lxm*%)N$VQC*mvLiAL1! znccc8zQDEBbZL3L<7=z?r;<{ByNW&Wb*sN+xAyv{8FmHlrW#r-Q#ji;xA$IPcXwG? zkZ$+!2hY|_ogK+-zWDypv-}$B^*!Cz5C2qU&0CNs*E#FvhN-hQi)9@{Vn3l=x=a1U*~PZ3Hja4{M84D?o%H!OzphqS zesjj>x$js5{s)`hzIbiw=lK!m)FvBxZ9Ct2C;nQ>d}+h_DX;R9XE_{y*T3QSq4|7Y z^F{4Tug}YFe{t_|&&+qRPj~NpnYFjNq~_C;J!_6jKmHzTs`D@Q(ax*&0e|vQ3jjv* z=d9tt;-CbGnZy_-_pnIR`<^l6Yf#{ExVT!P%%S|qW`#v2E3a(1T2bSE=ijHgh|O2mRd$@a)Q!b#_lgN-=r_bdA`ok>JW1F%P?L0E#bzIxs&1_Cx5Vi y^ZPS%fHxzP2s5^_hk-#7Xgv%}c4X0302NNiZWDva0_g@uF&3ahHP{xifJ6ZrNI3@p From d5c8613edb3d407441176a09d55ec0f84fe8289e Mon Sep 17 00:00:00 2001 From: Shiina Mashiro <21091736+0xMashiro@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:47:37 +0800 Subject: [PATCH 4/4] Remove unused Staff include after moving TAB layout tests The remaining Beam tests do not use Staff APIs. The test translation unit compiles independently without this include, and all 18 enabled Beam tests pass. Refs: #34839 --- src/engraving/tests/beam_tests.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engraving/tests/beam_tests.cpp b/src/engraving/tests/beam_tests.cpp index 5a47a6cc5dcf6..f4b2a127eeb1b 100644 --- a/src/engraving/tests/beam_tests.cpp +++ b/src/engraving/tests/beam_tests.cpp @@ -29,7 +29,6 @@ #include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" #include "engraving/dom/note.h" -#include "engraving/dom/staff.h" #include "engraving/dom/tremolotwochord.h" #include "engraving/editing/editbeam.h"