Skip to content

Commit

Permalink
Remove hanging ties on XML import
Browse files Browse the repository at this point in the history
Backport of musescore#22455, which in turn is a port of musescore#8556 (which got ported to 3.7 earlier), but doesn't replace hanging ties with l.v. articulations, opting instead to simply delete them pending a proper feature.
  • Loading branch information
miiizen authored and Jojo-Schmitz committed Apr 17, 2024
1 parent cd9f0b2 commit 6d609a4
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 32 deletions.
106 changes: 75 additions & 31 deletions importexport/musicxml/importmxmlpass2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ namespace Ms {
//---------------------------------------------------------

static void addTie(const Notation& notation, Score* score, Note* note, const int track,
std::map<int, Tie*>& tie, MxmlLogger* logger, const QXmlStreamReader* const xmlreader);
std::map<int, Tie*>& tie, MxmlLogger* logger, const QXmlStreamReader* const xmlreader,
const bool fixForCrossStaff);

//---------------------------------------------------------
// support enums / structs / classes
Expand Down Expand Up @@ -1644,6 +1645,7 @@ void MusicXMLParserPass2::initPartState(const QString& partId)
{
Q_UNUSED(partId);
_timeSigDura = Fraction(0, 0); // invalid
_ties.clear();
_lastVolta = 0;
_hasDrumset = false;
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
Expand Down Expand Up @@ -1696,35 +1698,42 @@ SpannerSet MusicXMLParserPass2::findIncompleteSpannersAtPartEnd()
return res;
}

//---------------------------------------------------------
// addArticLaissezVibrer
//---------------------------------------------------------

static void addArticLaissezVibrer(const Note* const note)
{
if (!note)
return;
auto chord = note->chord();
if (!hasLaissezVibrer(chord)) {
Articulation* na = new Articulation(SymId::articLaissezVibrerBelow, chord->score());
chord->add(na);
}

}

//---------------------------------------------------------
// cleanupUnterminatedTie
//---------------------------------------------------------
/**
Delete tie and add Laissez Vibrer where it was
*/

static void cleanupUnterminatedTie(Tie*& tie)
static void cleanupUnterminatedTie(Tie*& tie, const Score* score, bool fixForCrossStaff = false)
{
Note* unterminatedTieNote = tie->startNote();
const Chord* unterminatedChord = unterminatedTieNote->chord();

// Dolet 6 doesn't export cross staff information
// If a tie is unterminated, try to find a candidate to tie it to on a different track/stave
if (fixForCrossStaff) {
const Segment* nextSeg = score->tick2leftSegment(unterminatedChord->tick() + unterminatedChord->ticks());
if (nextSeg) {
const Part* part = unterminatedTieNote->part();
for (int track = part->startTrack(); track <= part->endTrack(); ++track) {
const Element* el = nextSeg->element(track);
if (el && el->isChord()) {
Note* matchingNote = toChord(el)->findNote(unterminatedTieNote->pitch());
if (matchingNote && matchingNote->tpc1() == unterminatedTieNote->tpc1()
&& matchingNote->tpc2() == unterminatedTieNote->tpc2()) {
tie->setEndNote(matchingNote);
matchingNote->setTieBack(tie);
return;
}
}
}
}
}

// Delete unterminated ties pending fully featured l.v. ties & ties over repeats
unterminatedTieNote->remove(tie);
delete tie;
addArticLaissezVibrer(unterminatedTieNote); // Treat as let-ring
}

//---------------------------------------------------------
Expand Down Expand Up @@ -2278,7 +2287,7 @@ void MusicXMLParserPass2::part()
// Clean up unterminated ties
for (auto tie : _ties) {
if (tie.second) {
cleanupUnterminatedTie(tie.second);
cleanupUnterminatedTie(tie.second, _score, _pass1.exporterString().contains("dolet 6"));
_ties[tie.first] = 0;
}
}
Expand Down Expand Up @@ -5803,7 +5812,7 @@ Note* MusicXMLParserPass2::note(const QString& partId,
QString instrumentId;
QString tieType;
MusicXMLParserLyric lyric { _pass1.getMusicXmlPart(partId).lyricNumberHandler(), _e, _score, _logger };
MusicXMLParserNotations notations { _e, _score, _logger };
MusicXMLParserNotations notations { _e, _score, _logger, _pass1 };

mxmlNoteDuration mnd { _divs, _logger, &_pass1 };
mxmlNotePitch mnp { _logger };
Expand Down Expand Up @@ -6128,7 +6137,7 @@ Note* MusicXMLParserPass2::note(const QString& partId,
Notation notation { "tied" };
const QString ctype { "type" };
notation.addAttribute(&ctype, &tieType);
addTie(notation, _score, note, cr->track(), _ties, _logger, &_e);
addTie(notation, _score, note, cr->track(), _ties, _logger, &_e, _pass1.exporterString().contains("dolet 6"));
}
}

Expand Down Expand Up @@ -7478,12 +7487,29 @@ static void addArpeggio(ChordRest* cr, const QString& arpeggioType,
}
}

//---------------------------------------------------------
// addArticLaissezVibrer
//---------------------------------------------------------

