Skip to content

Commit

Permalink
ENG-17: Fix beams in multi-voice MusicXML import
Browse files Browse the repository at this point in the history
The presence of a second voice would prematurely break
beams upon MusicXML import in some cases, as only one beam was
stored at a time as the "current beam", and this would get thrown away
if a note in another voice/track was handled before the end of a
beam. This commit adds a QMap to store one beam *per voice* at a time,
avoiding the conflict while retaining the existing logic.
  • Loading branch information
iveshenry18 committed May 15, 2021
1 parent 0611279 commit acdedb6
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 10 deletions.
26 changes: 17 additions & 9 deletions importexport/musicxml/importmxmlpass2.cpp
Expand Up @@ -1764,7 +1764,7 @@ static void removeBeam(Beam*& beam)
for (int i = 0; i < beam->elements().size(); ++i)
beam->elements().at(i)->setBeamMode(Beam::Mode::NONE);
delete beam;
beam = 0;
beam = nullptr;
}

//---------------------------------------------------------
Expand All @@ -1788,9 +1788,10 @@ static void handleBeamAndStemDir(ChordRest* cr, const Beam::Mode bm, const Direc
}
// add ChordRest to beam
if (beam) {
// verify still in the same track (switching voices in the middle of a beam is not supported)
// verify still in the same track (if still in the same voice)
// and in a beam ...
// (note no check is done on correct order of beam begin/continue/end)
// TODO: Some BEGINs are being skipped
if (cr->track() != beam->track()) {
qDebug("handleBeamAndStemDir() from track %d to track %d -> abort beam",
beam->track(), cr->track());
Expand Down Expand Up @@ -1826,7 +1827,7 @@ static void handleBeamAndStemDir(ChordRest* cr, const Beam::Mode bm, const Direc
}
// terminate the current beam and add to the score
if (beam && bm == Beam::Mode::END)
beam = 0;
beam = nullptr;
}


Expand Down Expand Up @@ -1989,7 +1990,7 @@ void MusicXMLParserPass2::measure(const QString& partId,
Fraction mDura; // current total measure duration
GraceChordList gcl; // grace chords collected sofar
int gac = 0; // grace after count in the grace chord list
Beam* beam = 0; // current beam
Beams beams; // Current beam for each voice in the current part
QString cv = "1"; // current voice for chords, default is 1
FiguredBassList fbl; // List of figured bass elements under a single note
MxmlTupletStates tupletStates; // Tuplet state for each voice in the current part
Expand Down Expand Up @@ -2019,7 +2020,7 @@ void MusicXMLParserPass2::measure(const QString& partId,
int alt = -10; // any number outside range of xml-tag "alter"
// note: chord and grace note handling done in note()
// dura > 0 iff valid rest or first note of chord found
Note* n = note(partId, measure, time + mTime, time + prevTime, missingPrev, dura, missingCurr, cv, gcl, gac, beam, fbl, alt, tupletStates, tuplets);
Note* n = note(partId, measure, time + mTime, time + prevTime, missingPrev, dura, missingCurr, cv, gcl, gac, beams, fbl, alt, tupletStates, tuplets);
if (n && !n->chord()->isGrace())
prevChord = n->chord(); // remember last non-grace chord
if (n && n->accidental() && n->accidental()->accidentalType() != AccidentalType::NONE)
Expand Down Expand Up @@ -2120,9 +2121,11 @@ void MusicXMLParserPass2::measure(const QString& partId,
Part* part = _pass1.getPart(partId); // should not fail, we only get here if the part exists
fillGapsInFirstVoices(measure, part);

// can't have beams extending into the next measure
if (beam)
removeBeam(beam);
// Prevent any beams from extending into the next measure
for (Beam* beam : beams.values()) {
if (beam)
removeBeam(beam);
}

// TODO:
// - how to handle _timeSigDura.isZero (shouldn't happen ?)
Expand Down Expand Up @@ -4292,7 +4295,7 @@ Note* MusicXMLParserPass2::note(const QString& partId,
QString& currentVoice,
GraceChordList& gcl,
int& gac,
Beam*& currBeam,
Beams& currBeams,
FiguredBassList& fbl,
int& alt,
MxmlTupletStates& tupletStates,
Expand Down Expand Up @@ -4418,6 +4421,11 @@ Note* MusicXMLParserPass2::note(const QString& partId,
if (voice == "")
voice = "1";

// Define currBeam based on currentVoice to handle multi-voice beaming (and instantiate if not already)
if (!currBeams.contains(currentVoice))
currBeams.insert(currentVoice, (Beam *)nullptr);
Beam* &currBeam = currBeams[currentVoice];

// check for timing error(s) and set dura
// keep in this order as checkTiming() might change dura
auto errorStr = mnd.checkTiming(type, rest, grace);
Expand Down
3 changes: 2 additions & 1 deletion importexport/musicxml/importmxmlpass2.h
Expand Up @@ -40,6 +40,7 @@ using FiguredBassList = QVector<FiguredBass*>;
// typedef QList<Chord*> GraceChordList;
// typedef QVector<FiguredBass*> FiguredBassList;
using Tuplets = std::map<QString, Tuplet*>;
using Beams = QMap<QString, Beam*>;

//---------------------------------------------------------
// MxmlStartStop
Expand Down Expand Up @@ -269,7 +270,7 @@ class MusicXMLParserPass2 {
void transpose(const QString& partId, const Fraction& tick);
Note* note(const QString& partId, Measure* measure, const Fraction sTime, const Fraction prevTime,
Fraction& missingPrev, Fraction& dura, Fraction& missingCurr, QString& currentVoice, GraceChordList& gcl, int& gac,
Beam*& beam, FiguredBassList& fbl, int& alt, MxmlTupletStates& tupletStates, Tuplets& tuplets);
Beams& beams, FiguredBassList& fbl, int& alt, MxmlTupletStates& tupletStates, Tuplets& tuplets);
void notePrintSpacingNo(Fraction& dura);
FiguredBassItem* figure(const int idx, const bool paren);
FiguredBass* figuredBass();
Expand Down

0 comments on commit acdedb6

Please sign in to comment.