diff --git a/share/instruments/string_tunings_presets.json b/share/instruments/string_tunings_presets.json index 1c057f4c1dece..b6f5e6bd2dd3e 100644 --- a/share/instruments/string_tunings_presets.json +++ b/share/instruments/string_tunings_presets.json @@ -24,28 +24,40 @@ "presets": [ { "name": "Standard", - "value": [40, 45, 50, 55, 59, 64] + "value": [40, 45, 50, 55, 59, 64], + "useFlats": true },{ "name": "Tune down 1/2 step", - "value": [39, 44, 49, 54, 58, 63] + "value": [39, 44, 49, 54, 58, 63], + "useFlats": true },{ "name": "Tune down 1 step", - "value": [38, 43, 48, 53, 57, 62] + "value": [38, 43, 48, 53, 57, 62], + "useFlats": true },{ "name": "Tune down 2 step", - "value": [36, 41, 46, 51, 55, 60] + "value": [36, 41, 46, 51, 55, 60], + "useFlats": true + },{ + "name": "Dropped D", + "value": [38, 45, 50, 55, 59, 64], + "useFlats": true },{ "name": "Dropped D tune down 1/2 step", - "value": [37, 44, 49, 54, 58, 63] + "value": [37, 44, 49, 54, 58, 63], + "useFlats": true },{ "name": "Dropped D Variant", - "value": [38, 45, 50, 55, 57, 64] + "value": [38, 45, 50, 55, 57, 64], + "useFlats": true },{ "name": "Double Dropped D", - "value": [38, 45, 50, 55, 59, 62] + "value": [38, 45, 50, 55, 59, 62], + "useFlats": true },{ "name": "Dropped C", - "value": [36, 43, 48, 53, 57, 62] + "value": [36, 43, 48, 53, 57, 62], + "useFlats": true },{ "name": "Dropped E", "value": [40, 47, 52, 57, 61, 66] @@ -305,4 +317,4 @@ } ] } -] \ No newline at end of file +] diff --git a/src/app/commandlineparser.cpp b/src/app/commandlineparser.cpp index 66f67626577b6..3df6dc40f7039 100644 --- a/src/app/commandlineparser.cpp +++ b/src/app/commandlineparser.cpp @@ -21,6 +21,8 @@ */ #include "commandlineparser.h" +#include + #include "global/io/dir.h" #include "global/muversion.h" @@ -471,7 +473,7 @@ void CommandLineParser::parse(int argc, char** argv) // Startup if (m_runMode == IApplication::RunMode::GuiApp) { if (!scorefiles.isEmpty()) { - m_options.startup.scoreUrl = QUrl::fromUserInput(scorefiles[0]); + m_options.startup.scoreUrl = QUrl::fromUserInput(scorefiles[0], QDir::currentPath(), QUrl::AssumeLocalFile); } if (m_parser.isSet("score-display-name-override")) { diff --git a/src/engraving/dom/arpeggio.cpp b/src/engraving/dom/arpeggio.cpp index 13e2c98055b05..532ec0a24bd52 100644 --- a/src/engraving/dom/arpeggio.cpp +++ b/src/engraving/dom/arpeggio.cpp @@ -62,7 +62,7 @@ Arpeggio::~Arpeggio() { // Remove reference to this arpeggio in any chords it may have spanned Chord* _chord = chord(); - if (!_chord) { + if (!_chord || !_chord->segment()) { return; } for (track_idx_t _track = track(); _track <= track() + m_span; _track++) { @@ -83,13 +83,20 @@ void Arpeggio::findAndAttachToChords() Chord* _chord = chord(); track_idx_t strack = track(); track_idx_t etrack = track() + (m_span - 1); + track_idx_t lastTrack = strack; for (track_idx_t track = strack; track <= etrack; track++) { EngravingItem* e = _chord->segment()->element(track); if (e && e->isChord()) { toChord(e)->undoChangeSpanArpeggio(this); + lastTrack = track; } } + + if (lastTrack != etrack) { + int newSpan = lastTrack - track() + 1; + undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); + } } void Arpeggio::detachFromChords(track_idx_t strack, track_idx_t etrack) @@ -121,7 +128,7 @@ void Arpeggio::rebaseStartAnchor(AnchorRebaseDirection direction) track_idx_t newSpan = m_span + track() - curTrack; if (newSpan != 0) { undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); - score()->undo(new ChangeParent(this, e, e->staffIdx())); + score()->undoChangeParent(this, e, e->staffIdx()); break; } } @@ -136,7 +143,7 @@ void Arpeggio::rebaseStartAnchor(AnchorRebaseDirection direction) if (newSpan != 0) { chord()->undoChangeSpanArpeggio(nullptr); undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); - score()->undo(new ChangeParent(this, e, e->staffIdx())); + score()->undoChangeParent(this, e, e->staffIdx()); break; } } @@ -217,7 +224,7 @@ void Arpeggio::editDrag(EditData& ed) detachFromChords(track(), c->track() - 1); } undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); - score()->undo(new ChangeParent(this, c, c->staffIdx())); + score()->undoChangeParent(this, c, c->staffIdx()); m_userLen1 = 0.0; } } diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index 17008d51fab0a..02c8af92820ed 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -778,7 +778,10 @@ void Chord::remove(EngravingItem* e) break; case ElementType::ARPEGGIO: - m_arpeggio = 0; + if (m_spanArpeggio == m_arpeggio) { + m_spanArpeggio = nullptr; + } + m_arpeggio = nullptr; break; case ElementType::TREMOLO: setTremolo(nullptr); @@ -1817,7 +1820,7 @@ void Chord::undoChangeSpanArpeggio(Arpeggio* a) } Chord* chord = toChord(linkedObject); Score* score = chord->score(); - EngravingItem* linkedArp = a->findLinkedInScore(score); + EngravingItem* linkedArp = chord->spanArpeggio(); if (score && linkedArp) { score->undo(new ChangeSpanArpeggio(chord, toArpeggio(linkedArp))); } diff --git a/src/engraving/dom/chordrest.cpp b/src/engraving/dom/chordrest.cpp index 99fb7f9448dac..cdc8499d93d02 100644 --- a/src/engraving/dom/chordrest.cpp +++ b/src/engraving/dom/chordrest.cpp @@ -241,6 +241,7 @@ EngravingItem* ChordRest::drop(EditData& data) case ElementType::FRET_DIAGRAM: case ElementType::TREMOLOBAR: case ElementType::SYMBOL: + case ElementType::IMAGE: e->setTrack(track()); e->setParent(segment()); score()->undoAddElement(e); @@ -356,11 +357,6 @@ EngravingItem* ChordRest::drop(EditData& data) return e; } - case ElementType::IMAGE: - e->setParent(segment()); - score()->undoAddElement(e); - return e; - case ElementType::ACTION_ICON: { switch (toActionIcon(e)->actionType()) { diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index 4c9300bcb9798..a774c922fa3d3 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -108,7 +108,7 @@ static UndoMacro::ChangesInfo changesInfo(const UndoStack* stack) return actualMacro->changesInfo(); } -static std::pair changedTicksRange(const CmdState& cmdState, const std::vector& changedItems) +static std::pair changedTicksRange(const CmdState& cmdState, const std::set& changedItems) { int startTick = cmdState.startTick().ticks(); int endTick = cmdState.endTick().ticks(); @@ -318,6 +318,10 @@ void Score::undoRedo(bool undo, EditData* ed) ScoreChangesRange range = changesRange(); + if (range.changedItems.empty()) { + range.changedItems = std::move(changes.changedItems); + } + if (range.changedTypes.empty()) { range.changedTypes = std::move(changes.changedObjectTypes); } @@ -387,7 +391,10 @@ ScoreChangesRange Score::changesRange() const return { ticksRange.first, ticksRange.second, cmdState.startStaff(), cmdState.endStaff(), - changes.changedObjectTypes, changes.changedPropertyIdSet, changes.changedStyleIdSet }; + std::move(changes.changedItems), + std::move(changes.changedObjectTypes), + std::move(changes.changedPropertyIdSet), + std::move(changes.changedStyleIdSet) }; } #ifndef NDEBUG @@ -1840,6 +1847,7 @@ void Score::upDown(bool up, UpDownMode mode) int tpc1 = oNote->tpc1(); int tpc2 = oNote->tpc2(); int pitch = oNote->pitch(); + int pitchOffset = staff->pitchOffset(tick); int newTpc1 = tpc1; // default to unchanged int newTpc2 = tpc2; // default to unchanged int newPitch = pitch; // default to unchanged @@ -1876,7 +1884,7 @@ void Score::upDown(bool up, UpDownMode mode) return; // no next string to move to } string = stt->visualStringToPhys(string); - fret = stringData->fret(pitch, string, staff); + fret = stringData->fret(pitch + pitchOffset, string, staff); if (fret == -1) { // can't have that note on that string return; } @@ -1903,7 +1911,7 @@ void Score::upDown(bool up, UpDownMode mode) } // update pitch and tpc's and check it matches stringData upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2); - if (newPitch != stringData->getPitch(string, fret, staff)) { + if (newPitch + pitchOffset != stringData->getPitch(string, fret, staff) && !oNote->bendBack()) { // oh-oh: something went very wrong! LOGD("upDown tab in-string: pitch mismatch"); return; diff --git a/src/engraving/dom/durationelement.cpp b/src/engraving/dom/durationelement.cpp index 00bc5734b2b01..ca09d8e8714b9 100644 --- a/src/engraving/dom/durationelement.cpp +++ b/src/engraving/dom/durationelement.cpp @@ -116,9 +116,15 @@ float DurationElement::timeStretchFactor() const // actualTicks //--------------------------------------------------------- +Fraction DurationElement::actualTicksAt(const Fraction& tick) const +{ + // Use when tick() is unreliable, for example when pasting + return globalTicks() / staff()->timeStretch(tick); +} + Fraction DurationElement::actualTicks() const { - return globalTicks() / staff()->timeStretch(tick()); + return actualTicksAt(tick()); } //--------------------------------------------------------- diff --git a/src/engraving/dom/durationelement.h b/src/engraving/dom/durationelement.h index 0689a92669547..a54152fb1802d 100644 --- a/src/engraving/dom/durationelement.h +++ b/src/engraving/dom/durationelement.h @@ -53,6 +53,7 @@ class DurationElement : public EngravingItem Tuplet* topTuplet() const; virtual Beam* beam() const { return nullptr; } + Fraction actualTicksAt(const Fraction& tick) const; Fraction actualTicks() const; // Length expressed as a fraction of a whole note diff --git a/src/engraving/dom/excerpt.cpp b/src/engraving/dom/excerpt.cpp index 4336d81ec4f79..8980b1d879f3b 100644 --- a/src/engraving/dom/excerpt.cpp +++ b/src/engraving/dom/excerpt.cpp @@ -1522,6 +1522,9 @@ void Excerpt::cloneStaff2(Staff* srcStaff, Staff* dstStaff, const Fraction& star if (bendBack && newStartNote) { GuitarBend* newBend = toGuitarBend(bendBack->linkedClone()); newBend->setScore(score); + newBend->setParent(newStartNote); + newBend->setTrack(newStartNote->track()); + newBend->setTrack2(nn->track()); newBend->setStartElement(newStartNote); newBend->setEndElement(nn); newStartNote->addSpannerFor(newBend); @@ -1532,6 +1535,9 @@ void Excerpt::cloneStaff2(Staff* srcStaff, Staff* dstStaff, const Fraction& star // Because slight bends aren't detected as "bendBack" GuitarBend* newBend = toGuitarBend(bendFor->linkedClone()); newBend->setScore(score); + newBend->setParent(nn); + newBend->setTrack(nn->track()); + newBend->setTrack2(nn->track()); newBend->setStartElement(nn); newBend->setEndElement(nn); nn->addSpannerFor(newBend); diff --git a/src/engraving/dom/paste.cpp b/src/engraving/dom/paste.cpp index 63faec126cb54..26ee2a2a25bd7 100644 --- a/src/engraving/dom/paste.cpp +++ b/src/engraving/dom/paste.cpp @@ -49,6 +49,7 @@ #include "sig.h" #include "staff.h" #include "tie.h" +#include "timesig.h" #include "tremolo.h" #include "tuplet.h" #include "undo.h" @@ -124,7 +125,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) transposeChord(toChord(cr), tick); if (toChord(cr)->tremolo() && toChord(cr)->tremolo()->twoNotes()) { twoNoteTremoloFactor = 2; - } else if (cr->durationTypeTicks() == (cr->actualTicks() * 2)) { + } else if (cr->durationTypeTicks() == (cr->actualTicksAt(tick) * 2)) { // this could be the 2nd note of a two-note tremolo // check previous CR on same track, if it has a two-note tremolo, then set twoNoteTremoloFactor to 2 Segment* seg = measure->undoGetSegment(SegmentType::ChordRest, tick); @@ -167,7 +168,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) if (cr->isMeasureRepeat()) { partialCopy = toMeasureRepeat(cr)->actualTicks() != measure->ticks(); } else if (!isGrace && !cr->tuplet()) { - partialCopy = cr->durationTypeTicks() != (cr->actualTicks() * twoNoteTremoloFactor); + partialCopy = cr->durationTypeTicks() != (cr->actualTicksAt(tick) * twoNoteTremoloFactor); } // if note is too long to fit in measure, split it up with a tie across the barline @@ -175,11 +176,11 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) // we have already disallowed a tuplet from crossing the barline, so there is no problem here // but due to rounding, it might appear from actualTicks() that the last note is too long by a couple of ticks - if (!isGrace && !cr->tuplet() && (tick + cr->actualTicks() > measureEnd || partialCopy || convertMeasureRest)) { + if (!isGrace && !cr->tuplet() && (tick + cr->actualTicksAt(tick) > measureEnd || partialCopy || convertMeasureRest)) { if (cr->isChord()) { // split Chord Chord* c = toChord(cr); - Fraction rest = c->actualTicks(); + Fraction rest = c->actualTicksAt(tick); bool firstpart = true; while (rest.isNotZero()) { measure = tick2measure(tick); @@ -192,9 +193,10 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) std::vector dl = toRhythmicDurationList(len, false, tick - measure->tick(), sigmap()->timesig( tick).nominal(), measure, MAX_DOTS); TDuration d = dl[0]; + Fraction c2Tick(tick + c->tick()); c2->setDurationType(d); c2->setTicks(d.fraction()); - rest -= c2->actualTicks(); + rest -= c2->actualTicksAt(c2Tick); undoAddCR(c2, measure, tick); std::vector nl1 = c->notes(); @@ -219,7 +221,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) } c = c2; firstpart = false; - tick += c->actualTicks(); + tick += c->actualTicksAt(c2Tick); } } else if (cr->isRest()) { // split Rest @@ -239,7 +241,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) r2->setTicks(d.isMeasure() ? measure->ticks() : d.fraction()); undoAddCR(r2, measure, tick); rest -= r2->ticks(); - tick += r2->actualTicks(); + tick += r2->actualTicksAt(tick); firstpart = false; } } else if (cr->isMeasureRepeat()) { @@ -260,7 +262,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) r2->setDurationType(d); undoAddCR(r2, measure, tick); rest -= d.fraction(); - tick += r2->actualTicks(); + tick += r2->actualTicksAt(tick); } delete r; } diff --git a/src/engraving/dom/rest.cpp b/src/engraving/dom/rest.cpp index 9f766d6eb767d..f00e6332f43bd 100644 --- a/src/engraving/dom/rest.cpp +++ b/src/engraving/dom/rest.cpp @@ -272,12 +272,6 @@ EngravingItem* Rest::drop(EditData& data) } break; } - case ElementType::SYMBOL: - case ElementType::IMAGE: - e->setParent(this); - score()->undoAddElement(e); - return e; - case ElementType::STRING_TUNINGS: return measure()->drop(data); @@ -790,12 +784,6 @@ void Rest::add(EngravingItem* e) break; case ElementType::DEAD_SLAPPED: m_deadSlapped = toDeadSlapped(e); - // fallthrough - case ElementType::SYMBOL: - case ElementType::IMAGE: - addEl(e); - e->added(); - break; default: ChordRest::add(e); break; diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index c987c7e654299..6d26b444201a8 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -5597,6 +5597,19 @@ void Score::doLayoutRange(const Fraction& st, const Fraction& et) { TRACEFUNC; + Fraction start = st; + Fraction end = et; + + auto spanners = score()->spannerMap().findOverlapping(st.ticks(), et.ticks()); + for (auto interval : spanners) { + Spanner* spanner = interval.value; + if (!spanner->staff()->visible()) { + continue; + } + start = std::min(st, spanner->tick()); + end = std::max(et, spanner->tick2()); + } + m_engravingFont = engravingFonts()->fontByName(style().value(Sid::MusicalSymbolFont).value().toStdString()); m_layoutOptions.noteHeadWidth = m_engravingFont->width(SymId::noteheadBlack, style().spatium() / SPATIUM20); @@ -5610,7 +5623,7 @@ void Score::doLayoutRange(const Fraction& st, const Fraction& et) this->updateVelo(); } - renderer()->layoutScore(this, st, et); + renderer()->layoutScore(this, start, end); if (m_resetAutoplace) { m_resetAutoplace = false; diff --git a/src/engraving/dom/slur.cpp b/src/engraving/dom/slur.cpp index b6383964a5cc2..cab0c122c1ac9 100644 --- a/src/engraving/dom/slur.cpp +++ b/src/engraving/dom/slur.cpp @@ -338,8 +338,12 @@ Shape SlurSegment::getSegmentShape(Segment* seg, ChordRest* startCR, ChordRest* if (!e || !e->isChordRest()) { continue; } - // Gets ties shapes + // Gets tie and 2 note tremolo shapes if (e->isChord()) { + Chord* chord = toChord(e); + if (chord->tremolo() && chord->tremolo()->twoNotes()) { + segShape.add(chord->tremolo()->shape()); + } for (Note* note : toChord(e)->notes()) { Tie* tieFor = note->tieFor(); Tie* tieBack = note->tieBack(); @@ -913,41 +917,9 @@ int Slur::calcStemArrangement(EngravingItem* start, EngravingItem* end) bool Slur::isDirectionMixture(Chord* c1, Chord* c2) { - if (c1->track() != c2->track()) { - return false; - } - bool up = c1->up(); - if (c2->isGrace() && c2->up() != up) { - return true; - } - if (c1->isGraceBefore() && c2->isGraceAfter() && c1->parentItem() == c2->parentItem()) { - if (toChord(c1->parentItem())->stem() && toChord(c1->parentItem())->up() != up) { - return true; - } - } - track_idx_t track = c1->track(); - for (Measure* m = c1->measure(); m; m = m->nextMeasure()) { - for (Segment* seg = m->first(); seg; seg = seg->next(SegmentType::ChordRest)) { - if (!seg || seg->tick() < c1->tick() || !seg->isChordRestType()) { - continue; - } - if (seg->tick() > c2->tick()) { - return false; - } - if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() >= c2->tick()) { - // if slur ends at a grace-note-before, we don't need to look at the main note - return false; - } - EngravingItem* e = seg->element(track); - if (!e || !e->isChord()) { - continue; - } - Chord* c = toChord(e); - if (c->up() != up) { - return true; - } - } - } + UNUSED(c1); + UNUSED(c2); + UNREACHABLE; return false; } diff --git a/src/engraving/dom/spanner.cpp b/src/engraving/dom/spanner.cpp index 6e9194fe452d1..2f07f9ee63d73 100644 --- a/src/engraving/dom/spanner.cpp +++ b/src/engraving/dom/spanner.cpp @@ -734,14 +734,19 @@ void Spanner::computeStartElement() if (!seg || seg->empty()) { seg = score()->tick2segment(tick(), false, SegmentType::ChordRest); } - track_idx_t strack = (track() / VOICES) * VOICES; - track_idx_t etrack = strack + VOICES; - m_startElement = 0; + m_startElement = nullptr; if (seg) { - for (track_idx_t t = strack; t < etrack; ++t) { - if (seg->element(t)) { - m_startElement = seg->element(t); - break; + EngravingItem* e = seg->element(track()); + if (e) { + m_startElement = e; + } else { + track_idx_t strack = (track() / VOICES) * VOICES; + track_idx_t etrack = strack + VOICES; + for (track_idx_t t = strack; t < etrack; ++t) { + if (seg->element(t)) { + m_startElement = seg->element(t); + break; + } } } } diff --git a/src/engraving/dom/stringdata.h b/src/engraving/dom/stringdata.h index 9446c176b0f5b..14c08708a173e 100644 --- a/src/engraving/dom/stringdata.h +++ b/src/engraving/dom/stringdata.h @@ -45,6 +45,7 @@ struct instrString { int pitch = 0; // the pitch of the string bool open = false; // true: string is open | false: string is fretted int startFret = 0; // banjo 5th string starts on 5th fret + bool useFlat = false; bool operator==(const instrString& d) const { return d.pitch == pitch && d.open == open; } }; diff --git a/src/engraving/dom/stringtunings.cpp b/src/engraving/dom/stringtunings.cpp index 419c63b903f85..290062c47de9f 100644 --- a/src/engraving/dom/stringtunings.cpp +++ b/src/engraving/dom/stringtunings.cpp @@ -164,9 +164,10 @@ String StringTunings::accessibleInfo() const for (int i = 0; i < numOfStrings; ++i) { string_idx_t index = numOfStrings - i - 1; if (mu::contains(m_visibleStrings, index)) { - String pitchStr = pitch2string(stringList[index].pitch); + const instrString str = stringList[index]; + String pitchStr = pitch2string(str.pitch, str.useFlat); if (pitchStr.empty()) { - LOGE() << "Invalid get pitch name for " << stringList[index].pitch; + LOGE() << "Invalid get pitch name for " << str.pitch; continue; } @@ -252,9 +253,10 @@ String StringTunings::generateText() const for (int i = 0; i < numOfStrings; ++i) { string_idx_t index = numOfStrings - i - 1; if (mu::contains(m_visibleStrings, index)) { - String pitchStr = pitch2string(stringList[index].pitch); + const instrString str = stringList[index]; + String pitchStr = pitch2string(str.pitch, str.useFlat); if (pitchStr.empty()) { - LOGE() << "Invalid get pitch name for " << stringList[index].pitch; + LOGE() << "Invalid get pitch name for " << str.pitch; continue; } diff --git a/src/engraving/dom/textbase.cpp b/src/engraving/dom/textbase.cpp index 9809d6a603996..fe8d11ac01182 100644 --- a/src/engraving/dom/textbase.cpp +++ b/src/engraving/dom/textbase.cpp @@ -3422,7 +3422,14 @@ void TextBase::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags } } - if (propertyGroup(id) != PropertyGroup::TEXT) { + static const PropertyIdSet CHARACTER_SPECIFIC_PROPERTIES { + Pid::FONT_STYLE, + Pid::FONT_FACE, + Pid::FONT_SIZE, + Pid::TEXT_SCRIPT_ALIGN + }; + + if (!mu::contains(CHARACTER_SPECIFIC_PROPERTIES, id)) { EngravingItem::undoChangeProperty(id, v, ps); return; } diff --git a/src/engraving/dom/types.h b/src/engraving/dom/types.h index 4f34584dc44f7..974a15fbefc6f 100644 --- a/src/engraving/dom/types.h +++ b/src/engraving/dom/types.h @@ -32,6 +32,8 @@ #include "style/styledef.h" namespace mu::engraving { +class EngravingItem; + enum class CommandType { Unknown = -1, @@ -508,6 +510,7 @@ struct ScoreChangesRange { staff_idx_t staffIdxFrom = mu::nidx; staff_idx_t staffIdxTo = mu::nidx; + std::set changedItems; ElementTypeSet changedTypes; PropertyIdSet changedPropertyIdSet; StyleIdSet changedStyleIdSet; diff --git a/src/engraving/dom/undo.cpp b/src/engraving/dom/undo.cpp index 0b82519586d6a..5d075616154a0 100644 --- a/src/engraving/dom/undo.cpp +++ b/src/engraving/dom/undo.cpp @@ -672,7 +672,7 @@ UndoMacro::ChangesInfo UndoMacro::changesInfo() const continue; } - result.changedItems.push_back(item); + result.changedItems.insert(item); } } diff --git a/src/engraving/dom/undo.h b/src/engraving/dom/undo.h index 921921b934896..53021fd6bee16 100644 --- a/src/engraving/dom/undo.h +++ b/src/engraving/dom/undo.h @@ -174,7 +174,7 @@ class UndoMacro : public UndoCommand struct ChangesInfo { ElementTypeSet changedObjectTypes; - std::vector changedItems; + std::set changedItems; StyleIdSet changedStyleIdSet; PropertyIdSet changedPropertyIdSet; }; diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 88e4d52c3c597..faa861e812d6b 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -485,7 +485,7 @@ int quantizeLen(int len, int raster) return int(((float)len / raster) + 0.5) * raster; //round to the closest multiple of raster } -static const char16_t* vall[] = { +static const char16_t* valSharp[] = { u"c", u"c♯", u"d", @@ -499,19 +499,19 @@ static const char16_t* vall[] = { u"a♯", u"b" }; -static const char16_t* valu[] = { - u"C", - u"C♯", - u"D", - u"D♯", - u"E", - u"F", - u"F♯", - u"G", - u"G♯", - u"A", - u"A♯", - u"B" +static const char16_t* valFlat[] = { + u"c", + u"d♭", + u"d", + u"e♭", + u"e", + u"f", + u"g♭", + u"g", + u"a♭", + u"a", + u"b♭", + u"b" }; /*! @@ -526,7 +526,7 @@ static const char16_t* valu[] = { * @return * The string representation of the note. */ -String pitch2string(int v) +String pitch2string(int v, bool useFlats) { if (v < 0 || v > 127) { return String(u"----"); @@ -535,7 +535,13 @@ String pitch2string(int v) String o; o = String::number(octave); int i = v % PITCH_DELTA_OCTAVE; - return (octave < 0 ? valu[i] : vall[i]) + o; + + String pitchStr = useFlats ? valFlat[i] : valSharp[i]; + if (octave < 0) { + pitchStr = pitchStr.toUpper(); + } + + return pitchStr + o; } /*! @@ -553,7 +559,7 @@ int string2pitch(const String& s) return -1; } - String origin = s; + String value = s; bool negative = s.contains(u'-'); int octave = String(s[s.size() - 1]).toInt() * (negative ? -1 : 1); @@ -561,11 +567,12 @@ int string2pitch(const String& s) return -1; } - origin = origin.mid(0, origin.size() - (negative ? 2 : 1)); + value = value.mid(0, value.size() - (negative ? 2 : 1)); + value = value.toLower(); int pitchIndex = -1; for (int i = 0; i < PITCH_DELTA_OCTAVE; ++i) { - if (origin.toLower() == String(octave < 0 ? valu[i] : vall[i]).toLower()) { + if (value == valFlat[i] || value == valSharp[i]) { pitchIndex = i; break; } diff --git a/src/engraving/dom/utils.h b/src/engraving/dom/utils.h index f380d06f06d72..1538a03a0bcfd 100644 --- a/src/engraving/dom/utils.h +++ b/src/engraving/dom/utils.h @@ -48,7 +48,7 @@ extern int pitchKeyAdjust(int note, Key); extern int line2pitch(int line, ClefType clef, Key); extern int y2pitch(double y, ClefType clef, double spatium); extern int quantizeLen(int, int); -extern String pitch2string(int v); +extern String pitch2string(int v, bool useFlats = false); extern int string2pitch(const String& s); extern void transposeInterval(int pitch, int tpc, int* rpitch, int* rtpc, Interval, bool useDoubleSharpsFlats); extern int transposeTpc(int tpc, Interval interval, bool useDoubleSharpsFlats); diff --git a/src/engraving/playback/playbackmodel.cpp b/src/engraving/playback/playbackmodel.cpp index 574d63a770881..793dcca08bfc1 100644 --- a/src/engraving/playback/playbackmodel.cpp +++ b/src/engraving/playback/playbackmodel.cpp @@ -32,6 +32,8 @@ #include "dom/repeatlist.h" #include "dom/segment.h" #include "dom/tempo.h" +#include "dom/tie.h" +#include "dom/tremolo.h" #include "log.h" @@ -766,6 +768,46 @@ PlaybackModel::TickBoundaries PlaybackModel::tickBoundaries(const ScoreChangesRa const Measure* lastMeasure = m_score->lastMeasure(); result.tickFrom = 0; result.tickTo = lastMeasure ? lastMeasure->endTick().ticks() : 0; + + return result; + } + + for (const EngravingItem* item : changesRange.changedItems) { + if (item->isNote()) { + const Note* note = toNote(item); + const Chord* chord = note->chord(); + const Tremolo* tremolo = chord->tremolo(); + + if (tremolo && tremolo->twoNotes()) { + const Chord* startChord = tremolo->chord1(); + const Chord* endChord = tremolo->chord2(); + + IF_ASSERT_FAILED(startChord && endChord) { + continue; + } + + result.tickFrom = std::min(result.tickFrom, startChord->tick().ticks()); + result.tickTo = std::max(result.tickTo, endChord->tick().ticks()); + } + } else if (item->isTie()) { + const Tie* tie = toTie(item); + const Note* startNote = tie->startNote(); + const Note* endNote = tie->endNote(); + + IF_ASSERT_FAILED(startNote && endNote) { + continue; + } + + const Note* firstTiedNote = startNote->firstTiedNote(); + const Note* lastTiedNote = endNote->lastTiedNote(); + + IF_ASSERT_FAILED(firstTiedNote && lastTiedNote) { + continue; + } + + result.tickFrom = std::min(result.tickFrom, firstTiedNote->tick().ticks()); + result.tickTo = std::max(result.tickTo, lastTiedNote->tick().ticks()); + } } return result; diff --git a/src/engraving/playback/renderers/arpeggiorenderer.cpp b/src/engraving/playback/renderers/arpeggiorenderer.cpp index 9b238ea0704b4..1485565e4cb00 100644 --- a/src/engraving/playback/renderers/arpeggiorenderer.cpp +++ b/src/engraving/playback/renderers/arpeggiorenderer.cpp @@ -25,6 +25,8 @@ #include "dom/chord.h" #include "dom/arpeggio.h" +#include "playback/metaparsers/notearticulationsparser.h" + using namespace mu::engraving; using namespace mu::mpe; @@ -44,7 +46,6 @@ void ArpeggioRenderer::doRender(const EngravingItem* item, const mpe::Articulati mpe::PlaybackEventList& result) { const Chord* chord = toChord(item); - IF_ASSERT_FAILED(chord) { return; } @@ -56,11 +57,16 @@ void ArpeggioRenderer::doRender(const EngravingItem* item, const mpe::Articulati int stepsCount = static_cast(chord->notes().size()); mpe::percentage_t percentageStep = mpe::HUNDRED_PERCENT / stepsCount; + msecs_t offsetStep = timestampOffsetStep(context, stepsCount); + double stretch = arpeggio->Stretch(); auto buildEvent = [&](NominalNoteCtx& noteCtx, const int stepNumber) { noteCtx.chordCtx.commonArticulations.updateOccupiedRange(preferredType, stepNumber * percentageStep, (stepNumber + 1) * percentageStep); - noteCtx.timestamp += timestampOffsetStep(context) * stepNumber * arpeggio->Stretch(); + timestamp_t offset = offsetStep * stepNumber * stretch; + noteCtx.timestamp += offset; + noteCtx.duration -= offset; + result.emplace_back(buildNoteEvent(std::move(noteCtx))); }; @@ -92,35 +98,46 @@ bool ArpeggioRenderer::isDirectionUp(const mpe::ArticulationType type) } } -msecs_t ArpeggioRenderer::timestampOffsetStep(const RenderingContext& ctx) +msecs_t ArpeggioRenderer::timestampOffsetStep(const RenderingContext& ctx, int stepCount) { - constexpr int MINIMAL_TIMESTAMP_OFFSET_STEP = 60000; + constexpr int MAX_TIMESTAMP_OFFSET_STEP = 60000; + + int offsetStep = ctx.nominalDuration / stepCount; + if (offsetStep < MAX_TIMESTAMP_OFFSET_STEP) { + return offsetStep; + } if (RealIsEqualOrMore(ctx.beatsPerSecond.val, PRESTISSIMO_BPS_BOUND)) { - return MINIMAL_TIMESTAMP_OFFSET_STEP * 1.5; + return MAX_TIMESTAMP_OFFSET_STEP * 1.5; } if (RealIsEqualOrMore(ctx.beatsPerSecond.val, PRESTO_BPS_BOUND)) { - return MINIMAL_TIMESTAMP_OFFSET_STEP * 1.25; + return MAX_TIMESTAMP_OFFSET_STEP * 1.25; } if (RealIsEqualOrMore(ctx.beatsPerSecond.val, MODERATO_BPS_BOUND)) { - return MINIMAL_TIMESTAMP_OFFSET_STEP; + return MAX_TIMESTAMP_OFFSET_STEP; } - return MINIMAL_TIMESTAMP_OFFSET_STEP; + return MAX_TIMESTAMP_OFFSET_STEP; } std::map ArpeggioRenderer::arpeggioNotes(const Chord* chord, const RenderingContext& ctx) { std::map result; + const Score* score = chord->score(); + for (const Note* note : chord->notes()) { if (!isNotePlayable(note, ctx.commonArticulations)) { continue; } NominalNoteCtx noteCtx(note, ctx); + noteCtx.duration = timestampFromTicks(score, note->playTicks()); + + NoteArticulationsParser::buildNoteArticulationMap(note, ctx, noteCtx.chordCtx.commonArticulations); + result.emplace(noteCtx.pitchLevel, std::move(noteCtx)); } diff --git a/src/engraving/playback/renderers/arpeggiorenderer.h b/src/engraving/playback/renderers/arpeggiorenderer.h index ffea8aedcd72d..46fe0b10c2757 100644 --- a/src/engraving/playback/renderers/arpeggiorenderer.h +++ b/src/engraving/playback/renderers/arpeggiorenderer.h @@ -38,7 +38,7 @@ class ArpeggioRenderer : public RenderBase private: static bool isDirectionUp(const mpe::ArticulationType type); - static mpe::msecs_t timestampOffsetStep(const RenderingContext& context); + static mpe::msecs_t timestampOffsetStep(const RenderingContext& context, int stepCount); static std::map arpeggioNotes(const Chord* chord, const RenderingContext& ctx); }; } diff --git a/src/engraving/playback/renderers/bendsrenderer.cpp b/src/engraving/playback/renderers/bendsrenderer.cpp index 86d8038213a54..cacad47d12635 100644 --- a/src/engraving/playback/renderers/bendsrenderer.cpp +++ b/src/engraving/playback/renderers/bendsrenderer.cpp @@ -244,6 +244,7 @@ mu::mpe::NoteEvent BendsRenderer::buildBendEvent(const Note* startNote, const Re NominalNoteCtx noteCtx(startNote, startNoteCtx); const mpe::NoteEvent& startNoteEvent = std::get(bendNoteEvents.front()); + noteCtx.chordCtx.commonArticulations = startNoteEvent.expressionCtx().articulations; noteCtx.timestamp = startNoteEvent.arrangementCtx().actualTimestamp; PitchOffsets pitchOffsets; diff --git a/src/engraving/playback/renderers/gracechordsrenderer.cpp b/src/engraving/playback/renderers/gracechordsrenderer.cpp index b9e5d4c6195da..756b1cad048d6 100644 --- a/src/engraving/playback/renderers/gracechordsrenderer.cpp +++ b/src/engraving/playback/renderers/gracechordsrenderer.cpp @@ -26,6 +26,8 @@ #include "chordarticulationsrenderer.h" +#include "playback/metaparsers/notearticulationsparser.h" + using namespace mu::engraving; using namespace mu::mpe; @@ -122,6 +124,7 @@ void GraceChordsRenderer::renderGraceNoteEvents(const std::vector& grace noteCtx.duration = duration; noteCtx.timestamp = timestamp; + NoteArticulationsParser::buildNoteArticulationMap(graceNote, ctx, noteCtx.chordCtx.commonArticulations); updateArticulationBoundaries(graceCtx.type, noteCtx.timestamp, noteCtx.duration, noteCtx.chordCtx.commonArticulations); result.emplace_back(buildNoteEvent(std::move(noteCtx))); diff --git a/src/engraving/playback/renderers/tremolorenderer.cpp b/src/engraving/playback/renderers/tremolorenderer.cpp index 48b845f77bc6d..d90649a51daec 100644 --- a/src/engraving/playback/renderers/tremolorenderer.cpp +++ b/src/engraving/playback/renderers/tremolorenderer.cpp @@ -25,6 +25,8 @@ #include "dom/chord.h" #include "dom/tremolo.h" +#include "playback/metaparsers/notearticulationsparser.h" + using namespace mu::engraving; using namespace mu::mpe; @@ -114,7 +116,9 @@ void TremoloRenderer::buildAndAppendEvents(const Chord* chord, const Articulatio noteCtx.duration = stepDuration; noteCtx.timestamp += timestampOffset; + NoteArticulationsParser::buildNoteArticulationMap(note, context, noteCtx.chordCtx.commonArticulations); updateArticulationBoundaries(type, noteCtx.timestamp, noteCtx.duration, noteCtx.chordCtx.commonArticulations); + result.emplace_back(buildNoteEvent(std::move(noteCtx))); } } diff --git a/src/engraving/rendering/dev/arpeggiolayout.cpp b/src/engraving/rendering/dev/arpeggiolayout.cpp index 9cef711c70a91..33ad39a4d903a 100644 --- a/src/engraving/rendering/dev/arpeggiolayout.cpp +++ b/src/engraving/rendering/dev/arpeggiolayout.cpp @@ -123,10 +123,9 @@ void ArpeggioLayout::clearAccidentals(Arpeggio* item, LayoutContext& ctx) } const Segment* seg = item->chord()->segment(); - const Chord* endChord = toChord(seg->elementAt(item->endTrack())); - if (!endChord) { - endChord = item->chord(); - } + const EngravingItem* endEl = seg->elementAt(item->endTrack()); + const Chord* endChord = endEl && endEl->isChord() ? toChord(endEl) : item->chord(); + const Part* part = item->part(); const track_idx_t partStartTrack = part->startTrack(); const track_idx_t partEndTrack = part->endTrack(); diff --git a/src/engraving/rendering/dev/measurelayout.cpp b/src/engraving/rendering/dev/measurelayout.cpp index 2673f83b9e2a7..3ddcde36fe382 100644 --- a/src/engraving/rendering/dev/measurelayout.cpp +++ b/src/engraving/rendering/dev/measurelayout.cpp @@ -137,7 +137,8 @@ static const std::unordered_set BREAK_TYPES { ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, ElementType::INSTRUMENT_CHANGE, - ElementType::STRING_TUNINGS + ElementType::STRING_TUNINGS, + ElementType::SYMBOL }; static const std::unordered_set ALWAYS_BREAK_TYPES { @@ -154,7 +155,8 @@ static const std::unordered_set CONDITIONAL_BREAK_TYPES { ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, ElementType::INSTRUMENT_CHANGE, - ElementType::STRING_TUNINGS + ElementType::STRING_TUNINGS, + ElementType::SYMBOL }; //--------------------------------------------------------- diff --git a/src/engraving/rendering/dev/pagelayout.cpp b/src/engraving/rendering/dev/pagelayout.cpp index 6c4a87f3e3beb..9d6a868b3194b 100644 --- a/src/engraving/rendering/dev/pagelayout.cpp +++ b/src/engraving/rendering/dev/pagelayout.cpp @@ -359,6 +359,23 @@ void PageLayout::collectPage(LayoutContext& ctx) } } + // If this is the last page we layout, we must also relayout the first barlines of the + // next page, because they may have been altered while collecting the systems. + MeasureBase* lastOfThisPage = ctx.mutState().page()->systems().back()->measures().back(); + MeasureBase* firstOfNextPage = lastOfThisPage ? lastOfThisPage->next() : nullptr; + if (firstOfNextPage && firstOfNextPage->isMeasure() && firstOfNextPage->tick() > ctx.state().endTick()) { + for (Segment& segment : toMeasure(firstOfNextPage)->segments()) { + if (!segment.isType(SegmentType::BarLineType)) { + continue; + } + for (EngravingItem* item : segment.elist()) { + if (item && item->isBarLine()) { + rendering::dev::TLayout::layoutBarLine2(toBarLine(item), ctx); + } + } + } + } + if (ctx.conf().isMode(LayoutMode::SYSTEM)) { const System* s = ctx.state().page()->systems().back(); double height = s ? s->pos().y() + s->height() + s->minBottom() : ctx.state().page()->tm(); diff --git a/src/engraving/rendering/dev/slurtielayout.cpp b/src/engraving/rendering/dev/slurtielayout.cpp index 5dc051ea603e3..7fcbe82ff7157 100644 --- a/src/engraving/rendering/dev/slurtielayout.cpp +++ b/src/engraving/rendering/dev/slurtielayout.cpp @@ -118,7 +118,7 @@ void SlurTieLayout::layout(Slur* item, LayoutContext& ctx) item->setUp(!(item->startCR()->up())); } - if (c1 && c2 && Slur::isDirectionMixture(c1, c2) && (c1->noteType() == NoteType::NORMAL)) { + if (c1 && c2 && isDirectionMixture(c1, c2, ctx) && (c1->noteType() == NoteType::NORMAL)) { // slurs go above if start and end note have different stem directions, // but grace notes are exceptions item->setUp(true); @@ -1741,11 +1741,11 @@ void SlurTieLayout::computeUp(Slur* slur, LayoutContext& ctx) } else { slur->setUp(true); } - } else if (chord1 && chord2 && !chord1->isGrace() && slur->isDirectionMixture(chord1, chord2)) { + } else if (chord1 && chord2 && !chord1->isGrace() && isDirectionMixture(chord1, chord2, ctx)) { // slurs go above if there are mixed direction stems between c1 and c2 // but grace notes are exceptions slur->setUp(true); - } else if (chord1 && chord2 && chord1->isGrace() && chord2 != chord1->parent() && slur->isDirectionMixture(chord1, chord2)) { + } else if (chord1 && chord2 && chord1->isGrace() && chord2 != chord1->parent() && isDirectionMixture(chord1, chord2, ctx)) { slur->setUp(true); } } @@ -1767,6 +1767,47 @@ double SlurTieLayout::defaultStemLengthEnd(Tremolo* tremolo) tremolo->chord2()->defaultStemLength()).second; } +bool SlurTieLayout::isDirectionMixture(const Chord* c1, const Chord* c2, LayoutContext& ctx) +{ + if (c1->track() != c2->track()) { + return false; + } + const bool up = c1->up(); + if (c2->isGrace() && c2->up() != up) { + return true; + } + if (c1->isGraceBefore() && c2->isGraceAfter() && c1->parentItem() == c2->parentItem()) { + if (toChord(c1->parentItem())->stem() && toChord(c1->parentItem())->up() != up) { + return true; + } + } + const track_idx_t track = c1->track(); + for (const Segment* seg = c1->segment(); seg && seg->tick() <= c2->tick(); seg = seg->next1(SegmentType::ChordRest)) { + if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() == c2->tick()) { + // if slur ends at a grace-note-before, we don't need to look at the main note + return false; + } + EngravingItem* e = seg->element(track); + if (!e || !e->isChord()) { + continue; + } + Chord* c = toChord(e); + const Measure* m = c->measure(); + if (!c->staff()->isDrumStaff(c->tick()) && c1->measure()->system() != m->system()) { + // This chord is on a different system and may not have been laid out yet + for (Note* note : c->notes()) { + note->updateLine(); // because chord direction is based on note lines + } + ChordLayout::computeUp(c, ctx); + } + + if (c->up() != up) { + return true; + } + } + return false; +} + void SlurTieLayout::layoutSegment(SlurSegment* item, LayoutContext& ctx, const PointF& p1, const PointF& p2) { SlurSegment::LayoutData* ldata = item->mutldata(); diff --git a/src/engraving/rendering/dev/slurtielayout.h b/src/engraving/rendering/dev/slurtielayout.h index 986786e4b42c9..67c4c560cabb3 100644 --- a/src/engraving/rendering/dev/slurtielayout.h +++ b/src/engraving/rendering/dev/slurtielayout.h @@ -74,6 +74,8 @@ class SlurTieLayout static double defaultStemLengthStart(Tremolo* tremolo); static double defaultStemLengthEnd(Tremolo* tremolo); + static bool isDirectionMixture(const Chord* c1, const Chord* c2, LayoutContext& ctx); + static void layoutSegment(SlurSegment* item, LayoutContext& ctx, const PointF& p1, const PointF& p2); }; } diff --git a/src/engraving/rw/read400/read400.cpp b/src/engraving/rw/read400/read400.cpp index 02216e097db1b..7b9e329a99b4b 100644 --- a/src/engraving/rw/read400/read400.cpp +++ b/src/engraving/rw/read400/read400.cpp @@ -704,6 +704,12 @@ bool Read400::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract if (endStaff > score->nstaves()) { endStaff = score->nstaves(); } + + if (score->cmdState().layoutRange()) { + score->cmdState().reset(); + score->setLayout(dstTick, dstTick + tickLen, dstStaff, endStaff, dst); + } + //check and add truly invisible rests instead of gaps //TODO: look if this could be done different Measure* dstM = score->tick2measure(dstTick); diff --git a/src/engraving/rw/read400/tread.cpp b/src/engraving/rw/read400/tread.cpp index c1938e14b03a1..65e9effbabec6 100644 --- a/src/engraving/rw/read400/tread.cpp +++ b/src/engraving/rw/read400/tread.cpp @@ -3405,18 +3405,20 @@ void TRead::read(Rest* r, XmlReader& e, ReadContext& ctx) while (e.readNextStartElement()) { const AsciiStringView tag(e.name()); if (tag == "Symbol") { - Symbol* s = new Symbol(r); + Segment* seg = toSegment(r->parent()); + Symbol* s = new Symbol(seg); s->setTrack(r->track()); TRead::read(s, e, ctx); - r->add(s); + seg->add(s); } else if (tag == "Image") { if (MScore::noImages) { e.skipCurrentElement(); } else { - Image* image = new Image(r); + Segment* seg = toSegment(r->parent()); + Image* image = new Image(seg); image->setTrack(r->track()); TRead::read(image, e, ctx); - r->add(image); + seg->add(image); } } else if (tag == "NoteDot") { NoteDot* dot = Factory::createNoteDot(r); diff --git a/src/engraving/rw/read410/read410.cpp b/src/engraving/rw/read410/read410.cpp index ce9f9b6ab93fd..d0022ed590f74 100644 --- a/src/engraving/rw/read410/read410.cpp +++ b/src/engraving/rw/read410/read410.cpp @@ -465,7 +465,7 @@ bool Read410::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract if (tuplet) { cr->readAddTuplet(tuplet); } - ctx.incTick(cr->actualTicks()); + ctx.incTick(cr->actualTicksAt(tick)); if (doScale) { Fraction d = cr->durationTypeTicks(); cr->setTicks(cr->ticks() * scale); @@ -713,6 +713,12 @@ bool Read410::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract if (endStaff > score->nstaves()) { endStaff = score->nstaves(); } + + if (score->cmdState().layoutRange()) { + score->cmdState().reset(); + score->setLayout(dstTick, dstTick + tickLen, dstStaff, endStaff, dst); + } + //check and add truly invisible rests instead of gaps //TODO: look if this could be done different Measure* dstM = score->tick2measure(dstTick); diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 31e4c81bf90c7..c8a1a465e20f0 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -3517,19 +3517,21 @@ void TRead::read(Rest* r, XmlReader& e, ReadContext& ctx) { while (e.readNextStartElement()) { const AsciiStringView tag(e.name()); - if (tag == "Symbol") { - Symbol* s = new Symbol(r); + if (tag == "Symbol" && r->score()->mscVersion() < 420) { + Segment* seg = toSegment(r->parent()); + Symbol* s = new Symbol(seg); s->setTrack(r->track()); TRead::read(s, e, ctx); - r->add(s); - } else if (tag == "Image") { + seg->add(s); + } else if (tag == "Image" && r->score()->mscVersion() < 420) { if (MScore::noImages) { e.skipCurrentElement(); } else { - Image* image = new Image(r); + Segment* seg = toSegment(r->parent()); + Image* image = new Image(seg); image->setTrack(r->track()); TRead::read(image, e, ctx); - r->add(image); + seg->add(image); } } else if (tag == "NoteDot") { NoteDot* dot = Factory::createNoteDot(r); @@ -3971,6 +3973,7 @@ void TRead::read(StringData* item, XmlReader& e) } else if (tag == "string") { instrString strg; strg.open = e.intAttribute("open", 0); + strg.useFlat = e.intAttribute("useFlat", 0); strg.pitch = e.readInt(); item->stringList().push_back(strg); } else { diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index f81769e4ea883..3535a604e8b24 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -2609,11 +2609,17 @@ void TWrite::write(const StringData* item, XmlWriter& xml) xml.startElement("StringData"); xml.tag("frets", item->frets()); for (const instrString& strg : item->stringList()) { + XmlWriter::Attributes attrs; + if (strg.open) { - xml.tag("string", { { "open", "1" } }, strg.pitch); - } else { - xml.tag("string", strg.pitch); + attrs.push_back({ "open", "1" }); } + + if (strg.useFlat) { + attrs.push_back({ "useFlat", "1" }); + } + + xml.tag("string", attrs, strg.pitch); } xml.endElement(); } diff --git a/src/engraving/tests/bendsrenderer_tests.cpp b/src/engraving/tests/bendsrenderer_tests.cpp index b2f2ffddcf89d..f95145ff77734 100644 --- a/src/engraving/tests/bendsrenderer_tests.cpp +++ b/src/engraving/tests/bendsrenderer_tests.cpp @@ -87,7 +87,8 @@ class Engraving_BendsRendererTests : public ::testing::Test return pattern; } - RenderingContext buildCtx(const Chord* chord, ArticulationsProfilePtr profile) + RenderingContext buildCtx(const Chord* chord, ArticulationsProfilePtr profile, + ArticulationType persistentArticulationType = ArticulationType::Standard) { int chordPosTick = chord->tick().ticks(); int chordDurationTicks = chord->actualTicks().ticks(); @@ -103,7 +104,7 @@ class Engraving_BendsRendererTests : public ::testing::Test chordDurationTicks, bps, timeSignatureFraction, - ArticulationType::Undefined, + persistentArticulationType, ArticulationMap(), profile); @@ -130,7 +131,8 @@ TEST_F(Engraving_BendsRendererTests, Multibend) profile->setPattern(ArticulationType::Standard, buildTestArticulationPattern()); profile->setPattern(ArticulationType::Multibend, buildTestArticulationPattern()); profile->setPattern(ArticulationType::PreAppoggiatura, buildTestArticulationPattern()); - RenderingContext ctx = buildCtx(startChord, profile); + profile->setPattern(ArticulationType::Distortion, buildTestArticulationPattern()); + RenderingContext ctx = buildCtx(startChord, profile, ArticulationType::Distortion); // [THEN] BendsRenderer can render the multibend articulation EXPECT_TRUE(BendsRenderer::isAbleToRender(ArticulationType::Multibend)); @@ -145,7 +147,8 @@ TEST_F(Engraving_BendsRendererTests, Multibend) const mu::mpe::NoteEvent& noteEvent = std::get(events.front()); - //EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Multibend)); + EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Multibend)); + EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Distortion)); // persistent articulation applied EXPECT_EQ(noteEvent.arrangementCtx().actualTimestamp, 500000); // starts after a quarter rest EXPECT_EQ(noteEvent.arrangementCtx().actualDuration, 3000000); // quarters: F3 + G3 + F3 + A3 + A3 + G3 EXPECT_EQ(noteEvent.pitchCtx().nominalPitchLevel, 2050); // F3 diff --git a/src/engraving/tests/copypaste_data/copypaste27-ref.mscx b/src/engraving/tests/copypaste_data/copypaste27-ref.mscx new file mode 100644 index 0000000000000..ef1d9182e481f --- /dev/null +++ b/src/engraving/tests/copypaste_data/copypaste27-ref.mscx @@ -0,0 +1,234 @@ + + + + 480 + + 1 + 1 + 1 + 0 + + Composer / arranger + + + + + + Subtitle + + + Untitled score + + Orchestral + + Keyboards + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + 1 + + + + stdNormal + + F + + Piano + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + + + + + + + + + 0 + + + 3 + 2 + 3 + 2 + + + measure + 3/2 + + + + + + + 3 + 8 + + + quarter + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + + measure + 3/8 + + + + + + + quarter + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + + + + 0 + + + 4 + 4 + + + measure + 4/4 + + + + + + + 3 + 8 + + + quarter + + 59 + 19 + + + + eighth + + 59 + 19 + + + + + + + + measure + 3/8 + + + + + + + quarter + + 59 + 19 + + + + eighth + + 59 + 19 + + + + + +
+
diff --git a/src/engraving/tests/copypaste_data/copypaste27.mscx b/src/engraving/tests/copypaste_data/copypaste27.mscx new file mode 100644 index 0000000000000..8c18d431f7c52 --- /dev/null +++ b/src/engraving/tests/copypaste_data/copypaste27.mscx @@ -0,0 +1,217 @@ + + + 4.3.0 + + + 480 + 1 + 1 + 1 + 0 + 1 + + Composer / arranger + + 2023-12-01 + + + + Apple Macintosh + + Subtitle + + + Untitled score + + Orchestral + + Keyboards + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + 1 + + + + stdNormal + + F + + Piano + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + + Fluid + + + + + + + + 0 + + + 3 + 2 + 3 + 2 + + + measure + 3/2 + + + + + + + 3 + 8 + + + quarter + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + + measure + 3/8 + + + + + + + measure + 3/8 + + + + + + + + + 0 + + + 4 + 4 + + + measure + 4/4 + + + + + + + 3 + 8 + + + quarter + + 59 + 19 + + + + eighth + + 59 + 19 + + + + + + + + measure + 3/8 + + + + + + + measure + 3/8 + + + + +
+
diff --git a/src/engraving/tests/copypaste_tests.cpp b/src/engraving/tests/copypaste_tests.cpp index 4a734df33ad5d..56a82f2e67a80 100644 --- a/src/engraving/tests/copypaste_tests.cpp +++ b/src/engraving/tests/copypaste_tests.cpp @@ -188,6 +188,11 @@ TEST_F(Engraving_CopyPasteTests, copypaste26) copypaste("26"); // Copy chords (#298541) } +TEST_F(Engraving_CopyPasteTests, copypaste27) +{ + copypaste("27"); // Paste after local time signature (#18940) +} + //--------------------------------------------------------- // copy measure 2 from first staff, paste into staff 2 //--------------------------------------------------------- diff --git a/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx b/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx index 3c3925ccb0073..437e74d65eda3 100755 --- a/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx +++ b/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx @@ -168,6 +168,11 @@ Multibend + + distortion + 167503724594 + distort + 25769803890 diff --git a/src/engraving/tests/playbackeventsrendering_tests.cpp b/src/engraving/tests/playbackeventsrendering_tests.cpp index 280fbfe5f1515..cdeee3f01d291 100644 --- a/src/engraving/tests/playbackeventsrendering_tests.cpp +++ b/src/engraving/tests/playbackeventsrendering_tests.cpp @@ -1460,11 +1460,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1492,7 +1499,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Arpeggio)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1522,11 +1530,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Up) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1554,7 +1569,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Up) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioUp)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1584,11 +1600,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Down) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1616,7 +1639,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Down) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioDown)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1647,11 +1671,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Down) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1679,7 +1710,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Down) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioStraightDown)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1710,11 +1742,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Up) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1742,7 +1781,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Up) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioStraightUp)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure diff --git a/src/importexport/mei/internal/meiexporter.cpp b/src/importexport/mei/internal/meiexporter.cpp index f4302ee3b840c..1c9a35846c4ac 100644 --- a/src/importexport/mei/internal/meiexporter.cpp +++ b/src/importexport/mei/internal/meiexporter.cpp @@ -2055,7 +2055,7 @@ void MeiExporter::fillControlEventMap(const std::string& xmlId, const ChordRest* // The arpeggio is spanning to a lower staff if (arpeggio->span() > 1) { // We need to retrieve the chord it is spanning to - track_idx_t bottomTrack = arpeggio->track() + (arpeggio->span() - 1) * VOICES; + track_idx_t bottomTrack = arpeggio->track() + (arpeggio->span() - 1); const EngravingItem* element = chord->segment()->element(bottomTrack); // We do not know the xml:id of the chord yet, keep it in a map if (element && element->isChord()) { diff --git a/src/importexport/mei/internal/meiimporter.cpp b/src/importexport/mei/internal/meiimporter.cpp index b7c44d3c1aad1..abcf779530566 100644 --- a/src/importexport/mei/internal/meiimporter.cpp +++ b/src/importexport/mei/internal/meiimporter.cpp @@ -3112,7 +3112,7 @@ void MeiImporter::addSpannerEnds() // Go through the list of chord rest and check if they are on a staff below for (auto chordRest : plistChordRests) { Arpeggio* arpeggio = arpegMapEntry.first; - int span = static_cast(chordRest->staffIdx() - arpeggio->staffIdx()) + 1; + int span = static_cast(chordRest->track() - arpeggio->track()) + 1; // Adjust the span if it is currently smaller if (arpeggio->span() < span) { arpeggio->setSpan(span); diff --git a/src/importexport/mei/tests/data/arpeg-01.mscx b/src/importexport/mei/tests/data/arpeg-01.mscx index 289abc8d4ffef..63098d80dccd8 100644 --- a/src/importexport/mei/tests/data/arpeg-01.mscx +++ b/src/importexport/mei/tests/data/arpeg-01.mscx @@ -126,7 +126,7 @@ 0 - 2 + 5 @@ -145,7 +145,7 @@ 1 - 2 + 5 @@ -164,7 +164,7 @@ 3 - 2 + 5 diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index d70cbd3b93416..c8b851439cf84 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -6247,9 +6247,16 @@ void MusicXMLParserNotations::articulations() while (_e.readNextStartElement()) { SymId id { SymId::noSym }; if (convertArticulationToSymId(_e.name().toString(), id)) { - Notation artic = Notation::notationWithAttributes(_e.name().toString(), - _e.attributes(), "articulations", id); - _notations.push_back(artic); + if (_e.name() == "detached-legato") { + _notations.push_back(Notation::notationWithAttributes("tenuto", + _e.attributes(), "articulations", SymId::articTenutoAbove)); + _notations.push_back(Notation::notationWithAttributes("staccato", + _e.attributes(), "articulations", SymId::articStaccatoAbove)); + } else { + Notation artic = Notation::notationWithAttributes(_e.name().toString(), + _e.attributes(), "articulations", id); + _notations.push_back(artic); + } _e.skipCurrentElement(); // skip but don't log } else if (_e.name() == "breath-mark") { auto value = _e.readElementText(); @@ -6752,35 +6759,6 @@ Notation Notation::notationWithAttributes(const QString& name, const QXmlStreamA return notation; } -//--------------------------------------------------------- -// mergeNotations -//--------------------------------------------------------- - -/** - Helper function to merge two Notations. Used to combine articulations in combineArticulations. - */ - -Notation Notation::mergeNotations(const Notation& n1, const Notation& n2, const SymId& symId) -{ - // Sort and combine the names - std::vector names{ n1.name(), n2.name() }; - std::sort(names.begin(), names.end()); - QString name = names[0] + " " + names[1]; - - // Parents should match (and will both be "articulation") - Q_ASSERT(n1.parent() == n2.parent()); - QString parent = n1.parent(); - - Notation mergedNotation{ name, parent, symId }; - for (const auto& attr : n1.attributes()) { - mergedNotation.addAttribute(attr.first, attr.second); - } - for (const auto& attr : n2.attributes()) { - mergedNotation.addAttribute(attr.first, attr.second); - } - return mergedNotation; -} - //--------------------------------------------------------- // addAttribute //--------------------------------------------------------- @@ -6872,75 +6850,6 @@ void MusicXMLParserNotations::skipLogCurrElem() _e.skipCurrentElement(); } -//--------------------------------------------------------- -// skipCombine -//--------------------------------------------------------- - -/** - Helper function to hold conditions under which a potential combine should be skipped. - */ - -bool MusicXMLParserNotations::skipCombine(const Notation& n1, const Notation& n2) -{ - // at this point, if only one placement is specified, don't combine. - // we may revisit this in the future once we have a better idea of how we want to combine - // things by default. - bool placementsSpecifiedAndDifferent = n1.attribute("placement") != n2.attribute("placement"); - bool upMarcatoDownOther = (n1.name() == "strong-accent" && n1.attribute("type") == "up" - && n2.attribute("placement") == "below") - || (n2.name() == "strong-accent" && n2.attribute("type") == "up" - && n1.attribute("placement") == "below"); - bool downMarcatoUpOther = (n1.name() == "strong-accent" && n1.attribute("type") == "down" - && n2.attribute("placement") == "above") - || (n2.name() == "strong-accent" && n2.attribute("type") == "down" - && n1.attribute("placement") == "above"); - bool slurEndpoint = _slurStart || _slurStop; - return placementsSpecifiedAndDifferent || upMarcatoDownOther || downMarcatoUpOther || slurEndpoint; -} - -//--------------------------------------------------------- -// combineArticulations -//--------------------------------------------------------- - -/** - Combine any eligible articulations. - i.e. accent + staccato = staccato accent - */ - -void MusicXMLParserNotations::combineArticulations() -{ - QMap, SymId> map; // map set of symbols to combined symbol - map[{ SymId::articAccentAbove, SymId::articStaccatoAbove }] = SymId::articAccentStaccatoAbove; - map[{ SymId::articMarcatoAbove, SymId::articStaccatoAbove }] = SymId::articMarcatoStaccatoAbove; - map[{ SymId::articMarcatoAbove, SymId::articTenutoAbove }] = SymId::articMarcatoTenutoAbove; - map[{ SymId::articAccentAbove, SymId::articTenutoAbove }] = SymId::articTenutoAccentAbove; - map[{ SymId::articSoftAccentAbove, SymId::articStaccatoAbove }] = SymId::articSoftAccentStaccatoAbove; - map[{ SymId::articSoftAccentAbove, SymId::articTenutoAbove }] = SymId::articSoftAccentTenutoAbove; - map[{ SymId::articSoftAccentAbove, SymId::articTenutoStaccatoAbove }] = SymId::articSoftAccentTenutoStaccatoAbove; - - // Iterate through each distinct pair (backwards, to allow for deletions) - for (std::vector::reverse_iterator n1 = _notations.rbegin(), n1Next = n1; n1 != _notations.rend(); n1 = n1Next) { - n1Next = std::next(n1); - if (n1->parent() != "articulations") { - continue; - } - for (std::vector::reverse_iterator n2 = n1 + 1, n2Next = n1; n2 != _notations.rend(); n2 = n2Next) { - n2Next = std::next(n2); - if (n2->parent() != "articulations" || skipCombine(*n1, *n2)) { - continue; - } - // Combine and remove articulations if present in map - std::set currentPair = { n1->symId(), n2->symId() }; - if (map.contains(currentPair)) { - Notation mergedNotation = Notation::mergeNotations(*n1, *n2, map.value(currentPair)); - n1Next = decltype(n1){ _notations.erase(std::next(n1).base()) }; - n2Next = decltype(n2){ _notations.erase(std::next(n2).base()) }; - _notations.push_back(mergedNotation); - } - } - } -} - //--------------------------------------------------------- // parse //--------------------------------------------------------- @@ -6987,7 +6896,6 @@ void MusicXMLParserNotations::parse() LOGD("%s", qPrintable(notation.print())); } */ - combineArticulations(); addError(checkAtEndElement(_e, "notations")); } diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h index c5e9eb436bea6..8323e9c2c17ed 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h @@ -170,7 +170,6 @@ class Notation QString text() const { return _text; } static Notation notationWithAttributes(const QString& name, const QXmlStreamAttributes attributes, const QString& parent = "", const SymId& symId = SymId::noSym); - static Notation mergeNotations(const Notation& n1, const Notation& n2, const SymId& symId = SymId::noSym); private: QString _name; QString _parent; @@ -217,12 +216,10 @@ class MusicXMLParserNotations QString tremoloType() const { return _tremoloType; } int tremoloNr() const { return _tremoloNr; } bool mustStopGraceAFter() const { return _slurStop || _wavyLineStop; } - bool skipCombine(const Notation& n1, const Notation& n2); private: void addError(const QString& error); ///< Add an error to be shown in the GUI void addNotation(const Notation& notation, ChordRest* const cr, Note* const note); void addTechnical(const Notation& notation, Note* note); - void combineArticulations(); void harmonic(); void articulations(); void dynamics(); diff --git a/src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml b/src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml new file mode 100644 index 0000000000000..2ef77d032a465 --- /dev/null +++ b/src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml @@ -0,0 +1,454 @@ + + + + + Title + + + Composer + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + Piano + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + 1 + + 0 + + + + G + 2 + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + D + 5 + + 1 + 1 + quarter + down + + + + + + + + + + E + 5 + + 1 + 1 + quarter + down + + + + + + + + + + F + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + + G + 5 + + 1 + 1 + quarter + down + + + + + + + + + F + 5 + + 1 + 1 + quarter + down + + + + + + + + + + E + 5 + + 1 + 1 + quarter + down + + + + + + + + + + D + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + + + + A + 4 + + 1 + 1 + quarter + up + + + + + + + + + + G + 4 + + 1 + 1 + quarter + up + + + + + + + + + + F + 4 + + 1 + 1 + quarter + up + + + + + + + + + + E + 4 + + 1 + 1 + quarter + up + + + + + + + + + + + + D + 4 + + 1 + 1 + quarter + up + + + + + + + + + E + 4 + + 1 + 1 + quarter + up + + + + + + + + + + F + 4 + + 1 + 1 + quarter + up + + + + + + + + + + G + 4 + + 1 + 1 + quarter + up + + + + + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + light-heavy + + + + diff --git a/src/importexport/musicxml/tests/data/testColors.xml b/src/importexport/musicxml/tests/data/testColors.xml index 90cc1a5ee7610..da020898bdb66 100644 --- a/src/importexport/musicxml/tests/data/testColors.xml +++ b/src/importexport/musicxml/tests/data/testColors.xml @@ -72,8 +72,8 @@ down - + diff --git a/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml b/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml index b2ec46a6823ac..3c04cc65c868f 100644 --- a/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml +++ b/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml @@ -448,7 +448,8 @@ up - + + diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index cf85348dd0809..1d7b33e93def0 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -378,7 +378,7 @@ TEST_F(Musicxml_Tests, arpGliss3) { mxmlIoTest("testArpGliss3"); } TEST_F(Musicxml_Tests, articulationCombination) { - mxmlIoTest("testArticulationCombination"); + mxmlIoTestRef("testArticulationCombination"); } TEST_F(Musicxml_Tests, backupRoundingError) { mxmlImportTestRef("testBackupRoundingError"); diff --git a/src/notation/internal/instrumentsrepository.cpp b/src/notation/internal/instrumentsrepository.cpp index e82bc5a830118..cff2d533403b4 100644 --- a/src/notation/internal/instrumentsrepository.cpp +++ b/src/notation/internal/instrumentsrepository.cpp @@ -192,11 +192,15 @@ bool InstrumentsRepository::loadStringTuningsPresets(const io::path_t& path) preset.value.push_back(valueVal.toInt()); } - if (info.number != static_cast(preset.value.size())) { + if (info.number != preset.value.size()) { LOGE() << "Invalid preset " << preset.name; continue; } + if (presetObj.contains("useFlats")) { + preset.useFlats = presetObj.value("useFlats").toBool(); + } + info.presets.emplace_back(std::move(preset)); } diff --git a/src/notation/notationtypes.h b/src/notation/notationtypes.h index 97f7fb1de8fb7..70ff5086c975b 100644 --- a/src/notation/notationtypes.h +++ b/src/notation/notationtypes.h @@ -686,6 +686,7 @@ struct StringTuningPreset { std::string name; std::vector value; + bool useFlats = false; }; struct StringTuningsInfo diff --git a/src/notation/view/internal/stringtuningssettingsmodel.cpp b/src/notation/view/internal/stringtuningssettingsmodel.cpp index 066fe4fe81c8d..597ae6e3ac9d7 100644 --- a/src/notation/view/internal/stringtuningssettingsmodel.cpp +++ b/src/notation/view/internal/stringtuningssettingsmodel.cpp @@ -88,6 +88,7 @@ void StringTuningsSettingsModel::init() item->setShow(contains(visibleStrings, instrStringIndex)); item->setNumber(QString::number(i + 1)); item->setValue(string.pitch); + item->setUseFlat(string.useFlat); item->blockSignals(false); m_strings.push_back(item); @@ -134,6 +135,9 @@ bool StringTuningsSettingsModel::setStringValue(int stringIndex, const QString& item->setValue(value); + bool useFlat = _stringValue.contains("♭"); + item->setUseFlat(useFlat); + beginMultiCommands(); updateCurrentPreset(); @@ -153,7 +157,7 @@ bool StringTuningsSettingsModel::canIncreaseStringValue(const QString& stringVal QString StringTuningsSettingsModel::increaseStringValue(const QString& stringValue) { QString value = convertToUnicode(stringValue); - return engraving::pitch2string(engraving::string2pitch(value) + 1); + return engraving::pitch2string(engraving::string2pitch(value) + 1, false /* useFlats */); } bool StringTuningsSettingsModel::canDecreaseStringValue(const QString& stringValue) const @@ -165,7 +169,7 @@ bool StringTuningsSettingsModel::canDecreaseStringValue(const QString& stringVal QString StringTuningsSettingsModel::decreaseStringValue(const QString& stringValue) { QString value = convertToUnicode(stringValue); - return engraving::pitch2string(engraving::string2pitch(value) - 1); + return engraving::pitch2string(engraving::string2pitch(value) - 1, true /* useFlats */); } QVariantList StringTuningsSettingsModel::presets(bool withCustom) const @@ -184,8 +188,9 @@ QVariantList StringTuningsSettingsModel::presets(bool withCustom) const customMap.insert("text", custom); QVariantList valueList; - for (const StringTuningsItem* item : m_strings) { - valueList << item->value(); + int numOfStrings = static_cast(m_strings.size()); + for (int i = 0; i < numOfStrings; ++i) { + valueList << m_strings.at(numOfStrings - i - 1)->value(); } customMap.insert("value", valueList); @@ -210,6 +215,7 @@ QVariantList StringTuningsSettingsModel::presets(bool withCustom) const } presetMap.insert("value", valueList); + presetMap.insert("useFlats", preset.useFlats); presetsList.push_back(presetMap); } @@ -313,6 +319,7 @@ void StringTuningsSettingsModel::updateStrings() for (const QVariant& _preset : presets) { if (_preset.toMap()["text"].toString() == currentPreset) { QVariantList valueList = _preset.toMap()["value"].toList(); + bool useFlats = _preset.toMap()["useFlats"].toBool(); int numOfStrings = valueList.size(); for (int i = 0; i < numOfStrings; ++i) { int valueIndex = numOfStrings - i - 1; @@ -322,6 +329,7 @@ void StringTuningsSettingsModel::updateStrings() item->setShow(true); item->setNumber(QString::number(i + 1)); item->setValue(valueList[valueIndex].toInt()); + item->setUseFlat(useFlats); item->blockSignals(false); m_strings.push_back(item); @@ -348,7 +356,9 @@ void StringTuningsSettingsModel::saveStrings() int numOfStrings = m_strings.size(); for (int i = 0; i < numOfStrings; ++i) { - stringList[i].pitch = m_strings[numOfStrings - i - 1]->value(); + const StringTuningsItem* item = m_strings.at(numOfStrings - i - 1); + stringList[i].pitch = item->value(); + stringList[i].useFlat = item->useFlat(); } beginCommand(); @@ -378,8 +388,9 @@ void StringTuningsSettingsModel::saveStringsVisibleState() void StringTuningsSettingsModel::updateCurrentPreset() { QVariantList currentValueList; - for (int i = 0; i < m_strings.size(); ++i) { - currentValueList << m_strings[i]->value(); + int numOfStrings = static_cast(m_strings.size()); + for (int i = 0; i < numOfStrings; ++i) { + currentValueList << m_strings.at(numOfStrings - i - 1)->value(); } const QVariantList presets = this->presets(false /*withCustom*/); @@ -471,7 +482,7 @@ int StringTuningsItem::value() const QString StringTuningsItem::valueStr() const { - return engraving::pitch2string(m_value).toUpper(); + return engraving::pitch2string(m_value, m_useFlat).toUpper(); } void StringTuningsItem::setValue(int value) @@ -483,3 +494,18 @@ void StringTuningsItem::setValue(int value) m_value = value; emit valueChanged(); } + +bool StringTuningsItem::useFlat() const +{ + return m_useFlat; +} + +void StringTuningsItem::setUseFlat(bool use) +{ + if (m_useFlat == use) { + return; + } + + m_useFlat = use; + emit valueChanged(); +} diff --git a/src/notation/view/internal/stringtuningssettingsmodel.h b/src/notation/view/internal/stringtuningssettingsmodel.h index 545ead31c50e5..ea5e619eb0f95 100644 --- a/src/notation/view/internal/stringtuningssettingsmodel.h +++ b/src/notation/view/internal/stringtuningssettingsmodel.h @@ -107,6 +107,7 @@ class StringTuningsItem : public QObject Q_PROPERTY(QString number READ number NOTIFY numberChanged) Q_PROPERTY(int value READ value NOTIFY valueChanged) Q_PROPERTY(QString valueStr READ valueStr NOTIFY valueChanged) + Q_PROPERTY(bool useFlat READ useFlat WRITE setUseFlat NOTIFY valueChanged) public: explicit StringTuningsItem(QObject* parent = nullptr); @@ -121,6 +122,9 @@ class StringTuningsItem : public QObject QString valueStr() const; void setValue(int value); + bool useFlat() const; + void setUseFlat(bool use); + signals: void showChanged(); void numberChanged(); @@ -130,6 +134,7 @@ class StringTuningsItem : public QObject bool m_show = false; QString m_number; int m_value = 0; + bool m_useFlat = false; }; } //namespace mu::notation diff --git a/vtest/scores/mmrest-10.mscz b/vtest/scores/mmrest-10.mscz new file mode 100644 index 0000000000000..12161bf0a3279 Binary files /dev/null and b/vtest/scores/mmrest-10.mscz differ diff --git a/vtest/scores/slurs-25.mscz b/vtest/scores/slurs-25.mscz new file mode 100644 index 0000000000000..89c1b9277a414 Binary files /dev/null and b/vtest/scores/slurs-25.mscz differ