Skip to content

Commit

Permalink
Enable MusicXML export of MeasureRepeats
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaacWeiss committed Aug 10, 2020
1 parent f195758 commit 19943c2
Showing 1 changed file with 140 additions and 84 deletions.
224 changes: 140 additions & 84 deletions mu4/domain/importexport/internal/musicxml/exportxml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -5052,18 +5053,52 @@ 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()) {
attr.doAttr(xml, true);
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();
}
}
}
}

Expand Down Expand Up @@ -6396,7 +6431,7 @@ static std::vector<TBox*> 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,
Expand All @@ -6413,88 +6448,113 @@ void ExportMusicXml::writeMeasureTracks(const Measure* const m,
// required to prevent multiple spanner stops for the same spanner
QSet<const Spanner*> 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 <measure-repeat> "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<const ChordRest*>(el), fbMap, div);
spannerStart(this, strack, etrack, st, sstaff, seg);
}
figuredBass(_xml, strack, etrack, st, static_cast<const ChordRest*>(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 = ...
}

//---------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 19943c2

Please sign in to comment.