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..3579f9917f385 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" @@ -5683,11 +5684,32 @@ 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) { + 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; + 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 +5732,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 +5744,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_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 d08991bf8f752..f4b2a127eeb1b 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" @@ -30,11 +31,14 @@ #include "engraving/dom/note.h" #include "engraving/dom/tremolotwochord.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; @@ -61,6 +65,73 @@ void Engraving_BeamTests::beam(const char* path) delete score; } +TEST_F(Engraving_BeamTests, historicalTabGridModes) +{ + 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); + ChordRest* second = Navigation::nextChordRest(first); + ASSERT_TRUE(second); + ASSERT_EQ(first->durationType().type(), DurationType::V_QUARTER); + ASSERT_EQ(second->durationType().type(), DurationType::V_QUARTER); + + auto setMode = [&](ChordRest* cr, BeamMode mode) { + score->select(cr, SelectType::SINGLE); + 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); + setMode(second, BeamMode::MID); + ASSERT_EQ(second->beamMode(), BeamMode::MID); + + EditData ed; + score->undoRedo(true, &ed); + EXPECT_EQ(second->beamMode(), BeamMode::AUTO); + 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); + + 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); + EXPECT_EQ(restored->beamMode(), BeamMode::MID); + + // 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("Engraving beam tests"), [&](Transaction& tx) { + EditBeam::resetBeamMode(tx, score.get()); + }); + EXPECT_EQ(first->beamMode(), BeamMode::AUTO); + EXPECT_EQ(second->beamMode(), BeamMode::AUTO); + 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, 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 0000000000000..83ee01b67782c Binary files /dev/null and b/vtest/scores/historical-tab-grid-boundaries.mscz differ diff --git a/vtest/scores/historical-tab-grid-french-small.mscz b/vtest/scores/historical-tab-grid-french-small.mscz new file mode 100644 index 0000000000000..b62d2de13059e Binary files /dev/null and b/vtest/scores/historical-tab-grid-french-small.mscz differ diff --git a/vtest/scores/historical-tab-grids.mscz b/vtest/scores/historical-tab-grids.mscz new file mode 100644 index 0000000000000..82d8bb2e3dae8 Binary files /dev/null and b/vtest/scores/historical-tab-grids.mscz differ