diff --git a/src/engraving/dom/check.cpp b/src/engraving/dom/check.cpp index 077597fe66739..f09d3694349d4 100644 --- a/src/engraving/dom/check.cpp +++ b/src/engraving/dom/check.cpp @@ -272,19 +272,6 @@ void Measure::fillGap(const Fraction& pos, const Fraction& len, track_idx_t trac stretch.numerator(), stretch.denominator(), track); - if (useGapRests) { - // fill this gap with a single gap rest, where the duration does not need to correspond to a valid DurationType - TDuration d; - d.setVal(len.ticks()); - Rest* rest = Factory::createRest(score()->dummy()->segment()); - rest->setTicks(len); - rest->setDurationType(d); - rest->setTrack(track); - rest->setGap(useGapRests); - score()->undoAddCR(rest, this, (pos / stretch) + tick()); - return; - } - // break the gap into shorter durations if necessary std::vector durationList = toRhythmicDurationList(len, true, pos, score()->sigmap()->timesig(tick()).nominal(), this, 0); diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index 439f685710575..b8ba37f696bbe 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -485,7 +485,9 @@ std::vector Score::setRests(const Fraction& _tick, track_idx_t track, con f = l; } - if ((track % VOICES) && !measure->hasVoice(track) && (tick == measure->tick())) { + // Don't fill with rests a non-zero voice, *unless* it has links in voice zero + bool emptyNonZeroVoice = track2voice(track) != 0 && !measure->hasVoice(track) && tick == measure->tick(); + if (emptyNonZeroVoice && !staff->trackHasLinksInVoiceZero(track)) { l -= f; measure = measure->nextMeasure(); if (!measure) { @@ -2570,7 +2572,7 @@ void Score::deleteItem(EngravingItem* el) // delete them really when only gap rests are in the actual measure. Measure* m = toRest(el)->measure(); track_idx_t track = el->track(); - if (m->isOnlyDeletedRests(track)) { + if (m->isOnlyDeletedRests(track) && !el->staff()->trackHasLinksInVoiceZero(track)) { static const SegmentType st { SegmentType::ChordRest }; for (const Segment* s = m->first(st); s; s = s->next(st)) { EngravingItem* del = s->element(track); @@ -2635,13 +2637,15 @@ void Score::deleteItem(EngravingItem* el) break; } + Fraction curTick = stick; for (const TDuration& d : dList) { Rest* rr = Factory::createRest(this->dummy()->segment()); rr->setTicks(d.fraction()); rr->setDurationType(d); rr->setTrack(track); rr->setGap(true); - undoAddCR(rr, m, stick); + undoAddCR(rr, m, curTick); + curTick += d.fraction(); } } } @@ -6014,8 +6018,6 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool Score* score = staff->score(); staff_idx_t staffIdx = staff->idx(); - std::vector linkedTracks = ostaff->getLinkedTracksInStaff(staff, strack); - // Some elements in voice 1 of a staff should be copied to every track which has a linked voice in this staff static const std::set VOICE1_COPY_TYPES = { ElementType::SYMBOL, @@ -6039,359 +6041,362 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool ElementType::PEDAL, ElementType::LYRICS }; - voice_idx_t voice = track2voice(strack); - if (staff->isVoiceVisible(voice) && linkedTracks.empty() && muse::contains(VOICE1_COPY_TYPES, et)) { - linkedTracks.push_back(staffIdx * VOICES); - } - for (track_idx_t ntrack : linkedTracks) { - EngravingItem* ne; - if (staff == ostaff) { - ne = element; + track_idx_t linkedTrack = ostaff->getLinkedTrackInStaff(staff, strack); + if (linkedTrack == muse::nidx) { + if (track2voice(strack) == 0 && muse::contains(VOICE1_COPY_TYPES, et)) { + linkedTrack = staff2track(staffIdx); } else { - if (staff->rstaff() != ostaff->rstaff()) { - switch (element->type()) { - // exclude certain element types except on corresponding staff in part - // this should be same list excluded in cloneStaff() - case ElementType::STAFF_TEXT: - case ElementType::SYSTEM_TEXT: - case ElementType::TRIPLET_FEEL: - case ElementType::PLAYTECH_ANNOTATION: - case ElementType::CAPO: - case ElementType::STRING_TUNINGS: - case ElementType::FRET_DIAGRAM: - case ElementType::HARMONY: - case ElementType::FIGURED_BASS: - case ElementType::DYNAMIC: - case ElementType::EXPRESSION: - case ElementType::LYRICS: // not normally segment-attached - continue; - default: - break; - } - } - ne = element->linkedClone(); - ne->setScore(score); - ne->setSelected(false); - ne->setTrack(ntrack); - - if (ne->isFretDiagram()) { - FretDiagram* fd = toFretDiagram(ne); - Harmony* fdHarmony = fd->harmony(); - if (fdHarmony) { - fdHarmony->setScore(score); - fdHarmony->setSelected(false); - fdHarmony->setTrack(ntrack); - } - } + continue; } + } - if (element->isArticulationFamily()) { - Articulation* a = toArticulation(element); - Segment* segment; - SegmentType st; - Measure* m; - Fraction tick; - if (a->explicitParent()->isChordRest()) { - ChordRest* cr = a->chordRest(); - segment = cr->segment(); - st = SegmentType::ChordRest; - tick = segment->tick(); - m = score->tick2measure(tick); - } else { - segment = toSegment(a->explicitParent()->explicitParent()); - st = SegmentType::EndBarLine; - tick = segment->tick(); - m = score->tick2measure(tick); - if (m->tick() == tick) { - m = m->prevMeasure(); - } - } - Segment* seg = m->findSegment(st, tick); - if (seg == 0) { - LOGW("undoAddSegment: segment not found"); + EngravingItem* ne; + if (staff == ostaff) { + ne = element; + } else { + if (staff->rstaff() != ostaff->rstaff()) { + switch (element->type()) { + // exclude certain element types except on corresponding staff in part + // this should be same list excluded in cloneStaff() + case ElementType::STAFF_TEXT: + case ElementType::SYSTEM_TEXT: + case ElementType::TRIPLET_FEEL: + case ElementType::PLAYTECH_ANNOTATION: + case ElementType::CAPO: + case ElementType::STRING_TUNINGS: + case ElementType::FRET_DIAGRAM: + case ElementType::HARMONY: + case ElementType::FIGURED_BASS: + case ElementType::DYNAMIC: + case ElementType::EXPRESSION: + case ElementType::LYRICS: // not normally segment-attached + continue; + default: break; } - Articulation* na = toArticulation(ne); - na->setTrack(ntrack); - if (a->explicitParent()->isChordRest()) { - ChordRest* cr = a->chordRest(); - ChordRest* ncr; - if (cr->isGrace()) { - ncr = findLinkedChord(toChord(cr), score->staff(staffIdx)); - } else { - ncr = toChordRest(seg->element(ntrack)); - } - na->setParent(ncr); - } else { - BarLine* bl = toBarLine(seg->element(ntrack)); - na->setParent(bl); - } - doUndoAddElement(na); - } else if (element->isChordLine() || element->isLyrics()) { - ChordRest* cr = toChordRest(element->explicitParent()); - Segment* segment = cr->segment(); - Fraction tick = segment->tick(); - Measure* m = score->tick2measure(tick); - Segment* seg = m->findSegment(SegmentType::ChordRest, tick); - if (seg == 0) { - LOGW("undoAddSegment: segment not found"); - break; + } + ne = element->linkedClone(); + ne->setScore(score); + ne->setSelected(false); + ne->setTrack(linkedTrack); + + if (ne->isFretDiagram()) { + FretDiagram* fd = toFretDiagram(ne); + Harmony* fdHarmony = fd->harmony(); + if (fdHarmony) { + fdHarmony->setScore(score); + fdHarmony->setSelected(false); + fdHarmony->setTrack(linkedTrack); + } + } + } + + if (element->isArticulationFamily()) { + Articulation* a = toArticulation(element); + Segment* segment; + SegmentType st; + Measure* m; + Fraction tick; + if (a->explicitParent()->isChordRest()) { + ChordRest* cr = a->chordRest(); + segment = cr->segment(); + st = SegmentType::ChordRest; + tick = segment->tick(); + m = score->tick2measure(tick); + } else { + segment = toSegment(a->explicitParent()->explicitParent()); + st = SegmentType::EndBarLine; + tick = segment->tick(); + m = score->tick2measure(tick); + if (m->tick() == tick) { + m = m->prevMeasure(); } - ne->setTrack(ntrack); - ChordRest* ncr = toChordRest(seg->element(ntrack)); - ne->setParent(ncr); - if (element->isChordLine()) { - ChordLine* oldChordLine = toChordLine(element); - ChordLine* newChordLine = toChordLine(ne); - // Chordline also needs to know the new note - Note* newNote = toChord(ncr)->findNote(oldChordLine->note()->pitch()); - newChordLine->setNote(newNote); + } + Segment* seg = m->findSegment(st, tick); + if (seg == 0) { + LOGW("undoAddSegment: segment not found"); + break; + } + Articulation* na = toArticulation(ne); + na->setTrack(linkedTrack); + if (a->explicitParent()->isChordRest()) { + ChordRest* cr = a->chordRest(); + ChordRest* ncr; + if (cr->isGrace()) { + ncr = findLinkedChord(toChord(cr), score->staff(staffIdx)); + } else { + ncr = toChordRest(seg->element(linkedTrack)); } - doUndoAddElement(ne); + na->setParent(ncr); + } else { + BarLine* bl = toBarLine(seg->element(linkedTrack)); + na->setParent(bl); + } + doUndoAddElement(na); + } else if (element->isChordLine() || element->isLyrics()) { + ChordRest* cr = toChordRest(element->explicitParent()); + Segment* segment = cr->segment(); + Fraction tick = segment->tick(); + Measure* m = score->tick2measure(tick); + Segment* seg = m->findSegment(SegmentType::ChordRest, tick); + if (seg == 0) { + LOGW("undoAddSegment: segment not found"); + break; } - // - // elements with Segment as parent - // - else if (element->isSymbol() - || element->isImage() - || element->isTremoloBar() - || element->isDynamic() - || element->isExpression() - || element->isStaffText() - || element->isPlayTechAnnotation() - || element->isCapo() - || element->isStringTunings() - || element->isSticking() - || element->isFretDiagram() - || element->isFermata() - || element->isHarmony() - || element->isHarpPedalDiagram() - || element->isFiguredBass()) { - Segment* segment - = element->explicitParent()->isFretDiagram() ? toSegment(element->explicitParent()->explicitParent()) : toSegment( - element->explicitParent()); - Fraction tick = segment->tick(); - Measure* m = score->tick2measure(tick); - if ((segment->segmentType() == SegmentType::EndBarLine) && (m->tick() == tick)) { - m = m->prevMeasure(); + ne->setTrack(linkedTrack); + ChordRest* ncr = toChordRest(seg->element(linkedTrack)); + ne->setParent(ncr); + if (element->isChordLine()) { + ChordLine* oldChordLine = toChordLine(element); + ChordLine* newChordLine = toChordLine(ne); + // Chordline also needs to know the new note + Note* newNote = toChord(ncr)->findNote(oldChordLine->note()->pitch()); + newChordLine->setNote(newNote); + } + doUndoAddElement(ne); + } + // + // elements with Segment as parent + // + else if (element->isSymbol() + || element->isImage() + || element->isTremoloBar() + || element->isDynamic() + || element->isExpression() + || element->isStaffText() + || element->isPlayTechAnnotation() + || element->isCapo() + || element->isStringTunings() + || element->isSticking() + || element->isFretDiagram() + || element->isFermata() + || element->isHarmony() + || element->isHarpPedalDiagram() + || element->isFiguredBass()) { + Segment* segment + = element->explicitParent()->isFretDiagram() ? toSegment(element->explicitParent()->explicitParent()) : toSegment( + element->explicitParent()); + Fraction tick = segment->tick(); + Measure* m = score->tick2measure(tick); + if ((segment->segmentType() == SegmentType::EndBarLine) && (m->tick() == tick)) { + m = m->prevMeasure(); + } + Segment* seg = m->undoGetSegment(segment->segmentType(), tick); + ne->setTrack(linkedTrack); + ne->setParent(seg); + + // make harmony child of fret diagram if possible + if (ne->isHarmony()) { + for (EngravingItem* segel : segment->annotations()) { + if (segel && segel->isFretDiagram() && segel->track() == linkedTrack) { + segel->add(ne); + break; + } } - Segment* seg = m->undoGetSegment(segment->segmentType(), tick); - ne->setTrack(ntrack); - ne->setParent(seg); + } else if (ne->isFretDiagram()) { + // update track of child harmony + FretDiagram* fd = toFretDiagram(ne); + if (fd->harmony()) { + fd->harmony()->setTrack(linkedTrack); + } + } else if (ne->isStringTunings()) { + StringTunings* stringTunings = toStringTunings(ne); + if (stringTunings->stringData()->isNull()) { + const StringData* stringData = stringTunings->part()->stringData(tick, staff->idx()); + int frets = stringData->frets(); + std::vector stringList = stringData->stringList(); - // make harmony child of fret diagram if possible - if (ne->isHarmony()) { - for (EngravingItem* segel : segment->annotations()) { - if (segel && segel->isFretDiagram() && segel->track() == ntrack) { - segel->add(ne); - break; - } - } - } else if (ne->isFretDiagram()) { - // update track of child harmony - FretDiagram* fd = toFretDiagram(ne); - if (fd->harmony()) { - fd->harmony()->setTrack(ntrack); - } - } else if (ne->isStringTunings()) { - StringTunings* stringTunings = toStringTunings(ne); - if (stringTunings->stringData()->isNull()) { - const StringData* stringData = stringTunings->part()->stringData(tick, staff->idx()); - int frets = stringData->frets(); - std::vector stringList = stringData->stringList(); - - stringTunings->setStringData(StringData(frets, stringList)); - } + stringTunings->setStringData(StringData(frets, stringList)); } + } - doUndoAddElement(ne); - // transpose harmony if necessary - if (element->isHarmony() && ne != element) { - Harmony* h = toHarmony(ne); - if (score->style().styleB(Sid::concertPitch) != element->style().styleB(Sid::concertPitch)) { - Staff* staffDest = h->staff(); - Interval interval = staffDest->transpose(tick); - if (!interval.isZero()) { - if (!score->style().styleB(Sid::concertPitch)) { - interval.flip(); - } - int rootTpc = transposeTpc(h->rootTpc(), interval, true); - int baseTpc = transposeTpc(h->baseTpc(), interval, true); - score->undoTransposeHarmony(h, rootTpc, baseTpc); + doUndoAddElement(ne); + // transpose harmony if necessary + if (element->isHarmony() && ne != element) { + Harmony* h = toHarmony(ne); + if (score->style().styleB(Sid::concertPitch) != element->style().styleB(Sid::concertPitch)) { + Staff* staffDest = h->staff(); + Interval interval = staffDest->transpose(tick); + if (!interval.isZero()) { + if (!score->style().styleB(Sid::concertPitch)) { + interval.flip(); } + int rootTpc = transposeTpc(h->rootTpc(), interval, true); + int baseTpc = transposeTpc(h->baseTpc(), interval, true); + score->undoTransposeHarmony(h, rootTpc, baseTpc); } } - } else if (element->isSlur() - || element->isHairpin() - || element->isOttava() - || element->isTrill() - || element->isVibrato() - || element->isTextLine() - || element->isPedal()) { - Spanner* sp = toSpanner(element); - Spanner* nsp = toSpanner(ne); - track_idx_t tr2 = sp->effectiveTrack2(); - int diff = static_cast(tr2 - sp->track()); - nsp->setTrack2(ntrack + diff); - nsp->setTrack(ntrack); - - // determine start/end element for slurs - // this is only necessary if start/end element is - // a grace note, otherwise the element can be set to zero - // and will later be calculated from tick/track values - // - if (element->isSlur() && sp != nsp) { - if (sp->startElement()) { - std::list sel = sp->startElement()->linkList(); - for (EngravingObject* ee : sel) { - EngravingItem* e = static_cast(ee); - if (e->score() == nsp->score() && e->track() == nsp->track()) { - nsp->setStartElement(e); - break; - } + } + } else if (element->isSlur() + || element->isHairpin() + || element->isOttava() + || element->isTrill() + || element->isVibrato() + || element->isTextLine() + || element->isPedal()) { + Spanner* sp = toSpanner(element); + Spanner* nsp = toSpanner(ne); + track_idx_t tr2 = sp->effectiveTrack2(); + int diff = static_cast(tr2 - sp->track()); + nsp->setTrack2(linkedTrack + diff); + nsp->setTrack(linkedTrack); + + // determine start/end element for slurs + // this is only necessary if start/end element is + // a grace note, otherwise the element can be set to zero + // and will later be calculated from tick/track values + // + if (element->isSlur() && sp != nsp) { + if (sp->startElement()) { + std::list sel = sp->startElement()->linkList(); + for (EngravingObject* ee : sel) { + EngravingItem* e = static_cast(ee); + if (e->score() == nsp->score() && e->track() == nsp->track()) { + nsp->setStartElement(e); + break; } } - if (sp->endElement()) { - std::list eel = sp->endElement()->linkList(); - for (EngravingObject* ee : eel) { - EngravingItem* e = static_cast(ee); - if (e->score() == nsp->score() && e->track() == nsp->track2()) { - nsp->setEndElement(e); - break; - } + } + if (sp->endElement()) { + std::list eel = sp->endElement()->linkList(); + for (EngravingObject* ee : eel) { + EngravingItem* e = static_cast(ee); + if (e->score() == nsp->score() && e->track() == nsp->track2()) { + nsp->setEndElement(e); + break; } } } + } - if (sp->isTextLine() && sp != nsp) { - EngravingItem* parent = sp->parentItem(); - if (parent && parent->isNote()) { - nsp->setParent(parent->findLinkedInStaff(staff)); - } - EngravingItem* endEl = sp->endElement(); - if (endEl && endEl->isNote()) { - nsp->setEndElement(endEl->findLinkedInStaff(staff)); - } - } + if (sp->isTextLine() && sp != nsp) { + EngravingItem* parent = sp->parentItem(); + if (parent && parent->isNote()) { + nsp->setParent(parent->findLinkedInStaff(staff)); + } + EngravingItem* endEl = sp->endElement(); + if (endEl && endEl->isNote()) { + nsp->setEndElement(endEl->findLinkedInStaff(staff)); + } + } + + doUndoAddElement(nsp); + } else if (et == ElementType::GLISSANDO || et == ElementType::GUITAR_BEND) { + doUndoAddElement(toSpanner(ne)); + } else if (element->isType(ElementType::TREMOLO_TWOCHORD)) { + TremoloTwoChord* tremolo = item_cast(element); + ChordRest* cr1 = toChordRest(tremolo->chord1()); + ChordRest* cr2 = toChordRest(tremolo->chord2()); + int diff = static_cast(cr2->track() - cr1->track()); + Segment* s1 = cr1->segment(); + Segment* s2 = cr2->segment(); + Measure* m1 = s1->measure(); + Measure* m2 = s2->measure(); + Measure* nm1 = score->tick2measure(m1->tick()); + Measure* nm2 = score->tick2measure(m2->tick()); + Segment* ns1 = nm1->findSegment(s1->segmentType(), s1->tick()); + Segment* ns2 = nm2->findSegment(s2->segmentType(), s2->tick()); + Chord* c1 = toChord(ns1->element(linkedTrack)); + Chord* c2 = toChord(ns2->element(linkedTrack + diff)); + TremoloTwoChord* ntremolo = item_cast(ne); + ntremolo->setChords(c1, c2); + ntremolo->setParent(c1); + doUndoAddElement(ntremolo); + } else if (element->isType(ElementType::TREMOLO_SINGLECHORD)) { + Chord* cr = toChord(element->explicitParent()); + Chord* c1 = findLinkedChord(cr, score->staff(staffIdx)); + ne->setParent(c1); + doUndoAddElement(ne); + } else if (element->isArpeggio()) { + ChordRest* cr = toChordRest(element->explicitParent()); + Segment* s = cr->segment(); + Measure* m = s->measure(); + Measure* nm = score->tick2measure(m->tick()); + Segment* ns = nm->findSegment(s->segmentType(), s->tick()); + Chord* c1 = toChord(ns->element(linkedTrack)); + ne->setParent(c1); + doUndoAddElement(ne); + } else if (element->isTie()) { + Tie* tie = toTie(element); + Note* n1 = tie->startNote(); + Note* n2 = tie->endNote(); + Chord* cr1 = n1->chord(); + Chord* cr2 = n2 ? n2->chord() : 0; + + // find corresponding notes in linked staff + // accounting for grace notes and cross-staff notation + int sm = 0; + if (cr1->staffIdx() != cr2->staffIdx()) { + sm = static_cast(cr2->staffIdx() - cr1->staffIdx()); + } + Chord* c1 = findLinkedChord(cr1, score->staff(staffIdx)); + Chord* c2 = findLinkedChord(cr2, score->staff(staffIdx + sm)); + + IF_ASSERT_FAILED(c1) { + return; + } - doUndoAddElement(nsp); - } else if (et == ElementType::GLISSANDO || et == ElementType::GUITAR_BEND) { - doUndoAddElement(toSpanner(ne)); - } else if (element->isType(ElementType::TREMOLO_TWOCHORD)) { - TremoloTwoChord* tremolo = item_cast(element); - ChordRest* cr1 = toChordRest(tremolo->chord1()); - ChordRest* cr2 = toChordRest(tremolo->chord2()); - int diff = static_cast(cr2->track() - cr1->track()); - Segment* s1 = cr1->segment(); - Segment* s2 = cr2->segment(); - Measure* m1 = s1->measure(); - Measure* m2 = s2->measure(); - Measure* nm1 = score->tick2measure(m1->tick()); - Measure* nm2 = score->tick2measure(m2->tick()); - Segment* ns1 = nm1->findSegment(s1->segmentType(), s1->tick()); - Segment* ns2 = nm2->findSegment(s2->segmentType(), s2->tick()); - Chord* c1 = toChord(ns1->element(ntrack)); - Chord* c2 = toChord(ns2->element(ntrack + diff)); - TremoloTwoChord* ntremolo = item_cast(ne); - ntremolo->setChords(c1, c2); - ntremolo->setParent(c1); - doUndoAddElement(ntremolo); - } else if (element->isType(ElementType::TREMOLO_SINGLECHORD)) { - Chord* cr = toChord(element->explicitParent()); - Chord* c1 = findLinkedChord(cr, score->staff(staffIdx)); - ne->setParent(c1); - doUndoAddElement(ne); - } else if (element->isArpeggio()) { - ChordRest* cr = toChordRest(element->explicitParent()); - Segment* s = cr->segment(); - Measure* m = s->measure(); - Measure* nm = score->tick2measure(m->tick()); - Segment* ns = nm->findSegment(s->segmentType(), s->tick()); - Chord* c1 = toChord(ns->element(ntrack)); - ne->setParent(c1); - doUndoAddElement(ne); - } else if (element->isTie()) { - Tie* tie = toTie(element); - Note* n1 = tie->startNote(); - Note* n2 = tie->endNote(); - Chord* cr1 = n1->chord(); - Chord* cr2 = n2 ? n2->chord() : 0; - - // find corresponding notes in linked staff - // accounting for grace notes and cross-staff notation - int sm = 0; - if (cr1->staffIdx() != cr2->staffIdx()) { - sm = static_cast(cr2->staffIdx() - cr1->staffIdx()); - } - Chord* c1 = findLinkedChord(cr1, score->staff(staffIdx)); - Chord* c2 = findLinkedChord(cr2, score->staff(staffIdx + sm)); - - IF_ASSERT_FAILED(c1) { - return; - } + Note* nn1 = c1->findNote(n1->pitch(), n1->unisonIndex()); + Note* nn2 = c2 ? c2->findNote(n2->pitch(), n2->unisonIndex()) : 0; + + // create tie + Tie* ntie = toTie(ne); + ntie->eraseSpannerSegments(); + ntie->setTrack(c1->track()); + ntie->setStartNote(nn1); + ntie->setEndNote(nn2); + doUndoAddElement(ntie); + } else if (element->isInstrumentChange()) { + InstrumentChange* is = toInstrumentChange(element); + Segment* s1 = is->segment(); + Measure* m1 = s1->measure(); + Measure* nm1 = score->tick2measure(m1->tick()); + Segment* ns1 = nm1->findSegment(s1->segmentType(), s1->tick()); + InstrumentChange* nis = toInstrumentChange(ne); + nis->setParent(ns1); + Fraction tickStart = nis->segment()->tick(); + Part* part = nis->part(); + Interval oldV = nis->staff()->transpose(tickStart); + // ws: instrument should not be changed here + if (is->instrument()->channel().empty() || is->instrument()->channel(0)->program() == -1) { + nis->setInstrument(*staff->part()->instrument(s1->tick())); + } else if (nis != is) { + nis->setInstrument(*is->instrument()); + } + doUndoAddElement(nis); + // transpose root score; parts will follow + if (score->isMaster() && nis->staff()->transpose(tickStart) != oldV) { + auto i = part->instruments().upper_bound(tickStart.ticks()); + Fraction tickEnd = i == part->instruments().end() ? Fraction(-1, 1) : Fraction::fromTicks(i->first); + transpositionChanged(part, oldV, tickStart, tickEnd); + } + } else if (element->isBreath()) { + Breath* breath = toBreath(element); + Fraction tick = breath->segment()->tick(); + Measure* m = score->tick2measure(tick); + // breath appears before barline + if (m->tick() == tick) { + m = m->prevMeasure(); + } + Segment* seg = m->undoGetSegment(SegmentType::Breath, tick); + Breath* nbreath = toBreath(ne); + nbreath->setScore(score); + nbreath->setTrack(linkedTrack); + nbreath->setParent(seg); + doUndoAddElement(nbreath); + } else { + LOGW("undoAddElement: unhandled: <%s>", element->typeName()); + } + ne->styleChanged(); - Note* nn1 = c1->findNote(n1->pitch(), n1->unisonIndex()); - Note* nn2 = c2 ? c2->findNote(n2->pitch(), n2->unisonIndex()) : 0; - - // create tie - Tie* ntie = toTie(ne); - ntie->eraseSpannerSegments(); - ntie->setTrack(c1->track()); - ntie->setStartNote(nn1); - ntie->setEndNote(nn2); - doUndoAddElement(ntie); - } else if (element->isInstrumentChange()) { - InstrumentChange* is = toInstrumentChange(element); - Segment* s1 = is->segment(); - Measure* m1 = s1->measure(); - Measure* nm1 = score->tick2measure(m1->tick()); - Segment* ns1 = nm1->findSegment(s1->segmentType(), s1->tick()); - InstrumentChange* nis = toInstrumentChange(ne); - nis->setParent(ns1); - Fraction tickStart = nis->segment()->tick(); - Part* part = nis->part(); - Interval oldV = nis->staff()->transpose(tickStart); - // ws: instrument should not be changed here - if (is->instrument()->channel().empty() || is->instrument()->channel(0)->program() == -1) { - nis->setInstrument(*staff->part()->instrument(s1->tick())); - } else if (nis != is) { - nis->setInstrument(*is->instrument()); - } - doUndoAddElement(nis); - // transpose root score; parts will follow - if (score->isMaster() && nis->staff()->transpose(tickStart) != oldV) { - auto i = part->instruments().upper_bound(tickStart.ticks()); - Fraction tickEnd = i == part->instruments().end() ? Fraction(-1, 1) : Fraction::fromTicks(i->first); - transpositionChanged(part, oldV, tickStart, tickEnd); - } - } else if (element->isBreath()) { - Breath* breath = toBreath(element); - Fraction tick = breath->segment()->tick(); - Measure* m = score->tick2measure(tick); - // breath appears before barline - if (m->tick() == tick) { - m = m->prevMeasure(); - } - Segment* seg = m->undoGetSegment(SegmentType::Breath, tick); - Breath* nbreath = toBreath(ne); - nbreath->setScore(score); - nbreath->setTrack(ntrack); - nbreath->setParent(seg); - doUndoAddElement(nbreath); + if (elementToRelink) { + LinkedObjects* links = ne->links(); + if (!links) { + ne->linkTo(elementToRelink); } else { - LOGW("undoAddElement: unhandled: <%s>", element->typeName()); - } - ne->styleChanged(); - - if (elementToRelink) { - LinkedObjects* links = ne->links(); - if (!links) { - ne->linkTo(elementToRelink); - } else { - elementToRelink->setLinks(links); - links->push_back(elementToRelink); - } + elementToRelink->setLinks(links); + links->push_back(elementToRelink); } } } @@ -6431,68 +6436,66 @@ void Score::undoAddCR(ChordRest* cr, Measure* measure, const Fraction& tick) SegmentType segmentType = SegmentType::ChordRest; for (const Staff* staff : ostaff->staffList()) { - const std::vector linkedTracks = ostaff->getLinkedTracksInStaff(staff, strack); + track_idx_t linkedTrack = ostaff->getLinkedTrackInStaff(staff, strack); - for (track_idx_t ntrack : linkedTracks) { - if (ntrack < staff->part()->startTrack() || ntrack >= staff->part()->endTrack()) { - continue; - } + if (linkedTrack == muse::nidx || linkedTrack < staff->part()->startTrack() || linkedTrack >= staff->part()->endTrack()) { + continue; + } - Score* score = staff->score(); - Measure* m = (score == this) ? measure : score->tick2measure(tick); - if (!m) { - LOGD("measure not found"); - break; - } - Segment* seg = m->undoGetSegment(segmentType, tick); + Score* score = staff->score(); + Measure* m = (score == this) ? measure : score->tick2measure(tick); + if (!m) { + LOGD("measure not found"); + break; + } + Segment* seg = m->undoGetSegment(segmentType, tick); - assert(seg->segmentType() == segmentType); + assert(seg->segmentType() == segmentType); - ChordRest* newcr = (staff == ostaff) ? cr : toChordRest(cr->linkedClone()); - newcr->setScore(score); + ChordRest* newcr = (staff == ostaff) ? cr : toChordRest(cr->linkedClone()); + newcr->setScore(score); - newcr->setTrack(ntrack); - newcr->setParent(seg); + newcr->setTrack(linkedTrack); + newcr->setParent(seg); #ifndef QT_NO_DEBUG - if (newcr->isChord()) { - Chord* chord = toChord(newcr); - // setTpcFromPitch needs to know the note tick position - for (Note* note : chord->notes()) { - // if (note->tpc() == Tpc::TPC_INVALID) - // note->setTpcFromPitch(); - assert(note->tpc() != Tpc::TPC_INVALID); - } + if (newcr->isChord()) { + Chord* chord = toChord(newcr); + // setTpcFromPitch needs to know the note tick position + for (Note* note : chord->notes()) { + // if (note->tpc() == Tpc::TPC_INVALID) + // note->setTpcFromPitch(); + assert(note->tpc() != Tpc::TPC_INVALID); } + } #endif - // Climb up the (possibly nested) tuplets from this chordRest - // Make sure all tuplets are cloned and correctly nested - DurationElement* elementBelow = cr; - Tuplet* tupletAbove = elementBelow->tuplet(); - while (tupletAbove) { - DurationElement* linkedElementBelow = (DurationElement*)elementBelow->findLinkedInStaff(staff); - if (!linkedElementBelow) { // shouldn't happen - break; - } - Tuplet* linkedTuplet = (Tuplet*)tupletAbove->findLinkedInStaff(staff); - if (!linkedTuplet) { - linkedTuplet = toTuplet(tupletAbove->linkedClone()); - linkedTuplet->setScore(score); - linkedTuplet->setTrack(newcr->track()); - linkedTuplet->setParent(m); - } - linkedElementBelow->setTuplet(linkedTuplet); - - elementBelow = tupletAbove; - tupletAbove = tupletAbove->tuplet(); + // Climb up the (possibly nested) tuplets from this chordRest + // Make sure all tuplets are cloned and correctly nested + DurationElement* elementBelow = cr; + Tuplet* tupletAbove = elementBelow->tuplet(); + while (tupletAbove) { + DurationElement* linkedElementBelow = (DurationElement*)elementBelow->findLinkedInStaff(staff); + if (!linkedElementBelow) { // shouldn't happen + break; } - - if (newcr->isRest() && (toRest(newcr)->isGap()) && !(toRest(newcr)->track() % VOICES)) { - toRest(newcr)->setGap(false); + Tuplet* linkedTuplet = (Tuplet*)tupletAbove->findLinkedInStaff(staff); + if (!linkedTuplet) { + linkedTuplet = toTuplet(tupletAbove->linkedClone()); + linkedTuplet->setScore(score); + linkedTuplet->setTrack(newcr->track()); + linkedTuplet->setParent(m); } + linkedElementBelow->setTuplet(linkedTuplet); - doUndoAddElement(newcr); + elementBelow = tupletAbove; + tupletAbove = tupletAbove->tuplet(); } + + if (newcr->isRest() && (toRest(newcr)->isGap()) && !(toRest(newcr)->track() % VOICES)) { + toRest(newcr)->setGap(false); + } + + doUndoAddElement(newcr); } } diff --git a/src/engraving/dom/property.cpp b/src/engraving/dom/property.cpp index 17211e54f02d2..68ca034e50b23 100644 --- a/src/engraving/dom/property.cpp +++ b/src/engraving/dom/property.cpp @@ -269,7 +269,7 @@ static constexpr PropertyMetaData propertyList[] = { { Pid::HARMONY_DURATION, true, "harmonyDuration", P_TYPE::INT, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "harmony duration") }, { Pid::SYSTEM_BRACKET, false, "type", P_TYPE::INT, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "type") }, - { Pid::GAP, false, 0, P_TYPE::BOOL, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "gap") }, + { Pid::GAP, false, 0, P_TYPE::BOOL, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "gap") }, { Pid::AUTOPLACE, false, "autoplace", P_TYPE::BOOL, PropertyGroup::POSITION, DUMMY_QT_TR_NOOP("propertyName", "autoplace") }, { Pid::DASH_LINE_LEN, false, "dashLineLength", P_TYPE::REAL, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "dash line length") }, { Pid::DASH_GAP_LEN, false, "dashGapLength", P_TYPE::REAL, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "dash gap length") }, diff --git a/src/engraving/dom/staff.cpp b/src/engraving/dom/staff.cpp index 5fdc33b63cfa6..fb0e9959e37c1 100644 --- a/src/engraving/dom/staff.cpp +++ b/src/engraving/dom/staff.cpp @@ -125,69 +125,97 @@ Staff* Staff::findLinkedInScore(const Score* score) const return nullptr; } -std::vector Staff::getLinkedTracksInStaff(const Staff* linkedStaff, const track_idx_t originalTrack) const +track_idx_t Staff::getLinkedTrackInStaff(const Staff* linkedStaff, const track_idx_t originalTrack) const { - // For linked staves the length of staffList is always > 1 since the list contains the staff itself too! - const bool linked = staffList().size() > 1; + IF_ASSERT_FAILED(linkedStaff && originalTrack != muse::nidx) { + return muse::nidx; + } - staff_idx_t staffIdx = linkedStaff->idx(); + Score* thisScore = score(); + Score* linkedStaffScore = linkedStaff->score(); + staff_idx_t linkedStaffIdx = linkedStaff->idx(); - std::vector linkedTracks; - if (!linkedStaff->score()->excerpt()) { - // On masterScore. - track_idx_t track = staffIdx * VOICES + (originalTrack % VOICES); - linkedTracks.push_back(track); - } else { - const TracksMap& mapping = linkedStaff->score()->excerpt()->tracksMapping(); - if (mapping.empty()) { - // This can happen during reading the score and there is - // no Tracklist tag specified. - // TODO solve this in read302.cpp. - linkedTracks.push_back(originalTrack); - } else { - std::vector mappedTracks = muse::values(mapping, originalTrack); - if (mappedTracks.empty()) { - // This is a linked staff in a part and the element has been added to a linked staff in the main score - track_idx_t track = staffIdx * VOICES + (originalTrack % VOICES); - track_idx_t mappedTrack = muse::value(mapping, track, muse::nidx); - mappedTrack = (mappedTrack != muse::nidx) ? mappedTrack : track; - if (linkedStaff->isVoiceVisible(originalTrack % VOICES)) { - mappedTracks.push_back(mappedTrack); - } + if (thisScore == linkedStaffScore) { + // For staves linked within the same score, voices are always mapped 1 to 1 + voice_idx_t voice = track2voice(originalTrack); + return staff2track(linkedStaffIdx, voice); + } + + // NOTE 1: if linkedStaff has a different id than *this, it means that there isn't direct voice mapping between + // the two. Example: if we have guitar+TAB in the score, with corresponding guitar+TAB in the part, *this + // may be the notation-staff of the score and linkedStaff may be the TAB-staff of the part. In that case, the + // correct voice mapping must be obtained by looking for the staff with same id in thisScore. + + track_idx_t refTrack = originalTrack; + if (linkedStaff->id() != id()) { + Staff* correspondingStaffInThisScore = linkedStaff->findLinkedInScore(thisScore); + if (!correspondingStaffInThisScore) { + return muse::nidx; + } + voice_idx_t originalVoice = track2voice(originalTrack); + refTrack = staff2track(correspondingStaffInThisScore->idx(), originalVoice); + } + + // NOTE 2: TracksMap is a map from a track in the *score* to track(s) in the *part*. + // If we are in the master score, refTrack corresponds to one of the keys in the map, so we can retrieve + // the linked tracks by simply querying the map by key. However, if we are in a part, we need to search for + // refTrack among the *values* of the map, and the corresponding key gives us the linked track in the score. + // If linkedStaff is *also* in a part, we need to do it in two steps: first find the linked track in the score, + // then use it to find the linked track in linkeStaff's part. + + if (thisScore->isMaster()) { + const TracksMap& tracksMap = linkedStaffScore->excerpt()->tracksMapping(); + std::vector linkedTracks = muse::values(tracksMap, refTrack); + for (track_idx_t track : linkedTracks) { + if (track2staff(track) == linkedStaffIdx) { + return track; } - for (track_idx_t track : mappedTracks) { - const bool isLinkedWithinSameScore = linked && (linkedStaff != this) && (linkedStaff->score() == this->score()); - const bool isLinkedToDifferentScore = linked && (linkedStaff != this) && (linkedStaff->score() != this->score()); - if (isLinkedWithinSameScore && !isLinkedToDifferentScore) { - track_idx_t mappedTrack = muse::value(mapping, track, muse::nidx); - if (mappedTrack == muse::nidx) { - continue; - } - track_idx_t linkedTrack = staffIdx * VOICES + mappedTrack % VOICES; - if (!linkedStaff->isVoiceVisible(originalTrack % VOICES)) { - continue; - } - linkedTracks.push_back(linkedTrack); - } else if (!isLinkedWithinSameScore && isLinkedToDifferentScore) { - // Staff in linked score - const staff_idx_t trackStaff = track2staff(track); - if (trackStaff != staffIdx) { - // Element on linked staff in linked part - track += (staffIdx - trackStaff) * VOICES; - if (!linkedStaff->isVoiceVisible(originalTrack % VOICES)) { - continue; - } - } - linkedTracks.push_back(track); - } else { - // Staff in part edit was performed on - linkedTracks.push_back(track); - } + } + + return muse::nidx; + } + + const TracksMap& thisTracksMap = thisScore->excerpt()->tracksMapping(); + track_idx_t linkedTrackInScore = muse::nidx; + for (auto pair : thisTracksMap) { + track_idx_t trackInScore = pair.first; + std::vector tracksInPart = muse::values(thisTracksMap, trackInScore); + for (track_idx_t trackInPart : tracksInPart) { + if (trackInPart == refTrack) { + linkedTrackInScore = trackInScore; + break; } } + if (linkedTrackInScore != muse::nidx) { + break; + } + } + + if (linkedStaffScore->isMaster() || linkedTrackInScore == muse::nidx) { + return linkedTrackInScore; + } + + const TracksMap& linkedTracksMap = linkedStaffScore->excerpt()->tracksMapping(); + std::vector linkedTracks = muse::values(linkedTracksMap, linkedTrackInScore); + for (track_idx_t track : linkedTracks) { + if (track2staff(track) == linkedStaffIdx) { + return track; + } + } + + return muse::nidx; +} + +bool Staff::trackHasLinksInVoiceZero(track_idx_t track) +{ + for (Staff* linkedStaff : staffList()) { + track_idx_t linkedTrack = getLinkedTrackInStaff(linkedStaff, track); + if (linkedTrack != muse::nidx && track2voice(linkedTrack) == 0) { + return true; + } } - return linkedTracks; + return false; } //--------------------------------------------------------- diff --git a/src/engraving/dom/staff.h b/src/engraving/dom/staff.h index ee0d05b51997b..5df16d187b0de 100644 --- a/src/engraving/dom/staff.h +++ b/src/engraving/dom/staff.h @@ -261,7 +261,8 @@ class Staff final : public EngravingItem Staff* findLinkedInScore(const Score* score) const override; - std::vector getLinkedTracksInStaff(const Staff* linkedStaff, const track_idx_t strack) const; + track_idx_t getLinkedTrackInStaff(const Staff* linkedStaff, const track_idx_t strack) const; + bool trackHasLinksInVoiceZero(track_idx_t track); private: diff --git a/src/engraving/tests/exchangevoices_data/undoChangeVoice02-ref.mscx b/src/engraving/tests/exchangevoices_data/undoChangeVoice02-ref.mscx index 517ffb76871ea..9fc65e12b19b6 100644 --- a/src/engraving/tests/exchangevoices_data/undoChangeVoice02-ref.mscx +++ b/src/engraving/tests/exchangevoices_data/undoChangeVoice02-ref.mscx @@ -211,6 +211,9 @@ 15 + + 1/4 + @@ -769,6 +772,9 @@ 15 + + 1/4 + diff --git a/src/engraving/tests/implode_explode_data/implodeScore.mscx b/src/engraving/tests/implode_explode_data/implodeScore.mscx index d20d855a401a8..08cd99ede306c 100644 --- a/src/engraving/tests/implode_explode_data/implodeScore.mscx +++ b/src/engraving/tests/implode_explode_data/implodeScore.mscx @@ -678,6 +678,9 @@ 14 + + 1/4 + diff --git a/src/engraving/tests/implode_explode_data/implodeScore01-ref.mscx b/src/engraving/tests/implode_explode_data/implodeScore01-ref.mscx index 1ad4fc7b1c813..150842513dd7d 100644 --- a/src/engraving/tests/implode_explode_data/implodeScore01-ref.mscx +++ b/src/engraving/tests/implode_explode_data/implodeScore01-ref.mscx @@ -1110,6 +1110,9 @@ 14 + + 1/4 + diff --git a/src/engraving/tests/implode_explode_data/implodeScore02-ref.mscx b/src/engraving/tests/implode_explode_data/implodeScore02-ref.mscx index d20d855a401a8..08cd99ede306c 100644 --- a/src/engraving/tests/implode_explode_data/implodeScore02-ref.mscx +++ b/src/engraving/tests/implode_explode_data/implodeScore02-ref.mscx @@ -678,6 +678,9 @@ 14 + + 1/4 + diff --git a/src/engraving/tests/readwriteundoreset_data/slurs.mscx b/src/engraving/tests/readwriteundoreset_data/slurs.mscx index 5d5ad6e475098..37e1ddc340602 100644 --- a/src/engraving/tests/readwriteundoreset_data/slurs.mscx +++ b/src/engraving/tests/readwriteundoreset_data/slurs.mscx @@ -349,6 +349,9 @@ 16 + + 1/4 + diff --git a/src/engraving/tests/split_data/split184061-other-inst-only-one-tie-ref.mscx b/src/engraving/tests/split_data/split184061-other-inst-only-one-tie-ref.mscx index 766eacd0ce4b5..8692c1ff9a127 100644 --- a/src/engraving/tests/split_data/split184061-other-inst-only-one-tie-ref.mscx +++ b/src/engraving/tests/split_data/split184061-other-inst-only-one-tie-ref.mscx @@ -288,6 +288,9 @@ 18 + + 1/4 + diff --git a/src/importexport/mei/internal/meiexporter.cpp b/src/importexport/mei/internal/meiexporter.cpp index 9a09fc0f0c9e1..c6c8f6af4a8e5 100644 --- a/src/importexport/mei/internal/meiexporter.cpp +++ b/src/importexport/mei/internal/meiexporter.cpp @@ -1353,7 +1353,7 @@ bool MeiExporter::writeRest(const Rest* rest, const Staff* staff) this->writeBeamAndTupletEnd(closingBeam, closingTuplet, closingBeamInTuplet); // Change invisible rests to space by simply adjusting the element name - if (!rest->visible()) { + if (!rest->visible() || rest->isGap()) { restNode.set_name("space"); } }