Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/include/mx/api/MeasureData.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "mx/api/TransposeData.h"

#include <map>
#include <optional>
#include <string>

namespace mx
Expand Down Expand Up @@ -51,6 +52,15 @@ class MeasureData
// about measure numbering can be defined using the measure-numbering element.
std::string number;

// The measure number as it should appear on the page, when that differs from the 'number'
// above. 'number' identifies the measure (and aligns it with the same measure in other parts),
// while this is purely what the engraver prints. Set it when the printed numbering does not
// follow the identifying numbering -- a second ending that restarts at 8, an editor's numbering
// like "12a", or a rehearsal-driven relabeling. Leave it absent and the printed number is the
// 'number' value. An empty string is not written; use implicit = Bool::yes to suppress the
// number entirely, which is also what makes engravers ignore this field.
std::optional<std::string> displayedNumber;

// The measure-numbering-value type describes how measure numbers are displayed on this part:
// no numbers, numbers every measure, or numbers every system.
MeasureNumbering measureNumbering;
Expand All @@ -65,6 +75,10 @@ class MeasureData
// The <measure-numbering system="..."> attribute; see SystemRelation.
SystemRelation measureNumberingSystemRelation;

// Which staff of the part the measure number is vertically positioned against, zero-based from
// the top staff. Meaningful only when measureNumbering != unspecified. Absent means the top staff.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaningful only when measureNumber != unspecified is not ideal. But there are plenty of patterns already like this and it's easier/faster to just do this than to redesign it now. So I agree with the choice.

std::optional<int> measureNumberingStaffIndex;

// a number greater than zero indicates that this measure is the beginning of a mult-measure
// rest that will last for the indicated number of measures. following measures will be affected
// by this.
Expand Down Expand Up @@ -112,10 +126,12 @@ MXAPI_EQUALS_MEMBER(staves)
MXAPI_EQUALS_MEMBER(timeSignature)
MXAPI_EQUALS_MEMBER(staffTimeSignatures)
MXAPI_EQUALS_MEMBER(number)
MXAPI_EQUALS_MEMBER(displayedNumber)
MXAPI_EQUALS_MEMBER(measureNumbering)
MXAPI_EQUALS_MEMBER(measureNumberingMultipleRestAlways)
MXAPI_EQUALS_MEMBER(measureNumberingMultipleRestRange)
MXAPI_EQUALS_MEMBER(measureNumberingSystemRelation)
MXAPI_EQUALS_MEMBER(measureNumberingStaffIndex)
MXAPI_EQUALS_MEMBER(multiMeasureRest)
MXAPI_EQUALS_MEMBER(multiMeasureRestUseSymbols)
MXAPI_EQUALS_MEMBER(implicit)
Expand Down
11 changes: 11 additions & 0 deletions src/private/mx/impl/MeasureReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ std::pair<api::MeasureData, std::optional<api::TransposeData>> MeasureReader::ge
myOutMeasureData.number = "";
}

if (myPartwiseMeasure.text().has_value())
{
myOutMeasureData.displayedNumber = myPartwiseMeasure.text()->value();
}

if (myPartwiseMeasure.width().has_value())
{
myOutMeasureData.width = static_cast<double>(myPartwiseMeasure.width()->value().value());
Expand Down Expand Up @@ -883,6 +888,12 @@ void MeasureReader::parsePrint(const core::Print &inMxPrint) const
myOutMeasureData.measureNumberingSystemRelation =
myConverter.convertSystemRelation(*measureNumbering.system());
}

if (measureNumbering.staff().has_value())
{
// core staff numbers are one-based; the api index is zero-based.
myOutMeasureData.measureNumberingStaffIndex = measureNumbering.staff()->value() - 1;
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/private/mx/impl/MeasureWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "mx/core/generated/LeftRightMarginsGroup.h"
#include "mx/core/generated/MarginType.h"
#include "mx/core/generated/MeasureNumbering.h"
#include "mx/core/generated/MeasureText.h"
#include "mx/core/generated/MusicDataChoice.h"
#include "mx/core/generated/PageLayout.h"
#include "mx/core/generated/PageLayoutGroup.h"
Expand Down Expand Up @@ -88,6 +89,12 @@ void MeasureWriter::writeMeasureGlobals()
myOutMeasure.setNumber(std::to_string(myHistory.getCursor().measureIndex + 1));
}

// <measure text=""> is not legal MusicXML; an empty displayedNumber means "no override".
if (myMeasureData.displayedNumber.has_value() && !myMeasureData.displayedNumber->empty())
{
myOutMeasure.setText(core::MeasureText{*myMeasureData.displayedNumber});
}

