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
10 changes: 6 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,15 @@ if(MX_CORE_DEV)
# corert: round-trips each data/ file through mx::core (pugixml-backed)
# and compares against a normalized, fixup-adjusted form of the input.
# Fixer applies .fixup.xml sidecars; cpul is the Catch test runner
# (main); the import/ helpers normalize whitespace, decimals, and
# attribute order.
# (main); the import/ helpers normalize whitespace, decimals,
# comma-separated-text spacing, and attribute order.
file(GLOB_RECURSE SRC_CORERT ${PRIVATE_DIR}/mxtest/corert/*.cpp ${PRIVATE_DIR}/mxtest/corert/*.h)
file(GLOB_RECURSE SRC_CPUL ${PRIVATE_DIR}/cpul/*.cpp ${PRIVATE_DIR}/cpul/*.h)
set(SRC_CORERT_HELPERS
${PRIVATE_DIR}/mxtest/import/Normalize.cpp
${PRIVATE_DIR}/mxtest/import/Normalize.h
${PRIVATE_DIR}/mxtest/import/DecimalFields.h)
${PRIVATE_DIR}/mxtest/import/DecimalFields.h
${PRIVATE_DIR}/mxtest/import/CommaSeparatedFields.h)

# PathRoot.h hands the corert file walker the repo root at configure time.
file(WRITE ${PRIVATE_DIR}/mxtest/file/PathRoot.h
Expand Down Expand Up @@ -219,7 +220,8 @@ if(MX_API)
${PRIVATE_DIR}/mxtest/corert/Fixer.h
${PRIVATE_DIR}/mxtest/import/Normalize.cpp
${PRIVATE_DIR}/mxtest/import/Normalize.h
${PRIVATE_DIR}/mxtest/import/DecimalFields.h)
${PRIVATE_DIR}/mxtest/import/DecimalFields.h
${PRIVATE_DIR}/mxtest/import/CommaSeparatedFields.h)
target_include_directories(mxtest-api-roundtrip PRIVATE ${PRIVATE_DIR})
target_link_libraries(mxtest-api-roundtrip mx ${CMAKE_THREAD_LIBS_INIT})

Expand Down
6 changes: 5 additions & 1 deletion data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ both so the comparison is canonical-against-canonical. The pipeline lives in
3. Strip trailing zeros from decimal fields: `mx` serializes the shortest round-trip form, so a
trailing-zero decimal like `40.00000` in a source file would otherwise mismatch. The field list
lives in `DecimalFields.h`.
4. Sort each element's attributes alphabetically by qualified name (`xlink:href`, not `href`); it
4. Re-space comma-separated-text fields (e.g. `font-family`) to `"item, item"`: the type allows
`"x,y"` or `"x, y"` on the wire and a parsed-but-unmutated value keeps its exact spelling on
write, but `mx`'s api round trip always re-serializes as `"x, y"`. The field list lives in
`CommaSeparatedFields.h`.
5. Sort each element's attributes alphabetically by qualified name (`xlink:href`, not `href`); it
runs last.

Comparison rules that took debugging to get right:
Expand Down
13 changes: 13 additions & 0 deletions src/include/mx/api/ApiCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ enum class MeasureNumbering
measure,
system
};

// The <measure-numbering system="..."> attribute: whether this part's measure numbers are also,
// or only, associated with the system (as opposed to just this part). unspecified means the
// attribute is absent.
enum class SystemRelation
{
unspecified,
none,
onlyTop,
onlyBottom,
alsoTop,
alsoBottom
};
} // namespace api
} // namespace mx

Expand Down
9 changes: 1 addition & 8 deletions src/include/mx/api/DefaultsData.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,9 @@ class DefaultsData
/// The `<lyric-font>` defaults (one per lyric line/verse).
std::vector<LyricFontData> lyricFonts;

// TODO - this appears not to be used anywhere, please do not use
/// Measure numbering setting, at the global level, will be stated
/// in first measure's <print> tag. This can can be overridden by a
/// value in the Measure.
MeasureNumbering measureNumbering;

DefaultsData()
: scalingMillimeters{DOUBLE_UNSPECIFIED}, scalingTenths{DOUBLE_UNSPECIFIED}, pageLayout{}, systemLayout{},
appearance{}, musicFont{}, wordFont{}, lyricFonts{}, measureNumbering{MeasureNumbering::unspecified}
appearance{}, musicFont{}, wordFont{}, lyricFonts{}
{
}
};
Expand All @@ -157,7 +151,6 @@ if (!areVectorsEqual(lhs.lyricFonts, rhs.lyricFonts))
MX_SHOW_UNEQUAL("DefaultsData", "lyricFonts");
return false;
}
MXAPI_EQUALS_MEMBER(measureNumbering)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(DefaultsData);
} // namespace api
Expand Down
18 changes: 16 additions & 2 deletions src/include/mx/api/MeasureData.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ class MeasureData
// no numbers, numbers every measure, or numbers every system.
MeasureNumbering measureNumbering;

// Rare <measure-numbering> attributes, meaningful only when measureNumbering !=
// unspecified. multipleRestAlways forces the number to show even when a multi-measure rest
// starts mid-system; multipleRestRange shows the first-last range instead of just the first
// measure's number.
Bool measureNumberingMultipleRestAlways;
Bool measureNumberingMultipleRestRange;

// The <measure-numbering system="..."> attribute; see SystemRelation.
SystemRelation measureNumberingSystemRelation;

// 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 @@ -85,8 +95,9 @@ class MeasureData

MeasureData()
: staves{}, timeSignature{}, number{}, measureNumbering{MeasureNumbering::unspecified},
multiMeasureRest{VALUE_UNSPECIFIED}, implicit{Bool::unspecified}, nonControlling{Bool::unspecified},
width{DOUBLE_UNSPECIFIED}
measureNumberingMultipleRestAlways{Bool::unspecified}, measureNumberingMultipleRestRange{Bool::unspecified},
measureNumberingSystemRelation{SystemRelation::unspecified}, multiMeasureRest{VALUE_UNSPECIFIED},
implicit{Bool::unspecified}, nonControlling{Bool::unspecified}, width{DOUBLE_UNSPECIFIED}
{
}
};
Expand All @@ -97,6 +108,9 @@ MXAPI_EQUALS_MEMBER(timeSignature)
MXAPI_EQUALS_MEMBER(staffTimeSignatures)
MXAPI_EQUALS_MEMBER(number)
MXAPI_EQUALS_MEMBER(measureNumbering)
MXAPI_EQUALS_MEMBER(measureNumberingMultipleRestAlways)
MXAPI_EQUALS_MEMBER(measureNumberingMultipleRestRange)
MXAPI_EQUALS_MEMBER(measureNumberingSystemRelation)
MXAPI_EQUALS_MEMBER(multiMeasureRest)
MXAPI_EQUALS_MEMBER(implicit)
MXAPI_EQUALS_MEMBER(nonControlling)
Expand Down
13 changes: 13 additions & 0 deletions src/include/mx/api/PartData.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ class PartData
PrintData displayAbbreviationPrintData;
PositionData displayAbbreviationPositionData;

// Names of this <score-part>'s repeatable <group> elements. MusicXML documentation:
// "The group element allows the use of different versions of the part for different
// purposes. Typical values include score, parts, sound, and data." In practice this is
// an implementation detail of some notation software (e.g. Finale), which uses it to
// tag which exported/extracted version of a part a <score-part> entry represents.
//
// This is NOT the "real"/visual part grouping mechanism -- braces and brackets tying
// staves together on the page are PartGroupData (see ScoreData::partGroups), a
// completely different element (<part-group>, declared in <part-list> around a run of
// <score-part> entries, not inside one).
std::vector<std::string> groups;

InstrumentData instrumentData;

/// The initial transposition for the part. If the music entered into the part is not in
Expand Down Expand Up @@ -254,6 +266,7 @@ MXAPI_EQUALS_MEMBER(displayNamePositionData)
MXAPI_EQUALS_MEMBER(displayAbbreviation)
MXAPI_EQUALS_MEMBER(displayAbbreviationPrintData)
MXAPI_EQUALS_MEMBER(displayAbbreviationPositionData)
MXAPI_EQUALS_MEMBER(groups)
MXAPI_EQUALS_MEMBER(instrumentData)
MXAPI_EQUALS_MEMBER(transposition)
MXAPI_EQUALS_MEMBER(measures)
Expand Down
54 changes: 50 additions & 4 deletions src/include/mx/api/SoundData.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,50 @@

#include "mx/api/ApiCommon.h"

#include <optional>
#include <string>

namespace mx
{
namespace api
{
// The swing-type-value type: which note type (eighth or 16th) a SwingData ratio applies to.
// unspecified means the source did not state it, which MusicXML treats as "eighth".
enum class SwingNoteType
{
unspecified,
eighth,
dur16th
};

// MusicXML Documentation: The swing element specifies whether or not to use swing playback, where
// consecutive on-beat/off-beat eighth or 16th notes are played with unequal nominal durations.
struct SwingData
{
// true: <straight/>, no swing, consecutive notes have equal durations. false: a swing ratio
// is in effect, described by ratioFirst/ratioSecond/noteType below.
bool isStraight;

// The swing ratio's two terms (e.g. 2 and 1 for a 2:1 ratio). Only meaningful when
// isStraight is false.
int ratioFirst;
int ratioSecond;

// Which note type the ratio applies to. Only meaningful when isStraight is false.
SwingNoteType noteType;

// A free-text description of the swing style (e.g. "Swing"). Empty means unspecified.
std::string style;

SwingData() : isStraight{true}, ratioFirst{0}, ratioSecond{0}, noteType{SwingNoteType::unspecified}, style{}
{
}
};

// MusicXML Documentation: The sound element contains general playback parameters. They can stand
// alone within a part/measure, or be a component element within a direction.
//
// mx::api models the commonly-used scalar attributes of <sound>. The nested child elements
// (<midi-instrument>, <midi-device>, <play>, <swing>, <offset>) are intentionally not modeled.
// mx::api models the commonly-used scalar attributes of <sound>, plus <swing>.
//
// A SoundData is carried on DirectionData. When a DirectionData holds a SoundData but no other
// direction content, it round-trips as a standalone <sound> element within the measure. When a
Expand Down Expand Up @@ -52,20 +85,32 @@ struct SoundData
std::string tocoda;
std::string fine;

std::optional<SwingData> swing;

SoundData()
: tempo{DOUBLE_UNSPECIFIED}, dynamics{DOUBLE_UNSPECIFIED}, dacapo{Bool::unspecified},
forwardRepeat{Bool::unspecified}, pizzicato{Bool::unspecified}, segno{}, dalsegno{}, coda{}, tocoda{}, fine{}
forwardRepeat{Bool::unspecified}, pizzicato{Bool::unspecified}, segno{}, dalsegno{}, coda{}, tocoda{}, fine{},
swing{}
{
}

bool isSpecified() const
{
return tempo >= 0.0 || dynamics >= 0.0 || dacapo != Bool::unspecified || forwardRepeat != Bool::unspecified ||
pizzicato != Bool::unspecified || !segno.empty() || !dalsegno.empty() || !coda.empty() ||
!tocoda.empty() || !fine.empty();
!tocoda.empty() || !fine.empty() || swing.has_value();
}
};

MXAPI_EQUALS_BEGIN(SwingData)
MXAPI_EQUALS_MEMBER(isStraight)
MXAPI_EQUALS_MEMBER(ratioFirst)
MXAPI_EQUALS_MEMBER(ratioSecond)
MXAPI_EQUALS_MEMBER(noteType)
MXAPI_EQUALS_MEMBER(style)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(SwingData);

MXAPI_EQUALS_BEGIN(SoundData)
MXAPI_DOUBLES_EQUALS_MEMBER(tempo)
MXAPI_DOUBLES_EQUALS_MEMBER(dynamics)
Expand All @@ -77,6 +122,7 @@ MXAPI_EQUALS_MEMBER(dalsegno)
MXAPI_EQUALS_MEMBER(coda)
MXAPI_EQUALS_MEMBER(tocoda)
MXAPI_EQUALS_MEMBER(fine)
MXAPI_EQUALS_MEMBER(swing)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(SoundData);
} // namespace api
Expand Down
18 changes: 18 additions & 0 deletions src/private/mx/impl/Converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@ const Converter::EnumMap<core::MeasureNumberingValue, api::MeasureNumbering> Con
{core::MeasureNumberingValue::system(), api::MeasureNumbering::system},
};

const Converter::EnumMap<core::SystemRelationNumber, api::SystemRelation> Converter::systemRelationMap = {
{core::SystemRelationNumber::none(), api::SystemRelation::none},
{core::SystemRelationNumber::onlyTop(), api::SystemRelation::onlyTop},
{core::SystemRelationNumber::onlyBottom(), api::SystemRelation::onlyBottom},
{core::SystemRelationNumber::alsoTop(), api::SystemRelation::alsoTop},
{core::SystemRelationNumber::alsoBottom(), api::SystemRelation::alsoBottom},
};

const Converter::EnumMap<core::TechnicalChoice::Kind, api::MarkType> Converter::technicalMarkMap = {
// { core::TechnicalChoice::Kind::technical,
// api::MarkType::unspecified },
Expand Down Expand Up @@ -1571,6 +1579,16 @@ api::MeasureNumbering Converter::convertMeasureNumbering(core::MeasureNumberingV
return findApiItem(measureNumberingMap, api::MeasureNumbering::unspecified, value);
}

core::SystemRelationNumber Converter::convertSystemRelation(api::SystemRelation value) const
{
return findCoreItem(systemRelationMap, core::SystemRelationNumber::none(), value);
}

api::SystemRelation Converter::convertSystemRelation(core::SystemRelationNumber value) const
{
return findApiItem(systemRelationMap, api::SystemRelation::unspecified, value);
}

core::StemValue Converter::convert(api::Stem value) const
{
return findCoreItem(stemMap, core::StemValue::up(), value);
Expand Down
5 changes: 5 additions & 0 deletions src/private/mx/impl/Converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "mx/core/generated/StartStopDiscontinue.h"
#include "mx/core/generated/StemValue.h"
#include "mx/core/generated/Step.h"
#include "mx/core/generated/SystemRelationNumber.h"
#include "mx/core/generated/TechnicalChoice.h"
#include "mx/core/generated/TimeRelation.h"
#include "mx/core/generated/TimeSeparator.h"
Expand Down Expand Up @@ -124,6 +125,9 @@ class Converter
core::MeasureNumberingValue convertMeasureNumbering(api::MeasureNumbering value) const;
api::MeasureNumbering convertMeasureNumbering(core::MeasureNumberingValue value) const;

core::SystemRelationNumber convertSystemRelation(api::SystemRelation value) const;
api::SystemRelation convertSystemRelation(core::SystemRelationNumber value) const;

core::StemValue convert(api::Stem value) const;
api::Stem convert(core::StemValue value) const;

Expand Down Expand Up @@ -205,6 +209,7 @@ class Converter
const static EnumMap<core::AccidentalValue, api::MarkType> accidentalMarkMap;
const static EnumMap<core::TechnicalChoice::Kind, api::MarkType> technicalMarkMap;
const static EnumMap<core::MeasureNumberingValue, api::MeasureNumbering> measureNumberingMap;
const static EnumMap<core::SystemRelationNumber, api::SystemRelation> systemRelationMap;
const static EnumMap<core::StemValue, api::Stem> stemMap;
const static EnumMap<core::LineType, api::LineType> lineType;
const static EnumMap<core::WedgeType, api::WedgeType> wedgeMap;
Expand Down
21 changes: 20 additions & 1 deletion src/private/mx/impl/MeasureReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,26 @@ void MeasureReader::parsePrint(const core::Print &inMxPrint) const
// not carried forward like time/key), so it is captured directly here instead.
if (inMxPrint.measureNumbering().has_value())
{
myOutMeasureData.measureNumbering = myConverter.convertMeasureNumbering(inMxPrint.measureNumbering()->value());
const auto &measureNumbering = *inMxPrint.measureNumbering();
myOutMeasureData.measureNumbering = myConverter.convertMeasureNumbering(measureNumbering.value());

if (measureNumbering.multipleRestAlways().has_value())
{
myOutMeasureData.measureNumberingMultipleRestAlways =
myConverter.convert(*measureNumbering.multipleRestAlways());
}

if (measureNumbering.multipleRestRange().has_value())
{
myOutMeasureData.measureNumberingMultipleRestRange =
myConverter.convert(*measureNumbering.multipleRestRange());
}

if (measureNumbering.system().has_value())
{
myOutMeasureData.measureNumberingSystemRelation =
myConverter.convertSystemRelation(*measureNumbering.system());
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/private/mx/impl/MeasureWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,23 @@ void MeasureWriter::writeMeasureNumbering()

core::MeasureNumbering outMeasureNumbering{};
outMeasureNumbering.setValue(myConverter.convertMeasureNumbering(myMeasureData.measureNumbering));

if (myMeasureData.measureNumberingMultipleRestAlways != api::Bool::unspecified)
{
outMeasureNumbering.setMultipleRestAlways(
myConverter.convert(myMeasureData.measureNumberingMultipleRestAlways));
}

if (myMeasureData.measureNumberingMultipleRestRange != api::Bool::unspecified)
{
outMeasureNumbering.setMultipleRestRange(myConverter.convert(myMeasureData.measureNumberingMultipleRestRange));
}

if (myMeasureData.measureNumberingSystemRelation != api::SystemRelation::unspecified)
{
outMeasureNumbering.setSystem(myConverter.convertSystemRelation(myMeasureData.measureNumberingSystemRelation));
}

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

if (printIndex >= 0)
Expand Down
5 changes: 5 additions & 0 deletions src/private/mx/impl/PartReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ void PartReader::parseScorePart() const
myOutPartData.displayAbbreviationPositionData);
}

for (const auto &group : myScorePart.group())
{
myOutPartData.groups.push_back(group);
}

if (!myScorePart.scoreInstrument().empty())
{
parseScoreInstrument(myScorePart.scoreInstrument().front());
Expand Down
5 changes: 5 additions & 0 deletions src/private/mx/impl/PartWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ core::ScorePart PartWriter::getScorePart() const
myPartData.displayAbbreviationPositionData));
}

for (const auto &group : myPartData.groups)
{
scorePart.addGroup(group);
}

core::ScoreInstrument scoreInstrument{};
bool addScoreInstrument = false;
scoreInstrument.setID(core::Token{myPartData.instrumentData.uniqueId});
Expand Down
Loading
Loading