From 19943c290247100a507382e275828a5a935f12e8 Mon Sep 17 00:00:00 2001 From: IsaacWeiss Date: Sun, 9 Aug 2020 22:52:51 -0400 Subject: [PATCH] Enable MusicXML export of MeasureRepeats --- .../internal/musicxml/exportxml.cpp | 224 +++++++++++------- 1 file changed, 140 insertions(+), 84 deletions(-) diff --git a/mu4/domain/importexport/internal/musicxml/exportxml.cpp b/mu4/domain/importexport/internal/musicxml/exportxml.cpp index 6266fc0358b97..e16ba5cc90d95 100644 --- a/mu4/domain/importexport/internal/musicxml/exportxml.cpp +++ b/mu4/domain/importexport/internal/musicxml/exportxml.cpp @@ -57,6 +57,7 @@ #include "libmscore/staff.h" #include "libmscore/part.h" #include "libmscore/measure.h" +#include "libmscore/measurerepeat.h" #include "libmscore/style.h" #include "libmscore/slur.h" #include "libmscore/hairpin.h" @@ -362,10 +363,10 @@ class ExportMusicXml void findAndExportClef(const Measure* const m, const int staves, const int strack, const int etrack); void exportDefaultClef(const Part* const part, const Measure* const m); void writeElement(Element* el, const Measure* m, int sstaff, bool useDrumset); - void writeMeasureTracks(const Measure* const m, const int partIndex, const int strack, const int staves,const bool useDrumset, - FigBassMap& fbMap); - void writeMeasure(const Measure* const m, const int idx, const int staffCount, MeasureNumberStateHandler& mnsh,FigBassMap& fbMap, - const MeasurePrintContext& mpc); + void writeMeasureTracks(const Measure* m, const int partIndex, const int strack, const int staves, + const bool useDrumset, FigBassMap& fbMap); + void writeMeasure(const Measure* const m, const int idx, const int staffCount, MeasureNumberStateHandler& mnsh, + FigBassMap& fbMap, const MeasurePrintContext& mpc); void writeParts(); public: @@ -5052,11 +5053,11 @@ static bool elementRighter(const Element* e1, const Element* e2) // measureStyle -- write measure-style //--------------------------------------------------------- -// this is done at the first measure of a multi-meaure rest +// this is done at the first measure of a multimeasure rest // note: for a normal measure, mmRest1 is the measure itself, // for a multi-meaure rest, it is the replacing measure -static void measureStyle(XmlWriter& xml, Attributes& attr, const Measure* const m) +static void measureStyle(XmlWriter& xml, Attributes& attr, const Measure* const m, const int partIndex) { const Measure* mmR1 = m->mmRest1(); if (m != mmR1 && m == mmR1->mmRestFirst()) { @@ -5064,6 +5065,40 @@ static void measureStyle(XmlWriter& xml, Attributes& attr, const Measure* const xml.stag("measure-style"); xml.tag("multiple-rest", mmR1->mmRestCount()); xml.etag(); + } else { + Part* part = m->score()->parts().at(partIndex); + const int scoreRelStaff = m->score()->staffIdx(part); + for (int i = 0; i < part->nstaves(); ++i) { + int staffIdx = scoreRelStaff + i; + if (m->isMeasureRepeatGroup(staffIdx) && + (!m->prevMeasure() || !m->prevMeasure()->isMeasureRepeatGroup(staffIdx) + || (m->measureRepeatElement(staffIdx)->numMeasures() + != m->prevMeasure()->measureRepeatElement(staffIdx)->numMeasures()))) { + attr.doAttr(xml, true); + xml.stag(QString("measure-style number=\"%1\"").arg(i + 1)); + int numMeasures = m->measureRepeatElement(staffIdx)->numMeasures(); + if (numMeasures > 1) { + // slashes == numMeasures for everything MuseScore currently supports + xml.tag(QString("measure-repeat slashes=\"%1\" type=\"start\"").arg(numMeasures), numMeasures); + } + else { + // no need to include slashes + xml.tag("measure-repeat type=\"start\"", numMeasures); + } + xml.etag(); + } else if (m->prevMeasure() && + ((m->prevMeasure()->isMeasureRepeatGroup(staffIdx) && !m->isMeasureRepeatGroup(staffIdx)) + // no longer in measure repeats, or + || (m->prevMeasure()->measureRepeatElement(staffIdx) && m->measureRepeatElement(staffIdx) + && (m->measureRepeatElement(staffIdx)->numMeasures() + != m->prevMeasure()->measureRepeatElement(staffIdx)->numMeasures())))) { + // still in measure repeats, but now of different duration + attr.doAttr(xml, true); + xml.stag(QString("measure-style number=\"%1\"").arg(i + 1)); + xml.tag("measure-repeat type=\"stop\"", ""); + xml.etag(); + } + } } } @@ -6396,7 +6431,7 @@ static std::vector findTextFramesToWriteAsWordsBelow(const Measure* const Write data contained in the measure's tracks. */ -void ExportMusicXml::writeMeasureTracks(const Measure* const m, +void ExportMusicXml::writeMeasureTracks(const Measure* m, const int partIndex, const int strack, const int staves, // TODO remove ?? const bool useDrumset, @@ -6413,88 +6448,113 @@ void ExportMusicXml::writeMeasureTracks(const Measure* const m, // required to prevent multiple spanner stops for the same spanner QSet spannersStopped; - for (int st = strack; st < etrack; ++st) { - // sstaff - xml staff number, counting from 1 for this - // instrument - // special number 0 -> don’t show staff number in - // xml output (because there is only one staff) + const Measure* const origM = m; - int sstaff = (staves > 1) ? st - strack + VOICES : 0; - sstaff /= VOICES; - for (auto seg = m->first(); seg; seg = seg->next()) { - const auto el = seg->element(st); - if (!el) { - continue; - } - // must ignore start repeat to prevent spurious backup/forward - if (el->isBarLine() && toBarLine(el)->barLineType() == BarLineType::START_REPEAT) { - continue; - } + for (int staffIdx = track2staff(strack); staffIdx < track2staff(etrack); ++staffIdx) { + Q_ASSERT(m = origM); // some staves may need to make m point somewhere else, so just in case, ensure start in same place + moveToTick(m->tick()); - // generate backup or forward to the start time of the element - if (_tick != seg->tick()) { - _attr.doAttr(_xml, false); - moveToTick(seg->tick()); + // in addition to "start" and "stop" tags written by measureStyle(), + // write the actual content being repeated for extra compatibility + if (m->isMeasureRepeatGroup(staffIdx)) { + MeasureRepeat* mr; + while (m->isMeasureRepeatGroup(staffIdx) && m->prevMeasure()) { // keep going back until out of measure repeat groups + mr = m->measureRepeatElement(staffIdx); + for (int i = 0; i < mr->numMeasures() && m->prevMeasure(); ++i) { + m = m->prevMeasure(); + } } + } - // handle annotations and spanners (directions attached to this note or rest) - if (el->isChordRest()) { - _attr.doAttr(_xml, false); - const bool isFirstPart = (partIndex == 0); - const bool isLastPart = (partIndex == (_score->parts().size() - 1)); - if (!tboxesAboveWritten && isFirstPart) { - for (const auto tbox : tboxesAbove) { - // note: use mmRest1() to get at a possible multi-measure rest, - // as the covered measure would be positioned at 0,0. - tboxTextAsWords(tbox->text(), 0, tbox->text()->canvasPos() - m->mmRest1()->canvasPos()); - } - tboxesAboveWritten = true; + // in case m was changed, also rewind _tick so as not to generate unnecessary backup/forward tags + auto tickDelta = _tick - m->tick(); + _tick -= tickDelta; + + int track = staff2track(staffIdx); + for (int st = track; st < track + VOICES; ++st) { + // sstaff - xml staff number, counting from 1 for this instrument + // special number 0 -> don’t show staff number in xml output (because there is only one staff) + int sstaff = (staves > 1) ? st - strack + VOICES : 0; + sstaff /= VOICES; + for (auto seg = m->first(); seg; seg = seg->next()) { + const auto el = seg->element(st); + if (!el) { + continue; } - if (!tboxesBelowWritten && isLastPart && (etrack - VOICES) <= st) { - for (const auto tbox : tboxesBelow) { - const auto lastStaffNr = st / VOICES; - const auto sys = m->mmRest1()->system(); - auto textPos = tbox->text()->canvasPos() - m->mmRest1()->canvasPos(); - if (lastStaffNr < sys->staves()->size()) { - // convert to position relative to last staff of system - textPos.setY(textPos.y() - (sys->staffCanvasYpage(lastStaffNr) - sys->staffCanvasYpage(0))); + // must ignore start repeat to prevent spurious backup/forward + if (el->isBarLine() && toBarLine(el)->barLineType() == BarLineType::START_REPEAT) { + continue; + } + + // generate backup or forward to the start time of the element + if (_tick != seg->tick()) { + _attr.doAttr(_xml, false); + moveToTick(seg->tick()); + } + + // handle annotations and spanners (directions attached to this note or rest) + if (el->isChordRest()) { + _attr.doAttr(_xml, false); + const bool isFirstPart = (partIndex == 0); + const bool isLastPart = (partIndex == (_score->parts().size() - 1)); + if (!tboxesAboveWritten && isFirstPart) { + for (const auto tbox : tboxesAbove) { + // note: use mmRest1() to get at a possible multi-measure rest, + // as the covered measure would be positioned at 0,0. + tboxTextAsWords(tbox->text(), 0, tbox->text()->canvasPos() - m->mmRest1()->canvasPos()); } - tboxTextAsWords(tbox->text(), sstaff, textPos); + tboxesAboveWritten = true; } - tboxesBelowWritten = true; - } - annotations(this, strack, etrack, st, sstaff, seg); - // look for more harmony - for (auto seg1 = seg->next(); seg1; seg1 = seg1->next()) { - if (seg1->isChordRestType()) { - const auto el1 = seg1->element(st); - if (el1) { // found a ChordRest, next harmony will be attach to this one - break; + if (!tboxesBelowWritten && isLastPart && (etrack - VOICES) <= st) { + for (const auto tbox : tboxesBelow) { + const auto lastStaffNr = st / VOICES; + const auto sys = m->mmRest1()->system(); + auto textPos = tbox->text()->canvasPos() - m->mmRest1()->canvasPos(); + if (lastStaffNr < sys->staves()->size()) { + // convert to position relative to last staff of system + textPos.setY(textPos.y() - (sys->staffCanvasYpage(lastStaffNr) - sys->staffCanvasYpage(0))); + } + tboxTextAsWords(tbox->text(), sstaff, textPos); } - for (auto annot : seg1->annotations()) { - if (annot->isHarmony() && annot->track() == st) { - harmony(toHarmony(annot), 0, (seg1->tick() - seg->tick()).ticks() / div); + tboxesBelowWritten = true; + } + annotations(this, strack, etrack, st, sstaff, seg); + // look for more harmony + for (auto seg1 = seg->next(); seg1; seg1 = seg1->next()) { + if (seg1->isChordRestType()) { + const auto el1 = seg1->element(st); + if (el1) { // found a ChordRest, next harmony will be attach to this one + break; + } + for (auto annot : seg1->annotations()) { + if (annot->isHarmony() && annot->track() == st) { + harmony(toHarmony(annot), 0, (seg1->tick() - seg->tick()).ticks() / div); + } } } } + figuredBass(_xml, strack, etrack, st, static_cast(el), fbMap, div); + spannerStart(this, strack, etrack, st, sstaff, seg); } - figuredBass(_xml, strack, etrack, st, static_cast(el), fbMap, div); - spannerStart(this, strack, etrack, st, sstaff, seg); - } - // write element el if necessary - writeElement(el, m, sstaff, useDrumset); + // write element el if necessary + writeElement(el, m, sstaff, useDrumset); - // handle annotations and spanners (directions attached to this note or rest) - if (el->isChordRest()) { - const int spannerStaff = st / VOICES; - const int starttrack = spannerStaff * VOICES; - const int endtrack = (spannerStaff + 1) * VOICES; - spannerStop(this, starttrack, endtrack, _tick, sstaff, spannersStopped); - } - } // for (Segment* seg = ... - _attr.stop(_xml); - } // for (int st = ... + // handle annotations and spanners (directions attached to this note or rest) + if (el->isChordRest()) { + const int spannerStaff = st / VOICES; + const int starttrack = spannerStaff * VOICES; + const int endtrack = (spannerStaff + 1) * VOICES; + spannerStop(this, starttrack, endtrack, _tick, sstaff, spannersStopped); + } + } // for (Segment* seg = ... + _attr.stop(_xml); + } // for (int st = ... + + // restore m and _tick before advancing to next staff in part + m = origM; + _tick += tickDelta; + } // for (int staffIdx = ... } //--------------------------------------------------------- @@ -6571,7 +6631,7 @@ void ExportMusicXml::writeMeasure(const Measure* const m, } // output attribute at start of measure: measure-style - measureStyle(_xml, _attr, m); + measureStyle(_xml, _attr, m, partIndex); // MuseScore limitation: repeats are always in the first part // and are implicitly placed at either measure start or stop @@ -6654,20 +6714,16 @@ void ExportMusicXml::writeParts() } const auto m = toMeasure(mb); - // write the measure or, in case of a multi measure rest, - // the measure range it replaces + // write the measure, or, + // in case of a multimeasure rest, the measure range it replaces, or + // in case of measure repeat, the measure that it indicates for the musician to play if (m->isMMRest()) { - auto m1 = m->mmRestFirst(); const auto m2 = m->mmRestLast(); - for (;;) { + for (auto m1 = m->mmRestFirst(); m1 != m2; m1 = m1->nextMeasure()) { if (m1->isMeasure()) { writeMeasure(m1, partIndex, staffCount, mnsh, fbMap, mpc); mpc.measureWritten(m1); } - if (m1 == m2) { - break; - } - m1 = m1->nextMeasure(); } } else { writeMeasure(m, partIndex, staffCount, mnsh, fbMap, mpc);