diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index f05417d7..2dadf6ad 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -19,6 +19,15 @@ 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 lines as an example where this differs + // from staffSize. For example it might be staffSize=150, staffScaling = 100. + double staffScaling = -1.0; + std::vector clefs; // for the use case where key signatures @@ -55,6 +64,8 @@ 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(); + 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 9064d516..a7338b12 100644 --- a/src/private/mx/impl/MeasureWriter.cpp +++ b/src/private/mx/impl/MeasureWriter.cpp @@ -130,14 +130,15 @@ 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, + staff.staffScaling); } auto clefIter = staff.clefs.cbegin(); diff --git a/src/private/mx/impl/PropertiesWriter.cpp b/src/private/mx/impl/PropertiesWriter.cpp index 55f24e66..6351f413 100644 --- a/src/private/mx/impl/PropertiesWriter.cpp +++ b/src/private/mx/impl/PropertiesWriter.cpp @@ -182,6 +182,16 @@ void PropertiesWriter::writeNumStaves(int value) } void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines) +{ + 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{}; @@ -190,9 +200,23 @@ 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}}); + if (staffScaling >= 0.0) + { + size.setScaling(core::NonNegativeDecimal{core::Decimal{staffScaling}}); + } + 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 60d80e32..ffa03cb4 100644 --- a/src/private/mx/impl/PropertiesWriter.h +++ b/src/private/mx/impl/PropertiesWriter.h @@ -52,6 +52,8 @@ 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 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 3d7f8def..81ac7740 100644 --- a/src/private/mxtest/api/MeasureDataTest.cpp +++ b/src/private/mxtest/api/MeasureDataTest.cpp @@ -176,4 +176,90 @@ 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(-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); +} + +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); + CHECK(-1.0 == outScore.parts.front().measures.front().staves.front().staffScaling); +} + +T_END; + #endif diff --git a/src/private/mxtest/impl/MeasureWriterTest.cpp b/src/private/mxtest/impl/MeasureWriterTest.cpp index 1703485c..4c2a629d 100644 --- a/src/private/mxtest/impl/MeasureWriterTest.cpp +++ b/src/private/mxtest/impl/MeasureWriterTest.cpp @@ -256,4 +256,104 @@ 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.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()); +} + +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.staffSize()->scaling().has_value()); + CHECK(!details.number().has_value()); +} + +T_END + #endif