static void addArticLaissezVibrer(const Note* const note)
{
if (!note)
return;
auto chord = note->chord();
if (!hasLaissezVibrer(chord)) {
Articulation* na = new Articulation(SymId::articLaissezVibrerBelow, chord->score());
chord->add(na);
}

}

//---------------------------------------------------------
// addTie
//---------------------------------------------------------

static void addTie(const Notation& notation, Score* score, Note* note, const int track,
std::map<int, Tie*>& ties, MxmlLogger* logger, const QXmlStreamReader* const xmlreader)
std::map<int, Tie*>& ties, MxmlLogger* logger, const QXmlStreamReader* const xmlreader,
const bool fixForCrossStaff)
{
if (!note)
return;
Expand All @@ -7496,7 +7522,7 @@ static void addTie(const Notation& notation, Score* score, Note* note, const int
if (ties[note->pitch()]) {
// Unterminated tie on this pitch. Clean this up, then resume.
logger->logError(QString("Tie already active"), xmlreader);
cleanupUnterminatedTie(ties[note->pitch()]);
cleanupUnterminatedTie(ties[note->pitch()], score, fixForCrossStaff);
ties[note->pitch()] = 0;
}
ties[note->pitch()] = new Tie(score);
Expand Down Expand Up @@ -7531,15 +7557,33 @@ static void addTie(const Notation& notation, Score* score, Note* note, const int
currTie->setLineType(2);

}
else if (type == "stop")
else if (type == "stop") {
if (ties[note->pitch()]) {
Tie* currTie = ties[note->pitch()];
currTie->setEndNote(note);
note->setTieBack(currTie);
ties[note->pitch()] = 0;
const Note* startNote = currTie->startNote();
const Chord* startChord = startNote ? startNote->chord() : nullptr;
const Chord* endChord = note->chord();
const Measure* startMeasure = startChord ? startChord->measure() : nullptr;
qInfo() << "startMeasure: " << startMeasure;
qInfo() << "endMeasure: " << endChord->measure();
qInfo() << "startChord->tick() + startChord->ticks(): " << (startChord->tick() + startChord->ticks()).toString();
qInfo() << "endChord->tick(): " << endChord->tick().toString();
if (startMeasure == endChord->measure() || startChord->tick() + startChord->ticks() == endChord->tick()) {
// only connect if they're in the same bar, or there are no notes/rests in the same voice between them
qInfo() << "Connect";
currTie->setEndNote(note);
note->setTieBack(currTie);
}
else {
qInfo() << "Discard";
cleanupUnterminatedTie(ties[note->pitch()], score, fixForCrossStaff);
}
ties[note->pitch()] = nullptr;
}
else
logger->logError(QString("Non-started tie terminated. No-op."), xmlreader);
}
// ignore
else if (type == "let-ring")
addArticLaissezVibrer(note);
else
Expand Down Expand Up @@ -7709,8 +7753,8 @@ QString Notation::print() const
// MusicXMLParserNotations
//---------------------------------------------------------

MusicXMLParserNotations::MusicXMLParserNotations(QXmlStreamReader& e, Score* score, MxmlLogger* logger)
: _e(e), _score(score), _logger(logger)
MusicXMLParserNotations::MusicXMLParserNotations(QXmlStreamReader& e, Score* score, MxmlLogger* logger, MusicXMLParserPass1& pass1)
: _e(e), _pass1(pass1), _score(score), _logger(logger)
{
// nothing
}
Expand Down Expand Up @@ -7894,7 +7938,7 @@ void MusicXMLParserNotations::addToScore(ChordRest* const cr, Note* const note,
addGlissandoSlide(notation, note, glissandi, spanners, _logger, &_e);
}
else if (note && notation.name() == "tied") {
addTie(notation, _score, note, cr->track(), ties, _logger, &_e);
addTie(notation, _score, note, cr->track(), ties, _logger, &_e, _pass1.exporterString().contains("dolet 6"));
}
else if (note && notation.parent() == "technical") {
addTechnical(notation, note);
Expand Down
3 changes: 2 additions & 1 deletion importexport/musicxml/importmxmlpass2.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class DelayedDirectionsList : public QList<MusicXMLDelayedDirectionElement*> {

class MusicXMLParserNotations {
public:
MusicXMLParserNotations(QXmlStreamReader& e, Score* score, MxmlLogger* logger);
MusicXMLParserNotations(QXmlStreamReader& e, Score* score, MxmlLogger* logger, MusicXMLParserPass1& pass1);
void parse();
void addToScore(ChordRest* const cr, Note* const note, const int tick, SlurStack& slurs,
Glissando* glissandi[MAX_NUMBER_LEVEL][2], MusicXmlSpannerMap& spanners, TrillStack& trills,
Expand Down Expand Up @@ -246,6 +246,7 @@ class MusicXMLParserNotations {
void tuplet();
void otherNotation();
QXmlStreamReader& _e;
MusicXMLParserPass1& _pass1;
Score* const _score; // the score
MxmlLogger* _logger; // the error logger
QString _errors; // errors to present to the user
Expand Down

0 comments on commit 6d609a4

Please sign in to comment.