diff --git a/src/include/mx/api/ChordData.h b/src/include/mx/api/ChordData.h index 18ec0f78..8f9a34ea 100644 --- a/src/include/mx/api/ChordData.h +++ b/src/include/mx/api/ChordData.h @@ -142,6 +142,55 @@ MXAPI_EQUALS_MEMBER(printObject) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(Extension); +enum class FrameBarre +{ + none, + start, + stop +}; + +class FrameNoteData +{ + public: + FrameNoteData(); + + int stringNumber; + int fretNumber; + int fingering; + bool isFingeringSpecified; + FrameBarre barre; +}; + +MXAPI_EQUALS_BEGIN(FrameNoteData) +MXAPI_EQUALS_MEMBER(stringNumber) +MXAPI_EQUALS_MEMBER(fretNumber) +MXAPI_EQUALS_MEMBER(fingering) +MXAPI_EQUALS_MEMBER(isFingeringSpecified) +MXAPI_EQUALS_MEMBER(barre) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(FrameNoteData); + +class FrameData +{ + public: + FrameData(); + + int stringCount; + int fretCount; + int firstFret; + bool isFirstFretSpecified; + std::vector notes; +}; + +MXAPI_EQUALS_BEGIN(FrameData) +MXAPI_EQUALS_MEMBER(stringCount) +MXAPI_EQUALS_MEMBER(fretCount) +MXAPI_EQUALS_MEMBER(firstFret) +MXAPI_EQUALS_MEMBER(isFirstFretSpecified) +MXAPI_EQUALS_MEMBER(notes) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(FrameData); + class ChordData { public: @@ -157,6 +206,8 @@ class ChordData int bassAlter; std::vector extensions; std::vector miscData; + bool hasFrameData; + FrameData frameData; PositionData positionData; }; @@ -170,6 +221,8 @@ MXAPI_EQUALS_MEMBER(bass) MXAPI_EQUALS_MEMBER(bassAlter) MXAPI_EQUALS_MEMBER(extensions) MXAPI_EQUALS_MEMBER(miscData) +MXAPI_EQUALS_MEMBER(hasFrameData) +MXAPI_EQUALS_MEMBER(frameData) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(ChordData); diff --git a/src/include/mx/api/DirectionData.h b/src/include/mx/api/DirectionData.h index 82c17fbb..aceb54b4 100644 --- a/src/include/mx/api/DirectionData.h +++ b/src/include/mx/api/DirectionData.h @@ -19,6 +19,46 @@ namespace mx { namespace api { +enum class DirectionComponentKind +{ + tempo, + mark, + wedgeStart, + wedgeStop, + ottavaStart, + ottavaStop, + bracketStart, + bracketStop, + dashesStart, + dashesStop, + pedalStart, + pedalStop, + words, + chord, + segno, + coda, + rehearsal +}; + +struct DirectionComponent +{ + DirectionComponentKind kind; + int index; + + DirectionComponent() : kind{DirectionComponentKind::tempo}, index{0} + { + } + + DirectionComponent(DirectionComponentKind inKind, int inIndex) : kind{inKind}, index{inIndex} + { + } +}; + +MXAPI_EQUALS_BEGIN(DirectionComponent) +MXAPI_EQUALS_MEMBER(kind) +MXAPI_EQUALS_MEMBER(index) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(DirectionComponent); // MusicXML Documentation: A direction is a musical indication that is not attached to a specific // note. Two or more may be combined to indicate starts and stops of wedges, dashes, etc. @@ -63,16 +103,21 @@ struct DirectionData std::vector ottavaStops; std::vector bracketStarts; std::vector bracketStops; + std::vector dashesStarts; + std::vector dashesStops; + std::vector pedalStarts; + std::vector pedalStops; std::vector words; std::vector chords; std::vector segnos; std::vector codas; std::vector rehearsals; + std::vector orderedComponents; DirectionData() : tickTimePosition{0}, placement{Placement::unspecified}, voice{-1}, isStaffValueSpecified{true}, marks{}, - wedgeStarts{}, wedgeStops{}, ottavaStarts{}, ottavaStops{}, bracketStarts{}, bracketStops{}, words{}, - chords{}, segnos{} + wedgeStarts{}, wedgeStops{}, ottavaStarts{}, ottavaStops{}, bracketStarts{}, bracketStops{}, dashesStarts{}, + dashesStops{}, pedalStarts{}, pedalStops{}, words{}, chords{}, segnos{} { } }; @@ -82,9 +127,12 @@ inline bool isDirectionDataEmpty(const DirectionData &directionData) return directionData.tempos.size() == 0 && directionData.marks.size() == 0 && directionData.wedgeStarts.size() == 0 && directionData.wedgeStops.size() == 0 && directionData.bracketStarts.size() == 0 && directionData.bracketStops.size() == 0 && + directionData.dashesStarts.size() == 0 && directionData.dashesStops.size() == 0 && + directionData.pedalStarts.size() == 0 && directionData.pedalStops.size() == 0 && directionData.tempos.size() == 0 && directionData.ottavaStarts.size() == 0 && directionData.ottavaStops.size() == 0 && directionData.words.size() == 0 && - directionData.segnos.size() == 0 && directionData.codas.size() == 0; + directionData.segnos.size() == 0 && directionData.codas.size() == 0 && + directionData.orderedComponents.size() == 0; } MXAPI_EQUALS_BEGIN(DirectionData) @@ -100,10 +148,15 @@ MXAPI_EQUALS_MEMBER(ottavaStarts) MXAPI_EQUALS_MEMBER(ottavaStops) MXAPI_EQUALS_MEMBER(bracketStarts) MXAPI_EQUALS_MEMBER(bracketStops) +MXAPI_EQUALS_MEMBER(dashesStarts) +MXAPI_EQUALS_MEMBER(dashesStops) +MXAPI_EQUALS_MEMBER(pedalStarts) +MXAPI_EQUALS_MEMBER(pedalStops) MXAPI_EQUALS_MEMBER(words) MXAPI_EQUALS_MEMBER(chords) MXAPI_EQUALS_MEMBER(segnos) MXAPI_EQUALS_MEMBER(codas) +MXAPI_EQUALS_MEMBER(orderedComponents) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(DirectionData); } // namespace api diff --git a/src/include/mx/api/LyricData.h b/src/include/mx/api/LyricData.h index 525f449e..41336e8e 100644 --- a/src/include/mx/api/LyricData.h +++ b/src/include/mx/api/LyricData.h @@ -5,22 +5,47 @@ #pragma once #include "mx/api/ApiCommon.h" +#include "mx/api/PositionData.h" +#include "mx/api/PrintData.h" namespace mx { namespace api { +enum class LyricSyllabic +{ + single, + begin, + end, + middle +}; + class LyricData { public: LyricData() + : text{}, verseNumber{}, verseName{}, syllabic{LyricSyllabic::single}, hasExtend{false}, positionData{}, + printData{} { } std::string text; + std::string verseNumber; + std::string verseName; + LyricSyllabic syllabic; + bool hasExtend; + PositionData positionData; + PrintData printData; }; MXAPI_EQUALS_BEGIN(LyricData) +MXAPI_EQUALS_MEMBER(text) +MXAPI_EQUALS_MEMBER(verseNumber) +MXAPI_EQUALS_MEMBER(verseName) +MXAPI_EQUALS_MEMBER(syllabic) +MXAPI_EQUALS_MEMBER(hasExtend) +MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(printData) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(LyricData); } // namespace api diff --git a/src/include/mx/api/MarkData.h b/src/include/mx/api/MarkData.h index c6af60e0..f61e3dde 100644 --- a/src/include/mx/api/MarkData.h +++ b/src/include/mx/api/MarkData.h @@ -131,8 +131,8 @@ enum class MarkType tripleTongue, stopped, snapPizzicato, - // fret, - // string_, + fret, + string_, // hammerOn, // pullOff, // bend, @@ -140,9 +140,9 @@ enum class MarkType heel, toe, fingernails, - // hole, - // arrow, - // handbell, + hole, + arrow, + handbell, otherTechnical, unknownTechnical, @@ -214,6 +214,12 @@ struct MarkData int tickTimePosition; PrintData printData; PositionData positionData; + Bool mordentLong; + bool hasMordentLong; + Placement mordentApproach; + bool hasMordentApproach; + Placement mordentDeparture; + bool hasMordentDeparture; MarkData(); MarkData(MarkType inMarkType); @@ -226,6 +232,12 @@ MXAPI_EQUALS_MEMBER(name) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(printData) MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(mordentLong) +MXAPI_EQUALS_MEMBER(hasMordentLong) +MXAPI_EQUALS_MEMBER(mordentApproach) +MXAPI_EQUALS_MEMBER(hasMordentApproach) +MXAPI_EQUALS_MEMBER(mordentDeparture) +MXAPI_EQUALS_MEMBER(hasMordentDeparture) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(MarkData); } // namespace api diff --git a/src/include/mx/api/NoteData.h b/src/include/mx/api/NoteData.h index 35df719c..1243e5f5 100644 --- a/src/include/mx/api/NoteData.h +++ b/src/include/mx/api/NoteData.h @@ -5,6 +5,7 @@ #pragma once #include "mx/api/DurationData.h" +#include "mx/api/LyricData.h" #include "mx/api/NoteAttachmentData.h" #include "mx/api/PitchData.h" #include "mx/api/PositionData.h" @@ -116,6 +117,7 @@ class NoteData PrintData printData; NoteAttachmentData noteAttachmentData; + std::vector lyrics; // these strings will be stuffed into the editorial element // preceded by the string ##misc-data##. for example if you have the @@ -145,6 +147,7 @@ MXAPI_EQUALS_MEMBER(beams) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(printData) MXAPI_EQUALS_MEMBER(noteAttachmentData) +MXAPI_EQUALS_MEMBER(lyrics) MXAPI_EQUALS_MEMBER(miscData) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(NoteData); diff --git a/src/include/mx/api/RehearsalData.h b/src/include/mx/api/RehearsalData.h index 309382b3..eece7090 100644 --- a/src/include/mx/api/RehearsalData.h +++ b/src/include/mx/api/RehearsalData.h @@ -12,6 +12,18 @@ namespace mx { namespace api { +enum class RehearsalEnclosure +{ + rectangle, + square, + oval, + circle, + bracket, + triangle, + diamond, + none +}; + class RehearsalData { public: @@ -20,11 +32,11 @@ class RehearsalData bool isColorSpecified; ColorData colorData; FontData fontData; + RehearsalEnclosure enclosure; - // Additional data about enclosing shape is available - // but not supported at this time. - - RehearsalData() : text{}, positionData{}, isColorSpecified{false}, colorData{}, fontData{} + RehearsalData() + : text{}, positionData{}, isColorSpecified{false}, colorData{}, fontData{}, + enclosure{RehearsalEnclosure::rectangle} { } }; @@ -35,6 +47,7 @@ MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(isColorSpecified) MXAPI_EQUALS_MEMBER(colorData) MXAPI_EQUALS_MEMBER(fontData) +MXAPI_EQUALS_MEMBER(enclosure) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(RehearsalData); } // namespace api diff --git a/src/include/mx/api/SpannerData.h b/src/include/mx/api/SpannerData.h index 452a215e..1359f80d 100644 --- a/src/include/mx/api/SpannerData.h +++ b/src/include/mx/api/SpannerData.h @@ -33,8 +33,9 @@ struct SpannerStop int numberLevel; int tickTimePosition; PositionData positionData; + LineData lineData; - SpannerStop() : numberLevel{-1}, tickTimePosition{0}, positionData{} + SpannerStop() : numberLevel{-1}, tickTimePosition{0}, positionData{}, lineData{} { } }; @@ -52,6 +53,7 @@ MXAPI_EQUALS_BEGIN(SpannerStop) MXAPI_EQUALS_MEMBER(numberLevel) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(lineData) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SpannerStop); } // namespace api diff --git a/src/include/mx/api/StaffData.h b/src/include/mx/api/StaffData.h index 90986d2a..f05417d7 100644 --- a/src/include/mx/api/StaffData.h +++ b/src/include/mx/api/StaffData.h @@ -18,6 +18,7 @@ namespace api class StaffData { public: + int staffLines = -1; std::vector clefs; // for the use case where key signatures @@ -53,6 +54,7 @@ inline bool voicesAreEqual(const std::map &l, const std::map tryParseNoteSizeType(const std::string &value) { return NoteSizeType::grace; } + else if (value == "grace-cue") + { + return NoteSizeType::graceCue; + } else if (value == "large") { return NoteSizeType::large; @@ -4191,6 +4195,9 @@ std::string toString(const NoteSizeType value) case NoteSizeType::grace: { return "grace"; } + case NoteSizeType::graceCue: { + return "grace-cue"; + } case NoteSizeType::large: { return "large"; } @@ -6440,6 +6447,53 @@ std::ostream &operator<<(std::ostream &os, const UpDown value) return toStream(os, value); } +/// UpDownNone ///////////////////////////////////////////////////////////////////////////////// + +UpDownNone parseUpDownNone(const std::string &value) +{ + const auto opt = tryParseUpDownNone(value); + return opt.value_or(UpDownNone::none); +} + +std::optional tryParseUpDownNone(const std::string &value) +{ + if (value == "up") + { + return UpDownNone::up; + } + else if (value == "down") + { + return UpDownNone::down; + } + return std::optional{}; +} + +std::string toString(const UpDownNone value) +{ + switch (value) + { + case UpDownNone::up: { + return "up"; + } + case UpDownNone::down: { + return "down"; + } + default: + break; + } + return "none"; +} + +std::ostream &toStream(std::ostream &os, const UpDownNone value) +{ + return os << toString(value); +} + +std::ostream &operator<<(std::ostream &os, const UpDownNone value) +{ + return toStream(os, value); +} + /// UpDownStopContinue ///////////////////////////////////////////////////////////////////// UpDownStopContinue parseUpDownStopContinue(const std::string &value) diff --git a/src/private/mx/core/Enums.h b/src/private/mx/core/Enums.h index cbdd1e7d..b8e26e9a 100644 --- a/src/private/mx/core/Enums.h +++ b/src/private/mx/core/Enums.h @@ -1167,15 +1167,17 @@ std::ostream &operator<<(std::ostream &os, const MuteEnum value); /// NoteSizeType /////////////////////////////////////////////////////////////////////////// /// /// The note-size-type type indicates the type of note being defined by a note-size element. -/// The grace type is used for notes of cue size that that include a grace element. The cue -/// type is used for all other notes with cue size, whether defined explicitly or implicitly -/// via a cue element. The large type is used for notes of large size. +/// The grace type is used for notes of cue size that that include a grace element. The +/// grace-cue type is used for notes that include both a grace and cue element. The cue type +/// is used for all other notes with cue size, whether defined explicitly or implicitly via a +/// cue element. The large type is used for notes of large size. /// enum class NoteSizeType { cue = 0, grace = 1, - large = 2 + graceCue = 2, + large = 3 }; NoteSizeType parseNoteSizeType(const std::string &value); @@ -1903,6 +1905,25 @@ std::string toString(const UpDownStopContinue value); std::ostream &toStream(std::ostream &os, const UpDownStopContinue value); std::ostream &operator<<(std::ostream &os, const UpDownStopContinue value); +/// UpDownNone ///////////////////////////////////////////////////////////////////// +/// +/// Extends the 3.x up-down type to add a "none" value for arpeggiate direction. +/// MusicXML 4.0 Backport: the "none" value (no arrow on the arpeggio sign) is +/// new in MusicXML 4.0. In 3.x the direction attribute used up-down (up/down only). +/// +enum class UpDownNone +{ + up = 0, + down = 1, + none = 2 // MusicXML 4.0 Backport +}; + +UpDownNone parseUpDownNone(const std::string &value); +std::optional tryParseUpDownNone(const std::string &value); +std::string toString(const UpDownNone value); +std::ostream &toStream(std::ostream &os, const UpDownNone value); +std::ostream &operator<<(std::ostream &os, const UpDownNone value); + /// UprightInverted //////////////////////////////////////////////////////////////////////// /// /// The upright-inverted type describes the appearance of a fermata element. The value is diff --git a/src/private/mx/core/elements/ArpeggiateAttributes.cpp b/src/private/mx/core/elements/ArpeggiateAttributes.cpp index ab172237..0e219f9b 100644 --- a/src/private/mx/core/elements/ArpeggiateAttributes.cpp +++ b/src/private/mx/core/elements/ArpeggiateAttributes.cpp @@ -51,7 +51,7 @@ bool ArpeggiateAttributes::fromXElementImpl(std::ostream &message, ::ezxml::XEle { continue; } - if (parseAttribute(message, it, className, isSuccess, direction, hasDirection, "direction", &parseUpDown)) + if (parseAttribute(message, it, className, isSuccess, direction, hasDirection, "direction", &parseUpDownNone)) { continue; } diff --git a/src/private/mx/core/elements/ArpeggiateAttributes.h b/src/private/mx/core/elements/ArpeggiateAttributes.h index 83d902d5..ea59a569 100644 --- a/src/private/mx/core/elements/ArpeggiateAttributes.h +++ b/src/private/mx/core/elements/ArpeggiateAttributes.h @@ -28,7 +28,7 @@ struct ArpeggiateAttributes : public AttributesInterface virtual bool hasValues() const; virtual std::ostream &toStream(std::ostream &os) const; NumberLevel number; - UpDown direction; + UpDownNone direction; // MusicXML 4.0 Backport: was UpDown in 3.x (no "none" value) TenthsValue defaultX; TenthsValue defaultY; TenthsValue relativeX; diff --git a/src/private/mx/examples/Write.cpp b/src/private/mx/examples/Write.cpp index 7af49e8b..bd58277a 100644 --- a/src/private/mx/examples/Write.cpp +++ b/src/private/mx/examples/Write.cpp @@ -39,8 +39,8 @@ int main(int argc, const char *argv[]) // add a measure part.measures.emplace_back(MeasureData{}); auto &measure = part.measures.back(); - measure.timeSignature.beats = 4; - measure.timeSignature.beatType = 4; + measure.timeSignature.beats = "4"; + measure.timeSignature.beatType = "4"; measure.timeSignature.isImplicit = false; // add a staff diff --git a/src/private/mx/impl/ArpeggiateFunctions.cpp b/src/private/mx/impl/ArpeggiateFunctions.cpp index 599ef9b6..5eaf47c6 100644 --- a/src/private/mx/impl/ArpeggiateFunctions.cpp +++ b/src/private/mx/impl/ArpeggiateFunctions.cpp @@ -25,13 +25,17 @@ api::MarkData ArpeggiateFunctions::parseArpeggiate() const switch (attr->direction) { - case core::UpDown::up: + case core::UpDownNone::up: markType = api::MarkType::arpeggiateUp; break; - case core::UpDown::down: + case core::UpDownNone::down: markType = api::MarkType::arpeggiateDown; break; + + case core::UpDownNone::none: // MusicXML 4.0 Backport + markType = api::MarkType::arpeggiate; + break; } } api::MarkData markData{markType}; diff --git a/src/private/mx/impl/Converter.cpp b/src/private/mx/impl/Converter.cpp index 9c8860b0..e85cab1c 100644 --- a/src/private/mx/impl/Converter.cpp +++ b/src/private/mx/impl/Converter.cpp @@ -356,22 +356,23 @@ const std::map Converter::technica api::MarkType::stopped}, std::pair{core::TechnicalChoice::Choice::snapPizzicato, api::MarkType::snapPizzicato}, - // std::pair{ core::TechnicalChoice::Choice::fret, + std::pair{core::TechnicalChoice::Choice::fret, api::MarkType::fret}, + std::pair{core::TechnicalChoice::Choice::string_, + api::MarkType::string_}, + // std::pair{ core::TechnicalChoice::Choice::hammerOn, // api::MarkType::unspecified }, std::pair{ - // core::TechnicalChoice::Choice::string_, api::MarkType::unspecified }, std::pair{ core::TechnicalChoice::Choice::hammerOn, api::MarkType::unspecified }, - // std::pair{ core::TechnicalChoice::Choice::pullOff, - // api::MarkType::unspecified }, std::pair{ - // core::TechnicalChoice::Choice::bend, api::MarkType::unspecified }, std::pair{ core::TechnicalChoice::Choice::tap, api::MarkType::unspecified }, + // core::TechnicalChoice::Choice::pullOff, api::MarkType::unspecified }, std::pair{ core::TechnicalChoice::Choice::bend, api::MarkType::unspecified }, + // std::pair{ core::TechnicalChoice::Choice::tap, + // api::MarkType::unspecified }, std::pair{core::TechnicalChoice::Choice::heel, api::MarkType::heel}, std::pair{core::TechnicalChoice::Choice::toe, api::MarkType::toe}, std::pair{core::TechnicalChoice::Choice::fingernails, - api::MarkType::unspecified}, - // std::pair{ core::TechnicalChoice::Choice::hole, - // api::MarkType::unspecified }, std::pair{ - // core::TechnicalChoice::Choice::arrow, api::MarkType::unspecified }, std::pair{ core::TechnicalChoice::Choice::handbell, api::MarkType::unspecified }, + api::MarkType::fingernails}, + std::pair{core::TechnicalChoice::Choice::hole, api::MarkType::hole}, + std::pair{core::TechnicalChoice::Choice::arrow, api::MarkType::arrow}, + std::pair{core::TechnicalChoice::Choice::handbell, + api::MarkType::handbell}, std::pair{core::TechnicalChoice::Choice::otherTechnical, api::MarkType::otherTechnical}, }; diff --git a/src/private/mx/impl/DirectionReader.cpp b/src/private/mx/impl/DirectionReader.cpp index 982413ad..a88d1d0e 100644 --- a/src/private/mx/impl/DirectionReader.cpp +++ b/src/private/mx/impl/DirectionReader.cpp @@ -5,6 +5,7 @@ #include "mx/impl/DirectionReader.h" #include "mx/api/WedgeData.h" #include "mx/core/elements/AccordionRegistration.h" +#include "mx/core/elements/Barre.h" #include "mx/core/elements/Bass.h" #include "mx/core/elements/BassAlter.h" #include "mx/core/elements/BassStep.h" @@ -23,8 +24,14 @@ #include "mx/core/elements/EditorialGroup.h" #include "mx/core/elements/EditorialVoiceDirectionGroup.h" #include "mx/core/elements/Eyeglasses.h" +#include "mx/core/elements/Fingering.h" +#include "mx/core/elements/FirstFret.h" #include "mx/core/elements/Footnote.h" #include "mx/core/elements/Frame.h" +#include "mx/core/elements/FrameFrets.h" +#include "mx/core/elements/FrameNote.h" +#include "mx/core/elements/FrameStrings.h" +#include "mx/core/elements/Fret.h" #include "mx/core/elements/Function.h" #include "mx/core/elements/Harmony.h" #include "mx/core/elements/HarmonyChordGroup.h" @@ -48,6 +55,7 @@ #include "mx/core/elements/Segno.h" #include "mx/core/elements/Sound.h" #include "mx/core/elements/Staff.h" +#include "mx/core/elements/String.h" #include "mx/core/elements/StringMute.h" #include "mx/core/elements/Voice.h" #include "mx/core/elements/Wedge.h" @@ -177,6 +185,11 @@ mx::api::DirectionData DirectionReader::returnData() return temp; } +void DirectionReader::appendOrderedComponent(api::DirectionComponentKind kind, int index) +{ + myOutDirectionData.orderedComponents.emplace_back(kind, index); +} + void DirectionReader::parseDirectionType(const core::DirectionType &directionType) { switch (directionType.getChoice()) @@ -234,7 +247,7 @@ void DirectionReader::parseDirectionType(const core::DirectionType &directionTyp break; } case core::DirectionType::Choice::dampAll: { - parseRehearsal(directionType); + parseDampAll(directionType); break; } case core::DirectionType::Choice::eyeglasses: { @@ -285,7 +298,39 @@ void DirectionReader::parseRehearsal(const core::DirectionType &directionType) outRehearsal.text = rehearsalPtr->getValue().getValue(); outRehearsal.positionData = getPositionData(attr); outRehearsal.colorData = getColor(attr); + if (attr.hasEnclosure) + { + switch (attr.enclosure) + { + case core::EnclosureShape::rectangle: + outRehearsal.enclosure = api::RehearsalEnclosure::rectangle; + break; + case core::EnclosureShape::square: + outRehearsal.enclosure = api::RehearsalEnclosure::square; + break; + case core::EnclosureShape::oval: + outRehearsal.enclosure = api::RehearsalEnclosure::oval; + break; + case core::EnclosureShape::circle: + outRehearsal.enclosure = api::RehearsalEnclosure::circle; + break; + case core::EnclosureShape::bracket: + outRehearsal.enclosure = api::RehearsalEnclosure::bracket; + break; + case core::EnclosureShape::triangle: + outRehearsal.enclosure = api::RehearsalEnclosure::triangle; + break; + case core::EnclosureShape::diamond: + outRehearsal.enclosure = api::RehearsalEnclosure::diamond; + break; + case core::EnclosureShape::none: + outRehearsal.enclosure = api::RehearsalEnclosure::none; + break; + } + } myOutDirectionData.rehearsals.emplace_back(std::move(outRehearsal)); + appendOrderedComponent(api::DirectionComponentKind::rehearsal, + static_cast(myOutDirectionData.rehearsals.size()) - 1); } MX_UNUSED(directionType); @@ -302,6 +347,8 @@ void DirectionReader::parseSegno(const core::DirectionType &directionType) outSegno.positionData = getPositionData(attr); outSegno.colorData = getColor(attr); myOutDirectionData.segnos.emplace_back(std::move(outSegno)); + appendOrderedComponent(api::DirectionComponentKind::segno, + static_cast(myOutDirectionData.segnos.size()) - 1); } } @@ -317,7 +364,39 @@ void DirectionReader::parseWords(const core::DirectionType &directionType) outWords.positionData = getPositionData(attr); outWords.colorData = getColor(attr); outWords.fontData = getFontData(attr); + if (attr.hasEnclosure) + { + switch (attr.enclosure) + { + case core::EnclosureShape::rectangle: + outWords.enclosure = api::RehearsalEnclosure::rectangle; + break; + case core::EnclosureShape::square: + outWords.enclosure = api::RehearsalEnclosure::square; + break; + case core::EnclosureShape::oval: + outWords.enclosure = api::RehearsalEnclosure::oval; + break; + case core::EnclosureShape::circle: + outWords.enclosure = api::RehearsalEnclosure::circle; + break; + case core::EnclosureShape::bracket: + outWords.enclosure = api::RehearsalEnclosure::bracket; + break; + case core::EnclosureShape::triangle: + outWords.enclosure = api::RehearsalEnclosure::triangle; + break; + case core::EnclosureShape::diamond: + outWords.enclosure = api::RehearsalEnclosure::diamond; + break; + case core::EnclosureShape::none: + outWords.enclosure = api::RehearsalEnclosure::none; + break; + } + } myOutDirectionData.words.emplace_back(std::move(outWords)); + appendOrderedComponent(api::DirectionComponentKind::words, + static_cast(myOutDirectionData.words.size()) - 1); } } @@ -332,6 +411,8 @@ void DirectionReader::parseCoda(const core::DirectionType &directionType) outCoda.positionData = getPositionData(attr); outCoda.colorData = getColor(attr); myOutDirectionData.codas.emplace_back(std::move(outCoda)); + appendOrderedComponent(api::DirectionComponentKind::coda, + static_cast(myOutDirectionData.codas.size()) - 1); } } @@ -360,6 +441,8 @@ void DirectionReader::parseWedge(const core::DirectionType &directionType) } stop.positionData = positionData; myOutDirectionData.wedgeStops.emplace_back(std::move(stop)); + appendOrderedComponent(api::DirectionComponentKind::wedgeStop, + static_cast(myOutDirectionData.wedgeStops.size()) - 1); return; } else @@ -379,17 +462,24 @@ void DirectionReader::parseWedge(const core::DirectionType &directionType) start.lineData = lineData; start.colorData = colorData; myOutDirectionData.wedgeStarts.emplace_back(std::move(start)); + appendOrderedComponent(api::DirectionComponentKind::wedgeStart, + static_cast(myOutDirectionData.wedgeStarts.size()) - 1); } } void DirectionReader::parseDynamics(const core::DirectionType &directionType) { + const auto markCountBefore = myOutDirectionData.marks.size(); for (const auto &dynamic : directionType.getDynamicsSet()) { DynamicsReader reader{*dynamic, myCursor}; reader.parseDynamics(myOutDirectionData.marks); // parseDynamic( *dynamic ); } + for (auto i = markCountBefore; i < myOutDirectionData.marks.size(); ++i) + { + appendOrderedComponent(api::DirectionComponentKind::mark, static_cast(i)); + } } void DirectionReader::parseDynamic(const core::Dynamics &dynamic) @@ -409,11 +499,36 @@ void DirectionReader::parseDynamic(const core::Dynamics &dynamic) mark.name = valueObject.getValueString(); myOutDirectionData.marks.emplace_back(std::move(mark)); + appendOrderedComponent(api::DirectionComponentKind::mark, static_cast(myOutDirectionData.marks.size()) - 1); } void DirectionReader::parseDashes(const core::DirectionType &directionType) { - MX_UNUSED(directionType); + const auto &dashes = *directionType.getDashes(); + const auto &attr = *dashes.getAttributes(); + + if (attr.type == core::StartStopContinue::stop) + { + auto stop = impl::getSpannerStop(attr); + stop.tickTimePosition = myCursor.tickTimePosition; + myOutDirectionData.dashesStops.emplace_back(std::move(stop)); + appendOrderedComponent(api::DirectionComponentKind::dashesStop, + static_cast(myOutDirectionData.dashesStops.size()) - 1); + return; + } + else if (attr.type == core::StartStopContinue::start) + { + auto start = impl::getSpannerStart(attr); + start.tickTimePosition = myCursor.tickTimePosition; + if (start.lineData.lineType == api::LineType::unspecified) + { + start.lineData.lineType = api::LineType::dashed; + } + myOutDirectionData.dashesStarts.emplace_back(std::move(start)); + appendOrderedComponent(api::DirectionComponentKind::dashesStart, + static_cast(myOutDirectionData.dashesStarts.size()) - 1); + return; + } } void DirectionReader::parseBracket(const core::DirectionType &directionType) @@ -421,13 +536,40 @@ void DirectionReader::parseBracket(const core::DirectionType &directionType) const auto &bracket = *directionType.getBracket(); const auto &attr = *bracket.getAttributes(); + const auto makeBracketLineData = [&]() { + api::LineData lineData{}; + lineData.lineHook = myConverter.convert(attr.lineEnd); + + if (attr.hasLineType) + { + lineData.lineType = myConverter.convert(attr.lineType); + } + if (attr.hasEndLength) + { + lineData.isStopLengthSpecified = true; + lineData.endLength = attr.endLength.getValue(); + } + if (attr.hasDashLength) + { + lineData.isDashLengthSpecified = true; + lineData.dashLength = attr.dashLength.getValue(); + } + if (attr.hasSpaceLength) + { + lineData.isSpaceLengthSpecified = true; + lineData.spaceLength = attr.spaceLength.getValue(); + } + return lineData; + }; + if (attr.type == core::StartStopContinue::stop) { - api::SpannerStop stop; + auto stop = impl::getSpannerStop(attr); stop.tickTimePosition = myCursor.tickTimePosition; - stop.numberLevel = impl::checkNumber(&attr); - stop.positionData = this->parsePositionData(attr); + stop.lineData = makeBracketLineData(); myOutDirectionData.bracketStops.emplace_back(std::move(stop)); + appendOrderedComponent(api::DirectionComponentKind::bracketStop, + static_cast(myOutDirectionData.bracketStops.size()) - 1); return; } else if (attr.type == core::StartStopContinue::start) @@ -436,9 +578,11 @@ void DirectionReader::parseBracket(const core::DirectionType &directionType) start.tickTimePosition = myCursor.tickTimePosition; start.numberLevel = impl::checkNumber(&attr); start.positionData = this->parsePositionData(attr); - start.lineData = impl::getLineData(attr); + start.lineData = makeBracketLineData(); start.printData = impl::getPrintData(attr); myOutDirectionData.bracketStarts.emplace_back(std::move(start)); + appendOrderedComponent(api::DirectionComponentKind::bracketStart, + static_cast(myOutDirectionData.bracketStarts.size()) - 1); return; } } @@ -453,6 +597,45 @@ void DirectionReader::parsePedal(const core::DirectionType &directionType) return; } + const auto placement = + myDirection->getAttributes()->hasPlacement + ? (myDirection->getAttributes()->placement == core::AboveBelow::above ? api::Placement::above + : api::Placement::below) + : api::Placement::unspecified; + + myOutDirectionData.placement = placement; + + if (attr.hasLine && attr.line == core::YesNo::yes) + { + if (attr.type == core::StartStopChangeContinue::start) + { + api::SpannerStart start; + start.tickTimePosition = myOutDirectionData.tickTimePosition; + start.positionData = getPositionData(attr); + start.positionData.placement = placement; + start.lineData.lineType = api::LineType::solid; + start.lineData.lineHook = api::LineHook::none; + myOutDirectionData.pedalStarts.emplace_back(std::move(start)); + appendOrderedComponent(api::DirectionComponentKind::pedalStart, + static_cast(myOutDirectionData.pedalStarts.size()) - 1); + } + else if (attr.type == core::StartStopChangeContinue::stop) + { + api::SpannerStop stop; + stop.tickTimePosition = myOutDirectionData.tickTimePosition; + stop.positionData = getPositionData(attr); + stop.positionData.placement = placement; + stop.lineData.lineType = api::LineType::solid; + stop.lineData.lineHook = api::LineHook::down; + stop.lineData.isStopLengthSpecified = true; + stop.lineData.endLength = 10.0; + myOutDirectionData.pedalStops.emplace_back(std::move(stop)); + appendOrderedComponent(api::DirectionComponentKind::pedalStop, + static_cast(myOutDirectionData.pedalStops.size()) - 1); + } + return; + } + auto pedalType = api::MarkType::pedal; if (attr.type == core::StartStopChangeContinue::stop) @@ -460,17 +643,11 @@ void DirectionReader::parsePedal(const core::DirectionType &directionType) pedalType = api::MarkType::damp; } - const auto placement = - myDirection->getAttributes()->hasPlacement - ? (myDirection->getAttributes()->placement == core::AboveBelow::above ? api::Placement::above - : api::Placement::below) - : api::Placement::unspecified; - auto mark = api::MarkData{placement, pedalType}; mark.positionData = getPositionData(attr); mark.positionData.placement = placement; - myOutDirectionData.placement = placement; myOutDirectionData.marks.emplace_back(std::move(mark)); + appendOrderedComponent(api::DirectionComponentKind::mark, static_cast(myOutDirectionData.marks.size()) - 1); } void DirectionReader::parseMetronome(const core::DirectionType &directionType) @@ -478,6 +655,7 @@ void DirectionReader::parseMetronome(const core::DirectionType &directionType) const auto &metronome = *directionType.getMetronome(); MetronomeReader reader{metronome}; myOutDirectionData.tempos.emplace_back(reader.getTempoData()); + appendOrderedComponent(api::DirectionComponentKind::tempo, static_cast(myOutDirectionData.tempos.size()) - 1); } void DirectionReader::parseOctaveShift(const core::DirectionType &directionType) @@ -497,6 +675,8 @@ void DirectionReader::parseOctaveShift(const core::DirectionType &directionType) auto stop = impl::getSpannerStop(attr); stop.tickTimePosition = myCursor.tickTimePosition; myOutDirectionData.ottavaStops.emplace_back(std::move(stop)); + appendOrderedComponent(api::DirectionComponentKind::ottavaStop, + static_cast(myOutDirectionData.ottavaStops.size()) - 1); return; } @@ -532,6 +712,8 @@ void DirectionReader::parseOctaveShift(const core::DirectionType &directionType) start.ottavaType = ottavaType; start.spannerStart.tickTimePosition = myCursor.tickTimePosition; myOutDirectionData.ottavaStarts.emplace_back(std::move(start)); + appendOrderedComponent(api::DirectionComponentKind::ottavaStart, + static_cast(myOutDirectionData.ottavaStarts.size()) - 1); } void DirectionReader::parseHarpPedals(const core::DirectionType &directionType) @@ -743,10 +925,61 @@ void DirectionReader::parseHarmony(const core::Harmony &inHarmony, const core::H chord.miscData.push_back(misc); } + if (inHarmony.getHasFrame()) + { + chord.hasFrameData = true; + const auto frame = inHarmony.getFrame(); + chord.frameData.stringCount = static_cast(frame->getFrameStrings()->getValue().getValue()); + chord.frameData.fretCount = static_cast(frame->getFrameFrets()->getValue().getValue()); + + if (frame->getHasFirstFret()) + { + chord.frameData.isFirstFretSpecified = true; + chord.frameData.firstFret = static_cast(frame->getFirstFret()->getValue().getValue()); + } + + for (const auto &frameNotePtr : frame->getFrameNoteSet()) + { + api::FrameNoteData frameNote; + frameNote.stringNumber = static_cast(frameNotePtr->getString()->getValue().getValue()); + frameNote.fretNumber = static_cast(frameNotePtr->getFret()->getValue().getValue()); + + if (frameNotePtr->getHasFingering()) + { + const auto fingeringText = frameNotePtr->getFingering()->getValue().getValue(); + try + { + frameNote.fingering = std::stoi(fingeringText); + frameNote.isFingeringSpecified = true; + } + catch (...) + { + } + } + + if (frameNotePtr->getHasBarre()) + { + const auto barreType = frameNotePtr->getBarre()->getAttributes()->type; + switch (barreType) + { + case core::StartStop::start: + frameNote.barre = api::FrameBarre::start; + break; + case core::StartStop::stop: + frameNote.barre = api::FrameBarre::stop; + break; + } + } + + chord.frameData.notes.push_back(frameNote); + } + } + const auto &attr = *inHarmony.getAttributes(); chord.positionData = getPositionData(attr); myOutDirectionData.chords.push_back(chord); + appendOrderedComponent(api::DirectionComponentKind::chord, static_cast(myOutDirectionData.chords.size()) - 1); } } // namespace impl } // namespace mx diff --git a/src/private/mx/impl/DirectionReader.h b/src/private/mx/impl/DirectionReader.h index ae65ddc2..ab4e4006 100644 --- a/src/private/mx/impl/DirectionReader.h +++ b/src/private/mx/impl/DirectionReader.h @@ -50,6 +50,7 @@ class DirectionReader void parseValues(); void fixTimes(); mx::api::DirectionData returnData(); + void appendOrderedComponent(api::DirectionComponentKind kind, int index); void parseStaffIndex(); void parseDirectionType(const core::DirectionType &directionType); void parseRehearsal(const core::DirectionType &directionType); diff --git a/src/private/mx/impl/DirectionWriter.cpp b/src/private/mx/impl/DirectionWriter.cpp index e01ca43a..a7a5cff9 100644 --- a/src/private/mx/impl/DirectionWriter.cpp +++ b/src/private/mx/impl/DirectionWriter.cpp @@ -5,6 +5,7 @@ #include "mx/impl/DirectionWriter.h" #include "mx/api/BarlineData.h" #include "mx/core/elements/AccordionRegistration.h" +#include "mx/core/elements/Barre.h" #include "mx/core/elements/Bass.h" #include "mx/core/elements/BassAlter.h" #include "mx/core/elements/BassStep.h" @@ -14,6 +15,7 @@ #include "mx/core/elements/BeatUnitPer.h" #include "mx/core/elements/BeatUnitPerOrNoteRelationNoteChoice.h" #include "mx/core/elements/Bracket.h" +#include "mx/core/elements/BracketAttributes.h" #include "mx/core/elements/Coda.h" #include "mx/core/elements/Damp.h" #include "mx/core/elements/DampAll.h" @@ -28,8 +30,14 @@ #include "mx/core/elements/EditorialGroup.h" #include "mx/core/elements/EditorialVoiceDirectionGroup.h" #include "mx/core/elements/Eyeglasses.h" +#include "mx/core/elements/Fingering.h" +#include "mx/core/elements/FirstFret.h" #include "mx/core/elements/Footnote.h" #include "mx/core/elements/Frame.h" +#include "mx/core/elements/FrameFrets.h" +#include "mx/core/elements/FrameNote.h" +#include "mx/core/elements/FrameStrings.h" +#include "mx/core/elements/Fret.h" #include "mx/core/elements/Function.h" #include "mx/core/elements/Harmony.h" #include "mx/core/elements/HarmonyChordGroup.h" @@ -56,6 +64,7 @@ #include "mx/core/elements/Segno.h" #include "mx/core/elements/Sound.h" #include "mx/core/elements/Staff.h" +#include "mx/core/elements/String.h" #include "mx/core/elements/StringMute.h" #include "mx/core/elements/Voice.h" #include "mx/core/elements/Wedge.h" @@ -154,6 +163,36 @@ core::MusicDataChoiceSet DirectionWriter::getDirectionLikeThings() } } + for (const auto &pedalStart : myDirectionData.pedalStarts) + { + auto directionTypePtr = core::makeDirectionType(); + this->addDirectionType(directionTypePtr, directionPtr); + directionTypePtr->setChoice(core::DirectionType::Choice::pedal); + auto pedalPtr = directionTypePtr->getPedal(); + auto attr = pedalPtr->getAttributes(); + attr->type = core::StartStopChangeContinue::start; + attr->hasLine = true; + attr->line = core::YesNo::yes; + attr->hasSign = true; + attr->sign = core::YesNo::yes; + setAttributesFromPositionData(pedalStart.positionData, *attr); + } + + for (const auto &pedalStop : myDirectionData.pedalStops) + { + auto directionTypePtr = core::makeDirectionType(); + this->addDirectionType(directionTypePtr, directionPtr); + directionTypePtr->setChoice(core::DirectionType::Choice::pedal); + auto pedalPtr = directionTypePtr->getPedal(); + auto attr = pedalPtr->getAttributes(); + attr->type = core::StartStopChangeContinue::stop; + attr->hasLine = true; + attr->line = core::YesNo::yes; + attr->hasSign = true; + attr->sign = core::YesNo::yes; + setAttributesFromPositionData(pedalStop.positionData, *attr); + } + for (const auto &wedgeStop : myDirectionData.wedgeStops) { auto wedgeStartDirectionTypePtr = core::makeDirectionType(); @@ -247,6 +286,32 @@ core::MusicDataChoiceSet DirectionWriter::getDirectionLikeThings() } } + const auto setBracketLineData = [&](const api::LineData &lineData, core::BracketAttributes &attr) { + attr.lineEnd = lineData.lineHook == api::LineHook::unspecified ? core::LineEnd::none + : myConverter.convert(lineData.lineHook); + + if (lineData.lineType != api::LineType::unspecified) + { + attr.hasLineType = true; + attr.lineType = myConverter.convert(lineData.lineType); + } + if (lineData.isStopLengthSpecified) + { + attr.hasEndLength = true; + attr.endLength = core::TenthsValue{static_cast(lineData.endLength)}; + } + if (lineData.isDashLengthSpecified) + { + attr.hasDashLength = true; + attr.dashLength = core::TenthsValue{static_cast(lineData.dashLength)}; + } + if (lineData.isSpaceLengthSpecified) + { + attr.hasSpaceLength = true; + attr.spaceLength = core::TenthsValue{static_cast(lineData.spaceLength)}; + } + }; + for (const auto &item : myDirectionData.bracketStarts) { auto outDirType = core::makeDirectionType(); @@ -255,6 +320,47 @@ core::MusicDataChoiceSet DirectionWriter::getDirectionLikeThings() auto outElement = outDirType->getBracket(); auto &attr = *outElement->getAttributes(); setAttributesFromSpannerStart(item, attr); + attr.type = core::StartStopContinue::start; + setAttributesFromPositionData(item.positionData, attr); + setAttributesFromPrintData(item.printData, attr); + setBracketLineData(item.lineData, attr); + } + + for (const auto &item : myDirectionData.bracketStops) + { + auto outDirType = core::makeDirectionType(); + this->addDirectionType(outDirType, directionPtr); + outDirType->setChoice(core::DirectionType::Choice::bracket); + auto outElement = outDirType->getBracket(); + auto &attr = *outElement->getAttributes(); + setAttributesFromSpannerStop(item, attr); + attr.type = core::StartStopContinue::stop; + setBracketLineData(item.lineData, attr); + } + + for (const auto &item : myDirectionData.dashesStarts) + { + auto outDirType = core::makeDirectionType(); + this->addDirectionType(outDirType, directionPtr); + outDirType->setChoice(core::DirectionType::Choice::dashes); + auto outElement = outDirType->getDashes(); + auto &attr = *outElement->getAttributes(); + setAttributesFromSpannerStart(item, attr); + attr.type = core::StartStopContinue::start; + setAttributesFromPositionData(item.positionData, attr); + setAttributesFromPrintData(item.printData, attr); + setAttributesFromLineData(item.lineData, attr); + } + + for (const auto &item : myDirectionData.dashesStops) + { + auto outDirType = core::makeDirectionType(); + this->addDirectionType(outDirType, directionPtr); + outDirType->setChoice(core::DirectionType::Choice::dashes); + auto outElement = outDirType->getDashes(); + auto &attr = *outElement->getAttributes(); + setAttributesFromSpannerStop(item, attr); + attr.type = core::StartStopContinue::stop; } for (const auto &tempo : myDirectionData.tempos) @@ -529,6 +635,47 @@ core::MusicDataChoiceSet DirectionWriter::createHarmonyElements(int inOffset) harmony->addProcessingInstruction(core::ProcessingInstruction{miscIter->name, miscIter->data}); } + if (chordIter->hasFrameData) + { + harmony->setHasFrame(true); + auto frame = harmony->getFrame(); + frame->getFrameStrings()->setValue(core::PositiveInteger{chordIter->frameData.stringCount}); + frame->getFrameFrets()->setValue(core::PositiveInteger{chordIter->frameData.fretCount}); + + if (chordIter->frameData.isFirstFretSpecified) + { + frame->setHasFirstFret(true); + frame->getFirstFret()->setValue(core::PositiveInteger{chordIter->frameData.firstFret}); + } + else + { + frame->setHasFirstFret(false); + } + + frame->clearFrameNoteSet(); + for (const auto ¬eData : chordIter->frameData.notes) + { + auto frameNote = core::makeFrameNote(); + frameNote->getString()->setValue(core::StringNumber{noteData.stringNumber}); + frameNote->getFret()->setValue(core::NonNegativeInteger{noteData.fretNumber}); + + if (noteData.isFingeringSpecified) + { + frameNote->setHasFingering(true); + frameNote->getFingering()->setValue(core::XsString{std::to_string(noteData.fingering)}); + } + + if (noteData.barre != api::FrameBarre::none) + { + frameNote->setHasBarre(true); + frameNote->getBarre()->getAttributes()->type = + noteData.barre == api::FrameBarre::start ? core::StartStop::start : core::StartStop::stop; + } + + frame->addFrameNote(frameNote); + } + } + if (output.empty()) { output.push_back(mdc); diff --git a/src/private/mx/impl/MeasureReader.cpp b/src/private/mx/impl/MeasureReader.cpp index e229f409..d4630210 100644 --- a/src/private/mx/impl/MeasureReader.cpp +++ b/src/private/mx/impl/MeasureReader.cpp @@ -4,6 +4,7 @@ #include "mx/impl/MeasureReader.h" #include "mx/api/ClefData.h" +#include "mx/api/DirectionData.h" #include "mx/api/KeyData.h" #include "mx/api/NoteData.h" #include "mx/core/elements/Alter.h" @@ -26,6 +27,8 @@ #include "mx/core/elements/EditorialVoiceGroup.h" #include "mx/core/elements/Ending.h" #include "mx/core/elements/Fifths.h" +#include "mx/core/elements/Figure.h" +#include "mx/core/elements/FigureNumber.h" #include "mx/core/elements/FiguredBass.h" #include "mx/core/elements/Forward.h" #include "mx/core/elements/FullNoteGroup.h" @@ -53,13 +56,17 @@ #include "mx/core/elements/NoteChoice.h" #include "mx/core/elements/Octave.h" #include "mx/core/elements/Pitch.h" +#include "mx/core/elements/Prefix.h" #include "mx/core/elements/Print.h" #include "mx/core/elements/Properties.h" #include "mx/core/elements/Rest.h" #include "mx/core/elements/Sign.h" #include "mx/core/elements/Sound.h" #include "mx/core/elements/Staff.h" +#include "mx/core/elements/StaffDetails.h" +#include "mx/core/elements/StaffLines.h" #include "mx/core/elements/Step.h" +#include "mx/core/elements/Suffix.h" #include "mx/core/elements/Time.h" #include "mx/core/elements/TimeChoice.h" #include "mx/core/elements/TimeSignatureGroup.h" @@ -80,6 +87,73 @@ namespace mx { namespace impl { +namespace +{ +std::string figureToText(const core::Figure &figure) +{ + std::string text; + + if (figure.getHasPrefix()) + { + text += figure.getPrefix()->getValue().getValue(); + } + + if (figure.getHasFigureNumber()) + { + text += figure.getFigureNumber()->getValue().getValue(); + } + + if (figure.getHasSuffix()) + { + text += figure.getSuffix()->getValue().getValue(); + } + + return text; +} + +std::string figuredBassToText(const core::FiguredBass &figuredBass) +{ + std::string text; + + for (const auto &figure : figuredBass.getFigureSet()) + { + const auto figureText = figureToText(*figure); + + if (figureText.empty()) + { + continue; + } + + if (!text.empty()) + { + text += "\n"; + } + + text += figureText; + } + + return text; +} + +int getFiguredBassStaffIndex(const MeasureCursor &cursor, const api::MeasureData &measure, + const core::NotePtr &nextNotePtr) +{ + auto staffIndex = cursor.staffIndex; + + if (nextNotePtr) + { + staffIndex = NoteReader{*nextNotePtr}.getStaffNumber() - 1; + } + + if (staffIndex < 0 || staffIndex >= static_cast(measure.staves.size())) + { + return 0; + } + + return staffIndex; +} +} // namespace + MeasureReader::MeasureReader(const core::PartwiseMeasure &inPartwiseMeasureRef, const MeasureCursor &cursor, const MeasureCursor &previousMeasureCursor) : myMutex{}, myPartwiseMeasure{inPartwiseMeasureRef}, myConverter{}, myOutMeasureData{}, myCurrentCursor{cursor}, @@ -248,7 +322,7 @@ std::optional MeasureReader::parseMusicDataChoice(const core } case core::MusicDataChoice::Choice::figuredBass: { myCurrentCursor.isBackupInProgress = false; - parseFiguredBass(*mdc.getFiguredBass()); + parseFiguredBass(*mdc.getFiguredBass(), nextNotePtr); advanceTickTimePosition(0, "parseFiguredBass"); break; } @@ -507,6 +581,7 @@ std::optional MeasureReader::parseProperties(const core::Pro myOutMeasureData.keys.emplace_back(std::move(keyData)); } + importStaffDetails(inMxProperties); importClefs(inMxProperties.getClefSet()); if (!inMxProperties.getTransposeSet().empty()) @@ -526,9 +601,36 @@ void MeasureReader::parseHarmony(std::shared_ptr inHarmony) parseDirectionImpl(inHarmony, myOutMeasureData, myCurrentCursor); } -void MeasureReader::parseFiguredBass(const core::FiguredBass &inMxFiguredBass) const +void MeasureReader::parseFiguredBass(const core::FiguredBass &inMxFiguredBass, const core::NotePtr &nextNotePtr) const { - coutItemNotSupported(inMxFiguredBass); + auto text = figuredBassToText(inMxFiguredBass); + + if (text.empty()) + { + return; + } + + auto direction = api::DirectionData{}; + direction.tickTimePosition = myCurrentCursor.tickTimePosition; + direction.placement = api::Placement::below; + direction.isStaffValueSpecified = true; + + if (nextNotePtr) + { + direction.voice = NoteReader{*nextNotePtr}.getVoiceNumber(); + } + + auto words = api::WordsData{}; + words.text = std::move(text); + direction.words.emplace_back(std::move(words)); + + if (myOutMeasureData.staves.empty()) + { + return; + } + + const auto staffIndex = getFiguredBassStaffIndex(myCurrentCursor, myOutMeasureData, nextNotePtr); + myOutMeasureData.staves.at(static_cast(staffIndex)).directions.emplace_back(std::move(direction)); } void MeasureReader::parsePrint(const core::Print &inMxPrint) const @@ -634,6 +736,32 @@ void MeasureReader::coutItemNotSupported(const core::ElementInterface &element) // std::cout << element.getElementName() << " is not supported" << std::endl; } +void MeasureReader::importStaffDetails(const core::Properties &inMxProperties) const +{ + for (const auto &staffDetailsPtr : inMxProperties.getStaffDetailsSet()) + { + if (!staffDetailsPtr || !staffDetailsPtr->getHasStaffLines()) + { + continue; + } + + const auto &attr = *staffDetailsPtr->getAttributes(); + auto staffIndex = 0; + if (attr.hasNumber) + { + staffIndex = attr.number.getValue() - 1; + } + + if (staffIndex < 0 || staffIndex >= static_cast(myOutMeasureData.staves.size())) + { + continue; + } + + myOutMeasureData.staves.at(static_cast(staffIndex)).staffLines = + staffDetailsPtr->getStaffLines()->getValue().getValue(); + } +} + void MeasureReader::importClefs(const core::ClefSet &inClefs) const { auto iter = inClefs.cbegin(); diff --git a/src/private/mx/impl/MeasureReader.h b/src/private/mx/impl/MeasureReader.h index 720e9b5c..7a8680df 100644 --- a/src/private/mx/impl/MeasureReader.h +++ b/src/private/mx/impl/MeasureReader.h @@ -95,7 +95,7 @@ class MeasureReader std::optional parseProperties(const core::Properties &inMxProperties) const; void parseHarmony(std::shared_ptr inHarmony) const; - void parseFiguredBass(const core::FiguredBass &inMxFiguredBass) const; + void parseFiguredBass(const core::FiguredBass &inMxFiguredBass, const core::NotePtr &nextNotePtr) const; void parsePrint(const core::Print &inMxPrint) const; void parseSound(const core::Sound &inMxSound) const; void parseBarline(const core::Barline &inMxBarline) const; @@ -103,6 +103,7 @@ class MeasureReader void parseLink(const core::Link &inMxLink) const; void parseBookmark(const core::Bookmark &inMxBookmark) const; void coutItemNotSupported(const core::ElementInterface &element) const; + void importStaffDetails(const core::Properties &inMxProperties) const; void importClefs(const core::ClefSet &inClefs) const; void importClef(const core::Clef &inClef) const; void insertNoteData(api::NoteData &¬eData, int staff, int voice) const; diff --git a/src/private/mx/impl/MeasureWriter.cpp b/src/private/mx/impl/MeasureWriter.cpp index 0e500676..7b9e41ae 100644 --- a/src/private/mx/impl/MeasureWriter.cpp +++ b/src/private/mx/impl/MeasureWriter.cpp @@ -215,6 +215,16 @@ void MeasureWriter::writeMeasureGlobals() for (const auto &staff : myMeasureData.staves) { + if (staff.staffLines >= 0) + { + int desiredStaffIndex = -1; + if (myHistory.getCursor().getNumStaves() > 1) + { + desiredStaffIndex = localStaffCounter; + } + myPropertiesWriter->writeStaffDetails(desiredStaffIndex, staff.staffLines); + } + auto clefIter = staff.clefs.cbegin(); auto clefEnd = staff.clefs.cend(); while (clefIter != clefEnd && clefIter->tickTimePosition == 0) @@ -413,13 +423,6 @@ void MeasureWriter::writeVoices(const api::StaffData &inStaff) isStartOfChord = true; } - const auto localNextNoteIter = noteIter + 1; - - if (localNextNoteIter != noteEnd) - { - (void)localNextNoteIter; - } - previousChordTickPosition = currentChordTickPosition; } @@ -446,10 +449,7 @@ void MeasureWriter::writeVoices(const api::StaffData &inStaff) } myPropertiesWriter->flushBuffer(); - - { - writeDirections(directionIter, directionEnd, noteIter, std::cbegin(voice.second.notes), noteEnd); - } + writeDirections(directionIter, directionEnd, noteIter, std::cbegin(voice.second.notes), noteEnd); auto mdc = core::makeMusicDataChoice(); mdc->setChoice(core::MusicDataChoice::Choice::note); diff --git a/src/private/mx/impl/NotationsWriter.cpp b/src/private/mx/impl/NotationsWriter.cpp index 39b88014..cae8f4fd 100644 --- a/src/private/mx/impl/NotationsWriter.cpp +++ b/src/private/mx/impl/NotationsWriter.cpp @@ -4,7 +4,10 @@ #include "mx/impl/NotationsWriter.h" #include "mx/core/elements/Accent.h" +#include "mx/core/elements/Arpeggiate.h" #include "mx/core/elements/Arrow.h" +#include "mx/core/elements/ArrowDirection.h" +#include "mx/core/elements/ArrowGroup.h" #include "mx/core/elements/Articulations.h" #include "mx/core/elements/ArticulationsChoice.h" #include "mx/core/elements/Bend.h" @@ -27,9 +30,11 @@ #include "mx/core/elements/Harmonic.h" #include "mx/core/elements/Heel.h" #include "mx/core/elements/Hole.h" +#include "mx/core/elements/HoleClosed.h" #include "mx/core/elements/InvertedMordent.h" #include "mx/core/elements/InvertedTurn.h" #include "mx/core/elements/Mordent.h" +#include "mx/core/elements/NonArpeggiate.h" #include "mx/core/elements/Notations.h" #include "mx/core/elements/NotationsChoice.h" #include "mx/core/elements/OpenString.h" @@ -82,6 +87,33 @@ namespace mx { namespace impl { +namespace +{ +template +void setMordentSpecificAttributes(const api::MarkData &mark, ATTRIBUTES_TYPE &attributes) +{ + Converter converter; + + if (mark.hasMordentLong) + { + attributes.hasLong = true; + attributes.long_ = converter.convert(mark.mordentLong); + } + + if (mark.hasMordentApproach && mark.mordentApproach != api::Placement::unspecified) + { + attributes.hasApproach = true; + attributes.approach = converter.convert(mark.mordentApproach); + } + + if (mark.hasMordentDeparture && mark.mordentDeparture != api::Placement::unspecified) + { + attributes.hasDeparture = true; + attributes.departure = converter.convert(mark.mordentDeparture); + } +} +} // namespace + NotationsWriter::NotationsWriter(const api::NoteData &inNoteData, const MeasureCursor &inCursor, const ScoreWriter & /*inScoreWriter*/) : myNoteData{inNoteData}, myCursor{inCursor}, myConverter{}, myOutNotations{nullptr} @@ -343,6 +375,40 @@ core::NotationsPtr NotationsWriter::getNotations() const attr.type = core::UprightInverted::inverted; } } + else if (isMarkNonArpeggiate(mark.markType)) + { + auto nonArpeggiateNotationsChoice = core::makeNotationsChoice(); + myOutNotations->addNotationsChoice(nonArpeggiateNotationsChoice); + nonArpeggiateNotationsChoice->setChoice(core::NotationsChoice::Choice::nonArpeggiate); + auto &nonArpeggiate = *nonArpeggiateNotationsChoice->getNonArpeggiate(); + auto &attr = *nonArpeggiate.getAttributes(); + impl::setAttributesFromMarkData(mark, attr); + } + else if (isMarkArpeggiate(mark.markType)) + { + auto arpeggiateNotationsChoice = core::makeNotationsChoice(); + myOutNotations->addNotationsChoice(arpeggiateNotationsChoice); + arpeggiateNotationsChoice->setChoice(core::NotationsChoice::Choice::arpeggiate); + auto &arpeggiate = *arpeggiateNotationsChoice->getArpeggiate(); + auto &attr = *arpeggiate.getAttributes(); + impl::setAttributesFromMarkData(mark, attr); + + if (mark.markType == api::MarkType::arpeggiate) + { + attr.direction = core::UpDownNone::none; // MusicXML 4.0 Backport + attr.hasDirection = false; + } + else if (mark.markType == api::MarkType::arpeggiateUp) + { + attr.direction = core::UpDownNone::up; + attr.hasDirection = true; + } + else if (mark.markType == api::MarkType::arpeggiateDown) + { + attr.direction = core::UpDownNone::down; + attr.hasDirection = true; + } + } } if (articulations->getArticulationsChoiceSet().size() > 0) @@ -569,12 +635,14 @@ void NotationsWriter::addOrnament(const api::MarkData &mark, const core::Ornamen auto element = ornamentsChoice->getMordent(); auto attributes = element->getAttributes(); setAttributesFromPositionData(mark.positionData, *attributes); + setMordentSpecificAttributes(mark, *attributes); } else if (mark.markType == api::MarkType::invertedMordent) { auto element = ornamentsChoice->getInvertedMordent(); auto attributes = element->getAttributes(); setAttributesFromPositionData(mark.positionData, *attributes); + setMordentSpecificAttributes(mark, *attributes); } else if (mark.markType == api::MarkType::schleifer) { @@ -675,6 +743,20 @@ void NotationsWriter::addTechnical(const api::MarkData &mark, const core::Techni auto attributes = element->getAttributes(); setAttributesFromPositionData(mark.positionData, *attributes); } + else if (mark.markType == api::MarkType::fret) + { + auto element = technicalChoice->getFret(); + auto value = core::NonNegativeInteger{}; + value.parse(mark.name); + element->setValue(value); + } + else if (mark.markType == api::MarkType::string_) + { + auto element = technicalChoice->getString(); + auto value = core::StringNumber{}; + value.parse(mark.name); + element->setValue(value); + } else if (mark.markType == api::MarkType::heel) { auto element = technicalChoice->getHeel(); @@ -693,6 +775,66 @@ void NotationsWriter::addTechnical(const api::MarkData &mark, const core::Techni auto attributes = element->getAttributes(); setAttributesFromPositionData(mark.positionData, *attributes); } + else if (mark.markType == api::MarkType::hole) + { + auto element = technicalChoice->getHole(); + setAttributesFromPositionData(mark.positionData, *element->getAttributes()); + core::HoleClosedValue closedValue = core::HoleClosedValue::no; + if (mark.name == "windClosedHole") + closedValue = core::HoleClosedValue::yes; + else if (mark.name == "windHalfClosedHole3") + closedValue = core::HoleClosedValue::half; + element->getHoleClosed()->setValue(closedValue); + } + else if (mark.markType == api::MarkType::arrow) + { + auto element = technicalChoice->getArrow(); + setAttributesFromPositionData(mark.positionData, *element->getAttributes()); + element->setChoice(core::Arrow::Choice::arrowGroup); + core::ArrowDirectionEnum direction = core::ArrowDirectionEnum::up; + if (mark.name == "arrowOpenLeft") + direction = core::ArrowDirectionEnum::left; + else if (mark.name == "arrowOpenRight") + direction = core::ArrowDirectionEnum::right; + else if (mark.name == "arrowOpenDown") + direction = core::ArrowDirectionEnum::down; + else if (mark.name == "arrowOpenUpLeft") + direction = core::ArrowDirectionEnum::northwest; + else if (mark.name == "arrowOpenUpRight") + direction = core::ArrowDirectionEnum::northeast; + else if (mark.name == "arrowOpenDownRight") + direction = core::ArrowDirectionEnum::southeast; + else if (mark.name == "arrowOpenDownLeft") + direction = core::ArrowDirectionEnum::southwest; + element->getArrowGroup()->getArrowDirection()->setValue(direction); + } + else if (mark.markType == api::MarkType::handbell) + { + auto element = technicalChoice->getHandbell(); + using HB = core::HandbellValue; + HB value = HB::gyro; + if (mark.name == "handbellsDamp3") + value = HB::damp; + else if (mark.name == "handbellsEcho1") + value = HB::echo; + else if (mark.name == "handbellsHandMartellato") + value = HB::handMartellato; + else if (mark.name == "handbellsMalletLft") + value = HB::malletLift; + else if (mark.name == "handbellsMalletBellOnTable") + value = HB::malletTable; + else if (mark.name == "handbellsMartellato") + value = HB::martellato; + else if (mark.name == "handbellsMartellatoLift") + value = HB::martellatoLift; + else if (mark.name == "handbellsMutedMartellato") + value = HB::mutedMartellato; + else if (mark.name == "handbellsPluckLift") + value = HB::pluckLift; + else if (mark.name == "handbellsSwing") + value = HB::swing; + element->setValue(value); + } else if ((mark.markType == api::MarkType::unknownTechnical) || (mark.markType == api::MarkType::otherTechnical)) { auto element = technicalChoice->getOtherTechnical(); diff --git a/src/private/mx/impl/NoteFunctions.cpp b/src/private/mx/impl/NoteFunctions.cpp index edff8dcd..7dd1e317 100644 --- a/src/private/mx/impl/NoteFunctions.cpp +++ b/src/private/mx/impl/NoteFunctions.cpp @@ -175,6 +175,7 @@ api::NoteData NoteFunctions::parseNote() const } myOutNoteData.isTieStart = reader.getIsTieStart(); myOutNoteData.isTieStop = reader.getIsTieStop(); + myOutNoteData.lyrics = reader.getLyrics(); parseMiscData(); const auto &incomingNoteAttributes = *(myNote.getAttributes()); diff --git a/src/private/mx/impl/NoteReader.cpp b/src/private/mx/impl/NoteReader.cpp index 0b931a1a..eed85558 100644 --- a/src/private/mx/impl/NoteReader.cpp +++ b/src/private/mx/impl/NoteReader.cpp @@ -13,6 +13,9 @@ #include "mx/core/elements/DisplayStepOctaveGroup.h" #include "mx/core/elements/Duration.h" #include "mx/core/elements/EditorialVoiceGroup.h" +#include "mx/core/elements/Elision.h" +#include "mx/core/elements/ElisionSyllabicGroup.h" +#include "mx/core/elements/ElisionSyllabicTextGroup.h" #include "mx/core/elements/FullNoteGroup.h" #include "mx/core/elements/FullNoteTypeChoice.h" #include "mx/core/elements/GraceNoteGroup.h" @@ -39,6 +42,9 @@ #include "mx/core/elements/Type.h" #include "mx/core/elements/Unpitched.h" #include "mx/core/elements/Voice.h" +#include "mx/impl/FontFunctions.h" +#include "mx/impl/PositionFunctions.h" +#include "mx/impl/PrintFunctions.h" #include "mx/utility/StringToInt.h" #include "Converter.h" @@ -49,6 +55,140 @@ namespace mx { namespace impl { +namespace +{ +api::LyricSyllabic convertLyricSyllabic(core::SyllabicEnum value) +{ + switch (value) + { + case core::SyllabicEnum::single: + return api::LyricSyllabic::single; + case core::SyllabicEnum::begin: + return api::LyricSyllabic::begin; + case core::SyllabicEnum::end: + return api::LyricSyllabic::end; + case core::SyllabicEnum::middle: + return api::LyricSyllabic::middle; + } + + return api::LyricSyllabic::single; +} + +api::PositionData getLyricPositionData(const core::LyricAttributes &inAttributes) +{ + api::PositionData outPositionData; + + if (inAttributes.hasDefaultX) + { + outPositionData.isDefaultXSpecified = true; + outPositionData.defaultX = inAttributes.defaultX.getValue(); + } + + if (inAttributes.hasDefaultY) + { + outPositionData.isDefaultYSpecified = true; + outPositionData.defaultY = inAttributes.defaultY.getValue(); + } + + if (inAttributes.hasRelativeX) + { + outPositionData.isRelativeXSpecified = true; + outPositionData.relativeX = inAttributes.relativeX.getValue(); + } + + if (inAttributes.hasRelativeY) + { + outPositionData.isRelativeYSpecified = true; + outPositionData.relativeY = inAttributes.relativeY.getValue(); + } + + Converter converter; + + if (inAttributes.hasPlacement) + { + outPositionData.placement = converter.convert(inAttributes.placement); + } + + if (inAttributes.hasJustify) + { + outPositionData.horizontalAlignmnet = converter.convert(inAttributes.justify); + } + + return outPositionData; +} + +api::PrintData getLyricPrintData(const core::LyricAttributes &lyricAttributes, + const core::TextAttributes *textAttributes) +{ + api::PrintData outPrintData; + outPrintData.printObject = getPrintObject(lyricAttributes); + + if (lyricAttributes.hasColor) + { + outPrintData.isColorSpecified = true; + outPrintData.color = getColor(lyricAttributes); + } + + if (textAttributes) + { + outPrintData.fontData = getFontData(*textAttributes); + } + + return outPrintData; +} + +std::string getElisionDisplayText(const core::ElisionSyllabicTextGroup &inGroup) +{ + if (inGroup.getHasElisionSyllabicGroup()) + { + const auto &elisionGroup = inGroup.getElisionSyllabicGroup(); + if (elisionGroup) + { + const auto &elision = elisionGroup->getElision(); + if (elision) + { + const auto value = elision->getValue().getValue(); + if (!value.empty()) + { + return value; + } + } + } + } + + return "\xE2\x80\xBF"; +} + +std::string getLyricDisplayText(const core::SyllabicTextGroup &inGroup) +{ + std::string result; + + const auto &leadingText = inGroup.getText(); + if (leadingText) + { + result += leadingText->getValue().getValue(); + } + + for (const auto &group : inGroup.getElisionSyllabicTextGroupSet()) + { + if (!group) + { + continue; + } + + result += getElisionDisplayText(*group); + + const auto &text = group->getText(); + if (text) + { + result += text->getValue().getValue(); + } + } + + return result; +} +} // namespace + NoteReader::NoteReader(const core::Note &mxNote) : myNote(mxNote), myNoteChoice(*myNote.getNoteChoice()), myFullNoteGroup(findFullNoteGroup(myNoteChoice)), myIsNormal(false), myIsGrace(false), myIsCue(false), myIsRest(false), myIsChord(false), myIsMeasureRest(false), @@ -371,6 +511,20 @@ void NoteReader::setLyric() const auto &textChoice = lyric->getLyricTextChoice(); if (textChoice) { + api::LyricData lyricData; + const auto &lyricAttributes = *lyric->getAttributes(); + lyricData.positionData = getLyricPositionData(lyricAttributes); + + if (lyricAttributes.hasNumber) + { + lyricData.verseNumber = lyricAttributes.number.getValue(); + } + + if (lyricAttributes.hasName) + { + lyricData.verseName = lyricAttributes.name.getValue(); + } + const auto choice = textChoice->getChoice(); switch (choice) { @@ -387,16 +541,25 @@ void NoteReader::setLyric() const auto &textPtr = textGroup->getText(); if (textPtr) { - const auto text = textPtr->getValue(); - const LyricType lyricType(text.getValue(), syllabic); - myLyrics.emplace_back(lyricType); - myHasLyric = true; + lyricData.text = getLyricDisplayText(*textGroup); + lyricData.printData = getLyricPrintData(lyricAttributes, textPtr->getAttributes().get()); } + + lyricData.syllabic = convertLyricSyllabic(syllabic); + lyricData.hasExtend = textGroup->getHasExtend(); + myLyrics.emplace_back(lyricData); + myHasLyric = true; } break; } case core::LyricTextChoice::Choice::extend: + lyricData.hasExtend = true; + lyricData.printData = getLyricPrintData(lyricAttributes, nullptr); + myLyrics.emplace_back(lyricData); + myHasLyric = true; + break; + case core::LyricTextChoice::Choice::laughing: case core::LyricTextChoice::Choice::humming: { break; diff --git a/src/private/mx/impl/NoteReader.h b/src/private/mx/impl/NoteReader.h index 11b178bf..ad0d783d 100644 --- a/src/private/mx/impl/NoteReader.h +++ b/src/private/mx/impl/NoteReader.h @@ -4,9 +4,9 @@ #pragma once +#include "mx/api/LyricData.h" #include "mx/core/Enums.h" #include "mx/core/elements/Tie.h" -#include "mx/impl/LyricType.h" #include namespace mx @@ -223,6 +223,11 @@ class NoteReader return myIsTieStop; } + inline const std::vector &getLyrics() const + { + return myLyrics; + } + private: const core::Note &myNote; const core::NoteChoice &myNoteChoice; @@ -263,7 +268,7 @@ class NoteReader bool myIsTieStart; bool myIsTieStop; bool myHasLyric; - std::vector myLyrics; + std::vector myLyrics; private: const core::FullNoteGroup &findFullNoteGroup(const core::NoteChoice ¬eChoice) const; diff --git a/src/private/mx/impl/NoteWriter.cpp b/src/private/mx/impl/NoteWriter.cpp index 37f09fc1..77bd1d79 100644 --- a/src/private/mx/impl/NoteWriter.cpp +++ b/src/private/mx/impl/NoteWriter.cpp @@ -332,7 +332,9 @@ void NoteWriter::setFullNoteTypeChoice() const myOutFullNoteTypeChoice->setChoice(core::FullNoteTypeChoice::Choice::rest); if (myNoteData.isDisplayStepOctaveSpecified) { - auto pitch = myOutFullNoteTypeChoice->getUnpitched()->getDisplayStepOctaveGroup(); + auto rest = myOutFullNoteTypeChoice->getRest(); + rest->setHasDisplayStepOctaveGroup(true); + auto pitch = rest->getDisplayStepOctaveGroup(); pitch->getDisplayStep()->setValue(myConverter.convert(myNoteData.pitchData.step)); pitch->getDisplayOctave()->setValue(core::OctaveValue{myNoteData.pitchData.octave}); } @@ -349,7 +351,9 @@ void NoteWriter::setFullNoteTypeChoice() const myOutFullNoteTypeChoice->setChoice(core::FullNoteTypeChoice::Choice::unpitched); if (myNoteData.isDisplayStepOctaveSpecified) { - auto pitch = myOutFullNoteTypeChoice->getUnpitched()->getDisplayStepOctaveGroup(); + auto unpitched = myOutFullNoteTypeChoice->getUnpitched(); + unpitched->setHasDisplayStepOctaveGroup(true); + auto pitch = unpitched->getDisplayStepOctaveGroup(); pitch->getDisplayStep()->setValue(myConverter.convert(myNoteData.pitchData.step)); pitch->getDisplayOctave()->setValue(core::OctaveValue{myNoteData.pitchData.octave}); } diff --git a/src/private/mx/impl/OrnamentsFunctions.cpp b/src/private/mx/impl/OrnamentsFunctions.cpp index ea1baae9..46f714d3 100644 --- a/src/private/mx/impl/OrnamentsFunctions.cpp +++ b/src/private/mx/impl/OrnamentsFunctions.cpp @@ -25,6 +25,33 @@ namespace mx { namespace impl { +namespace +{ +template +void parseMordentSpecificAttributes(const ATTRIBUTES_TYPE &attr, api::MarkData &outMark) +{ + Converter converter; + + if (attr.hasLong) + { + outMark.hasMordentLong = true; + outMark.mordentLong = converter.convert(attr.long_); + } + + if (attr.hasApproach) + { + outMark.hasMordentApproach = true; + outMark.mordentApproach = converter.convert(attr.approach); + } + + if (attr.hasDeparture) + { + outMark.hasMordentDeparture = true; + outMark.mordentDeparture = converter.convert(attr.departure); + } +} +} // namespace + OrnamentsFunctions::OrnamentsFunctions(const core::Ornaments &inOrnaments, impl::Cursor inCursor) : myOrnaments{inOrnaments}, myCursor{inCursor} { @@ -116,12 +143,16 @@ void OrnamentsFunctions::parseOrnament(const core::OrnamentsChoice &choiceObj, a } case core::OrnamentsChoice::Choice::mordent: { outMark.name = "mordent"; - parseMarkDataAttributes(*choiceObj.getMordent()->getAttributes(), outMark); + const auto &attr = *choiceObj.getMordent()->getAttributes(); + parseMarkDataAttributes(attr, outMark); + parseMordentSpecificAttributes(attr, outMark); break; } case core::OrnamentsChoice::Choice::invertedMordent: { outMark.name = "inverted-mordent"; - parseMarkDataAttributes(*choiceObj.getInvertedMordent()->getAttributes(), outMark); + const auto &attr = *choiceObj.getInvertedMordent()->getAttributes(); + parseMarkDataAttributes(attr, outMark); + parseMordentSpecificAttributes(attr, outMark); break; } case core::OrnamentsChoice::Choice::schleifer: { diff --git a/src/private/mx/impl/PropertiesWriter.cpp b/src/private/mx/impl/PropertiesWriter.cpp index 1f152097..9a9ade85 100644 --- a/src/private/mx/impl/PropertiesWriter.cpp +++ b/src/private/mx/impl/PropertiesWriter.cpp @@ -62,6 +62,7 @@ #include "mx/core/elements/Sign.h" #include "mx/core/elements/Staff.h" #include "mx/core/elements/StaffDetails.h" +#include "mx/core/elements/StaffLines.h" #include "mx/core/elements/Staves.h" #include "mx/core/elements/Stem.h" #include "mx/core/elements/SystemDistance.h" @@ -198,8 +199,8 @@ void PropertiesWriter::writeTime(const api::TimeSignatureData &value) myProperties->addTime(time); time->getTimeChoice()->setChoice(core::TimeChoice::Choice::timeSignature); auto sigGrp = time->getTimeChoice()->getTimeSignatureGroupSet().front(); - sigGrp->getBeats()->setValue(core::XsString{std::to_string(value.beats)}); - sigGrp->getBeatType()->setValue(core::XsString{std::to_string(value.beatType)}); + sigGrp->getBeats()->setValue(core::XsString{value.beats}); + sigGrp->getBeatType()->setValue(core::XsString{value.beatType}); const auto symbol = value.symbol; if (symbol != api::TimeSignatureSymbol::unspecified) @@ -213,6 +214,10 @@ void PropertiesWriter::writeTime(const api::TimeSignatureData &value) { time->getAttributes()->symbol = core::TimeSymbol::cut; } + else if (symbol == api::TimeSignatureSymbol::singleNumber) + { + time->getAttributes()->symbol = core::TimeSymbol::singleNumber; + } } Converter converter; @@ -229,6 +234,20 @@ void PropertiesWriter::writeNumStaves(int value) myProperties->getStaves()->setValue(core::NonNegativeInteger{value}); } +void PropertiesWriter::writeStaffDetails(int staffIndex, int staffLines) +{ + auto staffDetails = core::makeStaffDetails(); + if (staffIndex >= 0) + { + staffDetails->getAttributes()->hasNumber = true; + staffDetails->getAttributes()->number = core::StaffNumber{staffIndex + 1}; + } + + staffDetails->setHasStaffLines(true); + staffDetails->getStaffLines()->setValue(core::NonNegativeInteger{staffLines}); + myProperties->addStaffDetails(staffDetails); +} + void PropertiesWriter::writeClef(int staffIndex, const api::ClefData &inClefData) { auto mxClef = core::makeClef(); diff --git a/src/private/mx/impl/PropertiesWriter.h b/src/private/mx/impl/PropertiesWriter.h index d9bc1959..bffc0bf3 100644 --- a/src/private/mx/impl/PropertiesWriter.h +++ b/src/private/mx/impl/PropertiesWriter.h @@ -57,6 +57,7 @@ class PropertiesWriter static void writeNonTraditionalKey(const api::KeyData &inKeyData, mx::core::KeyPtr &ioKey); void writeTime(const api::TimeSignatureData &value); void writeNumStaves(int value); + void writeStaffDetails(int staffIndex, int staffLines); void writeClef(int staffIndex, const api::ClefData &inClefData); void writeTranspose(const api::TransposeData &inTransposeData); diff --git a/src/private/mx/impl/SpannerFunctions.h b/src/private/mx/impl/SpannerFunctions.h index d3e0d9aa..3c0cec3d 100644 --- a/src/private/mx/impl/SpannerFunctions.h +++ b/src/private/mx/impl/SpannerFunctions.h @@ -41,6 +41,7 @@ template api::SpannerStop getSpannerStop(const ATTRIB stop.numberLevel = checkNumber(&inAttributes); } stop.positionData = getPositionData(inAttributes); + stop.lineData = getLineData(inAttributes); return stop; } @@ -61,5 +62,23 @@ void setAttributesFromSpannerStart(const api::SpannerStart &start, ATTRIBUTES_TY lookForAndSetNumber(1, &outAttributes); } } + +template +void setAttributesFromSpannerStop(const api::SpannerStop &stop, ATTRIBUTES_TYPE &outAttributes) +{ + if (stop.numberLevel > 0) + { + lookForAndSetHasNumber(true, &outAttributes); + lookForAndSetNumber(stop.numberLevel, &outAttributes); + } + else + { + lookForAndSetHasNumber(false, &outAttributes); + lookForAndSetNumber(1, &outAttributes); + } + + setAttributesFromPositionData(stop.positionData, outAttributes); + setAttributesFromLineData(stop.lineData, outAttributes); +} } // namespace impl } // namespace mx diff --git a/src/private/mx/impl/TechnicalFunctions.cpp b/src/private/mx/impl/TechnicalFunctions.cpp index 44b1b6f2..3130ec0a 100644 --- a/src/private/mx/impl/TechnicalFunctions.cpp +++ b/src/private/mx/impl/TechnicalFunctions.cpp @@ -4,6 +4,8 @@ #include "mx/impl/TechnicalFunctions.h" #include "mx/core/elements/Arrow.h" +#include "mx/core/elements/ArrowDirection.h" +#include "mx/core/elements/ArrowGroup.h" #include "mx/core/elements/Bend.h" #include "mx/core/elements/DoubleTongue.h" #include "mx/core/elements/DownBow.h" @@ -15,6 +17,7 @@ #include "mx/core/elements/Harmonic.h" #include "mx/core/elements/Heel.h" #include "mx/core/elements/Hole.h" +#include "mx/core/elements/HoleClosed.h" #include "mx/core/elements/OpenString.h" #include "mx/core/elements/OtherTechnical.h" #include "mx/core/elements/Pluck.h" @@ -32,6 +35,88 @@ #include "mx/impl/Converter.h" #include "mx/impl/MarkDataFunctions.h" +namespace +{ +std::string holeToSmuflName(const mx::core::Hole &hole) +{ + const auto closedValue = hole.getHoleClosed()->getValue(); + switch (closedValue) + { + case mx::core::HoleClosedValue::yes: + return "windClosedHole"; + case mx::core::HoleClosedValue::half: + return "windHalfClosedHole3"; + case mx::core::HoleClosedValue::no: + default: + return "windOpenHole"; + } +} + +std::string arrowToSmuflName(const mx::core::Arrow &arrow) +{ + using Direction = mx::core::ArrowDirectionEnum; + if (arrow.getChoice() != mx::core::Arrow::Choice::arrowGroup) + { + return "arrowOpenUp"; + } + + const auto direction = arrow.getArrowGroup()->getArrowDirection()->getValue(); + switch (direction) + { + case Direction::left: + return "arrowOpenLeft"; + case Direction::up: + return "arrowOpenUp"; + case Direction::right: + return "arrowOpenRight"; + case Direction::down: + return "arrowOpenDown"; + case Direction::northwest: + return "arrowOpenUpLeft"; + case Direction::northeast: + return "arrowOpenUpRight"; + case Direction::southeast: + return "arrowOpenDownRight"; + case Direction::southwest: + return "arrowOpenDownLeft"; + default: + return "arrowOpenUp"; + } +} + +std::string handbellToSmuflName(const mx::core::HandbellValue value) +{ + using Handbell = mx::core::HandbellValue; + switch (value) + { + case Handbell::damp: + return "handbellsDamp3"; + case Handbell::echo: + return "handbellsEcho1"; + case Handbell::gyro: + return "handbellsGyro"; + case Handbell::handMartellato: + return "handbellsHandMartellato"; + case Handbell::malletLift: + return "handbellsMalletLft"; + case Handbell::malletTable: + return "handbellsMalletBellOnTable"; + case Handbell::martellato: + return "handbellsMartellato"; + case Handbell::martellatoLift: + return "handbellsMartellatoLift"; + case Handbell::mutedMartellato: + return "handbellsMutedMartellato"; + case Handbell::pluckLift: + return "handbellsPluckLift"; + case Handbell::swing: + return "handbellsSwing"; + default: + return "handbellsGyro"; + } +} +} // namespace + namespace mx { namespace impl @@ -124,10 +209,18 @@ bool TechnicalFunctions::parseTechicalMark(const core::TechnicalChoice &techical outMarkData.name = "snap-pizzicato"; return true; } - case core::TechnicalChoice::Choice::fret: - return false; - case core::TechnicalChoice::Choice::string_: - return false; + case core::TechnicalChoice::Choice::fret: { + const auto &fret = *techicalChoice.getFret(); + parseMarkDataAttributes(*fret.getAttributes(), outMarkData); + outMarkData.name = core::toString(fret.getValue()); + return true; + } + case core::TechnicalChoice::Choice::string_: { + const auto &stringNumber = *techicalChoice.getString(); + parseMarkDataAttributes(*stringNumber.getAttributes(), outMarkData); + outMarkData.name = core::toString(stringNumber.getValue()); + return true; + } case core::TechnicalChoice::Choice::hammerOn: return false; case core::TechnicalChoice::Choice::pullOff: @@ -151,12 +244,24 @@ bool TechnicalFunctions::parseTechicalMark(const core::TechnicalChoice &techical outMarkData.name = "fingernails"; return true; } - case core::TechnicalChoice::Choice::hole: - return false; - case core::TechnicalChoice::Choice::arrow: - return false; - case core::TechnicalChoice::Choice::handbell: - return false; + case core::TechnicalChoice::Choice::hole: { + const auto &hole = *techicalChoice.getHole(); + parseMarkDataAttributes(*hole.getAttributes(), outMarkData); + outMarkData.name = holeToSmuflName(hole); + return true; + } + case core::TechnicalChoice::Choice::arrow: { + const auto &arrow = *techicalChoice.getArrow(); + parseMarkDataAttributes(*arrow.getAttributes(), outMarkData); + outMarkData.name = arrowToSmuflName(arrow); + return true; + } + case core::TechnicalChoice::Choice::handbell: { + const auto &handbell = *techicalChoice.getHandbell(); + parseMarkDataAttributes(*handbell.getAttributes(), outMarkData); + outMarkData.name = handbellToSmuflName(handbell.getValue()); + return true; + } case core::TechnicalChoice::Choice::otherTechnical: { const auto &other = *techicalChoice.getOtherTechnical(); const auto &attr = *other.getAttributes(); diff --git a/src/private/mx/impl/TechnicalFunctions.h b/src/private/mx/impl/TechnicalFunctions.h index bd3288a1..c3bff1ae 100644 --- a/src/private/mx/impl/TechnicalFunctions.h +++ b/src/private/mx/impl/TechnicalFunctions.h @@ -12,8 +12,6 @@ // TODO - import fingering // TODO - import pluck -// TODO - import fret -// TODO - import string_ // TODO - import hammerOn // TODO - import pullOff // TODO - import bend diff --git a/src/private/mx/impl/TimeReader.cpp b/src/private/mx/impl/TimeReader.cpp index 0bbc36f4..a63f44ad 100644 --- a/src/private/mx/impl/TimeReader.cpp +++ b/src/private/mx/impl/TimeReader.cpp @@ -15,8 +15,6 @@ #include "mx/core/elements/Time.h" #include "mx/core/elements/TimeChoice.h" #include "mx/core/elements/TimeSignatureGroup.h" -#include "mx/impl/LcmGcd.h" -#include "mx/utility/StringToInt.h" #include "mx/utility/Throw.h" #include @@ -27,6 +25,7 @@ namespace mx { namespace impl { + TimeReader::TimeReader(const core::MusicDataChoiceSet &inMusicDataChoices) : myMusicDataChoiceSet{inMusicDataChoices}, myIsTimeFound{false}, myTimeSignatureData{} { @@ -79,24 +78,8 @@ bool TimeReader::parseTime() bool TimeReader::parseTimeSignatureGroup(const core::TimeSignatureGroup &timeSig) { - const auto beatsStr = timeSig.getBeats()->getValue().getValue(); - int beats = myTimeSignatureData.beats; - - if (!utility::stringToInt(beatsStr, beats)) - { - return false; - } - - const auto beatTypeStr = timeSig.getBeatType()->getValue().getValue(); - int beatType = myTimeSignatureData.beatType; - - if (!utility::stringToInt(beatTypeStr, beatType)) - { - return false; - } - - myTimeSignatureData.beats = beats; - myTimeSignatureData.beatType = beatType; + myTimeSignatureData.beats = timeSig.getBeats()->getValue().getValue(); + myTimeSignatureData.beatType = timeSig.getBeatType()->getValue().getValue(); if (myTime->getAttributes()->hasSymbol) { @@ -108,6 +91,10 @@ bool TimeReader::parseTimeSignatureGroup(const core::TimeSignatureGroup &timeSig { myTimeSignatureData.symbol = api::TimeSignatureSymbol::cut; } + else if (myTime->getAttributes()->symbol == core::TimeSymbol::singleNumber) + { + myTimeSignatureData.symbol = api::TimeSignatureSymbol::singleNumber; + } } else { diff --git a/src/private/mxtest/api/ApiK009bSlurScoreData.h b/src/private/mxtest/api/ApiK009bSlurScoreData.h index c0cea3d7..ec863931 100644 --- a/src/private/mxtest/api/ApiK009bSlurScoreData.h +++ b/src/private/mxtest/api/ApiK009bSlurScoreData.h @@ -29,8 +29,8 @@ inline mx::api::ScoreData apiK009bSlurAttributesScoreData() // 1 part.measures.emplace_back(MeasureData{}); auto measure = &part.measures.back(); - measure->timeSignature.beats = 4; - measure->timeSignature.beatType = 4; + measure->timeSignature.beats = "4"; + measure->timeSignature.beatType = "4"; measure->timeSignature.isImplicit = false; measure->staves.emplace_back(StaffData{}); auto staff = &measure->staves.back(); diff --git a/src/private/mxtest/api/ApiK014aFermatasScoreData.h b/src/private/mxtest/api/ApiK014aFermatasScoreData.h index 175fc384..d1416bb8 100644 --- a/src/private/mxtest/api/ApiK014aFermatasScoreData.h +++ b/src/private/mxtest/api/ApiK014aFermatasScoreData.h @@ -25,8 +25,8 @@ inline mx::api::ScoreData apiK014aFermatasScoreData() // 1 part.measures.emplace_back(MeasureData{}); auto measure = &part.measures.back(); - measure->timeSignature.beats = 4; - measure->timeSignature.beatType = 4; + measure->timeSignature.beats = "4"; + measure->timeSignature.beatType = "4"; measure->timeSignature.isImplicit = false; measure->staves.emplace_back(StaffData{}); auto staff = &measure->staves.back(); diff --git a/src/private/mxtest/api/ApiK015aLayoutScoreData.h b/src/private/mxtest/api/ApiK015aLayoutScoreData.h index 379a7f59..8bbb40fe 100644 --- a/src/private/mxtest/api/ApiK015aLayoutScoreData.h +++ b/src/private/mxtest/api/ApiK015aLayoutScoreData.h @@ -61,8 +61,8 @@ inline mx::api::ScoreData apiK015aLayoutScoreData() part.measures.emplace_back(MeasureData{}); auto measure = &part.measures.back(); measure->width = 0.0; - measure->timeSignature.beats = 4; - measure->timeSignature.beatType = 4; + measure->timeSignature.beats = "4"; + measure->timeSignature.beatType = "4"; measure->timeSignature.isImplicit = false; measure->staves.emplace_back(StaffData{}); auto staff = &measure->staves.back(); diff --git a/src/private/mxtest/api/ApiMuAccidentals1ScoreData.h b/src/private/mxtest/api/ApiMuAccidentals1ScoreData.h index 28820d75..a96dfbf7 100644 --- a/src/private/mxtest/api/ApiMuAccidentals1ScoreData.h +++ b/src/private/mxtest/api/ApiMuAccidentals1ScoreData.h @@ -38,8 +38,8 @@ inline mx::api::ScoreData apiMuAccidentals1ScoreData() part.measures.emplace_back(MeasureData{}); auto measure = &part.measures.back(); - measure->timeSignature.beats = 3; - measure->timeSignature.beatType = 4; + measure->timeSignature.beats = "3"; + measure->timeSignature.beatType = "4"; measure->timeSignature.isImplicit = false; measure->staves.emplace_back(StaffData{}); measure->keys.emplace_back(KeyData{}); @@ -87,8 +87,8 @@ inline mx::api::ScoreData apiMuAccidentals1ScoreData() part.measures.emplace_back(MeasureData{}); measure = &part.measures.back(); - measure->timeSignature.beats = 3; - measure->timeSignature.beatType = 4; + measure->timeSignature.beats = "3"; + measure->timeSignature.beatType = "4"; measure->timeSignature.isImplicit = true; measure->staves.emplace_back(StaffData{}); staff = &measure->staves.back(); diff --git a/src/private/mxtest/api/MeasureDataTest.cpp b/src/private/mxtest/api/MeasureDataTest.cpp index 8aef7b18..6b3fa9fc 100644 --- a/src/private/mxtest/api/MeasureDataTest.cpp +++ b/src/private/mxtest/api/MeasureDataTest.cpp @@ -27,6 +27,7 @@ #include "mx/core/elements/ScorePartwise.h" #include "mx/core/elements/Tied.h" #include "mxtest/api/RoundTrip.h" +#include "mxtest/api/TestHelpers.h" using namespace std; using namespace mx::api; @@ -123,4 +124,29 @@ TEST(backwardRepeat, MeasureData) T_END; +TEST(staffLinesRoundTrip, 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.voices[0].notes.emplace_back(); + + const auto xml = mxtest::toXml(score); + CHECK(xml.find("") != std::string::npos); + CHECK(xml.find("1") != 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); +} + +T_END; + #endif diff --git a/src/private/mxtest/api/NoteDataTest.cpp b/src/private/mxtest/api/NoteDataTest.cpp index a0ecce53..5da42ea3 100644 --- a/src/private/mxtest/api/NoteDataTest.cpp +++ b/src/private/mxtest/api/NoteDataTest.cpp @@ -28,6 +28,7 @@ #include "mx/core/elements/ScorePartwise.h" #include "mx/core/elements/Tied.h" #include "mxtest/api/RoundTrip.h" +#include "mxtest/file/Path.h" using namespace std; using namespace mx::api; @@ -83,6 +84,48 @@ TEST(otherArticulation, NoteData) T_END; +TEST(pitchedRestDisplayStepOctave, NoteData) +{ + ScoreData score; + score.parts.emplace_back(); + score.ticksPerQuarter = 96; + auto &part = score.parts.back(); + part.measures.emplace_back(); + auto &measure = part.measures.back(); + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + auto &voice = staff.voices[0]; + + NoteData rest; + rest.isRest = true; + rest.isDisplayStepOctaveSpecified = true; + rest.pitchData.step = Step::e; + rest.pitchData.octave = 4; + rest.durationData.durationName = DurationName::quarter; + rest.durationData.durationTimeTicks = 96; + voice.notes.push_back(rest); + + auto &mgr = DocumentManager::getInstance(); + auto docId = mgr.createFromScore(score); + std::stringstream ss; + mgr.writeToStream(docId, ss); + mgr.destroyDocument(docId); + const auto xml = ss.str(); + CHECK(xml.find("E") != std::string::npos); + CHECK(xml.find("4") != std::string::npos); + + std::istringstream iss{xml}; + docId = mgr.createFromStream(iss); + const auto outScore = mgr.getData(docId); + mgr.destroyDocument(docId); + + const auto &outRest = outScore.parts.back().measures.back().staves.back().voices.begin()->second.notes.back(); + CHECK(outRest.isRest); + CHECK(outRest.isDisplayStepOctaveSpecified); + CHECK(outRest.pitchData.step == Step::e); + CHECK_EQUAL(4, outRest.pitchData.octave); +} + TEST(customErrorUnknown, MarkData) { const auto expectedMark = mx::api::MarkType::customErrorUnknown; @@ -255,6 +298,87 @@ TEST(technical, NoteData) T_END; +TEST(technical_import_file, NoteData) +{ + auto &mgr = DocumentManager::getInstance(); + const auto path = std::string{mxtest::getResourcesDirectoryPath()} + std::string{"/ksuite/k004a_Technical.xml"}; + const auto docId = mgr.createFromFile(path); + const auto score = mgr.getData(docId); + mgr.destroyDocument(docId); + + const auto &part = score.parts.at(0); + + const auto &fingernailsMark = + part.measures.at(19).staves.at(0).voices.at(0).notes.at(0).noteAttachmentData.marks.at(0); + CHECK(fingernailsMark.markType == MarkType::fingernails); + CHECK_EQUAL("fingernails", fingernailsMark.name); + + const auto &holeMark = part.measures.at(20).staves.at(0).voices.at(0).notes.at(0).noteAttachmentData.marks.at(0); + CHECK(holeMark.markType == MarkType::hole); + CHECK_EQUAL("windOpenHole", holeMark.name); + + const auto &arrowMark = part.measures.at(21).staves.at(0).voices.at(0).notes.at(0).noteAttachmentData.marks.at(0); + CHECK(arrowMark.markType == MarkType::arrow); + CHECK_EQUAL("arrowOpenUp", arrowMark.name); + + const auto &handbellMark = + part.measures.at(22).staves.at(0).voices.at(0).notes.at(0).noteAttachmentData.marks.at(0); + CHECK(handbellMark.markType == MarkType::handbell); + CHECK_EQUAL("handbellsDamp3", handbellMark.name); +} + +T_END; + +// Exposes the missing write path: hole, arrow, and handbell are classified as +// isMarkTechnical but NotationsWriter::addTechnical has no branches for them, +// so they silently emit the wrong default element on round-trip. PR $146 Fixup +TEST(technical_hole_arrow_handbell_roundtrip, NoteData) +{ + auto makeScore = [](MarkType markType) { + ScoreData score; + score.parts.emplace_back(); + score.ticksPerQuarter = 96; + auto &part = score.parts.back(); + part.measures.emplace_back(); + auto &measure = part.measures.back(); + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + auto &voice = staff.voices[0]; + NoteData note; + note.durationData.durationName = DurationName::quarter; + note.durationData.durationTimeTicks = 96; + note.noteAttachmentData.marks.emplace_back(markType); + voice.notes.push_back(std::move(note)); + return score; + }; + + auto &mgr = DocumentManager::getInstance(); + + for (const auto markType : {MarkType::hole, MarkType::arrow, MarkType::handbell}) + { + auto docId = mgr.createFromScore(makeScore(markType)); + std::stringstream ss; + mgr.writeToStream(docId, ss); + mgr.destroyDocument(docId); + + std::istringstream iss{ss.str()}; + docId = mgr.createFromStream(iss); + const auto outScore = mgr.getData(docId); + mgr.destroyDocument(docId); + + const auto &outMarks = outScore.parts.back() + .measures.back() + .staves.back() + .voices.begin() + ->second.notes.back() + .noteAttachmentData.marks; + CHECK_EQUAL(1, outMarks.size()); + CHECK(outMarks.front().markType == markType); + } +} + +T_END; + TEST(words, NoteData) { ScoreData score; @@ -875,6 +999,7 @@ TEST(directionOrderRoundTrip, NoteData) direction.tickTimePosition = 0; mark.tickTimePosition = direction.tickTimePosition; direction.marks.push_back(mark); + direction.orderedComponents.emplace_back(DirectionComponentKind::mark, 0); staff.directions.push_back(direction); placement = Placement::unspecified; diff --git a/src/private/mxtest/api/TimeSignatureApiTest.cpp b/src/private/mxtest/api/TimeSignatureApiTest.cpp index fdc1eccf..702528d9 100644 --- a/src/private/mxtest/api/TimeSignatureApiTest.cpp +++ b/src/private/mxtest/api/TimeSignatureApiTest.cpp @@ -28,15 +28,15 @@ TEST(implicitCarryover, TimeSignatureApi) auto t = measureIter->timeSignature; CHECK(!t.isImplicit); - CHECK_EQUAL(3, t.beats); - CHECK_EQUAL(4, t.beatType); + CHECK_EQUAL("3", t.beats); + CHECK_EQUAL("4", t.beatType); CHECK(t.symbol == TimeSignatureSymbol::unspecified); ++measureIter; t = measureIter->timeSignature; CHECK(t.isImplicit); - CHECK_EQUAL(3, t.beats); - CHECK_EQUAL(4, t.beatType); + CHECK_EQUAL("3", t.beats); + CHECK_EQUAL("4", t.beatType); CHECK(t.symbol == TimeSignatureSymbol::unspecified); } diff --git a/src/private/mxtest/core/ArpeggiateTest.cpp b/src/private/mxtest/core/ArpeggiateTest.cpp index 4529c310..03f46f76 100644 --- a/src/private/mxtest/core/ArpeggiateTest.cpp +++ b/src/private/mxtest/core/ArpeggiateTest.cpp @@ -22,7 +22,7 @@ TEST(Test01, Arpeggiate) attributes1->hasDefaultX = true; attributes1->defaultX = TenthsValue{0.1}; attributes1->hasDirection = true; - attributes1->direction = UpDown::up; + attributes1->direction = UpDownNone::up; attributes1->hasNumber = true; attributes1->number = NumberLevel{2}; diff --git a/src/private/mxtest/core/EnumsTest.cpp b/src/private/mxtest/core/EnumsTest.cpp index 91a4f3d9..5f8123c2 100644 --- a/src/private/mxtest/core/EnumsTest.cpp +++ b/src/private/mxtest/core/EnumsTest.cpp @@ -7613,6 +7613,27 @@ TEST(NoteSizeType_grace, Enums) CHECK_EQUAL(e, e2) } +TEST(NoteSizeType_graceCue, Enums) +{ + NoteSizeType e = NoteSizeType::graceCue; + std::string expected = "grace-cue"; + std::string actual = toString(e); + CHECK_EQUAL(expected, actual); + + std::stringstream sstr; + toStream(sstr, e); + actual = sstr.str(); + CHECK_EQUAL(expected, actual); + + sstr.str(""); + sstr << e; + actual = sstr.str(); + CHECK_EQUAL(expected, actual); + + NoteSizeType e2 = parseNoteSizeType(expected); + CHECK_EQUAL(e, e2) +} + TEST(NoteSizeType_large, Enums) { NoteSizeType e = NoteSizeType::large; diff --git a/src/private/mxtest/impl/MeasureWriterTest.cpp b/src/private/mxtest/impl/MeasureWriterTest.cpp index 2d6bd82a..0c900d94 100644 --- a/src/private/mxtest/impl/MeasureWriterTest.cpp +++ b/src/private/mxtest/impl/MeasureWriterTest.cpp @@ -14,6 +14,8 @@ #include "mx/core/elements/MusicDataGroup.h" #include "mx/core/elements/Note.h" #include "mx/core/elements/Properties.h" +#include "mx/core/elements/StaffDetails.h" +#include "mx/core/elements/StaffLines.h" #include "mx/impl/MeasureWriter.h" #include "mx/impl/ScoreWriter.h" @@ -194,4 +196,33 @@ TEST(PropertiesButNoNotes, MeasureWriter) T_END +TEST(staffDetailsWritesStaffLines, 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; + + const auto partwiseMeasure = t.measureWriter->getPartwiseMeasure(); + auto mdcIter = partwiseMeasure->getMusicDataGroup()->getMusicDataChoiceSet().cbegin(); + const auto mdcEnd = partwiseMeasure->getMusicDataGroup()->getMusicDataChoiceSet().cend(); + + CHECK(mdcIter != mdcEnd); + CHECK((*mdcIter)->getChoice() == core::MusicDataChoice::Choice::properties); + + const auto props = (*mdcIter)->getProperties(); + CHECK_EQUAL(1, props->getStaffDetailsSet().size()); + + const auto details = props->getStaffDetailsSet().front(); + CHECK(details->getHasStaffLines()); + CHECK_EQUAL(1, details->getStaffLines()->getValue().getValue()); + CHECK(!details->getAttributes()->hasNumber); +} + +T_END + #endif diff --git a/xcode/Mx.xcodeproj/project.pbxproj b/xcode/Mx.xcodeproj/project.pbxproj index 588b68a7..11b825c3 100755 --- a/xcode/Mx.xcodeproj/project.pbxproj +++ b/xcode/Mx.xcodeproj/project.pbxproj @@ -5222,7 +5222,7 @@ 29A8E29E1EF0476300AD2478 /* utility */, ); name = mx; - path = ../src/private/mx; + path = ../Sourcecode/private/mx; sourceTree = ""; }; 29A8DD581EF0475D00AD2478 /* api */ = { @@ -6748,7 +6748,7 @@ 9CD50C9F238A3D0A00ED7DD8 /* XForwardDeclare.h */, ); name = ezxml; - path = ../src/private/mx/ezxml/src/include/ezxml; + path = ../Sourcecode/private/mx/ezxml/src/include/ezxml; sourceTree = ""; }; /* End PBXGroup section */ @@ -10424,7 +10424,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -10437,6 +10436,7 @@ EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; + MACOSX_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = "mx-macOS"; SDKROOT = macosx; @@ -10447,7 +10447,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -10460,6 +10459,7 @@ EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; + MACOSX_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "mx-macOS"; SDKROOT = macosx; @@ -10543,7 +10543,7 @@ ); OTHER_LDFLAGS = ""; SDKROOT = ""; - USER_HEADER_SEARCH_PATHS = "$PROJECT_DIR/../src/private $PROJECT_DIR/../src/include $PROJECT_DIR/../src/private/mx/ezxml/src/include $PROJECT_DIR/../src/private/mx/ezxml/src/private"; + USER_HEADER_SEARCH_PATHS = "$PROJECT_DIR/../Sourcecode/private $PROJECT_DIR/../Sourcecode/include $PROJECT_DIR/../Sourcecode/private/mx/ezxml/src/include $PROJECT_DIR/../Sourcecode/private/mx/ezxml/src/private"; VALID_ARCHS = "$(ARCHS_STANDARD)"; }; name = Debug; @@ -10619,7 +10619,7 @@ ); OTHER_LDFLAGS = ""; SDKROOT = ""; - USER_HEADER_SEARCH_PATHS = "$PROJECT_DIR/../src/private $PROJECT_DIR/../src/include $PROJECT_DIR/../src/private/mx/ezxml/src/include $PROJECT_DIR/../src/private/mx/ezxml/src/private"; + USER_HEADER_SEARCH_PATHS = "$PROJECT_DIR/../Sourcecode/private $PROJECT_DIR/../Sourcecode/include $PROJECT_DIR/../Sourcecode/private/mx/ezxml/src/include $PROJECT_DIR/../Sourcecode/private/mx/ezxml/src/private"; VALID_ARCHS = "$(ARCHS_STANDARD)"; }; name = Release; @@ -10640,6 +10640,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, ); MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 26.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -10660,6 +10661,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, ); MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 26.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -10669,7 +10671,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -10686,6 +10687,7 @@ INFOPLIST_FILE = MxmacOS/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.matthewjamesbriggs.MxmacOS; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -10701,7 +10703,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -10718,6 +10719,7 @@ INFOPLIST_FILE = MxmacOS/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.matthewjamesbriggs.MxmacOS; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -10732,7 +10734,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -10748,7 +10749,7 @@ HEADER_SEARCH_PATHS = /usr/local/include; INFOPLIST_FILE = MxiOS/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = ""; MTL_ENABLE_DEBUG_INFO = YES; @@ -10771,7 +10772,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -10788,7 +10788,7 @@ HEADER_SEARCH_PATHS = /usr/local/include; INFOPLIST_FILE = MxiOS/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = ""; MTL_ENABLE_DEBUG_INFO = NO; @@ -10812,7 +10812,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_CXX_LIBRARY = "compiler-default"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; @@ -10827,7 +10826,7 @@ EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = NO; PRODUCT_NAME = "mx-iOS"; @@ -10841,7 +10840,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_CXX_LIBRARY = "compiler-default"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; @@ -10856,7 +10854,7 @@ EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "mx-iOS"; diff --git a/xcode/build_xcframework.sh b/xcode/build_xcframework.sh index 942e7454..c9747398 100755 --- a/xcode/build_xcframework.sh +++ b/xcode/build_xcframework.sh @@ -1,45 +1,146 @@ #!/bin/zsh +set -euo pipefail -rm -r Mx.xcframework -rm -r build -#xcodebuild clean - -echo "Building for macOS" -xcodebuild archive \ --scheme MxmacOS \ --destination "platform=macOS" \ --archivePath "build/Mx.macOS.xcarchive" \ -SKIP_INSTALL=NO - -echo "Building for iOS devices (arm64)" -xcodebuild archive \ --scheme MxiOS \ --sdk iphoneos \ --destination "generic/platform=iOS" \ --archivePath "build/Mx.iOS.xcarchive" \ -SKIP_INSTALL=NO - -echo "Building for iOS simulator" -xcodebuild archive \ --scheme MxiOS \ --sdk iphonesimulator \ --archivePath "build/Mx.iOS-simulator.xcarchive" \ -SKIP_INSTALL=NO - -echo "Building for Mac Catalyst" -xcodebuild archive \ --scheme MxiOS \ --destination "platform=macOS,variant=Mac Catalyst" \ --archivePath "build/Mx.catalyst.xcarchive" \ -SKIP_INSTALL=NO +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +VENDORED_XCFRAMEWORK="$REPO_ROOT/../komp/Frameworks/FrameworksApple/Mx.xcframework" +OUTPUT_XCFRAMEWORK="/tmp/MxRebuilt.xcframework" +CONFIGURATION="Debug" +INSTALL_OUTPUT=0 +KEEP_TEMP=0 + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done + +require_path() { + local path="$1" + local label="$2" + if [[ ! -e "$path" ]]; then + echo "$label not found: $path" >&2 + exit 1 + fi +} + +require_path "$REPO_ROOT/Xcode/Mx.xcodeproj" "mx project" +require_path "$VENDORED_XCFRAMEWORK/ios-arm64/MxiOS.framework" "iOS arm64 slice" +require_path "$VENDORED_XCFRAMEWORK/ios-arm64_x86_64-simulator/MxiOS.framework" "iOS simulator slice" +require_path "$VENDORED_XCFRAMEWORK/ios-arm64_x86_64-maccatalyst/MxiOS.framework" "Mac Catalyst slice" + +TEMP_DIR="$(mktemp -d /tmp/mx-xcframework.XXXXXX)" +trap 'if [[ "$KEEP_TEMP" -eq 0 ]]; then rm -rf "$TEMP_DIR"; else echo "Keeping temp dir: $TEMP_DIR"; fi' EXIT + +DERIVED_ARM64="$TEMP_DIR/MxDerived-arm64" +DERIVED_X86_64="$TEMP_DIR/MxDerived-x86_64" +UNIVERSAL_FRAMEWORK="$TEMP_DIR/MxmacOS.framework" +ARM64_FRAMEWORK="$DERIVED_ARM64/Build/Products/$CONFIGURATION/MxmacOS.framework" +X86_64_FRAMEWORK="$DERIVED_X86_64/Build/Products/$CONFIGURATION/MxmacOS.framework" +UNIVERSAL_BINARY="$UNIVERSAL_FRAMEWORK/Versions/A/MxmacOS" + +build_framework() { + local arch="$1" + local derived_data="$2" + echo "Building MxmacOS.framework for $arch..." + xcodebuild \ + -project "$REPO_ROOT/Xcode/Mx.xcodeproj" \ + -scheme MxmacOS \ + -configuration "$CONFIGURATION" \ + -derivedDataPath "$derived_data" \ + build \ + CODE_SIGNING_ALLOWED=NO \ + ARCHS="$arch" \ + ONLY_ACTIVE_ARCH=NO +} + +build_framework arm64 "$DERIVED_ARM64" +build_framework x86_64 "$DERIVED_X86_64" + +require_path "$ARM64_FRAMEWORK/Versions/A/MxmacOS" "arm64 framework binary" +require_path "$X86_64_FRAMEWORK/Versions/A/MxmacOS" "x86_64 framework binary" + +echo "Creating universal MxmacOS.framework..." +rm -rf "$UNIVERSAL_FRAMEWORK" +rsync -a "$ARM64_FRAMEWORK/" "$UNIVERSAL_FRAMEWORK/" +lipo -create \ + "$ARM64_FRAMEWORK/Versions/A/MxmacOS" \ + "$X86_64_FRAMEWORK/Versions/A/MxmacOS" \ + -output "$UNIVERSAL_BINARY" +lipo -info "$UNIVERSAL_BINARY" + +echo "Recreating xcframework at $OUTPUT_XCFRAMEWORK..." +rm -rf "$OUTPUT_XCFRAMEWORK" xcodebuild -create-xcframework \ --framework "build/Mx.macOS.xcarchive/Products/Library/Frameworks/MxmacOS.framework" \ --framework "build/Mx.iOS.xcarchive/Products/Library/Frameworks/MxiOS.framework" \ --framework "build/Mx.iOS-simulator.xcarchive/Products/Library/Frameworks/MxiOS.framework" \ --framework "build/Mx.catalyst.xcarchive/Products/Library/Frameworks/MxiOS.framework" \ --output "Mx.xcframework" + -framework "$VENDORED_XCFRAMEWORK/ios-arm64/MxiOS.framework" \ + -framework "$VENDORED_XCFRAMEWORK/ios-arm64_x86_64-simulator/MxiOS.framework" \ + -framework "$VENDORED_XCFRAMEWORK/ios-arm64_x86_64-maccatalyst/MxiOS.framework" \ + -framework "$UNIVERSAL_FRAMEWORK" \ + -output "$OUTPUT_XCFRAMEWORK" -#rm -r build +if [[ "$INSTALL_OUTPUT" -eq 1 ]]; then + echo "Installing rebuilt xcframework into $VENDORED_XCFRAMEWORK..." + rsync -a --delete "$OUTPUT_XCFRAMEWORK/" "$VENDORED_XCFRAMEWORK/" +fi -open . +echo "Done." +echo "Rebuilt xcframework: $OUTPUT_XCFRAMEWORK" +if [[ "$INSTALL_OUTPUT" -eq 0 ]]; then + echo "Use --install to replace the vendored xcframework automatically." +fi