Skip to content

Commit

Permalink
ENG-21: Connect cross-voice arpeggios
Browse files Browse the repository at this point in the history
Due to the one-to-one relationship between Arpeggios and Chords, there
is no ideal way to import arpeggios that include notes across voices;
currently, such a case results in the creation of one arpeggio for each
chord with notes containing the <arpeggiated> tag. This commit adds a
function to Score that iterates through these and connects them; this
function is called after the score has been imported.

Additionally, this commits corrects the exporting for this case, adding
the <arpeggiated> tag to not only notes in a chord with an arpeggio, but
notes that intersect an arpeggio in any voice in the given segment and
staff. This effectively creates a correct round-trip for cross-voice
arpeggios, although with the (theoretical) false positive of connecting
multiple arpeggios in the same segment in the same staff even if they
are intended to remain separate.

Duplicate of musescore#8506, plus fixing a merge conflict due to musescore#8199
  • Loading branch information
iveshenry18 authored and Jojo-Schmitz committed Jul 27, 2021
1 parent e53f70a commit dc44b76
Show file tree
Hide file tree
Showing 9 changed files with 1,855 additions and 2 deletions.
24 changes: 22 additions & 2 deletions importexport/musicxml/exportxml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2824,6 +2824,26 @@ void ExportMusicXml::chordAttributes(Chord* chord, Notations& notations, Technic
}
}

//---------------------------------------------------------
// findArpeggio
//---------------------------------------------------------

static Arpeggio* findArpeggio(Note* note)
{
if (note->chord()->arpeggio()) return note->chord()->arpeggio();

// Check if there is an arpeggio in any voice that intersects the note on the y-axis
for (int i = staff2track(note->staffIdx()); i < staff2track(note->staffIdx() + 1); ++i) {
Element* elem = note->chord()->segment()->elist()[i];
if (elem && elem->isChord()
&& toChord(elem)->arpeggio()
&& note->pageBoundingRect().top() + note->headHeight() >= toChord(elem)->arpeggio()->pageBoundingRect().top()
&& note->pageBoundingRect().top() + note->headHeight() <= toChord(elem)->arpeggio()->pageBoundingRect().bottom())
return toChord(elem)->arpeggio();
}
return 0;
}

//---------------------------------------------------------
// arpeggiate
//---------------------------------------------------------
Expand Down Expand Up @@ -3464,8 +3484,8 @@ void ExportMusicXml::chord(Chord* chord, int staff, const std::vector<Lyrics*>*
}

technical.etag(_xml);
if (chord->arpeggio()) {
arpeggiate(chord->arpeggio(), note == nl.front(), note == nl.back(), _xml, notations);
if (Arpeggio* arp = findArpeggio(note)) {
arpeggiate(arp, note == nl.front(), note == nl.back(), _xml, notations);
}
for (Spanner* spanner : note->spannerFor())
if (spanner->type() == ElementType::GLISSANDO) {
Expand Down
2 changes: 2 additions & 0 deletions importexport/musicxml/importmxmlpass2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,8 @@ void MusicXMLParserPass2::scorePartwise()
auto lm = _score->lastMeasure();
if (lm && lm->endBarLineType() == BarLineType::NORMAL)
lm->setEndBarLineType(BarLineType::NORMAL, 0);

_score->connectArpeggios();
}

//---------------------------------------------------------
Expand Down
51 changes: 51 additions & 0 deletions libmscore/layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//=============================================================================

#include "accidental.h"
#include "arpeggio.h"
#include "barline.h"
#include "beam.h"
#include "box.h"
Expand Down Expand Up @@ -1525,6 +1526,56 @@ void Score::connectTies(bool silent)
}
}

//---------------------------------------------------------
// connectArpeggios
// Fake cross-voice arpeggios by hiding all but the first
// and extending the first to cover the others.
// Retains the other properties of the first arpeggio.
//---------------------------------------------------------

void Score::connectArpeggios()
{
for (auto segment = firstSegment(SegmentType::ChordRest); segment; segment = segment->next1(SegmentType::ChordRest)) {
for (int staff = 0; staff < nstaves(); ++staff) {
qreal minTop = 10000;
qreal maxBottom = -10000;
int firstArpeggio = -1;
bool multipleArpeggios = false;
for (int i = staff2track(staff); i < staff2track(staff + 1); ++i) {
if (segment->elist()[i] && segment->elist()[i]->isChord()) {
Chord* chord = toChord(segment->elist()[i]);
if (chord->arpeggio() && chord->arpeggio()->visible()) {
if (chord->pagePos() == QPointF(0, 0)) doLayout();
qreal localTop = chord->arpeggio()->pageBoundingRect().top();
qreal localBottom = chord->arpeggio()->pageBoundingRect().bottom();
minTop = qMin(localTop, minTop);
maxBottom = qMax(localBottom, maxBottom);
if (firstArpeggio == -1)
// Leave arpeggio, adjust height after collecting
firstArpeggio = i;
else {
// Hide arpeggio; firstArpeggio will be extended to cover it.
chord->arpeggio()->setVisible(false);
multipleArpeggios = true;
}
}
}
}
if (firstArpeggio != -1 && multipleArpeggios) {
// Stretch first arpeggio to cover deleted
Chord* firstArpeggioChord = toChord(segment->elist()[firstArpeggio]);
Arpeggio* arpeggio = firstArpeggioChord->arpeggio();
qreal topDiff = minTop - arpeggio->pageBoundingRect().top();
qreal bottomDiff = maxBottom - arpeggio->pageBoundingRect().bottom();
arpeggio->setUserLen1(topDiff);
arpeggio->setUserLen2(bottomDiff);
arpeggio->setPropertyFlags(Pid::ARP_USER_LEN1, PropertyFlags::UNSTYLED);
arpeggio->setPropertyFlags(Pid::ARP_USER_LEN2, PropertyFlags::UNSTYLED);
}
}
}
}

//---------------------------------------------------------
// checkDivider
//---------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions libmscore/score.h
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ class Score : public QObject, public ScoreElement {
Segment* lastSegmentMM() const;

void connectTies(bool silent=false);
void connectArpeggios();

qreal point(const Spatium sp) const { return sp.val() * spatium(); }

Expand Down
Binary file added mtest/musicxml/io/testConnectedArpeggios.pdf
Binary file not shown.

0 comments on commit dc44b76

Please sign in to comment.