From 1fda1b81af4689741d4f1e5a60a1c950086ad7d2 Mon Sep 17 00:00:00 2001 From: Robert Patterson Date: Tue, 30 Jun 2026 11:03:34 -0500 Subject: [PATCH 1/6] Add `` to API. --- src/include/mx/api/StaffData.h | 2 + src/private/mx/impl/MeasureReader.cpp | 12 +++- src/private/mx/impl/MeasureWriter.cpp | 4 +- src/private/mx/impl/PropertiesWriter.cpp | 21 +++++- src/private/mx/impl/PropertiesWriter.h | 1 + src/private/mxtest/api/MeasureDataTest.cpp | 55 ++++++++++++++++ src/private/mxtest/impl/MeasureWriterTest.cpp | 64 +++++++++++++++++++ 7 files changed, 152 insertions(+), 7 deletions(-) diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index f05417d77..a9e279e27 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -19,6 +19,7 @@ class StaffData { public: int staffLines = -1; + double staffSize = -1.0; std::vector clefs; // for the use case where key signatures @@ -55,6 +56,7 @@ inline bool voicesAreEqual(const std::map &l, const std::map(staffIndex)).staffLines = staffDetails.group()->staffLines(); + auto &staffData = myOutMeasureData.staves.at(static_cast(staffIndex)); + if (staffDetails.group().has_value()) + { + staffData.staffLines = staffDetails.group()->staffLines(); + } + if (staffDetails.staffSize().has_value()) + { + staffData.staffSize = staffDetails.staffSize()->value().value().value(); + } } } diff --git a/src/private/mx/impl/MeasureWriter.cpp b/src/private/mx/impl/MeasureWriter.cpp index 9064d5168..63190dafd 100644 --- a/src/private/mx/impl/MeasureWriter.cpp +++ b/src/private/mx/impl/MeasureWriter.cpp @@ -130,14 +130,14 @@ void MeasureWriter::writeMeasureGlobals() for (const auto &staff : myMeasureData.staves) { - if (staff.staffLines >= 0) + if (staff.staffLines >= 0 || staff.staffSize >= 0.0) { int desiredStaffIndex = -1; if (myHistory.getCursor().getNumStaves() > 1) { desiredStaffIndex = localStaffCounter; } - myPropertiesWriter->writeStaffDetails(desiredStaffIndex, staff.staffLines); + myPropertiesWriter->writeStaffDetails(desiredStaffIndex, staff.staffLines, staff.staffSize); } auto clefIter = staff.clefs.cbegin(); diff --git a/src/private/mx/impl/PropertiesWriter.cpp b/src/private/mx/impl/PropertiesWriter.cpp index 55f24e661..1f71aa181 100644 --- a/src/private/mx/impl/PropertiesWriter.cpp +++ b/src/private/mx/impl/PropertiesWriter.cpp @@ -182,6 +182,11 @@ void PropertiesWriter::writeNumStaves(int value) } void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines) +{ + writeStaffDetails(staffIndex, staffLines, -1.0); +} + +void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines, double staffSize) { core::StaffDetails staffDetails{}; @@ -190,9 +195,19 @@ void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines) staffDetails.setNumber(core::StaffNumber{staffIndex + 1}); } - core::StaffDetailsGroup sdg{}; - sdg.setStaffLines(staffLines); - staffDetails.setGroup(sdg); + if (staffLines >= 0) + { + core::StaffDetailsGroup sdg{}; + sdg.setStaffLines(staffLines); + staffDetails.setGroup(sdg); + } + + if (staffSize >= 0.0) + { + core::StaffSize size{}; + size.setValue(core::NonNegativeDecimal{core::Decimal{staffSize}}); + staffDetails.setStaffSize(size); + } myAttributes.addStaffDetails(staffDetails); myHasContent = true; diff --git a/src/private/mx/impl/PropertiesWriter.h b/src/private/mx/impl/PropertiesWriter.h index 60d80e32d..5d385ef4d 100644 --- a/src/private/mx/impl/PropertiesWriter.h +++ b/src/private/mx/impl/PropertiesWriter.h @@ -52,6 +52,7 @@ class PropertiesWriter void writeTime(const api::TimeSignatureData &value); void writeNumStaves(int value); void writeStaffDetails(int staffIndex, int staffLines); + void writeStaffDetails(int staffIndex, int staffLines, double staffSize); void writeClef(int staffIndex, const api::ClefData &inClefData); void writePartSymbol(const api::PartSymbolData &inPartSymbolData); void writeTranspose(const api::TransposeData &inTransposeData); diff --git a/src/private/mxtest/api/MeasureDataTest.cpp b/src/private/mxtest/api/MeasureDataTest.cpp index 3d7f8defd..2f85818a1 100644 --- a/src/private/mxtest/api/MeasureDataTest.cpp +++ b/src/private/mxtest/api/MeasureDataTest.cpp @@ -176,4 +176,59 @@ TEST(staffLinesRoundTrip, MeasureData) T_END; +TEST(staffSizeRoundTrip, 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(); + auto &staff = measure.staves.back(); + staff.staffSize = 80.5; + staff.voices[0].notes.emplace_back(); + + const auto xml = mxtest::toXml(score); + CHECK(xml.find("") != std::string::npos); + CHECK(xml.find("80.5") != std::string::npos); + CHECK(xml.find("") == std::string::npos); + + const auto outScore = mxtest::fromXml(xml); + CHECK_EQUAL(1, outScore.parts.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.size()); + CHECK(80.5 == outScore.parts.front().measures.front().staves.front().staffSize); + CHECK_EQUAL(-1, outScore.parts.front().measures.front().staves.front().staffLines); +} + +T_END; + +TEST(staffLinesAndStaffSizeRoundTrip, 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(); + auto &staff = measure.staves.back(); + staff.staffLines = 1; + staff.staffSize = 80.5; + staff.voices[0].notes.emplace_back(); + + const auto xml = mxtest::toXml(score); + CHECK(xml.find("") != std::string::npos); + CHECK(xml.find("1") != std::string::npos); + CHECK(xml.find("80.5") != std::string::npos); + + const auto outScore = mxtest::fromXml(xml); + CHECK_EQUAL(1, outScore.parts.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.front().staffLines); + CHECK(80.5 == outScore.parts.front().measures.front().staves.front().staffSize); +} + +T_END; + #endif diff --git a/src/private/mxtest/impl/MeasureWriterTest.cpp b/src/private/mxtest/impl/MeasureWriterTest.cpp index 1703485ce..deb97f24c 100644 --- a/src/private/mxtest/impl/MeasureWriterTest.cpp +++ b/src/private/mxtest/impl/MeasureWriterTest.cpp @@ -256,4 +256,68 @@ TEST(staffDetailsWritesStaffLines, MeasureWriter) T_END +TEST(staffDetailsWritesStaffSize, MeasureWriter) +{ + mxtest::TestParameters params; + params.ticksPerQuarter = 101; + params.measureIndex = 0; + params.partIndex = 0; + params.numStaves = 1; + mxtest::TestItems t = mxtest::setupTestItems(params); + auto &staff = t.measureData->staves.at(0); + staff.staffSize = 80.5; + + const auto partwiseMeasure = t.measureWriter->getPartwiseMeasure(); + auto musicData = partwiseMeasure.musicData(); + auto mdcIter = musicData.begin(); + const auto mdcEnd = musicData.end(); + + CHECK(mdcIter != mdcEnd); + CHECK(mdcIter->isAttributes()); + + const auto &props = mdcIter->asAttributes(); + CHECK_EQUAL(1, props.staffDetails().size()); + + const auto &details = props.staffDetails().front(); + CHECK(!details.group().has_value()); + CHECK(details.staffSize().has_value()); + CHECK(80.5 == details.staffSize()->value().value().value()); + CHECK(!details.number().has_value()); +} + +T_END + +TEST(staffDetailsWritesStaffLinesAndStaffSize, MeasureWriter) +{ + mxtest::TestParameters params; + params.ticksPerQuarter = 101; + params.measureIndex = 0; + params.partIndex = 0; + params.numStaves = 1; + mxtest::TestItems t = mxtest::setupTestItems(params); + auto &staff = t.measureData->staves.at(0); + staff.staffLines = 1; + staff.staffSize = 80.5; + + const auto partwiseMeasure = t.measureWriter->getPartwiseMeasure(); + auto musicData = partwiseMeasure.musicData(); + auto mdcIter = musicData.begin(); + const auto mdcEnd = musicData.end(); + + CHECK(mdcIter != mdcEnd); + CHECK(mdcIter->isAttributes()); + + const auto &props = mdcIter->asAttributes(); + CHECK_EQUAL(1, props.staffDetails().size()); + + const auto &details = props.staffDetails().front(); + CHECK(details.group().has_value()); + CHECK_EQUAL(1, details.group()->staffLines()); + CHECK(details.staffSize().has_value()); + CHECK(80.5 == details.staffSize()->value().value().value()); + CHECK(!details.number().has_value()); +} + +T_END + #endif From e8394dd86b3515ca8d2f81476d76c962a19c4a72 Mon Sep 17 00:00:00 2001 From: Robert Patterson Date: Wed, 1 Jul 2026 08:40:56 -0500 Subject: [PATCH 2/6] Add staff scaling to mx::api as well. It turns out you need both to do staff scaling. --- src/include/mx/api/StaffData.h | 4 ++- src/private/mx/impl/MeasureReader.cpp | 4 +++ src/private/mx/impl/MeasureWriter.cpp | 3 +- src/private/mx/impl/PropertiesWriter.cpp | 11 +++++- src/private/mx/impl/PropertiesWriter.h | 1 + src/private/mxtest/api/MeasureDataTest.cpp | 31 ++++++++++++++++ src/private/mxtest/impl/MeasureWriterTest.cpp | 36 +++++++++++++++++++ 7 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index a9e279e27..57a3d8bc8 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -20,6 +20,7 @@ class StaffData public: int staffLines = -1; double staffSize = -1.0; + double staffScaling = -1.0; std::vector clefs; // for the use case where key signatures @@ -56,7 +57,8 @@ inline bool voicesAreEqual(const std::map &l, const std::mapvalue().value().value(); + if (staffDetails.staffSize()->scaling().has_value()) + { + staffData.staffScaling = staffDetails.staffSize()->scaling()->value().value(); + } } } } diff --git a/src/private/mx/impl/MeasureWriter.cpp b/src/private/mx/impl/MeasureWriter.cpp index 63190dafd..a7338b122 100644 --- a/src/private/mx/impl/MeasureWriter.cpp +++ b/src/private/mx/impl/MeasureWriter.cpp @@ -137,7 +137,8 @@ void MeasureWriter::writeMeasureGlobals() { desiredStaffIndex = localStaffCounter; } - myPropertiesWriter->writeStaffDetails(desiredStaffIndex, staff.staffLines, staff.staffSize); + myPropertiesWriter->writeStaffDetails(desiredStaffIndex, staff.staffLines, staff.staffSize, + staff.staffScaling); } auto clefIter = staff.clefs.cbegin(); diff --git a/src/private/mx/impl/PropertiesWriter.cpp b/src/private/mx/impl/PropertiesWriter.cpp index 1f71aa181..6351f413c 100644 --- a/src/private/mx/impl/PropertiesWriter.cpp +++ b/src/private/mx/impl/PropertiesWriter.cpp @@ -183,10 +183,15 @@ void PropertiesWriter::writeNumStaves(int value) void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines) { - writeStaffDetails(staffIndex, staffLines, -1.0); + writeStaffDetails(staffIndex, staffLines, -1.0, -1.0); } void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines, double staffSize) +{ + writeStaffDetails(staffIndex, staffLines, staffSize, -1.0); +} + +void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines, double staffSize, double staffScaling) { core::StaffDetails staffDetails{}; @@ -206,6 +211,10 @@ void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines, double { core::StaffSize size{}; size.setValue(core::NonNegativeDecimal{core::Decimal{staffSize}}); + if (staffScaling >= 0.0) + { + size.setScaling(core::NonNegativeDecimal{core::Decimal{staffScaling}}); + } staffDetails.setStaffSize(size); } diff --git a/src/private/mx/impl/PropertiesWriter.h b/src/private/mx/impl/PropertiesWriter.h index 5d385ef4d..ffa03cb49 100644 --- a/src/private/mx/impl/PropertiesWriter.h +++ b/src/private/mx/impl/PropertiesWriter.h @@ -53,6 +53,7 @@ class PropertiesWriter void writeNumStaves(int value); void writeStaffDetails(int staffIndex, int staffLines); void writeStaffDetails(int staffIndex, int staffLines, double staffSize); + void writeStaffDetails(int staffIndex, int staffLines, double staffSize, double staffScaling); void writeClef(int staffIndex, const api::ClefData &inClefData); void writePartSymbol(const api::PartSymbolData &inPartSymbolData); void writeTranspose(const api::TransposeData &inTransposeData); diff --git a/src/private/mxtest/api/MeasureDataTest.cpp b/src/private/mxtest/api/MeasureDataTest.cpp index 2f85818a1..81ac77401 100644 --- a/src/private/mxtest/api/MeasureDataTest.cpp +++ b/src/private/mxtest/api/MeasureDataTest.cpp @@ -198,6 +198,36 @@ TEST(staffSizeRoundTrip, MeasureData) CHECK_EQUAL(1, outScore.parts.front().measures.size()); CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.size()); CHECK(80.5 == outScore.parts.front().measures.front().staves.front().staffSize); + CHECK(-1.0 == outScore.parts.front().measures.front().staves.front().staffScaling); + CHECK_EQUAL(-1, outScore.parts.front().measures.front().staves.front().staffLines); +} + +T_END; + +TEST(staffSizeScalingRoundTrip, 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(); + auto &staff = measure.staves.back(); + staff.staffSize = 80.5; + staff.staffScaling = 75.5; + staff.voices[0].notes.emplace_back(); + + const auto xml = mxtest::toXml(score); + CHECK(xml.find("") != std::string::npos); + CHECK(xml.find("80.5") != std::string::npos); + CHECK(xml.find("") == std::string::npos); + + const auto outScore = mxtest::fromXml(xml); + CHECK_EQUAL(1, outScore.parts.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.size()); + CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.size()); + CHECK(80.5 == outScore.parts.front().measures.front().staves.front().staffSize); + CHECK(75.5 == outScore.parts.front().measures.front().staves.front().staffScaling); CHECK_EQUAL(-1, outScore.parts.front().measures.front().staves.front().staffLines); } @@ -227,6 +257,7 @@ TEST(staffLinesAndStaffSizeRoundTrip, MeasureData) CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.size()); CHECK_EQUAL(1, outScore.parts.front().measures.front().staves.front().staffLines); CHECK(80.5 == outScore.parts.front().measures.front().staves.front().staffSize); + CHECK(-1.0 == outScore.parts.front().measures.front().staves.front().staffScaling); } T_END; diff --git a/src/private/mxtest/impl/MeasureWriterTest.cpp b/src/private/mxtest/impl/MeasureWriterTest.cpp index deb97f24c..4c2a629d5 100644 --- a/src/private/mxtest/impl/MeasureWriterTest.cpp +++ b/src/private/mxtest/impl/MeasureWriterTest.cpp @@ -282,6 +282,41 @@ TEST(staffDetailsWritesStaffSize, MeasureWriter) CHECK(!details.group().has_value()); CHECK(details.staffSize().has_value()); CHECK(80.5 == details.staffSize()->value().value().value()); + CHECK(!details.staffSize()->scaling().has_value()); + CHECK(!details.number().has_value()); +} + +T_END + +TEST(staffDetailsWritesStaffSizeScaling, MeasureWriter) +{ + mxtest::TestParameters params; + params.ticksPerQuarter = 101; + params.measureIndex = 0; + params.partIndex = 0; + params.numStaves = 1; + mxtest::TestItems t = mxtest::setupTestItems(params); + auto &staff = t.measureData->staves.at(0); + staff.staffSize = 80.5; + staff.staffScaling = 75.5; + + const auto partwiseMeasure = t.measureWriter->getPartwiseMeasure(); + auto musicData = partwiseMeasure.musicData(); + auto mdcIter = musicData.begin(); + const auto mdcEnd = musicData.end(); + + CHECK(mdcIter != mdcEnd); + CHECK(mdcIter->isAttributes()); + + const auto &props = mdcIter->asAttributes(); + CHECK_EQUAL(1, props.staffDetails().size()); + + const auto &details = props.staffDetails().front(); + CHECK(!details.group().has_value()); + CHECK(details.staffSize().has_value()); + CHECK(80.5 == details.staffSize()->value().value().value()); + CHECK(details.staffSize()->scaling().has_value()); + CHECK(75.5 == details.staffSize()->scaling()->value().value()); CHECK(!details.number().has_value()); } @@ -315,6 +350,7 @@ TEST(staffDetailsWritesStaffLinesAndStaffSize, MeasureWriter) CHECK_EQUAL(1, details.group()->staffLines()); CHECK(details.staffSize().has_value()); CHECK(80.5 == details.staffSize()->value().value().value()); + CHECK(!details.staffSize()->scaling().has_value()); CHECK(!details.number().has_value()); } From b4e5f5927ba5a4a604403fdd9120dd1c84bd2f69 Mon Sep 17 00:00:00 2001 From: Robert Patterson Date: Wed, 1 Jul 2026 08:58:50 -0500 Subject: [PATCH 3/6] add comments as to what these values mean --- src/include/mx/api/StaffData.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index 57a3d8bc8..22e613598 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -19,8 +19,14 @@ class StaffData { public: int staffLines = -1; + + // Specifies the staff space size relative the the global staff space size double staffSize = -1.0; + // Specifies the scaling of the notation. The MusicXml spec calls out the case + // of percussion staves with wider spaced line as an example where this differs + // from staffSize. For example it might be staffSize=100, staffScaling = 150. double staffScaling = -1.0; + std::vector clefs; // for the use case where key signatures From a7486f0322d48426be3a8a7e68340009f16072ae Mon Sep 17 00:00:00 2001 From: Robert Patterson Date: Wed, 1 Jul 2026 09:22:07 -0500 Subject: [PATCH 4/6] attempt to fix formatting error --- src/include/mx/api/StaffData.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index 22e613598..8037b4bde 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -22,11 +22,12 @@ class StaffData // Specifies the staff space size relative the the global staff space size double staffSize = -1.0; + // Specifies the scaling of the notation. The MusicXml spec calls out the case // of percussion staves with wider spaced line as an example where this differs // from staffSize. For example it might be staffSize=100, staffScaling = 150. double staffScaling = -1.0; - + std::vector clefs; // for the use case where key signatures From 7813e82a72a99ab734acbcd7ec45e4584ed9826e Mon Sep 17 00:00:00 2001 From: Robert Patterson Date: Wed, 1 Jul 2026 14:52:10 -0500 Subject: [PATCH 5/6] fix incorrect comment --- src/include/mx/api/StaffData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index 8037b4bde..d5045be24 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -25,7 +25,7 @@ class StaffData // Specifies the scaling of the notation. The MusicXml spec calls out the case // of percussion staves with wider spaced line as an example where this differs - // from staffSize. For example it might be staffSize=100, staffScaling = 150. + // from staffSize. For example it might be staffSize=150, staffScaling = 100. double staffScaling = -1.0; std::vector clefs; From db94446f795aef6e99d3acb45c6d1d3a77e8243d Mon Sep 17 00:00:00 2001 From: Robert Patterson Date: Wed, 1 Jul 2026 14:53:59 -0500 Subject: [PATCH 6/6] fix typo in comment --- src/include/mx/api/StaffData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index d5045be24..2dadf6ad5 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -24,7 +24,7 @@ class StaffData double staffSize = -1.0; // Specifies the scaling of the notation. The MusicXml spec calls out the case - // of percussion staves with wider spaced line as an example where this differs + // of percussion staves with wider spaced lines as an example where this differs // from staffSize. For example it might be staffSize=150, staffScaling = 100. double staffScaling = -1.0;