Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/include/mx/api/ClefData.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ enum class ClefSymbol
jianpu
};

// Where a clef sits relative to its measure. A clef at the start of a measure normally prints
// before the barline; beforeBarline and afterBarline choose between the two, which matters for cue
// clefs and for the clef that resumes after a repeated section. midMeasure describes a clef change
// partway through the measure, positioned by tickTimePosition. unspecified leaves the choice to the
// notation program.
enum class ClefLocation
{
unspecified,
Expand Down Expand Up @@ -53,6 +58,12 @@ class ClefData
bool isOctaveChangeSpecified;
int tickTimePosition;
ClefLocation location;
// Marks a supplementary clef placed on the staff alongside the regular one - a cue clef, or a
// second clef in effect at the same time. MusicXML writes this as clef@additional="yes"; such a
// clef sits at a non-standard line position, is not restated at the start of each system, and
// notation software disregards its line. unspecified (the default) omits the attribute, which
// describes an ordinary clef.
Bool additional;
// Visibility of the clef via the MusicXML print-object attribute.
// unspecified -> omit the attribute, yes/no -> write print-object verbatim.
Bool printObject;
Expand Down Expand Up @@ -86,6 +97,7 @@ MXAPI_EQUALS_MEMBER(octaveChange)
MXAPI_EQUALS_MEMBER(isOctaveChangeSpecified)
MXAPI_EQUALS_MEMBER(tickTimePosition)
MXAPI_EQUALS_MEMBER(location)
MXAPI_EQUALS_MEMBER(additional)
MXAPI_EQUALS_MEMBER(printObject)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(ClefData);
Expand Down
2 changes: 1 addition & 1 deletion src/private/mx/api/ClefData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace api
ClefData::ClefData()
: writeStaffNumber{Bool::unspecified}, symbol{DEFAULT_CLEF_SYMBOL}, line{DEFAULT_CLEF_LINE}, isLineSpecified{true},
octaveChange{DEFAULT_CLEF_OCTAVE_CHANGE}, isOctaveChangeSpecified{true}, tickTimePosition{0},
location{ClefLocation::unspecified}, printObject{Bool::unspecified}
location{ClefLocation::unspecified}, additional{Bool::unspecified}, printObject{Bool::unspecified}
{
}

Expand Down
5 changes: 5 additions & 0 deletions src/private/mx/impl/MeasureReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,11 @@ void MeasureReader::importClef(const core::Clef &inClef) const
clefData.isOctaveChangeSpecified = false;
}

if (inClef.additional().has_value())
{
clefData.additional = converter.convert(*inClef.additional());
}

if (inClef.printObject().has_value())
{
clefData.printObject = converter.convert(*inClef.printObject());
Expand Down
16 changes: 16 additions & 0 deletions src/private/mx/impl/PropertiesWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,22 @@ void PropertiesWriter::writeClef(int staffIndex, const api::ClefData &inClefData
cg.setClefOctaveChange(inClefData.octaveChange);
}

if (inClefData.additional != api::Bool::unspecified)
{
mxClef.setAdditional(converter.convert(inClefData.additional));
}

// after-barline states which side of the barline a start-of-measure clef prints on. MusicXML
// ignores it on mid-measure clefs, so those emit nothing.
if (inClefData.location == api::ClefLocation::afterBarline)
{
mxClef.setAfterBarline(core::YesNo::yes());
}
else if (inClefData.location == api::ClefLocation::beforeBarline)
{
mxClef.setAfterBarline(core::YesNo::no());
}

if (inClefData.printObject != api::Bool::unspecified)
{
mxClef.setPrintObject(converter.convert(inClefData.printObject));
Expand Down
171 changes: 171 additions & 0 deletions src/private/mxtest/api/ClefDataTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ ScoreData makeScoreWithClef(Bool printObject)
staff.voices[0].notes.emplace_back();
return score;
}

ScoreData makeScoreWithAdditionalClef(Bool additional)
{
ScoreData score = makeScoreWithClef(Bool::unspecified);
score.parts.front().measures.front().staves.front().clefs.front().additional = additional;
return score;
}

ScoreData makeScoreWithClefLocation(ClefLocation location)
{
ScoreData score = makeScoreWithClef(Bool::unspecified);
score.parts.front().measures.front().staves.front().clefs.front().location = location;
return score;
}

const ClefData &firstClef(const ScoreData &score)
{
return score.parts.front().measures.front().staves.front().clefs.front();
}
} // namespace mxtest::api::clef_data_test

TEST(clefPrintObjectNoRoundTrip, ClefData)
Expand Down Expand Up @@ -78,4 +97,156 @@ TEST(clefPrintObjectUnspecifiedIsOmitted, ClefData)

T_END;

TEST(clefAdditionalYesRoundTrip, ClefData)
{
const auto xml = mxtest::toXml(mxtest::api::clef_data_test::makeScoreWithAdditionalClef(Bool::yes));
CHECK(xml.find("additional=\"yes\"") != std::string::npos);

const auto out = mxtest::fromXml(xml);
CHECK_EQUAL(1, out.parts.size());
CHECK_EQUAL(1, out.parts.front().measures.size());
CHECK_EQUAL(1, out.parts.front().measures.front().staves.size());
CHECK_EQUAL(1, out.parts.front().measures.front().staves.front().clefs.size());
CHECK(Bool::yes == out.parts.front().measures.front().staves.front().clefs.front().additional);
}

T_END;

