diff --git a/src/engraving/dom/check.cpp b/src/engraving/dom/check.cpp index 04f54fcdf8938..929b9955fa50a 100644 --- a/src/engraving/dom/check.cpp +++ b/src/engraving/dom/check.cpp @@ -326,6 +326,9 @@ void Measure::checkMeasure(staff_idx_t staffIdx, bool useGapRests) LOGN("in measure underrun %6d at %d-%d track %zu", tick().ticks(), (currentPos / stretch).ticks(), (expectedPos / stretch).ticks(), track); fillGap(expectedPos, currentPos - expectedPos, track, stretch, useGapRests); + if (currentPos >= f) { + break; + } } DurationElement* de = cr; diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index 99ec33ad1195e..37f4c05554a6e 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -683,9 +683,10 @@ void Score::expandVoice(Segment* s, track_idx_t track) // Measure* m = s->measure(); Fraction stick = ps ? ps->tick() : m->tick(); + Fraction stretch = staff(track2staff(track))->timeStretch(stick); Fraction ticks = s->tick() - stick; if (ticks.isNotZero()) { - setRest(stick, track, ticks, false, 0); + setRest(stick, track, ticks * stretch, false, 0); } // @@ -701,7 +702,7 @@ void Score::expandVoice(Segment* s, track_idx_t track) if (ticks == m->ticks()) { addRest(s, track, TDuration(DurationType::V_MEASURE), 0); } else { - setRest(s->tick(), track, ticks, false, 0); + setRest(s->tick(), track, ticks * stretch, false, 0); } } diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index ce23219f5d88e..68a96898e2cc1 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -1201,18 +1201,21 @@ bool Score::rewriteMeasures(Measure* fm, const Fraction& ns, staff_idx_t staffId Measure* nm = nullptr; LayoutBreak* sectionBreak = nullptr; - // disable local time sig modifications in linked staves - if (staffIdx != muse::nidx && masterScore()->excerpts().size() > 0) { - MScore::setError(MsError::CANNOT_CHANGE_LOCAL_TIMESIG_HAS_EXCERPTS); - return false; - } - // // split into Measure segments fm-lm // + auto foundLM = [fm, staffIdx](MeasureBase* curMeas, Measure* curLM) { + if (!curMeas || !curMeas->isMeasure() || curLM->sectionBreak()) { + return true; + } + Segment* timeSigSeg = toMeasure(curMeas)->first(SegmentType::TimeSig); + if (timeSigSeg && curMeas != fm) { + return staffIdx == muse::nidx || timeSigSeg->element(staff2track(staffIdx)); + } + return false; + }; for (MeasureBase* measure = fm;; measure = measure->next()) { - if (!measure || !measure->isMeasure() || lm->sectionBreak() - || (toMeasure(measure)->first(SegmentType::TimeSig) && measure != fm)) { + if (foundLM(measure, lm)) { // save section break to reinstate after rewrite LayoutBreak* layoutBreak = lm->sectionBreakElement(); @@ -1317,6 +1320,9 @@ bool Score::rewriteMeasures(Measure* fm, const Fraction& ns, staff_idx_t staffId } Segment* s = nm->undoGetSegment(SegmentType::TimeSig, nm->tick()); for (size_t i = 0; i < nstaves(); ++i) { + if (staffIdx != muse::nidx && i != staffIdx) { + continue; + } if (!s->element(i * VOICES)) { TimeSig* ots = staff(i)->timeSig(nm->tick()); if (ots) { @@ -1376,10 +1382,15 @@ void Score::cmdAddTimeSig(Measure* fm, staff_idx_t staffIdx, TimeSig* ts, bool l startStaffIdx = staffIdx; endStaffIdx = startStaffIdx + 1; } else { - // TODO: get index for this score - LOGD("cmdAddTimeSig: unable to write local time signature change to linked score"); - startStaffIdx = 0; - endStaffIdx = 0; + const Staff* thisStaff = staff(staffIdx); + const Staff* linkedStaff = thisStaff->findLinkedInScore(score); + if (linkedStaff) { + startStaffIdx = linkedStaff->idx(); + endStaffIdx = startStaffIdx + 1; + } else { + startStaffIdx = 0; + endStaffIdx = 0; + } } } else { startStaffIdx = 0; @@ -1455,7 +1466,9 @@ void Score::cmdAddTimeSig(Measure* fm, staff_idx_t staffIdx, TimeSig* ts, bool l // we will only add time signatures if this succeeds // this means, however, that the rewrite cannot depend on the time signatures being in place if (mf) { - if (!mScore->rewriteMeasures(mf, ns, local ? staffIdx : muse::nidx)) { + auto staffIdxRangeOnMaster = getStaffIdxRange(mScore); + if (staffIdxRangeOnMaster.second != staffIdxRangeOnMaster.first + && !mScore->rewriteMeasures(mf, ns, local ? staffIdxRangeOnMaster.first : muse::nidx)) { undoStack()->activeCommand()->unwind(); return; } @@ -1514,11 +1527,6 @@ void Score::cmdAddTimeSig(Measure* fm, staff_idx_t staffIdx, TimeSig* ts, bool l void Score::cmdRemoveTimeSig(TimeSig* ts) { - if (ts->isLocal() && masterScore()->excerpts().size() > 0) { - MScore::setError(MsError::CANNOT_CHANGE_LOCAL_TIMESIG_HAS_EXCERPTS); - return; - } - Measure* m = ts->measure(); Segment* s = ts->segment(); diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index df5ce66773c02..d19c739798061 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -850,7 +850,7 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx) // don't layout any tuplets covered by this top level tuplet for this voice-- // they've already been laid out by layoutTuplet(). - skipTo[track] = de->tick() + de->ticks(); + skipTo[track] = de->tick() + de->actualTicks(); } } diff --git a/src/engraving/tests/timesig_data/timeSig-11.mscz b/src/engraving/tests/timesig_data/timeSig-11.mscz new file mode 100644 index 0000000000000..95a8f700cfe78 Binary files /dev/null and b/src/engraving/tests/timesig_data/timeSig-11.mscz differ diff --git a/src/engraving/tests/timesig_tests.cpp b/src/engraving/tests/timesig_tests.cpp index 05a91721399e0..a278daffef5db 100644 --- a/src/engraving/tests/timesig_tests.cpp +++ b/src/engraving/tests/timesig_tests.cpp @@ -306,3 +306,111 @@ TEST_F(Engraving_TimesigTests, timesig_78216) EXPECT_FALSE(m3->findSegment(SegmentType::TimeSig, m3->endTick())) << "Should be no timesig at the end of measure 3."; delete score; } + +TEST_F(Engraving_TimesigTests, timesig_11) +{ + MasterScore* score = ScoreRW::readScore(TIMESIG_DATA_DIR + u"timeSig-11.mscz"); + EXPECT_TRUE(score); + score->doLayout(); + + TimeSig* timeSig = Factory::createTimeSig(score->dummy()->segment()); + timeSig->setSig(Fraction(5, 4)); + + TimeSig* timeSig2 = Factory::createTimeSig(score->dummy()->segment()); + timeSig2->setSig(Fraction(7, 8)); + + TimeSig* timeSig3 = Factory::createTimeSig(score->dummy()->segment()); + timeSig3->setSig(Fraction(3, 4)); + + Measure* secondMeas = score->firstMeasure()->nextMeasure(); + Measure* thirdMeas = secondMeas->nextMeasure(); + staff_idx_t oboeStaff = 1; + staff_idx_t clarinetStaff = 2; + + // Add local timeSig to Clarinet staff at meas 3 + score->startCmd(TranslatableString::untranslatable("Engraving time signature tests")); + score->cmdAddTimeSig(thirdMeas, clarinetStaff, timeSig, true); + score->endCmd(); + // Check timeSig exist at meas 3 and is only on Clarinet + Segment* timeSigSegment = thirdMeas->findSegmentR(SegmentType::TimeSig, Fraction(0, 1)); + EXPECT_TRUE(timeSigSegment); + for (staff_idx_t stfIdx = 0; stfIdx < score->nstaves(); ++stfIdx) { + EngravingItem* timeSig = timeSigSegment->element(staff2track(stfIdx)); + if (stfIdx == clarinetStaff) { + EXPECT_TRUE(timeSig); + } else { + EXPECT_FALSE(timeSig); + } + } + + // Add local timeSig to Clarinet staff at meas 2 + score->startCmd(TranslatableString::untranslatable("Engraving time signature tests")); + score->cmdAddTimeSig(secondMeas, clarinetStaff, timeSig2, true); + score->endCmd(); + // Check timeSig exist at meas 2 and is only on Clarinet + Segment* timeSigSegment2 = secondMeas->findSegmentR(SegmentType::TimeSig, Fraction(0, 1)); + EXPECT_TRUE(timeSigSegment2); + for (staff_idx_t stfIdx = 0; stfIdx < score->nstaves(); ++stfIdx) { + EngravingItem* timeSig = timeSigSegment2->element(staff2track(stfIdx)); + if (stfIdx == clarinetStaff) { + EXPECT_TRUE(timeSig); + } else { + EXPECT_FALSE(timeSig); + } + } + // Check no other timeSigs were added at measure 3 + for (staff_idx_t stfIdx = 0; stfIdx < score->nstaves(); ++stfIdx) { + EngravingItem* timeSig = timeSigSegment->element(staff2track(stfIdx)); + if (stfIdx == clarinetStaff) { + EXPECT_TRUE(timeSig); + } else { + EXPECT_FALSE(timeSig); + } + } + + // Add local timeSig to Oboe staff at meas 2 + score->startCmd(TranslatableString::untranslatable("Engraving time signature tests")); + score->cmdAddTimeSig(secondMeas, oboeStaff, timeSig3, true); + score->endCmd(); + // Check timeSig exist at meas 2 on Oboe and Clarinet + for (staff_idx_t stfIdx = 0; stfIdx < score->nstaves(); ++stfIdx) { + EngravingItem* timeSig = timeSigSegment2->element(staff2track(stfIdx)); + if (stfIdx == oboeStaff || stfIdx == clarinetStaff) { + EXPECT_TRUE(timeSig); + } else { + EXPECT_FALSE(timeSig); + } + } + // Check no other timeSigs were added at measure 3 + for (staff_idx_t stfIdx = 0; stfIdx < score->nstaves(); ++stfIdx) { + EngravingItem* timeSig = timeSigSegment->element(staff2track(stfIdx)); + if (stfIdx == clarinetStaff) { + EXPECT_TRUE(timeSig); + } else { + EXPECT_FALSE(timeSig); + } + } + + // Check all timeSigs have been correctly cloned to parts + for (Score* partScore : score->scoreList()) { + if (partScore->isMaster()) { + continue; + } + Segment* timeSigSeg1 = partScore->tick2segment(secondMeas->tick(), true, SegmentType::TimeSig); + Segment* timeSigSeg2 = partScore->tick2segment(thirdMeas->tick(), true, SegmentType::TimeSig); + EXPECT_TRUE(timeSigSeg1); + EXPECT_TRUE(timeSigSeg2); + if (partScore->name() == u"Oboe") { + EXPECT_TRUE(timeSigSeg1->element(0)); + EXPECT_FALSE(timeSigSeg2->element(0)); + } else if (partScore->name() == u"Clarinet in B♭") { + EXPECT_TRUE(timeSigSeg1->element(0)); + EXPECT_TRUE(timeSigSeg2->element(0)); + } else { + EXPECT_FALSE(timeSigSeg1->element(0)); + EXPECT_FALSE(timeSigSeg2->element(0)); + } + } + + delete score; +}