Skip to content

Commit

Permalink
Cleanup of dot positioning code and manual direction bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
asattely committed Jun 15, 2023
1 parent 7e27586 commit b33ddac
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 131 deletions.
174 changes: 163 additions & 11 deletions src/engraving/layout/v0/chordlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2276,6 +2276,11 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector<Chord*>&

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()) {
Expand All @@ -2288,12 +2293,12 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector<Chord*>&
// 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;
Expand All @@ -2303,22 +2308,19 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector<Chord*>&
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;
}
Expand All @@ -2327,8 +2329,94 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector<Chord*>&
}
}
}
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<Note*> topDownNotes;
std::vector<Note*> bottomUpNotes;
std::vector<int> 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<int, Note*> 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
Expand Down Expand Up @@ -2564,6 +2652,70 @@ void ChordLayout::layoutChords3(const MStyle& style, const std::vector<Chord*>&
}
}

//---------------------------------------------------------
// 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<Note*>& topDownNotes, std::vector<Note*>& bottomUpNotes,
std::vector<int>& 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
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/engraving/layout/v0/chordlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class ChordLayout
static void layoutChords1(Score* score, Segment* segment, staff_idx_t staffIdx, LayoutContext& ctx);
static double layoutChords2(std::vector<Note*>& notes, bool up, LayoutContext& ctx);
static void layoutChords3(const MStyle& style, const std::vector<Chord*>&, const std::vector<Note*>&, const Staff*, LayoutContext& ctx);
static void getNoteListForDots(Chord* c, std::vector<Note*>&, std::vector<Note*>&, std::vector<int>&);
static void updateGraceNotes(Measure* measure, LayoutContext& ctx);
static void repositionGraceNotesAfter(Segment* segment);
static void appendGraceNotes(Chord* chord);
Expand Down
124 changes: 5 additions & 119 deletions src/engraving/libmscore/note.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Note*>& topDownNotes, std::vector<Note*>& bottomUpNotes, std::vector<int>& 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
Expand All @@ -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;
}
Expand All @@ -2068,56 +2003,7 @@ void Note::setDotY(DirectionV pos)
else {
return;
}
} else if (onLine) {
// NON-TAB
std::vector<Note*> topDownNotes;
std::vector<Note*> bottomUpNotes;
std::vector<int> 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
Expand Down
Loading

0 comments on commit b33ddac

Please sign in to comment.