From a8c7dcc9b6c4909438f43abf73784489abed41c6 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Wed, 17 Jun 2026 09:14:26 +0200 Subject: [PATCH] impl: full-fidelity segno and coda read/write Widen mx::api SegnoData and CodaData to carry font, smufl, and id in addition to position and color, and add the writer path so segno and coda now survive an api round-trip. The reader set colorData but never isColorSpecified, so color was dropped; the writer emitted nothing for segno or coda. --- src/include/mx/api/CodaData.h | 21 +++++- src/include/mx/api/SegnoData.h | 21 +++++- src/private/mx/impl/DirectionReader.cpp | 34 ++++++++- src/private/mx/impl/DirectionWriter.cpp | 46 ++++++++++++ .../mxtest/impl/DirectionWriterTest.cpp | 72 +++++++++++++++++++ 5 files changed, 190 insertions(+), 4 deletions(-) diff --git a/src/include/mx/api/CodaData.h b/src/include/mx/api/CodaData.h index f1553f38d..b85cdbdf9 100644 --- a/src/include/mx/api/CodaData.h +++ b/src/include/mx/api/CodaData.h @@ -6,28 +6,47 @@ #include "mx/api/ApiCommon.h" #include "mx/api/ColorData.h" +#include "mx/api/FontData.h" #include "mx/api/PositionData.h" +#include + namespace mx { namespace api { +// The MusicXML element carries the empty-print-object-style-align attribute group plus the +// smufl glyph-name and optional-unique-id. positionData captures default/relative x-y plus the +// horizontal and vertical alignment; its placement member is unused here because has no +// placement attribute (placement lives on the parent ). class CodaData { public: PositionData positionData; + FontData fontData; bool isColorSpecified; ColorData colorData; + bool isSmuflSpecified; + std::string smufl; + bool isIdSpecified; + std::string id; - CodaData() : positionData{}, isColorSpecified{false}, colorData{} + CodaData() + : positionData{}, fontData{}, isColorSpecified{false}, colorData{}, isSmuflSpecified{false}, smufl{}, + isIdSpecified{false}, id{} { } }; MXAPI_EQUALS_BEGIN(CodaData) MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(fontData) MXAPI_EQUALS_MEMBER(isColorSpecified) MXAPI_EQUALS_MEMBER(colorData) +MXAPI_EQUALS_MEMBER(isSmuflSpecified) +MXAPI_EQUALS_MEMBER(smufl) +MXAPI_EQUALS_MEMBER(isIdSpecified) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(CodaData); } // namespace api diff --git a/src/include/mx/api/SegnoData.h b/src/include/mx/api/SegnoData.h index eb72bdee0..520d83c4a 100644 --- a/src/include/mx/api/SegnoData.h +++ b/src/include/mx/api/SegnoData.h @@ -6,28 +6,47 @@ #include "mx/api/ApiCommon.h" #include "mx/api/ColorData.h" +#include "mx/api/FontData.h" #include "mx/api/PositionData.h" +#include + namespace mx { namespace api { +// The MusicXML element carries the empty-print-object-style-align attribute group plus the +// smufl glyph-name and optional-unique-id. positionData captures default/relative x-y plus the +// horizontal and vertical alignment; its placement member is unused here because has no +// placement attribute (placement lives on the parent ). class SegnoData { public: PositionData positionData; + FontData fontData; bool isColorSpecified; ColorData colorData; + bool isSmuflSpecified; + std::string smufl; + bool isIdSpecified; + std::string id; - SegnoData() : positionData{}, isColorSpecified{false}, colorData{} + SegnoData() + : positionData{}, fontData{}, isColorSpecified{false}, colorData{}, isSmuflSpecified{false}, smufl{}, + isIdSpecified{false}, id{} { } }; MXAPI_EQUALS_BEGIN(SegnoData) MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(fontData) MXAPI_EQUALS_MEMBER(isColorSpecified) MXAPI_EQUALS_MEMBER(colorData) +MXAPI_EQUALS_MEMBER(isSmuflSpecified) +MXAPI_EQUALS_MEMBER(smufl) +MXAPI_EQUALS_MEMBER(isIdSpecified) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SegnoData); } // namespace api diff --git a/src/private/mx/impl/DirectionReader.cpp b/src/private/mx/impl/DirectionReader.cpp index 1438e9dc6..247f0d5db 100644 --- a/src/private/mx/impl/DirectionReader.cpp +++ b/src/private/mx/impl/DirectionReader.cpp @@ -345,7 +345,22 @@ void DirectionReader::parseSegno(const core::DirectionType &directionType) { api::SegnoData outSegno; outSegno.positionData = getPositionData(segno); - outSegno.colorData = getColor(segno); + outSegno.fontData = getFontData(segno); + outSegno.isColorSpecified = segno.color().has_value(); + if (outSegno.isColorSpecified) + { + outSegno.colorData = getColor(segno); + } + if (segno.smufl().has_value()) + { + outSegno.isSmuflSpecified = true; + outSegno.smufl = segno.smufl()->toString(); + } + if (segno.id().has_value()) + { + outSegno.isIdSpecified = true; + outSegno.id = segno.id()->value(); + } myOutDirectionData.segnos.emplace_back(std::move(outSegno)); appendOrderedComponent(api::DirectionComponentKind::segno, static_cast(myOutDirectionData.segnos.size()) - 1); @@ -416,7 +431,22 @@ void DirectionReader::parseCoda(const core::DirectionType &directionType) { api::CodaData outCoda; outCoda.positionData = getPositionData(coda); - outCoda.colorData = getColor(coda); + outCoda.fontData = getFontData(coda); + outCoda.isColorSpecified = coda.color().has_value(); + if (outCoda.isColorSpecified) + { + outCoda.colorData = getColor(coda); + } + if (coda.smufl().has_value()) + { + outCoda.isSmuflSpecified = true; + outCoda.smufl = coda.smufl()->toString(); + } + if (coda.id().has_value()) + { + outCoda.isIdSpecified = true; + outCoda.id = coda.id()->value(); + } myOutDirectionData.codas.emplace_back(std::move(outCoda)); appendOrderedComponent(api::DirectionComponentKind::coda, static_cast(myOutDirectionData.codas.size()) - 1); diff --git a/src/private/mx/impl/DirectionWriter.cpp b/src/private/mx/impl/DirectionWriter.cpp index c9cf29c66..df32c7683 100644 --- a/src/private/mx/impl/DirectionWriter.cpp +++ b/src/private/mx/impl/DirectionWriter.cpp @@ -8,6 +8,7 @@ #include "mx/core/generated/BassStep.h" #include "mx/core/generated/BeatUnitGroup.h" #include "mx/core/generated/Bracket.h" +#include "mx/core/generated/Coda.h" #include "mx/core/generated/Dashes.h" #include "mx/core/generated/Degree.h" #include "mx/core/generated/DegreeAlter.h" @@ -43,6 +44,7 @@ #include "mx/core/generated/PerMinute.h" #include "mx/core/generated/Root.h" #include "mx/core/generated/RootStep.h" +#include "mx/core/generated/Segno.h" #include "mx/core/generated/Semitones.h" #include "mx/core/generated/StartStop.h" #include "mx/core/generated/StartStopContinue.h" @@ -391,6 +393,50 @@ std::vector DirectionWriter::getDirectionLikeThings() } } + for (const auto &item : myDirectionData.segnos) + { + core::Segno segno{}; + setAttributesFromPositionData(item.positionData, segno); + setAttributesFromFontData(item.fontData, segno); + if (item.isColorSpecified) + { + setAttributesFromColorData(item.colorData, segno); + } + if (item.isSmuflSpecified) + { + segno.setSmufl(core::SmuflSegnoGlyphName::parse(item.smufl)); + } + if (item.isIdSpecified) + { + segno.setID(core::Token{item.id}); + } + core::DirectionType dt{}; + dt.setChoice(core::DirectionTypeChoice::segno(core::OneOrMore{std::move(segno)})); + addDirectionType(std::move(dt), direction); + } + + for (const auto &item : myDirectionData.codas) + { + core::Coda coda{}; + setAttributesFromPositionData(item.positionData, coda); + setAttributesFromFontData(item.fontData, coda); + if (item.isColorSpecified) + { + setAttributesFromColorData(item.colorData, coda); + } + if (item.isSmuflSpecified) + { + coda.setSmufl(core::SmuflCodaGlyphName::parse(item.smufl)); + } + if (item.isIdSpecified) + { + coda.setID(core::Token{item.id}); + } + core::DirectionType dt{}; + dt.setChoice(core::DirectionTypeChoice::coda(core::OneOrMore{std::move(coda)})); + addDirectionType(std::move(dt), direction); + } + if (myIsFirstDirectionTypeAdded) { output.push_back(core::MusicDataChoice::direction(direction)); diff --git a/src/private/mxtest/impl/DirectionWriterTest.cpp b/src/private/mxtest/impl/DirectionWriterTest.cpp index 561170ce3..0f73d91df 100644 --- a/src/private/mxtest/impl/DirectionWriterTest.cpp +++ b/src/private/mxtest/impl/DirectionWriterTest.cpp @@ -11,6 +11,7 @@ #include "mx/core/generated/DirectionType.h" #include "mx/core/generated/MusicDataChoice.h" #include "mx/core/generated/OctaveShift.h" +#include "mx/impl/DirectionReader.h" #include "mx/impl/DirectionWriter.h" #include @@ -45,4 +46,75 @@ TEST(ottavaStartStop, DirectionWriter) T_END +// Build a segno and a coda carrying the full empty-print-object-style-align attribute set plus +// smufl and id, write them with DirectionWriter, read them back with DirectionReader, and confirm +// every field survives the api -> core -> api trip. +TEST(segnoAndCodaRoundTrip, DirectionWriter) +{ + api::SegnoData segno; + segno.positionData.isDefaultXSpecified = true; + segno.positionData.defaultX = 1.0; + segno.positionData.isDefaultYSpecified = true; + segno.positionData.defaultY = 2.0; + segno.positionData.isRelativeXSpecified = true; + segno.positionData.relativeX = 3.0; + segno.positionData.isRelativeYSpecified = true; + segno.positionData.relativeY = 4.0; + segno.positionData.horizontalAlignmnet = api::HorizontalAlignment::left; + segno.positionData.verticalAlignment = api::VerticalAlignment::top; + segno.fontData.fontFamily = {"Maestro"}; + segno.fontData.style = api::FontStyle::italic; + segno.fontData.weight = api::FontWeight::bold; + segno.fontData.sizeType = api::FontSizeType::point; + segno.fontData.sizePoint = 18.0; + segno.isColorSpecified = true; + segno.colorData.red = 0; + segno.colorData.green = 0; + segno.colorData.blue = 0; + segno.colorData.isAlphaSpecified = true; + segno.colorData.alpha = 255; + segno.isSmuflSpecified = true; + segno.smufl = "segno"; + segno.isIdSpecified = true; + segno.id = "id3"; + + api::CodaData coda; + coda.positionData.isDefaultXSpecified = true; + coda.positionData.defaultX = 5.0; + coda.positionData.horizontalAlignmnet = api::HorizontalAlignment::center; + coda.positionData.verticalAlignment = api::VerticalAlignment::middle; + coda.fontData.fontFamily = {"Maestro"}; + coda.fontData.style = api::FontStyle::normal; + coda.fontData.weight = api::FontWeight::normal; + coda.isColorSpecified = true; + coda.colorData.red = 12; + coda.colorData.green = 34; + coda.colorData.blue = 56; + coda.isSmuflSpecified = true; + coda.smufl = "coda"; + coda.isIdSpecified = true; + coda.id = "id7"; + + api::DirectionData directionData; + directionData.segnos.push_back(segno); + directionData.codas.push_back(coda); + + Cursor cursor{1, 100}; + DirectionWriter writer{directionData, cursor}; + const auto mdcSet = writer.getDirectionLikeThings(); + REQUIRE(mdcSet.size() >= 1); + CHECK(mdcSet.front().isDirection()); + const auto &direction = mdcSet.front().asDirection(); + + DirectionReader reader{direction, cursor}; + const auto roundTripped = reader.getDirectionData(); + + REQUIRE(roundTripped.segnos.size() == 1); + REQUIRE(roundTripped.codas.size() == 1); + CHECK(segno == roundTripped.segnos.front()); + CHECK(coda == roundTripped.codas.front()); +} + +T_END + #endif