TEST(clefAdditionalNoRoundTrip, ClefData)
{
const auto xml = mxtest::toXml(mxtest::api::clef_data_test::makeScoreWithAdditionalClef(Bool::no));
CHECK(xml.find("additional=\"no\"") != std::string::npos);

const auto out = mxtest::fromXml(xml);
CHECK_EQUAL(1, out.parts.size());
CHECK_EQUAL(1, out.parts.front().measures.size());
CHECK_EQUAL(1, out.parts.front().measures.front().staves.size());
CHECK_EQUAL(1, out.parts.front().measures.front().staves.front().clefs.size());
CHECK(Bool::no == out.parts.front().measures.front().staves.front().clefs.front().additional);
}

T_END;

TEST(clefAdditionalUnspecifiedIsOmitted, ClefData)
{
const auto xml = mxtest::toXml(mxtest::api::clef_data_test::makeScoreWithAdditionalClef(Bool::unspecified));
CHECK(xml.find("additional=") == std::string::npos);

const auto out = mxtest::fromXml(xml);
CHECK_EQUAL(1, out.parts.size());
CHECK_EQUAL(1, out.parts.front().measures.size());
CHECK_EQUAL(1, out.parts.front().measures.front().staves.size());
CHECK_EQUAL(1, out.parts.front().measures.front().staves.front().clefs.size());
CHECK(Bool::unspecified == out.parts.front().measures.front().staves.front().clefs.front().additional);
}

T_END;

TEST(clefAfterBarlineYesRoundTrip, ClefData)
{
using namespace mxtest::api::clef_data_test;
const auto xml = mxtest::toXml(makeScoreWithClefLocation(ClefLocation::afterBarline));
CHECK(xml.find("after-barline=\"yes\"") != std::string::npos);

const auto out = mxtest::fromXml(xml);
CHECK_EQUAL(1, out.parts.front().measures.front().staves.front().clefs.size());
CHECK(ClefLocation::afterBarline == firstClef(out).location);
}

T_END;

TEST(clefAfterBarlineNoRoundTrip, ClefData)
{
using namespace mxtest::api::clef_data_test;
const auto xml = mxtest::toXml(makeScoreWithClefLocation(ClefLocation::beforeBarline));
CHECK(xml.find("after-barline=\"no\"") != std::string::npos);

const auto out = mxtest::fromXml(xml);
CHECK_EQUAL(1, out.parts.front().measures.front().staves.front().clefs.size());
CHECK(ClefLocation::beforeBarline == firstClef(out).location);
}

T_END;

TEST(clefAfterBarlineUnspecifiedIsOmitted, ClefData)
{
using namespace mxtest::api::clef_data_test;
const auto xml = mxtest::toXml(makeScoreWithClefLocation(ClefLocation::unspecified));
CHECK(xml.find("after-barline=") == std::string::npos);

const auto out = mxtest::fromXml(xml);
CHECK_EQUAL(1, out.parts.front().measures.front().staves.front().clefs.size());
CHECK(ClefLocation::unspecified == firstClef(out).location);
}

T_END;

TEST(clefAllFieldsSurviveWrite, ClefData)
{
using namespace mxtest::api::clef_data_test;
ScoreData score = makeScoreWithClef(Bool::unspecified);
auto &clef = score.parts.front().measures.front().staves.front().clefs.front();
clef.writeStaffNumber = Bool::yes;
clef.symbol = ClefSymbol::f;
clef.line = 4;
clef.isLineSpecified = true;
clef.octaveChange = -1;
clef.isOctaveChangeSpecified = true;
clef.tickTimePosition = 0;
clef.location = ClefLocation::afterBarline;
clef.additional = Bool::yes;
clef.printObject = Bool::no;
const ClefData expected = clef;

const auto out = mxtest::fromXml(mxtest::toXml(score));
const auto &actual = firstClef(out);
CHECK(expected.writeStaffNumber == actual.writeStaffNumber);
CHECK(expected.symbol == actual.symbol);
CHECK_EQUAL(expected.line, actual.line);
CHECK_EQUAL(expected.isLineSpecified, actual.isLineSpecified);
CHECK_EQUAL(expected.octaveChange, actual.octaveChange);
CHECK_EQUAL(expected.isOctaveChangeSpecified, actual.isOctaveChangeSpecified);
CHECK_EQUAL(expected.tickTimePosition, actual.tickTimePosition);
CHECK(expected.location == actual.location);
CHECK(expected.additional == actual.additional);
CHECK(expected.printObject == actual.printObject);
CHECK(expected == actual);
}

T_END;

TEST(clefUnspecifiedElementsSurviveWrite, ClefData)
{
using namespace mxtest::api::clef_data_test;
ScoreData score = makeScoreWithClef(Bool::unspecified);
auto &clef = score.parts.front().measures.front().staves.front().clefs.front();
clef.symbol = ClefSymbol::c;
clef.isLineSpecified = false;
clef.line = 3;
clef.isOctaveChangeSpecified = false;
clef.octaveChange = 0;
clef.writeStaffNumber = Bool::no;
const ClefData expected = clef;

const auto xml = mxtest::toXml(score);
CHECK(xml.find("<line>") == std::string::npos);
CHECK(xml.find("<clef-octave-change>") == std::string::npos);
CHECK(xml.find("<clef>") != std::string::npos);

const auto out = mxtest::fromXml(xml);
const auto &actual = firstClef(out);
CHECK(expected.symbol == actual.symbol);
CHECK_EQUAL(expected.line, actual.line);
CHECK_EQUAL(expected.isLineSpecified, actual.isLineSpecified);
CHECK_EQUAL(expected.octaveChange, actual.octaveChange);
CHECK_EQUAL(expected.isOctaveChangeSpecified, actual.isOctaveChangeSpecified);

// writeStaffNumber records only a divergence from the automatic rule. On a single-staff part
// the automatic rule already omits the number, so no agrees with it and reads back as
// unspecified. The emitted XML is the same either way.
CHECK(Bool::unspecified == actual.writeStaffNumber);
}

T_END;

#endif
Loading