From cb0205066ff5cfef2b79305af6a0730d8b3945e9 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 31 Dec 2024 10:20:00 +0000 Subject: [PATCH] Add partial lyrics lines --- src/engraving/api/v1/apitypes.h | 2 + src/engraving/dom/chordrest.cpp | 5 + src/engraving/dom/edit.cpp | 34 ++- src/engraving/dom/engravingobject.h | 15 +- src/engraving/dom/factory.cpp | 9 +- src/engraving/dom/factory.h | 3 + src/engraving/dom/lyrics.cpp | 9 +- src/engraving/dom/lyrics.h | 82 ++++++- src/engraving/dom/lyricsline.cpp | 147 +++++++++++- src/engraving/dom/navigate.cpp | 21 +- src/engraving/dom/navigate.h | 7 +- src/engraving/dom/repeatlist.cpp | 9 + src/engraving/dom/repeatlist.h | 1 + src/engraving/dom/score.cpp | 2 + src/engraving/dom/system.cpp | 2 + src/engraving/dom/tie.cpp | 21 +- src/engraving/dom/utils.cpp | 70 ++++++ src/engraving/dom/utils.h | 4 + .../rendering/score/lyricslayout.cpp | 227 ++++++++++++------ src/engraving/rendering/score/lyricslayout.h | 8 +- src/engraving/rendering/score/tdraw.cpp | 4 +- src/engraving/rendering/score/tlayout.cpp | 6 +- .../rw/read410/connectorinforeader.cpp | 2 + src/engraving/rw/read410/tread.cpp | 15 ++ src/engraving/rw/read410/tread.h | 1 + src/engraving/rw/write/twrite.cpp | 14 ++ src/engraving/rw/write/twrite.h | 1 + src/engraving/types/types.h | 2 + src/engraving/types/typesconv.cpp | 3 + src/notation/internal/notationinteraction.cpp | 165 ++++++++----- src/notation/view/widgets/editstyle.cpp | 2 + 31 files changed, 704 insertions(+), 189 deletions(-) diff --git a/src/engraving/api/v1/apitypes.h b/src/engraving/api/v1/apitypes.h index 3282a4e6e4909..8e3977db6b623 100644 --- a/src/engraving/api/v1/apitypes.h +++ b/src/engraving/api/v1/apitypes.h @@ -286,6 +286,7 @@ enum class ElementType { VOLTA_SEGMENT = int(mu::engraving::ElementType::VOLTA_SEGMENT), PEDAL_SEGMENT = int(mu::engraving::ElementType::PEDAL_SEGMENT), LYRICSLINE_SEGMENT = int(mu::engraving::ElementType::LYRICSLINE_SEGMENT), + PARTIAL_LYRICSLINE_SEGMENT = int(mu::engraving::ElementType::PARTIAL_LYRICSLINE_SEGMENT), GLISSANDO_SEGMENT = int(mu::engraving::ElementType::GLISSANDO_SEGMENT), NOTELINE_SEGMENT = int(mu::engraving::ElementType::NOTELINE_SEGMENT), LAYOUT_BREAK = int(mu::engraving::ElementType::LAYOUT_BREAK), @@ -319,6 +320,7 @@ enum class ElementType { TEXTLINE_BASE = int(mu::engraving::ElementType::TEXTLINE_BASE), NOTELINE = int(mu::engraving::ElementType::NOTELINE), LYRICSLINE = int(mu::engraving::ElementType::LYRICSLINE), + PARTIAL_LYRICSLINE = int(mu::engraving::ElementType::PARTIAL_LYRICSLINE), GLISSANDO = int(mu::engraving::ElementType::GLISSANDO), BRACKET = int(mu::engraving::ElementType::BRACKET), SEGMENT = int(mu::engraving::ElementType::SEGMENT), diff --git a/src/engraving/dom/chordrest.cpp b/src/engraving/dom/chordrest.cpp index a55e6a08d127e..a3187d7f8f4f3 100644 --- a/src/engraving/dom/chordrest.cpp +++ b/src/engraving/dom/chordrest.cpp @@ -586,6 +586,11 @@ void ChordRest::remove(EngravingItem* e) } } break; + // case ElementType::PARTIAL_LYRICSLINE: { + // e->resetExplicitParent(); + // score()->undoRemoveElement() + // } + // break; default: ASSERT_X(u"ChordRest::remove: unknown element " + String::fromAscii(e->typeName())); } diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index 7cd06117e3f22..b831c2cfe2e86 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -872,6 +872,17 @@ TextBase* Score::addText(TextStyleType type, EngravingItem* destinationElement) } int no = static_cast(chordRest->lyrics().size()); + const auto& spanners = spannerMap().findOverlapping(chordRest->tick().ticks(), chordRest->endTick().ticks()); + for (auto& spanner : spanners) { + if (!spanner.value->isPartialLyricsLine() || spanner.start != chordRest->tick().ticks()) { + continue; + } + const PartialLyricsLine* line = toPartialLyricsLine(spanner.value); + + no = std::max(no, line->no() + 1); + } + + // Also check how many partial lines there are Lyrics* lyrics = Factory::createLyrics(chordRest); lyrics->setTrack(chordRest->track()); lyrics->setParent(chordRest); @@ -2905,6 +2916,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::LAISSEZ_VIB_SEGMENT: case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::GLISSANDO_SEGMENT: case ElementType::NOTELINE_SEGMENT: @@ -6080,6 +6092,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool && et != ElementType::VIBRATO && et != ElementType::TEXTLINE && et != ElementType::PEDAL + && et != ElementType::PARTIAL_LYRICSLINE && et != ElementType::BREATH && et != ElementType::DYNAMIC && et != ElementType::EXPRESSION @@ -6145,6 +6158,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool ElementType::TEXTLINE, ElementType::PEDAL, ElementType::LYRICS, + ElementType::PARTIAL_LYRICSLINE, ElementType::CLEF, ElementType::AMBITUS }; @@ -6178,6 +6192,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool case ElementType::DYNAMIC: case ElementType::EXPRESSION: case ElementType::LYRICS: // not normally segment-attached + case ElementType::PARTIAL_LYRICSLINE: continue; default: break; @@ -6347,7 +6362,8 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool || element->isTrill() || element->isVibrato() || element->isTextLine() - || element->isPedal()) { + || element->isPedal() + || element->isPartialLyricsLine()) { Spanner* sp = toSpanner(element); Spanner* nsp = toSpanner(ne); track_idx_t tr2 = sp->effectiveTrack2(); @@ -7203,9 +7219,23 @@ void Score::undoRemoveStaleTieJumpPoints() for (Segment* s = m->first(st); s; s = s->next1(st)) { for (track_idx_t i = 0; i < tracks; ++i) { EngravingItem* e = s->element(i); - if (e == 0 || !e->isChord()) { + if (e == 0 || !e->isChordRest()) { + continue; + } + + // Remove invalid lyrics dashes + for (Lyrics* lyrics : toChordRest(e)->lyrics()) { + LyricsLine* separator = lyrics->separator(); + if ((lyrics->syllabic() == LyricsSyllabic::BEGIN || lyrics->syllabic() == LyricsSyllabic::MIDDLE) && separator + && !separator->nextLyrics() && !separator->isEndMelisma()) { + lyrics->setNeedRemoveInvalidSegments(); + } + } + + if (!e->isChord()) { continue; } + Chord* c = toChord(e); for (Note* n : c->notes()) { if (!n->tieFor()) { diff --git a/src/engraving/dom/engravingobject.h b/src/engraving/dom/engravingobject.h index b9d005be50753..c58321952ddf4 100644 --- a/src/engraving/dom/engravingobject.h +++ b/src/engraving/dom/engravingobject.h @@ -126,6 +126,8 @@ class Page; class PalmMute; class PalmMuteSegment; class Part; +class PartialLyricsLine; +class PartialLyricsLineSegment; class PartialTie; class PartialTieSegment; class Pedal; @@ -422,8 +424,8 @@ class EngravingObject CONVERT(FSymbol, FSYMBOL) CONVERT(Fingering, FINGERING) CONVERT(NoteHead, NOTEHEAD) - CONVERT(LyricsLine, LYRICSLINE) - CONVERT(LyricsLineSegment, LYRICSLINE_SEGMENT) + CONVERT(PartialLyricsLine, PARTIAL_LYRICSLINE) + CONVERT(PartialLyricsLineSegment, PARTIAL_LYRICSLINE_SEGMENT) CONVERT(FiguredBass, FIGURED_BASS) CONVERT(FiguredBassItem, FIGURED_BASS_ITEM) CONVERT(StaffState, STAFF_STATE) @@ -528,6 +530,13 @@ class EngravingObject ; } + bool isLyricsLine() const { return type() == ElementType::LYRICSLINE || type() == ElementType::PARTIAL_LYRICSLINE; } + + bool isLyricsLineSegment() const + { + return type() == ElementType::LYRICSLINE_SEGMENT || type() == ElementType::PARTIAL_LYRICSLINE_SEGMENT; + } + bool isSLine() const { return isTextLineBase() || isTrill() || isGlissando() || isVibrato() || isGuitarBend() || isGuitarBendHold(); @@ -857,5 +866,7 @@ CONVERT(SoundFlag) CONVERT(TimeTickAnchor) CONVERT(LaissezVib) CONVERT(PartialTie) +CONVERT(PartialLyricsLine) +CONVERT(PartialLyricsLineSegment) #undef CONVERT } diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index 34e7a186cf21a..60397f5d7e603 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -238,6 +238,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::TIME_TICK_ANCHOR: return new TimeTickAnchor(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::LAISSEZ_VIB: return new LaissezVib(parent->isNote() ? toNote(parent) : dummy->note()); case ElementType::PARTIAL_TIE: return new PartialTie(parent->isNote() ? toNote(parent) : dummy->note()); + case ElementType::PARTIAL_LYRICSLINE: return new PartialLyricsLine(parent); case ElementType::LYRICSLINE: case ElementType::TEXTLINE_BASE: @@ -271,6 +272,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::VOLTA_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: case ElementType::LEDGER_LINE: case ElementType::STAFF_LINES: case ElementType::SELECTION: @@ -475,9 +477,12 @@ Page* Factory::createPage(RootItem* parent, bool isAccessibleEnabled) } CREATE_ITEM_IMPL(PartialTie, ElementType::PARTIAL_TIE, Note, isAccessibleEnabled) -COPY_ITEM_IMPL(PartialTie); +COPY_ITEM_IMPL(PartialTie) -Rest* Factory::createRest(Segment* parent, bool isAccessibleEnabled) +CREATE_ITEM_IMPL(PartialLyricsLine, ElementType::PARTIAL_LYRICSLINE, EngravingItem, isAccessibleEnabled) +COPY_ITEM_IMPL(PartialLyricsLine) + +Rest* Factory::createRest(Segment * parent, bool isAccessibleEnabled) { Rest* r = new Rest(parent); r->setAccessibleEnabled(isAccessibleEnabled); diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index 7b040fca7da73..4b71e6ba7de69 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -146,6 +146,9 @@ class Factory static PartialTie* createPartialTie(Note* parent, bool isAccessibleEnabled = true); static PartialTie* copyPartialTie(const PartialTie& src); + static PartialLyricsLine* createPartialLyricsLine(EngravingItem* parent, bool isAccessibleEnabled = true); + static PartialLyricsLine* copyPartialLyricsLine(const PartialLyricsLine& src); + static Rest* createRest(Segment* parent, bool isAccessibleEnabled = true); static Rest* createRest(Segment* parent, const TDuration& t, bool isAccessibleEnabled = true); static Rest* copyRest(const Rest& src, bool link = false); diff --git a/src/engraving/dom/lyrics.cpp b/src/engraving/dom/lyrics.cpp index d994e521c90ce..cf6e3cbe5bfea 100644 --- a/src/engraving/dom/lyrics.cpp +++ b/src/engraving/dom/lyrics.cpp @@ -88,11 +88,6 @@ TranslatableString Lyrics::subtypeUserName() const void Lyrics::add(EngravingItem* el) { -// el->setParent(this); -// if (el->type() == ElementType::LINE) -// _separator.append((Line*)el); // ignore! Internally managed -// ; -// else LOGD("Lyrics::add: unknown element %s", el->typeName()); } @@ -559,9 +554,9 @@ void Lyrics::removeInvalidSegments() m_separator = nullptr; setAlign(propertyDefault(Pid::ALIGN).value()); if (m_syllabic == LyricsSyllabic::BEGIN || m_syllabic == LyricsSyllabic::SINGLE) { - m_syllabic = LyricsSyllabic::SINGLE; + undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::SINGLE)); } else { - m_syllabic = LyricsSyllabic::END; + undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END)); } } } diff --git a/src/engraving/dom/lyrics.h b/src/engraving/dom/lyrics.h index 29b8c59f8ae5c..9d2008ad3533d 100644 --- a/src/engraving/dom/lyrics.h +++ b/src/engraving/dom/lyrics.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_LYRICS_H -#define MU_ENGRAVING_LYRICS_H +#pragma once #include "../types/types.h" #include "line.h" @@ -124,7 +123,7 @@ class Lyrics final : public TextBase /// \cond PLUGIN_API \private \endcond //--------------------------------------------------------- -class LyricsLine final : public SLine +class LyricsLine : public SLine { OBJECT_ALLOCATOR(engraving, LyricsLine) DECLARE_CLASSOF(ElementType::LYRICSLINE) @@ -139,14 +138,16 @@ class LyricsLine final : public SLine void removeUnmanaged() override; void styleChanged() override; - Lyrics* lyrics() const { return toLyrics(explicitParent()); } + virtual Lyrics* lyrics() const { return toLyrics(explicitParent()); } Lyrics* nextLyrics() const { return m_nextLyrics; } void setNextLyrics(Lyrics* l) { m_nextLyrics = l; } - bool isEndMelisma() const { return lyrics() && lyrics()->ticks().isNotZero(); } + virtual bool isEndMelisma() const { return lyrics() && lyrics()->ticks().isNotZero(); } bool isDash() const { return !isEndMelisma(); } bool setProperty(Pid propertyId, const PropertyValue& v) override; protected: + LyricsLine(const ElementType& type, EngravingItem* parent, ElementFlags = ElementFlag::NOTHING); + Lyrics* m_nextLyrics = nullptr; void doComputeEndElement() override; @@ -157,7 +158,7 @@ class LyricsLine final : public SLine /// \cond PLUGIN_API \private \endcond //--------------------------------------------------------- -class LyricsLineSegment final : public LineSegment +class LyricsLineSegment : public LineSegment { OBJECT_ALLOCATOR(engraving, LyricsLineSegment) DECLARE_CLASSOF(ElementType::LYRICSLINE_SEGMENT) @@ -168,9 +169,19 @@ class LyricsLineSegment final : public LineSegment LyricsLineSegment* clone() const override { return new LyricsLineSegment(*this); } LyricsLine* lyricsLine() const { return toLyricsLine(spanner()); } - Lyrics* lyrics() const { return lyricsLine()->lyrics(); } + virtual Lyrics* lyrics() const { return lyricsLine()->lyrics(); } + + virtual double baseLineShift() const; - double baseLineShift() const; + virtual int no() const { return lyrics()->no(); } + virtual bool lyricsPlaceAbove() const { return lyrics()->placeAbove(); } + virtual bool lyricsAddToSkyline() const { return lyrics()->addToSkyline(); } + virtual double lineSpacing() const { return lyrics()->lineSpacing(); } + Color color() const override { return lyrics()->color(); } + int gripsCount() const override { return 0; } + Grip initialEditModeGrip() const override { return Grip::NO_GRIP; } + Grip defaultGrip() const override { return Grip::NO_GRIP; } + bool needStartEditingAfterSelecting() const override { return false; } struct LayoutData : public LineSegment::LayoutData { public: @@ -181,6 +192,59 @@ class LyricsLineSegment final : public LineSegment std::vector m_dashes; }; DECLARE_LAYOUTDATA_METHODS(LyricsLineSegment) + +protected: + LyricsLineSegment(const ElementType& type, LyricsLine* sp, System* parent, ElementFlags f = ElementFlag::NOTHING); +}; + +class PartialLyricsLine final : public LyricsLine +{ + OBJECT_ALLOCATOR(engraving, PartialLyricsLine) + DECLARE_CLASSOF(ElementType::PARTIAL_LYRICSLINE) + + M_PROPERTY2(int, no, setNo, 0) +public: + PartialLyricsLine(EngravingItem* parent); + PartialLyricsLine(const PartialLyricsLine&); + PartialLyricsLine* clone() const override { return new PartialLyricsLine(*this); } + LineSegment* createLineSegment(System* parent) override; + + Lyrics* lyrics() const override { return nullptr; } + + void setIsEndMelisma(bool val) { m_isEndMelisma = val; } + bool isEndMelisma() const override { return m_isEndMelisma; } + + PropertyValue getProperty(Pid propertyId) const override; + bool setProperty(Pid propertyId, const PropertyValue&) override; + PropertyValue propertyDefault(Pid propertyId) const override; + + Lyrics* findLyricsInPreviousRepeatSeg(); + +protected: + void doComputeEndElement() override; + +private: + bool m_isEndMelisma = false; +}; + +class PartialLyricsLineSegment final : public LyricsLineSegment +{ + OBJECT_ALLOCATOR(engraving, PartialLyricsLineSegment) + DECLARE_CLASSOF(ElementType::PARTIAL_LYRICSLINE_SEGMENT) + +public: + PartialLyricsLineSegment(PartialLyricsLine*, System* parent); + + LyricsLineSegment* clone() const override { return new PartialLyricsLineSegment(*this); } + + PartialLyricsLine* lyricsLine() const { return toPartialLyricsLine(spanner()); } + Lyrics* lyrics() const override { return nullptr; } + + int no() const override { return lyricsLine()->no(); } + double lineSpacing() const override; + bool lyricsPlaceAbove() const override { return lyricsLine()->placeAbove(); }// Delegate? + bool lyricsAddToSkyline() const override { return lyricsLine()->addToSkyline(); } + Color color() const override { return lyricsLine()->color(); } + double baseLineShift() const override; }; } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index b4b796bc4cdfb..bd8198a0b4ffa 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -22,16 +22,14 @@ #include "lyrics.h" -#include "draw/types/pen.h" - -#include "chord.h" #include "chordrest.h" #include "measure.h" +#include "navigate.h" #include "note.h" #include "score.h" #include "segment.h" -#include "stafftype.h" #include "system.h" +#include "utils.h" using namespace mu; @@ -50,6 +48,16 @@ LyricsLine::LyricsLine(EngravingItem* parent) m_nextLyrics = 0; } +LyricsLine::LyricsLine(const ElementType& type, EngravingItem* parent, ElementFlags f) + : SLine(type, parent, f) +{ + setGenerated(true); // no need to save it, as it can be re-generated + setDiagonal(false); + setLineWidth(style().styleS(Sid::lyricsDashLineThickness)); + setAnchor(Spanner::Anchor::SEGMENT); + m_nextLyrics = 0; +} + LyricsLine::LyricsLine(const LyricsLine& g) : SLine(g) { @@ -142,13 +150,140 @@ LyricsLineSegment::LyricsLineSegment(LyricsLine* sp, System* parent) setGenerated(true); } +LyricsLineSegment::LyricsLineSegment(const ElementType& type, LyricsLine* sp, System* parent, ElementFlags f) + : LineSegment(type, sp, parent, f) +{ + setGenerated(true); +} + double LyricsLineSegment::baseLineShift() const { if (lyricsLine()->isEndMelisma()) { return -0.5 * absoluteFromSpatium(lineWidth()); } - Lyrics* lyrics = lyricsLine()->lyrics(); - return -style().styleD(Sid::lyricsDashYposRatio) * lyrics->fontMetrics().xHeight(); + Lyrics* segLyrics = lyrics(); + return -style().styleD(Sid::lyricsDashYposRatio) * segLyrics->fontMetrics().xHeight(); +} + +//========================================================= +// PartialLyricsLine +//========================================================= +PartialLyricsLine::PartialLyricsLine(EngravingItem* parent) + : LyricsLine(ElementType::PARTIAL_LYRICSLINE, parent) +{ + setGenerated(false); +} + +PartialLyricsLine::PartialLyricsLine(const PartialLyricsLine& other) + : LyricsLine(other) +{ + m_isEndMelisma = other.m_isEndMelisma; +} + +LineSegment* PartialLyricsLine::createLineSegment(System* parent) +{ + PartialLyricsLineSegment* seg = new PartialLyricsLineSegment(this, parent); + seg->setTrack(track()); + seg->setColor(color()); + return seg; +} + +PropertyValue PartialLyricsLine::getProperty(Pid propertyId) const +{ + switch (propertyId) { + case Pid::VERSE: + return _no; + default: + return LyricsLine::getProperty(propertyId); + } +} + +bool PartialLyricsLine::setProperty(Pid propertyId, const PropertyValue& val) +{ + switch (propertyId) { + case Pid::VERSE: + setNo(val.toInt()); + break; + default: + return LyricsLine::setProperty(propertyId, val); + } + + return true; +} + +PropertyValue PartialLyricsLine::propertyDefault(Pid propertyId) const +{ + switch (propertyId) { + case Pid::VERSE: + return 0; + default: + return LyricsLine::propertyDefault(propertyId); + } +} + +void PartialLyricsLine::doComputeEndElement() +{ + LyricsLine::doComputeEndElement(); + + if (startElement() && endElement() && startElement()->tick() > endElement()->tick()) { + setEndElement(startElement()); + } +} + +//========================================================= +// PartialLyricsLineSegment +//========================================================= + +PartialLyricsLineSegment::PartialLyricsLineSegment(PartialLyricsLine* line, System* parent) + : LyricsLineSegment(ElementType::PARTIAL_LYRICSLINE_SEGMENT, line, parent, ElementFlag::ON_STAFF) +{ + setGenerated(false); +} + +double PartialLyricsLineSegment::lineSpacing() const +{ + const Lyrics* prevLyrics = lyricsLine()->findLyricsInPreviousRepeatSeg(); + if (prevLyrics) { + return prevLyrics->lineSpacing(); + } + + const Lyrics* nextLyrics = lyricsLine()->nextLyrics(); + if (nextLyrics) { + return lyricsLine()->nextLyrics()->lineSpacing(); + } + + return 0.0; +} + +double PartialLyricsLineSegment::baseLineShift() const +{ + if (lyricsLine()->isEndMelisma()) { + return -0.5 * absoluteFromSpatium(lineWidth()); + } + + const Lyrics* prevLyrics = lyricsLine()->findLyricsInPreviousRepeatSeg(); + if (prevLyrics) { + return -style().styleD(Sid::lyricsDashYposRatio) * prevLyrics->fontMetrics().xHeight(); + } + + return 0.0; +} + +Lyrics* PartialLyricsLine::findLyricsInPreviousRepeatSeg() +{ + const std::vector measures = findPreviousRepeatMeasures(findStartMeasure()); + + for (const Measure* measure : measures) { + Lyrics* prev = prevLyrics(measure->last(SegmentType::ChordRest), staffIdx(), no(), placement()); + + if (!prev) { + continue; + } + + return prev; + } + + return nullptr; } } diff --git a/src/engraving/dom/navigate.cpp b/src/engraving/dom/navigate.cpp index 3db02fb20165d..cb5a427e689d4 100644 --- a/src/engraving/dom/navigate.cpp +++ b/src/engraving/dom/navigate.cpp @@ -1055,19 +1055,15 @@ EngravingItem* Score::prevElement() // - currently used to determine the first lyric of a melisma //--------------------------------------------------------- -Lyrics* prevLyrics(const Lyrics* lyrics) +Lyrics* prevLyrics(const Segment* seg, const staff_idx_t staffIdx, const int no, const PlacementV& placement) { - Segment* seg = lyrics->explicitParent() ? lyrics->segment() : nullptr; - if (!seg) { - return nullptr; - } - Segment* prevSegment = seg; + const Segment* prevSegment = seg; while ((prevSegment = prevSegment->prev1(mu::engraving::SegmentType::ChordRest))) { - const track_idx_t strack = lyrics->staffIdx() * VOICES; + const track_idx_t strack = staffIdx * VOICES; const track_idx_t etrack = strack + VOICES; for (track_idx_t track = strack; track < etrack; ++track) { EngravingItem* el = prevSegment->element(track); - Lyrics* prevLyrics = el && el->isChord() ? toChordRest(el)->lyrics(lyrics->no(), lyrics->placement()) : nullptr; + Lyrics* prevLyrics = el && el->isChord() ? toChordRest(el)->lyrics(no, placement) : nullptr; if (prevLyrics) { return prevLyrics; } @@ -1076,6 +1072,15 @@ Lyrics* prevLyrics(const Lyrics* lyrics) return nullptr; } +Lyrics* prevLyrics(const Lyrics* lyrics) +{ + Segment* seg = lyrics->explicitParent() ? lyrics->segment() : nullptr; + if (!seg) { + return nullptr; + } + return prevLyrics(seg, lyrics->staffIdx(), lyrics->no(), lyrics->placement()); +} + Lyrics* nextLyrics(const Lyrics* lyrics) { Segment* seg = lyrics->explicitParent() ? lyrics->segment() : nullptr; diff --git a/src/engraving/dom/navigate.h b/src/engraving/dom/navigate.h index 0944539244111..1252a653342e4 100644 --- a/src/engraving/dom/navigate.h +++ b/src/engraving/dom/navigate.h @@ -20,17 +20,18 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_NAVIGATE_H -#define MU_ENGRAVING_NAVIGATE_H +#pragma once +#include "types.h" namespace mu::engraving { class ChordRest; class Lyrics; +class Segment; extern int pitch2y(int pitch, int enh, int clefOffset, int key, int& prefix, const char* tversatz); extern ChordRest* nextChordRest(const ChordRest* cr, bool skipGrace = false, bool skipMeasureRepeatRests = true); extern ChordRest* prevChordRest(const ChordRest* cr, bool skipGrace = false, bool skipMeasureRepeatRests = true); +extern Lyrics* prevLyrics(const Segment* seg, const staff_idx_t staffIdx, const int no, const PlacementV& placement); extern Lyrics* prevLyrics(const Lyrics* lyrics); extern Lyrics* nextLyrics(const Lyrics* lyrics); } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/repeatlist.cpp b/src/engraving/dom/repeatlist.cpp index d9709625562ec..2688e072d019e 100644 --- a/src/engraving/dom/repeatlist.cpp +++ b/src/engraving/dom/repeatlist.cpp @@ -99,6 +99,15 @@ bool RepeatSegment::endsWithMeasure(Measure const* const m) const return m_measureList.back() == m; } +bool RepeatSegment::startsWithMeasure(const Measure* const m) const +{ + if (m_measureList.empty()) { + return false; + } + + return m_measureList.front() == m; +} + bool RepeatSegment::isEmpty() const { return m_measureList.empty(); diff --git a/src/engraving/dom/repeatlist.h b/src/engraving/dom/repeatlist.h index 640b462542ee4..09f56faccca79 100644 --- a/src/engraving/dom/repeatlist.h +++ b/src/engraving/dom/repeatlist.h @@ -58,6 +58,7 @@ class RepeatSegment void addMeasures(Measure const* const); bool containsMeasure(Measure const* const) const; bool endsWithMeasure(Measure const* const) const; + bool startsWithMeasure(Measure const* const) const; bool isEmpty() const; int len() const; void popMeasure(); diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index cfe676952b54b..80ad163db955f 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -1535,6 +1535,7 @@ void Score::addElement(EngravingItem* element) case ElementType::RASGUEADO: case ElementType::HARMONIC_MARK: case ElementType::PICK_SCRAPE: + case ElementType::PARTIAL_LYRICSLINE: { Spanner* spanner = toSpanner(element); if (et == ElementType::TEXTLINE && spanner->anchor() == Spanner::Anchor::NOTE) { @@ -1718,6 +1719,7 @@ void Score::removeElement(EngravingItem* element) case ElementType::LET_RING: case ElementType::GRADUAL_TEMPO_CHANGE: case ElementType::PALM_MUTE: + case ElementType::PARTIAL_LYRICSLINE: case ElementType::WHAMMY_BAR: case ElementType::RASGUEADO: case ElementType::HARMONIC_MARK: diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index b5c5af0f01e7f..c1cc204b2f70f 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -477,6 +477,7 @@ void System::add(EngravingItem* el) case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: case ElementType::GLISSANDO_SEGMENT: case ElementType::NOTELINE_SEGMENT: case ElementType::LET_RING_SEGMENT: @@ -559,6 +560,7 @@ void System::remove(EngravingItem* el) case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: case ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT: case ElementType::GLISSANDO_SEGMENT: case ElementType::NOTELINE_SEGMENT: diff --git a/src/engraving/dom/tie.cpp b/src/engraving/dom/tie.cpp index 5f9f7002b2948..a43809274d19a 100644 --- a/src/engraving/dom/tie.cpp +++ b/src/engraving/dom/tie.cpp @@ -217,12 +217,9 @@ Tie::Tie(const ElementType& type, EngravingItem* parent) void Tie::updatePossibleJumpPoints() { - MasterScore* master = masterScore(); const Note* note = toNote(parentItem()); const Chord* chord = note->chord(); const Measure* measure = chord->measure(); - const MeasureBase* masterMeasureBase = master->measure(measure->index()); - const Measure* masterMeasure = masterMeasureBase && masterMeasureBase->isMeasure() ? toMeasure(masterMeasureBase) : nullptr; if (!tieJumpPoints()) { return; } @@ -245,22 +242,8 @@ void Tie::updatePossibleJumpPoints() jumpPointIdx++; } - // Get following notes by taking repeats - const RepeatList& repeatList = master->repeatList(true, false); - - for (auto it = repeatList.begin(); it != repeatList.end(); it++) { - const RepeatSegment* rs = *it; - const auto nextSegIt = std::next(it); - if (!rs->endsWithMeasure(masterMeasure) || nextSegIt == repeatList.end()) { - continue; - } - - // Get next segment - const RepeatSegment* nextSeg = *nextSegIt; - const Measure* firstMasterMeasure = nextSeg->firstMeasure(); - const MeasureBase* firstMeasureBase = firstMasterMeasure ? score()->measure(firstMasterMeasure->index()) : nullptr; - const Measure* firstMeasure = firstMeasureBase && firstMeasureBase->isMeasure() ? toMeasure(firstMeasureBase) : nullptr; - const Segment* firstCrSeg = firstMeasure ? firstMeasure->first(SegmentType::ChordRest) : nullptr; + for (Measure* jumpMeasure : findFollowingRepeatMeasures(measure)) { + const Segment* firstCrSeg = jumpMeasure ? jumpMeasure->first(SegmentType::ChordRest) : nullptr; if (!firstCrSeg) { continue; } diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 8a0a8b3377088..8352f9d64c99b 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -30,6 +30,8 @@ #include "chord.h" #include "chordrest.h" #include "clef.h" +#include "dom/masterscore.h" +#include "dom/repeatlist.h" #include "keysig.h" #include "measure.h" #include "note.h" @@ -1414,4 +1416,72 @@ InstrumentTrackId makeInstrumentTrackId(const EngravingItem* item) return trackId; } + +std::vector findFollowingRepeatMeasures(const Measure* measure) +{ + const MasterScore* master = measure->masterScore(); + const Score* score = measure->score(); + + const MeasureBase* masterMeasureBase = master->measure(measure->index()); + const Measure* masterMeasure = masterMeasureBase && masterMeasureBase->isMeasure() ? toMeasure(masterMeasureBase) : nullptr; + + const RepeatList& repeatList = master->repeatList(true, false); + + std::vector measures; + + for (auto it = repeatList.begin(); it != repeatList.end(); it++) { + const RepeatSegment* rs = *it; + const auto nextSegIt = std::next(it); + if (!rs->endsWithMeasure(masterMeasure) || nextSegIt == repeatList.end()) { + continue; + } + + // Get next segment + const RepeatSegment* nextSeg = *nextSegIt; + const Measure* firstMasterMeasure = nextSeg->firstMeasure(); + MeasureBase* firstMeasureBase = firstMasterMeasure ? score->measure(firstMasterMeasure->index()) : nullptr; + Measure* firstMeasure = firstMeasureBase && firstMeasureBase->isMeasure() ? toMeasure(firstMeasureBase) : nullptr; + if (!firstMeasure) { + continue; + } + + measures.push_back(firstMeasure); + } + + return measures; +} + +std::vector findPreviousRepeatMeasures(const Measure* measure) +{ + const MasterScore* master = measure->masterScore(); + const Score* score = measure->score(); + + const MeasureBase* masterMeasureBase = master->measure(measure->index()); + const Measure* masterMeasure = masterMeasureBase && masterMeasureBase->isMeasure() ? toMeasure(masterMeasureBase) : nullptr; + + const RepeatList& repeatList = master->repeatList(true, false); + + std::vector measures; + + for (auto it = repeatList.begin(); it != repeatList.end(); it++) { + const RepeatSegment* rs = *it; + const auto prevSegIt = std::prev(it); + if (!rs->startsWithMeasure(masterMeasure) || prevSegIt == repeatList.end()) { + continue; + } + + // Get next segment + const RepeatSegment* prevSeg = *prevSegIt; + const Measure* lastMasterMeasure = prevSeg->lastMeasure(); + MeasureBase* lastMeasureBase = lastMasterMeasure ? score->measure(lastMasterMeasure->index()) : nullptr; + Measure* lastMeasure = lastMeasureBase && lastMeasureBase->isMeasure() ? toMeasure(lastMeasureBase) : nullptr; + if (!lastMeasure) { + continue; + } + + measures.push_back(lastMeasure); + } + + return measures; +} } diff --git a/src/engraving/dom/utils.h b/src/engraving/dom/utils.h index de96bcfa43db6..b16f09398e55b 100644 --- a/src/engraving/dom/utils.h +++ b/src/engraving/dom/utils.h @@ -34,6 +34,7 @@ class EngravingItem; class KeySig; class Note; class Rest; +class Measure; class Score; class Segment; class System; @@ -104,4 +105,7 @@ extern bool isFirstSystemKeySig(const KeySig* ks); extern String bendAmountToString(int fulls, int quarts); extern InstrumentTrackId makeInstrumentTrackId(const EngravingItem* item); + +extern std::vector findFollowingRepeatMeasures(const Measure* measure); +extern std::vector findPreviousRepeatMeasures(const Measure* measure); } // namespace mu::engraving diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index fadf58aac144b..09d880b81c55c 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -21,6 +21,8 @@ */ #include "lyricslayout.h" +#include "dom/masterscore.h" +#include "dom/repeatlist.h" #include "style/styledef.h" #include "dom/chordrest.h" @@ -215,7 +217,18 @@ void LyricsLayout::layout(LyricsLine* item, LayoutContext& ctx) )); item->setTrack2(item->nextLyrics() ? item->nextLyrics()->track() : item->track()); - item->setTick2(item->nextLyrics() ? item->nextLyrics()->segment()->tick() : item->tick()); + Fraction endTick = item->tick(); + const Measure* lyricsMeasure = item->lyrics()->segment()->measure(); + const Segment* endCRSeg = lyricsMeasure ? lyricsMeasure->last(SegmentType::ChordRest) : nullptr; + const ChordRest* endCR = endCRSeg && !endCRSeg->empty() ? toChordRest(endCRSeg->elist().front()) : nullptr; + + if (item->nextLyrics()) { + endTick = item->nextLyrics()->tick(); + } else if (endCR && endCR->hasFollowingJumpItem()) { + endTick = endCR->tick(); + } + + item->setTick2(endTick); } } @@ -223,7 +236,7 @@ void LyricsLayout::layout(LyricsLineSegment* item, LayoutContext& ctx) { UNUSED(ctx); - assert(item->lyricsLine()->lyrics()); + assert(item->isPartialLyricsLineSegment() || item->lyrics()); item->ryoffset() = 0.0; LyricsLineSegment::LayoutData* ldata = item->mutldata(); @@ -244,6 +257,7 @@ void LyricsLayout::layout(LyricsLineSegment* item, LayoutContext& ctx) void LyricsLayout::layoutMelismaLine(LyricsLineSegment* item) { + const bool isPartialLyricsLine = item->isPartialLyricsLineSegment(); LyricsLine* lyricsLine = item->lyricsLine(); Lyrics* startLyrics = lyricsLine->lyrics(); @@ -253,45 +267,20 @@ void LyricsLayout::layoutMelismaLine(LyricsLineSegment* item) } const MStyle& style = item->style(); - const double systemPageX = system->pageX(); - - double startX = 0.0; - if (!item->isSingleBeginType()) { - startX = system->firstNoteRestSegmentX(true); - } else { - double lyricsRightEdge = startLyrics->pageX() - system->pageX() + startLyrics->shape().right(); - startX = lyricsRightEdge + style.styleMM(Sid::lyricsMelismaPad); - } - - ChordRest* endChordRest = toChordRest(lyricsLine->endElement()); - double endX = 0.0; - if (!item->isSingleEndType() || !lyricsLine->endElement()) { - endX = system->endingXForOpenEndedLines(); - } else { - if (endChordRest->isChord()) { - Chord* endChord = toChord(endChordRest); - Note* note = endChord->up() ? endChord->downNote() : endChord->upNote(); - endX = note->pageX() - systemPageX + note->headWidth(); - } else { - endX = endChordRest->pageX() - systemPageX + endChordRest->rightEdge(); - } - } + double startX = lyricsLineStartX(item); + double endX = lyricsLineEndX(item); double tolerance = 0.05 * item->spatium(); if (endX - startX < style.styleMM(Sid::lyricsMelismaMinLength) - tolerance) { - if (style.styleB(Sid::lyricsMelismaForce) || startLyrics->ticks() == Lyrics::TEMP_MELISMA_TICKS) { + const Fraction ticks = isPartialLyricsLine ? lyricsLine->ticks() : startLyrics->ticks(); + if (style.styleB(Sid::lyricsMelismaForce) || ticks == Lyrics::TEMP_MELISMA_TICKS) { endX = startX + style.styleMM(Sid::lyricsMelismaMinLength); } else { endX = startX; } } - if (item->isSingleBeginType()) { - item->ryoffset() = startLyrics->offset().y(); - } else { - Lyrics* nextLyrics = findNextLyrics(endChordRest, startLyrics->no()); - item->ryoffset() = nextLyrics ? nextLyrics->offset().y() : startLyrics->offset().y(); - } + adjustLyricsLineYOffset(item); double y = 0.0; // actual value is set later @@ -303,18 +292,19 @@ void LyricsLayout::layoutMelismaLine(LyricsLineSegment* item) void LyricsLayout::layoutDashes(LyricsLineSegment* item) { + const bool isPartialLyricsLine = item->isPartialLyricsLineSegment(); LyricsLine* lyricsLine = item->lyricsLine(); - Lyrics* startLyrics = lyricsLine->lyrics(); + // Lyrics* startLyrics = lyricsLine->lyrics(); ChordRest* endChordRest = toChordRest(lyricsLine->endElement()); Lyrics* endLyrics = nullptr; for (Lyrics* lyr : endChordRest->lyrics()) { - if (lyr->no() == startLyrics->no()) { + if (lyr->no() == item->no()) { endLyrics = lyr; break; } } - if (!endLyrics) { + if (!endLyrics && !isPartialLyricsLine && !endChordRest->hasFollowingJumpItem()) { return; } @@ -324,31 +314,10 @@ void LyricsLayout::layoutDashes(LyricsLineSegment* item) } const MStyle& style = item->style(); - LyricsDashSystemStart lyricsDashSystemStart = style.styleV(Sid::lyricsDashPosAtStartOfSystem).value(); + double startX = lyricsLineStartX(item); + double endX = lyricsLineEndX(item, endLyrics); - const double systemPageX = system->pageX(); - - double startX = 0.0; - if (!item->isSingleBeginType()) { - startX = system->firstNoteRestSegmentX(lyricsDashSystemStart != LyricsDashSystemStart::UNDER_FIRST_NOTE); - } else { - double lyricsRightEdge = startLyrics->pageX() - system->pageX() + startLyrics->shape().right(); - startX = lyricsRightEdge + style.styleMM(Sid::lyricsDashPad); - } - - double endX = 0.0; - if (!item->isSingleEndType() || lyricsLine->tick2() == system->endTick() || !lyricsLine->endElement()) { - endX = system->endingXForOpenEndedLines(); - } else { - double lyricsLeftEdge = endLyrics->pageX() - systemPageX + endLyrics->ldata()->bbox().left(); - endX = lyricsLeftEdge - style.styleMM(Sid::lyricsDashPad); - } - - if (item->isSingleBeginType()) { - item->ryoffset() = startLyrics->offset().y(); - } else { - item->ryoffset() = endLyrics->offset().y(); - } + adjustLyricsLineYOffset(item, endLyrics); double y = 0.0; // actual value is set later @@ -384,6 +353,7 @@ void LyricsLayout::layoutDashes(LyricsLineSegment* item) double dashWidth = std::min(curLength, style.styleMM(Sid::lyricsDashMaxLength).val()); + LyricsDashSystemStart lyricsDashSystemStart = style.styleV(Sid::lyricsDashPosAtStartOfSystem).value(); bool dashesLeftAligned = lyricsDashSystemStart != LyricsDashSystemStart::STANDARD && !item->isSingleBeginType(); double dashDist = curLength / (dashesLeftAligned || firstAndLastGapAreHalf ? dashCount : dashCount + 1); double xDash = 0.0; @@ -404,7 +374,7 @@ void LyricsLayout::layoutDashes(LyricsLineSegment* item) } } -Lyrics* LyricsLayout::findNextLyrics(ChordRest* endChordRest, int verseNumber) +Lyrics* LyricsLayout::findNextLyrics(const ChordRest* endChordRest, int verseNumber) { for (Segment* segment = endChordRest->segment()->next1(SegmentType::ChordRest); segment; segment = segment->next1(SegmentType::ChordRest)) { @@ -609,9 +579,8 @@ void LyricsLayout::collectLyricsVerses(staff_idx_t staffIdx, System* system, Lyr continue; } LyricsLineSegment* lyricsLineSegment = toLyricsLineSegment(spannerSegment); - Lyrics* lyrics = lyricsLineSegment->lyricsLine()->lyrics(); - int verse = lyrics->no(); - if (lyrics->placeAbove()) { + int verse = lyricsLineSegment->no(); + if (lyricsLineSegment->lyricsPlaceAbove()) { lyricsVersesAbove[verse].addLine(lyricsLineSegment); } else { lyricsVersesBelow[verse].addLine(lyricsLineSegment); @@ -636,8 +605,7 @@ void LyricsLayout::setDefaultPositions(staff_idx_t staffIdx, const LyricsVersesM lyrics->setYRelativeToStaff(y); } for (LyricsLineSegment* lyricsLineSegment : lyricsVerse.lines()) { - Lyrics* lyrics = lyricsLineSegment->lyricsLine()->lyrics(); - double y = -(maxVerseAbove - verse) * lyrics->lineSpacing() * lyricsLineHeightFactor; + double y = -(maxVerseAbove - verse) * lyricsLineSegment->lineSpacing() * lyricsLineHeightFactor; lyricsLineSegment->move(PointF(0.0, y + lyricsLineSegment->baseLineShift())); } } @@ -650,8 +618,7 @@ void LyricsLayout::setDefaultPositions(staff_idx_t staffIdx, const LyricsVersesM lyrics->setYRelativeToStaff(y); } for (LyricsLineSegment* lyricsLineSegment : lyricsVerse.lines()) { - Lyrics* lyrics = lyricsLineSegment->lyricsLine()->lyrics(); - double y = staffHeight + verse * lyrics->lineSpacing() * lyricsLineHeightFactor; + double y = staffHeight + verse * lyricsLineSegment->lineSpacing() * lyricsLineHeightFactor; lyricsLineSegment->move(PointF(0.0, y + lyricsLineSegment->baseLineShift())); } } @@ -711,7 +678,7 @@ SkylineLine LyricsLayout::createSkylineForVerse(int verse, bool north, const Lyr } } for (LyricsLineSegment* lyricsLineSeg : lyricsVerse.lines()) { - if (lyricsLineSeg->lyricsLine()->lyrics()->addToSkyline()) { + if (lyricsLineSeg->lyricsAddToSkyline()) { lyricsSkyline.add(lyricsLineSeg->shape().translate(lyricsLineSeg->pos())); } } @@ -762,7 +729,7 @@ void LyricsLayout::addToSkyline(System* system, staff_idx_t staffIdx, LayoutCont } } for (LyricsLineSegment* lyricsLineSeg : lyricsVerse.lines()) { - if (lyricsLineSeg->lyricsLine()->lyrics()->addToSkyline()) { + if (lyricsLineSeg->lyricsAddToSkyline()) { Shape lineShape = lyricsLineSeg->shape().translate(lyricsLineSeg->pos()); skyline.north().add(lineShape.adjust(0.0, -lyricsVerticalPadding, 0.0, 0.0)); } @@ -778,10 +745,130 @@ void LyricsLayout::addToSkyline(System* system, staff_idx_t staffIdx, LayoutCont } } for (LyricsLineSegment* lyricsLineSeg : lyricsVerse.lines()) { - if (lyricsLineSeg->lyricsLine()->lyrics()->addToSkyline()) { + if (lyricsLineSeg->lyricsAddToSkyline()) { Shape lineShape = lyricsLineSeg->shape().translate(lyricsLineSeg->pos()); skyline.south().add(lineShape.adjust(0.0, 0.0, 0.0, lyricsVerticalPadding)); } } } } + +double LyricsLayout::lyricsLineStartX(const LyricsLineSegment* item) +{ + const System* system = item->system(); + const LyricsLine* lyricsLine = item->lyricsLine(); + const Lyrics* startLyrics = lyricsLine->lyrics(); + const MStyle& style = item->style(); + const bool melisma = lyricsLine->isEndMelisma(); + + const LyricsDashSystemStart lyricsDashSystemStart = style.styleV(Sid::lyricsDashPosAtStartOfSystem).value(); + const bool leading = melisma || (lyricsDashSystemStart != LyricsDashSystemStart::UNDER_FIRST_NOTE); + + // Full melisma or dashes at beginning of system + if (!item->isSingleBeginType()) { + return system->firstNoteRestSegmentX(leading); + } + + // Partial melisma or dashes + if (lyricsLine->isPartialLyricsLine()) { + const Measure* measure = lyricsLine->findEndMeasure(); + return measure->firstNoteRestSegmentX(leading); + } + + // Full melisma or dashes in middle of system + const double pad = melisma ? style.styleMM(Sid::lyricsMelismaPad) : style.styleMM(Sid::lyricsDashPad); + const double lyricsRightEdge = startLyrics->pageX() - system->pageX() + startLyrics->shape().right(); + return lyricsRightEdge + pad; +} + +double LyricsLayout::lyricsLineEndX(const LyricsLineSegment* item, const Lyrics* endLyrics) +{ + const System* system = item->system(); + const LyricsLine* lyricsLine = item->lyricsLine(); + const ChordRest* endChordRest = toChordRest(lyricsLine->endElement()); + const double systemPageX = system->pageX(); + const MStyle& style = item->style(); + const bool melisma = lyricsLine->isEndMelisma(); + const bool hasFollowingJump = endChordRest->hasFollowingJumpItem(); + + if (!melisma && !item->isPartialLyricsLineSegment() && !endLyrics && !hasFollowingJump) { + return 0.0; + } + + const bool dashesEndOfSystem = !melisma && lyricsLine->tick2() == system->endTick(); + + // Full melisma or dashes at end of system + if (!item->isSingleEndType() || dashesEndOfSystem || !lyricsLine->endElement()) { + return system->endingXForOpenEndedLines(); + } + + // Full dashes or melisma before a repeat - possibly with a partial dash/repeat on the other side of the repeat + if (endChordRest && hasFollowingJump) { + Measure* endMeasure = lyricsLine->findEndMeasure(); + // check if there is a partial lyrics line in a following measure + if (repeatHasPartialLyricLine(endMeasure) || endChordRest == lyricsLine->startElement()) { + return endMeasure->endingXForOpenEndedLines(); + } + } + + // Full or partial dashes in middle of system + if (!melisma && endLyrics) { + const double lyricsLeftEdge = endLyrics->pageX() - systemPageX + endLyrics->ldata()->bbox().left(); + return lyricsLeftEdge - style.styleMM(Sid::lyricsDashPad); + } + + // Full or partial melisma in middle of system + if (endChordRest->isChord()) { + const Chord* endChord = toChord(endChordRest); + const Note* note = endChord->up() ? endChord->downNote() : endChord->upNote(); + return note->pageX() - systemPageX + note->headWidth(); + } + + return endChordRest->pageX() - systemPageX + endChordRest->rightEdge(); +} + +void LyricsLayout::adjustLyricsLineYOffset(LyricsLineSegment* item, const Lyrics* endLyrics) +{ + const LyricsLine* lyricsLine = item->lyricsLine(); + const ChordRest* endChordRest = toChordRest(lyricsLine->endElement()); + const Lyrics* startLyrics = lyricsLine->lyrics(); + const bool melisma = lyricsLine->isEndMelisma(); + + // Partial melisma or dashes + if (lyricsLine->isPartialLyricsLine()) { + Lyrics* nextLyrics = findNextLyrics(endChordRest, item->no()); + item->ryoffset() = nextLyrics ? nextLyrics->offset().y() : 0.0; + return; + } + + if (item->isSingleBeginType()) { + item->ryoffset() = startLyrics->offset().y(); + return; + } + + if (melisma || !endLyrics) { + Lyrics* nextLyrics = findNextLyrics(endChordRest, item->no()); + item->ryoffset() = nextLyrics ? nextLyrics->offset().y() : startLyrics->offset().y(); + return; + } + + item->ryoffset() = endLyrics->offset().y(); +} + +bool LyricsLayout::repeatHasPartialLyricLine(const Measure* endRepeatMeasure) +{ + const std::vector measures = findFollowingRepeatMeasures(endRepeatMeasure); + const Score* score = endRepeatMeasure->score(); + + for (const Measure* measure : measures) { + const SpannerMap::IntervalList& spanners = score->spannerMap().findOverlapping(measure->tick().ticks(), measure->endTick().ticks()); + + for (auto& spanner : spanners) { + if (spanner.value->isPartialLyricsLine() && spanner.start == measure->tick().ticks()) { + return true; + } + } + } + + return false; +} diff --git a/src/engraving/rendering/score/lyricslayout.h b/src/engraving/rendering/score/lyricslayout.h index 8e8d4a049875e..f82fa89ba34bd 100644 --- a/src/engraving/rendering/score/lyricslayout.h +++ b/src/engraving/rendering/score/lyricslayout.h @@ -65,7 +65,7 @@ class LyricsLayout static void layoutMelismaLine(LyricsLineSegment* item); static void layoutDashes(LyricsLineSegment* item); - static Lyrics* findNextLyrics(ChordRest* endChordRest, int verseNumber); + static Lyrics* findNextLyrics(const ChordRest* endChordRest, int verseNumber); static void computeVerticalPositions(staff_idx_t staffIdx, System* system, LayoutContext& ctx); static void collectLyricsVerses(staff_idx_t staffIdx, System* system, LyricsVersesMap& lyricsVersesAbove, @@ -81,6 +81,12 @@ class LyricsLayout static void addToSkyline(System* system, staff_idx_t staffIdx, LayoutContext& ctx, const LyricsVersesMap& lyricsVersesAbove, const LyricsVersesMap& lyricsVersesBelow); + + static double lyricsLineStartX(const LyricsLineSegment* item); + static double lyricsLineEndX(const LyricsLineSegment* item, const Lyrics* endLyrics = nullptr); + static void adjustLyricsLineYOffset(LyricsLineSegment* item, const Lyrics* endLyrics = nullptr); + + static bool repeatHasPartialLyricLine(const Measure* endRepeatMeasure); }; } #endif // MU_ENGRAVING_LYRICSLAYOUT_DEV_H diff --git a/src/engraving/rendering/score/tdraw.cpp b/src/engraving/rendering/score/tdraw.cpp index 8bab16b684cfb..16f0374612dbc 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -277,6 +277,8 @@ void TDraw::drawItem(const EngravingItem* item, Painter* painter) break; case ElementType::LYRICSLINE_SEGMENT: draw(item_cast(item), painter); break; + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: draw(item_cast(item), painter); + break; case ElementType::MARKER: draw(item_cast(item), painter); break; @@ -2121,7 +2123,7 @@ void TDraw::draw(const LyricsLineSegment* item, Painter* painter) { TRACE_DRAW_ITEM; - Pen pen(item->lyricsLine()->lyrics()->curColor()); + Pen pen(item->curColor()); pen.setWidthF(item->absoluteFromSpatium(item->lineWidth())); pen.setCapStyle(PenCapStyle::FlatCap); painter->setPen(pen); diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index c0d3c3e33de56..bffb342ec05ff 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -318,6 +318,8 @@ void TLayout::layoutItem(EngravingItem* item, LayoutContext& ctx) break; case ElementType::LYRICSLINE_SEGMENT: layoutLyricsLineSegment(item_cast(item), ctx); break; + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: layoutLyricsLineSegment(item_cast(item), ctx); + break; case ElementType::MARKER: layoutMarker(item_cast(item), static_cast(ldata)); break; @@ -6820,7 +6822,7 @@ SpannerSegment* TLayout::layoutSystemSLine(SLine* line, System* system, LayoutCo SpannerSegment* TLayout::layoutSystem(LyricsLine* line, System* system, LayoutContext& ctx) { LAYOUT_CALL_ITEM(line); - if (!line->lyrics()) { + if (!line->isPartialLyricsLine() && !line->lyrics()) { return nullptr; } @@ -6848,7 +6850,7 @@ SpannerSegment* TLayout::layoutSystem(LyricsLine* line, System* system, LayoutCo lineSegm->setSpannerSegmentType(sst); TLayout::layoutLyricsLineSegment(lineSegm, ctx); - if (!line->lyrics()) { + if (!line->isPartialLyricsLine() && !line->lyrics()) { // this line could have been removed in the process of laying out surrounding lyrics return nullptr; } diff --git a/src/engraving/rw/read410/connectorinforeader.cpp b/src/engraving/rw/read410/connectorinforeader.cpp index f23dcce241bb7..493823f22c28a 100644 --- a/src/engraving/rw/read410/connectorinforeader.cpp +++ b/src/engraving/rw/read410/connectorinforeader.cpp @@ -327,6 +327,7 @@ void ConnectorInfoReader::readAddConnector(Measure* item, ConnectorInfoReader* i case ElementType::GRADUAL_TEMPO_CHANGE: case ElementType::VIBRATO: case ElementType::PALM_MUTE: + case ElementType::PARTIAL_LYRICSLINE: case ElementType::WHAMMY_BAR: case ElementType::RASGUEADO: case ElementType::HARMONIC_MARK: @@ -431,6 +432,7 @@ void ConnectorInfoReader::readAddConnector(Score* item, ConnectorInfoReader* inf case ElementType::TEXTLINE: case ElementType::VOLTA: case ElementType::PALM_MUTE: + case ElementType::PARTIAL_LYRICSLINE: case ElementType::WHAMMY_BAR: case ElementType::RASGUEADO: case ElementType::HARMONIC_MARK: diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 7f9c2ea6a06b1..2f25bdc38a32a 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -263,6 +263,8 @@ void TRead::readItem(EngravingItem* item, XmlReader& xml, ReadContext& ctx) break; case ElementType::PALM_MUTE: read(item_cast(item), xml, ctx); break; + case ElementType::PARTIAL_LYRICSLINE: read(item_cast(item), xml, ctx); + break; case ElementType::PARTIAL_TIE: read(item_cast(item), xml, ctx); break; case ElementType::PEDAL: read(item_cast(item), xml, ctx); @@ -3767,6 +3769,19 @@ void TRead::read(Part* p, XmlReader& e, ReadContext& ctx) } } +void TRead::read(PartialLyricsLine* p, XmlReader& xml, ReadContext& ctx) +{ + while (xml.readNextStartElement()) { + const AsciiStringView tag(xml.name()); + if (tag == "isEndMelisma") { + p->setIsEndMelisma(xml.readBool()); + } else if (TRead::readProperty(p, tag, xml, ctx, Pid::VERSE)) { + } else if (!readItemProperties(p, xml, ctx)) { + xml.unknown(); + } + } +} + void TRead::read(PartialTie* p, XmlReader& xml, ReadContext& ctx) { while (xml.readNextStartElement()) { diff --git a/src/engraving/rw/read410/tread.h b/src/engraving/rw/read410/tread.h index f25f58fad53da..28fc1e14165a6 100644 --- a/src/engraving/rw/read410/tread.h +++ b/src/engraving/rw/read410/tread.h @@ -270,6 +270,7 @@ class TRead static void read(Page* p, XmlReader& xml, ReadContext& ctx); static void read(PalmMute* p, XmlReader& xml, ReadContext& ctx); static void read(Part* p, XmlReader& xml, ReadContext& ctx); + static void read(PartialLyricsLine* p, XmlReader& xml, ReadContext& ctx); static void read(PartialTie* p, XmlReader& xml, ReadContext& ctx); static void read(Pedal* p, XmlReader& xml, ReadContext& ctx); static void read(PlayTechAnnotation* a, XmlReader& xml, ReadContext& ctx); diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index 3e83bf4f4501f..3a2318321365a 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -283,6 +283,8 @@ void TWrite::writeItem(const EngravingItem* item, XmlWriter& xml, WriteContext& break; case ElementType::PALM_MUTE: write(item_cast(item), xml, ctx); break; + case ElementType::PARTIAL_LYRICSLINE: write(item_cast(item), xml, ctx); + break; case ElementType::PARTIAL_TIE: write(item_cast(item), xml, ctx); break; case ElementType::PEDAL: write(item_cast(item), xml, ctx); @@ -2440,6 +2442,18 @@ void TWrite::write(const PartialTie* item, XmlWriter& xml, WriteContext& ctx) xml.endElement(); } +void TWrite::write(const PartialLyricsLine* item, XmlWriter& xml, WriteContext& ctx) +{ + if (!ctx.canWrite(item)) { + return; + } + xml.startElement(item); + writeProperty(item, xml, Pid::VERSE); + xml.tag("isEndMelisma", item->isEndMelisma()); + writeItemProperties(item, xml, ctx); + xml.endElement(); +} + void TWrite::write(const Pedal* item, XmlWriter& xml, WriteContext& ctx) { if (!ctx.canWrite(item)) { diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index 7189e1720f5c7..ef091d423786a 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -258,6 +258,7 @@ class TWrite static void write(const PalmMute* item, XmlWriter& xml, WriteContext& ctx); static void write(const Part* item, XmlWriter& xml, WriteContext& ctx); static void write(const PartialTie* item, XmlWriter& xml, WriteContext& ctx); + static void write(const PartialLyricsLine* item, XmlWriter& xml, WriteContext& ctx); static void write(const Pedal* item, XmlWriter& xml, WriteContext& ctx); static void write(const PickScrape* item, XmlWriter& xml, WriteContext& ctx); static void write(const PlayTechAnnotation* item, XmlWriter& xml, WriteContext& ctx); diff --git a/src/engraving/types/types.h b/src/engraving/types/types.h index ed19a90126680..3398d12bfde4e 100644 --- a/src/engraving/types/types.h +++ b/src/engraving/types/types.h @@ -149,6 +149,7 @@ enum class ElementType { VOLTA_SEGMENT, PEDAL_SEGMENT, LYRICSLINE_SEGMENT, + PARTIAL_LYRICSLINE_SEGMENT, GLISSANDO_SEGMENT, NOTELINE_SEGMENT, LAYOUT_BREAK, @@ -181,6 +182,7 @@ enum class ElementType { TEXTLINE_BASE, NOTELINE, LYRICSLINE, + PARTIAL_LYRICSLINE, GLISSANDO, BRACKET, SEGMENT, diff --git a/src/engraving/types/typesconv.cpp b/src/engraving/types/typesconv.cpp index bf51342ce4d09..b6a2e3a7b48ac 100644 --- a/src/engraving/types/typesconv.cpp +++ b/src/engraving/types/typesconv.cpp @@ -248,6 +248,8 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::VOLTA_SEGMENT, "VoltaSegment", muse::TranslatableString("engraving", "Volta segment") }, { ElementType::PEDAL_SEGMENT, "PedalSegment", muse::TranslatableString("engraving", "Pedal segment") }, { ElementType::LYRICSLINE_SEGMENT, "LyricsLineSegment", muse::TranslatableString("engraving", "Extension line segment") }, + { ElementType::PARTIAL_LYRICSLINE_SEGMENT, "PartialLyricsLineSegment", muse::TranslatableString("engraving", + "Partial extension line segment") }, { ElementType::GLISSANDO_SEGMENT, "GlissandoSegment", muse::TranslatableString("engraving", "Glissando segment") }, { ElementType::NOTELINE_SEGMENT, "NoteLineSegment", muse::TranslatableString("engraving", "Note-anchored line segment") }, { ElementType::LAYOUT_BREAK, "LayoutBreak", muse::TranslatableString("engraving", "Layout break") }, @@ -280,6 +282,7 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::TEXTLINE_BASE, "TextLineBase", muse::TranslatableString("engraving", "Text line base") }, // remove { ElementType::NOTELINE, "NoteLine", muse::TranslatableString("engraving", "Note-anchored line") }, { ElementType::LYRICSLINE, "LyricsLine", muse::TranslatableString("engraving", "Extension line") }, + { ElementType::PARTIAL_LYRICSLINE, "PartialLyricsLine", muse::TranslatableString("engraving", "Partial extension line") }, { ElementType::GLISSANDO, "Glissando", muse::TranslatableString("engraving", "Glissando") }, { ElementType::BRACKET, "Bracket", muse::TranslatableString("engraving", "Bracket") }, { ElementType::SEGMENT, "Segment", muse::TranslatableString("engraving", "Segment") }, diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 4df04e872f7c4..1b8ee74a4a405 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -5403,19 +5403,22 @@ void NotationInteraction::navigateToNextSyllable() LOGW("nextSyllable called with invalid current element"); return; } - mu::engraving::Lyrics* lyrics = toLyrics(m_editData.element); + Lyrics* lyrics = toLyrics(m_editData.element); + ChordRest* initialCR = lyrics->chordRest(); + const bool hasPrecedingRepeat = initialCR->hasPrecedingJumpItem(); + const bool hasFollowingRepeat = initialCR->hasFollowingJumpItem(); track_idx_t track = lyrics->track(); track_idx_t toLyricTrack = track; - mu::engraving::Segment* segment = lyrics->segment(); + Segment* segment = lyrics->segment(); int verse = lyrics->no(); - mu::engraving::PlacementV placement = lyrics->placement(); - mu::engraving::PropertyFlags pFlags = lyrics->propertyFlags(mu::engraving::Pid::PLACEMENT); - mu::engraving::FontStyle fStyle = lyrics->fontStyle(); - mu::engraving::PropertyFlags fFlags = lyrics->propertyFlags(mu::engraving::Pid::FONT_STYLE); + PlacementV placement = lyrics->placement(); + PropertyFlags pFlags = lyrics->propertyFlags(Pid::PLACEMENT); + FontStyle fStyle = lyrics->fontStyle(); + PropertyFlags fFlags = lyrics->propertyFlags(Pid::FONT_STYLE); // search next chord - mu::engraving::Segment* nextSegment = segment; - while ((nextSegment = nextSegment->next1(mu::engraving::SegmentType::ChordRest))) { + Segment* nextSegment = segment; + while ((nextSegment = nextSegment->next1(SegmentType::ChordRest))) { EngravingItem* el = nextSegment->element(track); if (!el || !el->isChord()) { const track_idx_t strack = track2staff(track) * VOICES; @@ -5433,7 +5436,7 @@ void NotationInteraction::navigateToNextSyllable() break; } } - if (nextSegment == 0) { + if (!nextSegment && !hasFollowingRepeat) { return; } @@ -5441,23 +5444,45 @@ void NotationInteraction::navigateToNextSyllable() // look for the lyrics we are moving from; may be the current lyrics or a previous one // we are extending with several dashes - mu::engraving::Lyrics* fromLyrics = 0; + Lyrics* fromLyrics = 0; while (segment) { ChordRest* cr = toChordRest(segment->element(track)); if (!cr) { - segment = segment->prev1(mu::engraving::SegmentType::ChordRest); + segment = segment->prev1(SegmentType::ChordRest); continue; } fromLyrics = cr->lyrics(verse, placement); if (fromLyrics) { break; } - segment = segment->prev1(mu::engraving::SegmentType::ChordRest); + segment = segment->prev1(SegmentType::ChordRest); + } + + if (!nextSegment && hasFollowingRepeat && fromLyrics) { + // Allow dash with no end syllable if there is a repeat + score()->startCmd(TranslatableString("undoableAction", "Navigate to next syllable")); + switch (fromLyrics->syllabic()) { + case LyricsSyllabic::BEGIN: + case LyricsSyllabic::MIDDLE: + break; + case LyricsSyllabic::SINGLE: + fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::BEGIN)); + break; + case LyricsSyllabic::END: + fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::MIDDLE)); + break; + } + fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, Fraction(0, 1)); + + score()->endCmd(); + score()->setLayoutAll(); + + return; } score()->startCmd(TranslatableString("undoableAction", "Navigate to next syllable")); ChordRest* cr = toChordRest(nextSegment->element(toLyricTrack)); - mu::engraving::Lyrics* toLyrics = cr->lyrics(verse, placement); + Lyrics* toLyrics = cr->lyrics(verse, placement); // If no lyrics in current track, check others if (!toLyrics) { @@ -5487,20 +5512,20 @@ void NotationInteraction::navigateToNextSyllable() toLyrics->setParent(cr); toLyrics->setNo(verse); - const mu::engraving::TextStyleType styleType(toLyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD); + const TextStyleType styleType(toLyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD); toLyrics->setTextStyleType(styleType); toLyrics->setPlacement(placement); - toLyrics->setPropertyFlags(mu::engraving::Pid::PLACEMENT, pFlags); - toLyrics->setSyllabic(mu::engraving::LyricsSyllabic::END); + toLyrics->setPropertyFlags(Pid::PLACEMENT, pFlags); + toLyrics->setSyllabic(LyricsSyllabic::END); toLyrics->setFontStyle(fStyle); - toLyrics->setPropertyFlags(mu::engraving::Pid::FONT_STYLE, fFlags); + toLyrics->setPropertyFlags(Pid::FONT_STYLE, fFlags); } else { // as we arrived at toLyrics by a dash, it cannot be initial or isolated - if (toLyrics->syllabic() == mu::engraving::LyricsSyllabic::BEGIN) { - toLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::MIDDLE)); - } else if (toLyrics->syllabic() == mu::engraving::LyricsSyllabic::SINGLE) { - toLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::END)); + if (toLyrics->syllabic() == LyricsSyllabic::BEGIN) { + toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::MIDDLE)); + } else if (toLyrics->syllabic() == LyricsSyllabic::SINGLE) { + toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END)); } } @@ -5508,18 +5533,30 @@ void NotationInteraction::navigateToNextSyllable() // as we moved away from fromLyrics by a dash, // it can have syll. dashes before and after but cannot be isolated or terminal switch (fromLyrics->syllabic()) { - case mu::engraving::LyricsSyllabic::BEGIN: - case mu::engraving::LyricsSyllabic::MIDDLE: + case LyricsSyllabic::BEGIN: + case LyricsSyllabic::MIDDLE: break; - case mu::engraving::LyricsSyllabic::SINGLE: - fromLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::BEGIN)); + case LyricsSyllabic::SINGLE: + fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::BEGIN)); break; - case mu::engraving::LyricsSyllabic::END: - fromLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::MIDDLE)); + case LyricsSyllabic::END: + fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::MIDDLE)); break; } // for the same reason, it cannot have a melisma - fromLyrics->undoChangeProperty(mu::engraving::Pid::LYRIC_TICKS, Fraction(0, 1)); + fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, Fraction(0, 1)); + } else if (hasPrecedingRepeat) { + // No from lyrics - create incoming partial dash + PartialLyricsLine* dash = Factory::createPartialLyricsLine(score()->dummy()); + dash->setIsEndMelisma(false); + dash->setNo(verse); + dash->setPlacement(lyrics->placement()); + dash->setTick(initialCR->tick()); + dash->setTicks(initialCR->ticks()); + dash->setTrack(initialCR->track()); + dash->setTrack2(initialCR->track()); + + score()->undoAddElement(dash); } if (newLyrics) { @@ -6113,21 +6150,23 @@ void NotationInteraction::addMelisma() LOGW("addMelisma called with invalid current element"); return; } - mu::engraving::Lyrics* lyrics = toLyrics(m_editData.element); + Lyrics* lyrics = toLyrics(m_editData.element); + ChordRest* initialCR = lyrics->chordRest(); + const bool hasPrecedingRepeat = initialCR->hasPrecedingJumpItem(); track_idx_t track = lyrics->track(); - mu::engraving::Segment* segment = lyrics->segment(); + Segment* segment = lyrics->segment(); int verse = lyrics->no(); - mu::engraving::PlacementV placement = lyrics->placement(); - mu::engraving::PropertyFlags pFlags = lyrics->propertyFlags(mu::engraving::Pid::PLACEMENT); - mu::engraving::FontStyle fStyle = lyrics->fontStyle(); - mu::engraving::PropertyFlags fFlags = lyrics->propertyFlags(mu::engraving::Pid::FONT_STYLE); + PlacementV placement = lyrics->placement(); + PropertyFlags pFlags = lyrics->propertyFlags(Pid::PLACEMENT); + FontStyle fStyle = lyrics->fontStyle(); + PropertyFlags fFlags = lyrics->propertyFlags(Pid::FONT_STYLE); Fraction endTick = segment->tick(); // a previous melisma cannot extend beyond this point endEditText(); // search next chord - mu::engraving::Segment* nextSegment = segment; - while ((nextSegment = nextSegment->next1(mu::engraving::SegmentType::ChordRest))) { + Segment* nextSegment = segment; + while ((nextSegment = nextSegment->next1(SegmentType::ChordRest))) { EngravingItem* el = nextSegment->element(track); if (el && el->isChord()) { break; @@ -6136,7 +6175,7 @@ void NotationInteraction::addMelisma() // look for the lyrics we are moving from; may be the current lyrics or a previous one // we are extending with several underscores - mu::engraving::Lyrics* fromLyrics = 0; + Lyrics* fromLyrics = 0; while (segment) { ChordRest* cr = toChordRest(segment->element(track)); if (cr) { @@ -6145,7 +6184,7 @@ void NotationInteraction::addMelisma() break; } } - segment = segment->prev1(mu::engraving::SegmentType::ChordRest); + segment = segment->prev1(SegmentType::ChordRest); // if the segment has a rest in this track, stop going back EngravingItem* e = segment ? segment->element(track) : 0; if (e && !e->isChord()) { @@ -6158,7 +6197,7 @@ void NotationInteraction::addMelisma() // there will be no melisma anyway), set a temporary melisma duration if (fromLyrics == lyrics && nextSegment) { score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line")); - lyrics->undoChangeProperty(mu::engraving::Pid::LYRIC_TICKS, mu::engraving::Lyrics::TEMP_MELISMA_TICKS); + lyrics->undoChangeProperty(Pid::LYRIC_TICKS, Lyrics::TEMP_MELISMA_TICKS); score()->setLayoutAll(); score()->endCmd(); } @@ -6167,15 +6206,15 @@ void NotationInteraction::addMelisma() score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line")); if (fromLyrics) { switch (fromLyrics->syllabic()) { - case mu::engraving::LyricsSyllabic::SINGLE: - case mu::engraving::LyricsSyllabic::END: + case LyricsSyllabic::SINGLE: + case LyricsSyllabic::END: break; default: - fromLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::END)); + fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END)); break; } if (fromLyrics->segment()->tick() < endTick) { - fromLyrics->undoChangeProperty(mu::engraving::Pid::LYRIC_TICKS, endTick - fromLyrics->segment()->tick()); + fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, endTick - fromLyrics->segment()->tick()); } } @@ -6191,7 +6230,7 @@ void NotationInteraction::addMelisma() score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line")); ChordRest* cr = toChordRest(nextSegment->element(track)); - mu::engraving::Lyrics* toLyrics = cr->lyrics(verse, placement); + Lyrics* toLyrics = cr->lyrics(verse, placement); bool newLyrics = (toLyrics == 0); if (!toLyrics) { toLyrics = Factory::createLyrics(cr); @@ -6199,36 +6238,48 @@ void NotationInteraction::addMelisma() toLyrics->setParent(cr); toLyrics->setNo(verse); - const mu::engraving::TextStyleType styleType(toLyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD); + const TextStyleType styleType(toLyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD); toLyrics->setTextStyleType(styleType); toLyrics->setPlacement(placement); - toLyrics->setPropertyFlags(mu::engraving::Pid::PLACEMENT, pFlags); - toLyrics->setSyllabic(mu::engraving::LyricsSyllabic::SINGLE); + toLyrics->setPropertyFlags(Pid::PLACEMENT, pFlags); + toLyrics->setSyllabic(LyricsSyllabic::SINGLE); toLyrics->setFontStyle(fStyle); - toLyrics->setPropertyFlags(mu::engraving::Pid::FONT_STYLE, fFlags); + toLyrics->setPropertyFlags(Pid::FONT_STYLE, fFlags); } // as we arrived at toLyrics by an underscore, it cannot have syllabic dashes before - else if (toLyrics->syllabic() == mu::engraving::LyricsSyllabic::MIDDLE) { - toLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::BEGIN)); - } else if (toLyrics->syllabic() == mu::engraving::LyricsSyllabic::END) { - toLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::SINGLE)); + else if (toLyrics->syllabic() == LyricsSyllabic::MIDDLE) { + toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::BEGIN)); + } else if (toLyrics->syllabic() == LyricsSyllabic::END) { + toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::SINGLE)); } if (fromLyrics) { // as we moved away from fromLyrics by an underscore, // it can be isolated or terminal but cannot have dashes after switch (fromLyrics->syllabic()) { - case mu::engraving::LyricsSyllabic::SINGLE: - case mu::engraving::LyricsSyllabic::END: + case LyricsSyllabic::SINGLE: + case LyricsSyllabic::END: break; default: - fromLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::END)); + fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END)); break; } // for the same reason, if it has a melisma, this cannot extend beyond toLyrics if (fromLyrics->segment()->tick() < endTick) { - fromLyrics->undoChangeProperty(mu::engraving::Pid::LYRIC_TICKS, endTick - fromLyrics->segment()->tick()); - } + fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, endTick - fromLyrics->segment()->tick()); + } + } else if (hasPrecedingRepeat) { + // No from lyrics - create incoming partial melisma + PartialLyricsLine* melisma = Factory::createPartialLyricsLine(score()->dummy()); + melisma->setIsEndMelisma(true); + melisma->setNo(verse); + melisma->setPlacement(lyrics->placement()); + melisma->setTick(initialCR->tick()); + melisma->setTicks(initialCR->ticks()); + melisma->setTrack(initialCR->track()); + melisma->setTrack2(initialCR->track()); + + score()->undoAddElement(melisma); } if (newLyrics) { score()->undoAddElement(toLyrics); diff --git a/src/notation/view/widgets/editstyle.cpp b/src/notation/view/widgets/editstyle.cpp index a28a328f21cb8..e305febfdf4d0 100644 --- a/src/notation/view/widgets/editstyle.cpp +++ b/src/notation/view/widgets/editstyle.cpp @@ -1639,7 +1639,9 @@ QString EditStyle::pageCodeForElement(const EngravingItem* element) case ElementType::LYRICS: case ElementType::LYRICSLINE: + case ElementType::PARTIAL_LYRICSLINE: case ElementType::LYRICSLINE_SEGMENT: + case ElementType::PARTIAL_LYRICSLINE_SEGMENT: return "lyrics"; case ElementType::EXPRESSION: