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.
  • Loading branch information
iveshenry18 authored and vpereverzev committed Jul 7, 2021
1 parent 5511ad6 commit 1a5e676
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 @@ -2803,6 +2803,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 @@ -3441,8 +3461,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 @@ -1624,6 +1624,8 @@ void MusicXMLParserPass2::scorePartwise()
// TODO, handle other tracks?
if (_score->lastMeasure()->endBarLineType() == BarLineType::NORMAL)
_score->lastMeasure()->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.
Loading

0 comments on commit 1a5e676

Please sign in to comment.