if (myMeasureData.width >= 0.0)
{
myOutMeasure.setWidth(core::Tenths{core::Decimal{static_cast<double>(myMeasureData.width)}});
Expand Down Expand Up @@ -453,6 +460,12 @@ void MeasureWriter::writeMeasureNumbering()
outMeasureNumbering.setSystem(myConverter.convertSystemRelation(myMeasureData.measureNumberingSystemRelation));
}

if (myMeasureData.measureNumberingStaffIndex.has_value())
{
// the api index is zero-based; core staff numbers are one-based.
outMeasureNumbering.setStaff(core::StaffNumber{*myMeasureData.measureNumberingStaffIndex + 1});
}

outPrint.setMeasureNumbering(std::move(outMeasureNumbering));

if (printIndex >= 0)
Expand Down
106 changes: 106 additions & 0 deletions src/private/mxtest/api/MeasureDataTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,112 @@ TEST(measureNumberingUnspecifiedOmitsElement, MeasureData)

T_END;

TEST(measureNumberingStaffRoundTrip, MeasureData)
{
ScoreData score;
score.parts.emplace_back();
auto &part = score.parts.back();
part.measures.emplace_back();
auto &measure = part.measures.back();
measure.measureNumbering = MeasureNumbering::system;
measure.measureNumberingStaffIndex = 1;
measure.staves.emplace_back();
measure.staves.back().voices[0].notes.emplace_back();
measure.staves.emplace_back();
measure.staves.back().voices[0].notes.emplace_back();

const auto xml = mxtest::toXml(score);
CHECK(xml.find("staff=\"2\"") != std::string::npos);

const auto outScore = mxtest::fromXml(xml);
const auto &outMeasure = outScore.parts.front().measures.front();
REQUIRE(outMeasure.measureNumberingStaffIndex.has_value());
CHECK_EQUAL(1, *outMeasure.measureNumberingStaffIndex);
}

T_END;

TEST(measureNumberingStaffAbsentOmitsAttribute, MeasureData)
{
ScoreData score;
score.parts.emplace_back();
auto &part = score.parts.back();
part.measures.emplace_back();
auto &measure = part.measures.back();
measure.measureNumbering = MeasureNumbering::system;
measure.staves.emplace_back();
measure.staves.back().voices[0].notes.emplace_back();

const auto xml = mxtest::toXml(score);
CHECK(xml.find("<measure-numbering staff=") == std::string::npos);

const auto outScore = mxtest::fromXml(xml);
CHECK(!outScore.parts.front().measures.front().measureNumberingStaffIndex.has_value());
}

T_END;

TEST(displayedNumberRoundTrip, MeasureData)
{
ScoreData score;
score.parts.emplace_back();
auto &part = score.parts.back();
part.measures.emplace_back();
auto &measure = part.measures.back();
measure.number = "7";
measure.displayedNumber = "12a";
measure.staves.emplace_back();
measure.staves.back().voices[0].notes.emplace_back();

const auto xml = mxtest::toXml(score);
CHECK(xml.find("number=\"7\"") != std::string::npos);
CHECK(xml.find("text=\"12a\"") != std::string::npos);

const auto outScore = mxtest::fromXml(xml);
const auto &outMeasure = outScore.parts.front().measures.front();
CHECK_EQUAL("7", outMeasure.number);
REQUIRE(outMeasure.displayedNumber.has_value());
CHECK_EQUAL("12a", *outMeasure.displayedNumber);
}

T_END;

TEST(displayedNumberAbsentOmitsAttribute, MeasureData)
{
ScoreData score;
score.parts.emplace_back();
auto &part = score.parts.back();
part.measures.emplace_back();
auto &measure = part.measures.back();
measure.staves.emplace_back();
measure.staves.back().voices[0].notes.emplace_back();

const auto xml = mxtest::toXml(score);
CHECK(xml.find("text=") == std::string::npos);

const auto outScore = mxtest::fromXml(xml);
CHECK(!outScore.parts.front().measures.front().displayedNumber.has_value());
}

T_END;

TEST(emptyDisplayedNumberOmitsAttribute, MeasureData)
{
ScoreData score;
score.parts.emplace_back();
auto &part = score.parts.back();
part.measures.emplace_back();
auto &measure = part.measures.back();
measure.displayedNumber = "";
measure.staves.emplace_back();
measure.staves.back().voices[0].notes.emplace_back();

const auto xml = mxtest::toXml(score);
CHECK(xml.find("text=") == std::string::npos);
}

T_END;

TEST(multiMeasureRestRoundTrip, MeasureData)
{
ScoreData score;
Expand Down
Loading