diff --git a/src/engraving/layout/v0/chordlayout.cpp b/src/engraving/layout/v0/chordlayout.cpp index 0cf367324ca9e..3da1b61f448a2 100644 --- a/src/engraving/layout/v0/chordlayout.cpp +++ b/src/engraving/layout/v0/chordlayout.cpp @@ -2276,6 +2276,11 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector& double xx = x + note->headBodyWidth() + chord->pos().x(); + //--------------------------------------------------- + // layout dots simply + // we will check for conflicts after all the notes have been processed + //--------------------------------------------------- + DirectionV dotPosition = note->userDotPosition(); if (chord->dots()) { if (chord->up()) { @@ -2288,12 +2293,12 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector& // resolve dot conflicts int line = note->line(); Note* above = (i < nNotes - 1) ? notes[i + 1] : 0; - if (above && (!above->visible() || above->dotsHidden())) { + if (above && (!above->visible() || above->dotsHidden() || !above->chord()->dots())) { above = 0; } int intervalAbove = above ? line - above->line() : 1000; Note* below = (i > 0) ? notes[i - 1] : 0; - if (below && (!below->visible() || below->dotsHidden())) { + if (below && (!below->visible() || below->dotsHidden() || !below->chord()->dots())) { below = 0; } int intervalBelow = below ? below->line() - line : 1000; @@ -2303,22 +2308,19 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector& dotPosition = DirectionV::DOWN; } else if (intervalBelow == 1 && intervalAbove != 1) { dotPosition = DirectionV::UP; - } else if (intervalAbove == 0 && above->chord()->dots()) { + } else if (intervalAbove == 0 || intervalBelow == 0) { // unison - if (((above->voice() & 1) == (note->voice() & 1))) { - above->setDotY(DirectionV::UP); - dotPosition = DirectionV::DOWN; - } + dotPosition = DirectionV::AUTO; // unison conflicts taken care of later } } else { // space if (intervalAbove == 0 && above->chord()->dots()) { // unison if (!(note->voice() & 1)) { - dotPosition = DirectionV::UP; + dotPosition = DirectionV::UP; // space, doesn't matter } else { if (!(above->voice() & 1)) { - above->setDotY(DirectionV::UP); + above->setDotPosition(DirectionV::UP); } else { dotPosition = DirectionV::DOWN; } @@ -2327,8 +2329,94 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector& } } } - note->setDotY(dotPosition); // also removes invalid dots + if (dotPosition == DirectionV::AUTO) { + dotPosition = note->voice() & 1 ? DirectionV::DOWN : DirectionV::UP; + } + note->setDotPosition(dotPosition); + } + // Now, we can resolve note conflicts as a superchord + Chord* chord = nullptr; + for (Chord* c : chords) { + if (c->dots()) { + chord = c; + break; + } } + if (chord && !chord->staff()->isTabStaff(chord->tick())) { + std::vector topDownNotes; + std::vector bottomUpNotes; + std::vector anchoredDots; + // construct combined chords using the notes from overlapping chords + getNoteListForDots(chord, topDownNotes, bottomUpNotes, anchoredDots); + + for (Note* note : notes) { + bool onLine = !(note->line() & 1); + if (onLine) { + std::unordered_map alreadyAdded; + bool finished = false; + for (Note* otherNote : bottomUpNotes) { + int dotMove = otherNote->dotPosition() == DirectionV::UP ? -1 : 1; + int otherDotLoc = otherNote->line() + dotMove; + bool added = alreadyAdded.count(otherDotLoc); + if (!added && mu::contains(anchoredDots, otherDotLoc)) { + dotMove = -dotMove; // if the desired space is taken, adjust opposite + } else if (added && alreadyAdded[otherDotLoc] != otherNote) { + dotMove = -dotMove; + } + // set y for this note + if (note == otherNote) { + note->setDotY(dotMove); + finished = true; + anchoredDots.push_back(note->line() + dotMove); + alreadyAdded[otherNote->line() + dotMove] = otherNote; + break; + } + } + if (!finished) { + alreadyAdded.clear(); + for (Note* otherNote : topDownNotes) { + int dotMove = otherNote->dotPosition() == DirectionV::DOWN ? 1 : -1; + int otherDotLoc = otherNote->line() + dotMove; + bool added = alreadyAdded.count(otherDotLoc); + if (!added && mu::contains(anchoredDots, otherDotLoc)) { + dotMove = -dotMove; + } else if (added && alreadyAdded[otherDotLoc] != otherNote) { + dotMove = -dotMove; + } + // set y for this note + if (note == otherNote) { + note->setDotY(dotMove); + finished = true; + anchoredDots.push_back(note->line() + dotMove); + break; + } + if (!added) { + alreadyAdded[otherNote->line() + dotMove] = otherNote; + } + } + } + IF_ASSERT_FAILED(finished) { + // this should never happen + // the note is on a line and topDownNotes and bottomUpNotes are all of the lined notes + note->setDotY(0); + } + } else { + // on a space; usually this means the dot is on this same line, but there is an exception + // for a unison within the same chord. + for (Note* otherNote : note->chord()->notes()) { + if (note == otherNote) { + note->setDotY(0); // same space as notehead + break; + } + if (note->line() == otherNote->line()) { + bool adjustDown = (note->chord()->voice() & 1) && !note->chord()->up(); + note->setDotY(adjustDown ? 2 : -2); + break; + } + } + } + } + } // done with dots! // if there are no non-mirrored notes in a downstem chord, // then use the stem X position as X origin for accidental layout @@ -2564,6 +2652,70 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector& } } +//--------------------------------------------------------- +// getNoteListForDots +// This method populates three lists: one for chord notes that need to be checked from the top down, +// one for chords from the bottom up, and one for spaces (where the dot will be in that space) +//--------------------------------------------------------- + +void ChordLayout::getNoteListForDots(Chord* c, std::vector& topDownNotes, std::vector& bottomUpNotes, + std::vector& anchoredDots) +{ + bool hasVoices = c->measure()->hasVoices(c->staffIdx(), c->tick(), c->ticks()); + if (!hasVoices) { + // only this voice, so topDownNotes is just the notes in the chord + for (Note* note : c->notes()) { + if (note->line() & 1) { + int newOffset = 0; + bool adjustDown = (c->voice() & 1) && !c->up(); + if (!anchoredDots.empty() && anchoredDots.back() == note->line()) { + if (anchoredDots.size() >= 2 && anchoredDots[anchoredDots.size() - 2] == note->line() + (adjustDown ? 2 : -2)) { + newOffset = adjustDown ? -2 : 2; + } else { + newOffset = adjustDown ? 2 : -2; + } + } + anchoredDots.push_back(note->line() + newOffset); + } else { + topDownNotes.push_back(note); + } + } + } else { + // Get a list of notes in this staff that adjust dots from top down, + // bottom up, and also start our locked-in dot list by adding all lines where dots are + // guaranteed + Measure* m = c->measure(); + size_t firstVoice = c->track() - c->voice(); + for (size_t i = firstVoice; i < firstVoice + VOICES; ++i) { + if (Chord* voiceChord = m->findChord(c->tick(), i)) { + bool startFromTop = !((voiceChord->voice() & 1) && !voiceChord->up()); + if (startFromTop) { + for (Note* note : voiceChord->notes()) { + if (note->line() & 1) { + anchoredDots.push_back(note->line()); + } else { + topDownNotes.push_back(note); + } + } + } else { + for (Note* note : voiceChord->notes()) { + if (note->line() & 1) { + anchoredDots.push_back(note->line()); + } else { + bottomUpNotes.push_back(note); + } + } + } + } + } + } + // our two lists now contain only notes that are on lines + std::sort(topDownNotes.begin(), topDownNotes.end(), + [](Note* n1, Note* n2) { return n1->line() < n2->line(); }); + std::sort(bottomUpNotes.begin(), bottomUpNotes.end(), + [](Note* n1, Note* n2) { return n1->line() > n2->line(); }); +} + /* updateGraceNotes() * Processes a full measure, making sure that all grace notes are * attacched to the correct segment. Has to be performed after @@ -3070,7 +3222,7 @@ void ChordLayout::layoutNote2(Note* item, LayoutContext& ctx) // with TAB's, dot Y is not calculated during layoutChords3(), // as layoutChords3() is not even called for TAB's; // setDotY() actually also manages creation/deletion of NoteDot's - item->setDotY(DirectionV::AUTO); + item->setDotY(0); // use TAB default note-to-dot spacing dd = STAFFTYPE_TAB_DEFAULTDOTDIST_X * item->spatium(); diff --git a/src/engraving/layout/v0/chordlayout.h b/src/engraving/layout/v0/chordlayout.h index d55e9cfd32a03..de67cce0d62d3 100644 --- a/src/engraving/layout/v0/chordlayout.h +++ b/src/engraving/layout/v0/chordlayout.h @@ -63,6 +63,7 @@ class ChordLayout static void layoutChords1(Score* score, Segment* segment, staff_idx_t staffIdx, LayoutContext& ctx); static double layoutChords2(std::vector& notes, bool up, LayoutContext& ctx); static void layoutChords3(const MStyle& style, const std::vector&, const std::vector&, const Staff*, LayoutContext& ctx); + static void getNoteListForDots(Chord* c, std::vector&, std::vector&, std::vector&); static void updateGraceNotes(Measure* measure, LayoutContext& ctx); static void repositionGraceNotesAfter(Segment* segment); static void appendGraceNotes(Chord* chord); diff --git a/src/engraving/libmscore/note.cpp b/src/engraving/libmscore/note.cpp index ba9f770ffc7e2..ff9489c17af0b 100644 --- a/src/engraving/libmscore/note.cpp +++ b/src/engraving/libmscore/note.cpp @@ -1972,78 +1972,15 @@ void Note::setHeadHasParentheses(bool hasParentheses) } } -//--------------------------------------------------------- -// getNoteListForDots -// This method populates three lists: one for chord notes that need to be checked from the top down, -// one for chords from the bottom up, and one for spaces (where the dot will be in that space) -//--------------------------------------------------------- - -void Note::getNoteListForDots(std::vector& topDownNotes, std::vector& bottomUpNotes, std::vector& anchoredDots) -{ - Chord* c = chord(); - bool hasVoices = c->measure()->hasVoices(c->staffIdx(), c->tick(), c->ticks()); - if (!hasVoices) { - // only this voice, so topDownNotes is just the notes in the chord - for (Note* note : c->notes()) { - if (note->line() & 1) { - int newOffset = 0; - bool adjustDown = (c->voice() & 1) && !c->up(); - if (!anchoredDots.empty() && anchoredDots.back() == note->line()) { - if (anchoredDots.size() >= 2 && anchoredDots[anchoredDots.size() - 2] == note->line() + (adjustDown ? 2 : -2)) { - newOffset = adjustDown ? -2 : 2; - } else { - newOffset = adjustDown ? 2 : -2; - } - } - anchoredDots.push_back(note->line() + newOffset); - } else { - topDownNotes.push_back(note); - } - } - } else { - // Get a list of notes in this staff that adjust dots from top down, - // bottom up, and also start our locked-in dot list by adding all lines where dots are - // guaranteed - Measure* m = c->measure(); - size_t firstVoice = c->track() - c->voice(); - for (size_t i = firstVoice; i < firstVoice + VOICES; ++i) { - if (Chord* voiceChord = m->findChord(c->tick(), i)) { - bool startFromTop = !((voiceChord->voice() & 1) && !voiceChord->up()); - if (startFromTop) { - for (Note* note : voiceChord->notes()) { - if (note->line() & 1) { - anchoredDots.push_back(note->line()); - } else { - topDownNotes.push_back(note); - } - } - } else { - for (Note* note : voiceChord->notes()) { - if (note->line() & 1) { - anchoredDots.push_back(note->line()); - } else { - bottomUpNotes.push_back(note); - } - } - } - } - } - } - // our two lists now contain only notes that are on lines - std::sort(topDownNotes.begin(), topDownNotes.end(), - [](Note* n1, Note* n2) { return n1->line() < n2->line(); }); - std::sort(bottomUpNotes.begin(), bottomUpNotes.end(), - [](Note* n1, Note* n2) { return n1->line() > n2->line(); }); -} - //--------------------------------------------------------- // setDotY +// dotMove is number of staff spaces/lines to move from the note's +// space or line //--------------------------------------------------------- -void Note::setDotY(DirectionV pos) +void Note::setDotY(int dotMove) { - double y = 0; - bool onLine = !(line() & 1); + double y = dotMove / 2.0; if (staff()->isTabStaff(chord()->tick())) { // with TAB's, dotPosX is not set: // get dot X from width of fret text and use TAB default spacing @@ -2055,11 +1992,9 @@ void Note::setDotY(DirectionV pos) // if fret marks above lines, raise the dots by half line distance y = -0.5; } - if (pos == DirectionV::AUTO) { + if (dotMove == 0) { bool oddVoice = voice() & 1; y = oddVoice ? 0.5 : -0.5; - } else if (pos == DirectionV::UP) { - y = -0.5; } else { y = 0.5; } @@ -2068,56 +2003,7 @@ void Note::setDotY(DirectionV pos) else { return; } - } else if (onLine) { - // NON-TAB - std::vector topDownNotes; - std::vector bottomUpNotes; - std::vector anchoredDots; - - // construct combined chords using the notes from overlapping chords - getNoteListForDots(topDownNotes, bottomUpNotes, anchoredDots); - - bool finished = false; - for (Note* note : topDownNotes) { - int dotMove = -1; - if (mu::contains(anchoredDots, note->line() + dotMove)) { - dotMove = 1; // if the desired space is taken, adjust downwards - } - if (note == this) { - y = dotMove / 2.0; - finished = true; - break; - } - anchoredDots.push_back(note->line() + dotMove); - } - if (!finished) { - for (Note* note : bottomUpNotes) { - int dotMove = 1; - if (mu::contains(anchoredDots, note->line() + dotMove)) { - dotMove = -1; // if the desired space is taken, adjust upwards - } - if (note == this) { - y = dotMove / 2.0; - finished = true; - break; - } - anchoredDots.push_back(note->line() + dotMove); - } - } - } else { - // on a space; usually this means the dot is on this same line, but there is an exception - // for a unison within the same chord. - for (Note* note : chord()->notes()) { - if (note == this) { - break; - } - if (note->line() == _line) { - bool adjustDown = (chord()->voice() & 1) && !chord()->up(); - y = adjustDown ? 1.0 : -1.0; - } - } } - y *= spatium() * staff()->lineDistance(tick()); // apply to dots diff --git a/src/engraving/libmscore/note.h b/src/engraving/libmscore/note.h index 194686ed136b8..a266ba5520b5a 100644 --- a/src/engraving/libmscore/note.h +++ b/src/engraving/libmscore/note.h @@ -331,6 +331,8 @@ class Note final : public EngravingItem DirectionV userDotPosition() const { return _userDotPosition; } void setUserDotPosition(DirectionV d) { _userDotPosition = d; } + DirectionV dotPosition() const { return _dotPosition; } + void setDotPosition(DirectionV d) { _dotPosition = d; } bool dotIsUp() const; // actual dot position void reset() override; @@ -386,7 +388,7 @@ class Note final : public EngravingItem bool mark() const { return _mark; } void setMark(bool v) const { _mark = v; } void setScore(Score* s) override; - void setDotY(DirectionV); + void setDotY(int); void setHeadHasParentheses(bool hasParentheses); bool headHasParentheses() const { return _hasHeadParentheses; } @@ -494,6 +496,7 @@ class Note final : public EngravingItem DirectionH _userMirror = DirectionH::AUTO; ///< user override of mirror DirectionV _userDotPosition = DirectionV::AUTO; ///< user override of dot position + DirectionV _dotPosition = DirectionV::AUTO; // used as an intermediate step when resolving dot conflicts NoteHeadScheme _headScheme = NoteHeadScheme::HEAD_AUTO; NoteHeadGroup _headGroup = NoteHeadGroup::HEAD_NORMAL;