diff --git a/src/include/mx/api/ChordData.h b/src/include/mx/api/ChordData.h index 15c923acf..a53f1e609 100644 --- a/src/include/mx/api/ChordData.h +++ b/src/include/mx/api/ChordData.h @@ -149,6 +149,15 @@ enum class FrameBarre stop }; +// Which side of the frame the first-fret label is printed on. Guitar chord diagrams conventionally +// carry it to the right of the grid. +enum class FirstFretLocation +{ + unspecified, + left, + right +}; + class FrameNoteData { public: @@ -181,6 +190,10 @@ class FrameData std::optional unplayed; int firstFret; bool isFirstFretSpecified; + // The label printed beside the frame naming the fret it starts on, such as "5fr." for a frame + // whose top space is the fifth fret. Leave absent to print the fret number by itself. + std::optional firstFretText; + FirstFretLocation firstFretLocation; std::vector notes; }; @@ -190,6 +203,8 @@ MXAPI_EQUALS_MEMBER(fretCount) MXAPI_EQUALS_MEMBER(unplayed) MXAPI_EQUALS_MEMBER(firstFret) MXAPI_EQUALS_MEMBER(isFirstFretSpecified) +MXAPI_EQUALS_MEMBER(firstFretText) +MXAPI_EQUALS_MEMBER(firstFretLocation) MXAPI_EQUALS_MEMBER(notes) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(FrameData); diff --git a/src/private/mx/api/ChordData.cpp b/src/private/mx/api/ChordData.cpp index e7bb91bea..535d486d2 100644 --- a/src/private/mx/api/ChordData.cpp +++ b/src/private/mx/api/ChordData.cpp @@ -24,7 +24,9 @@ FrameNoteData::FrameNoteData() { } -FrameData::FrameData() : stringCount{6}, fretCount{4}, unplayed{}, firstFret{1}, isFirstFretSpecified{false}, notes{} +FrameData::FrameData() + : stringCount{6}, fretCount{4}, unplayed{}, firstFret{1}, isFirstFretSpecified{false}, firstFretText{}, + firstFretLocation{FirstFretLocation::unspecified}, notes{} { } diff --git a/src/private/mx/impl/Converter.cpp b/src/private/mx/impl/Converter.cpp index eea1b0af7..79f541859 100644 --- a/src/private/mx/impl/Converter.cpp +++ b/src/private/mx/impl/Converter.cpp @@ -1376,6 +1376,11 @@ const Converter::EnumMap Converter::c {core::CancelLocation::beforeBarline(), api::CancelLocation::beforeBarline}, }; +const Converter::EnumMap Converter::firstFretLocationMap = { + {core::LeftRight::left(), api::FirstFretLocation::left}, + {core::LeftRight::right(), api::FirstFretLocation::right}, +}; + // The standard vocabulary. api::KeyMode::unspecified and api::KeyMode::unsupported are absent // because they have no wire spelling. const Converter::EnumMap Converter::keyModeMap = { @@ -2003,6 +2008,16 @@ api::CancelLocation Converter::convert(core::CancelLocation value) const return findApiItem(cancelLocationMap, api::CancelLocation::unspecified, value); } +core::LeftRight Converter::convert(api::FirstFretLocation value) const +{ + return findCoreItem(firstFretLocationMap, core::LeftRight::right(), value); +} + +api::FirstFretLocation Converter::convert(core::LeftRight value) const +{ + return findApiItem(firstFretLocationMap, api::FirstFretLocation::unspecified, value); +} + core::Mode Converter::convert(api::KeyMode value) const { return findCoreItem(keyModeMap, core::Mode{}, value); diff --git a/src/private/mx/impl/Converter.h b/src/private/mx/impl/Converter.h index a83359724..dcfceb447 100644 --- a/src/private/mx/impl/Converter.h +++ b/src/private/mx/impl/Converter.h @@ -34,6 +34,7 @@ #include "mx/core/generated/GroupSymbolValue.h" #include "mx/core/generated/KindValue.h" #include "mx/core/generated/LeftCenterRight.h" +#include "mx/core/generated/LeftRight.h" #include "mx/core/generated/LineEnd.h" #include "mx/core/generated/LineType.h" #include "mx/core/generated/MeasureNumberingValue.h" @@ -201,6 +202,9 @@ class Converter core::CancelLocation convert(api::CancelLocation value) const; api::CancelLocation convert(core::CancelLocation value) const; + core::LeftRight convert(api::FirstFretLocation value) const; + api::FirstFretLocation convert(core::LeftRight value) const; + // is an open vocabulary, so a core Mode holds an arbitrary string. api::KeyMode::unspecified // and api::KeyMode::unsupported have no wire spelling and convert to an empty core::Mode; callers // write no element for an empty Mode. A core Mode outside the standard vocabulary (including @@ -294,6 +298,7 @@ class Converter const static EnumMap instrumentMap; const static EnumMap kindMap; const static EnumMap cancelLocationMap; + const static EnumMap firstFretLocationMap; const static EnumMap keyModeMap; const static EnumMap simpleTimeSymbolMap; const static EnumMap complexTimeSymbolMap; diff --git a/src/private/mx/impl/DirectionReader.cpp b/src/private/mx/impl/DirectionReader.cpp index 38a4781dd..be8bf0650 100644 --- a/src/private/mx/impl/DirectionReader.cpp +++ b/src/private/mx/impl/DirectionReader.cpp @@ -1418,6 +1418,11 @@ void DirectionReader::parseHarmony(const core::Harmony &inHarmony, const core::H { chord.frameData.isFirstFretSpecified = true; chord.frameData.firstFret = frame.firstFret()->value(); + chord.frameData.firstFretText = frame.firstFret()->text(); + if (frame.firstFret()->location().has_value()) + { + chord.frameData.firstFretLocation = myConverter.convert(*frame.firstFret()->location()); + } } for (const auto &frameNote : frame.frameNote()) diff --git a/src/private/mx/impl/DirectionWriter.cpp b/src/private/mx/impl/DirectionWriter.cpp index f5e0a6283..3c3980dd5 100644 --- a/src/private/mx/impl/DirectionWriter.cpp +++ b/src/private/mx/impl/DirectionWriter.cpp @@ -1676,6 +1676,14 @@ std::vector DirectionWriter::createHarmonyElements(int in { core::FirstFret firstFret{}; firstFret.setValue(chordIter->frameData.firstFret); + if (chordIter->frameData.firstFretText.has_value()) + { + firstFret.setText(chordIter->frameData.firstFretText); + } + if (chordIter->frameData.firstFretLocation != api::FirstFretLocation::unspecified) + { + firstFret.setLocation(myConverter.convert(chordIter->frameData.firstFretLocation)); + } frame.setFirstFret(firstFret); } diff --git a/src/private/mxtest/api/HarmonyExtrasApiTest.cpp b/src/private/mxtest/api/HarmonyExtrasApiTest.cpp index cf36c0592..7d9ce72aa 100644 --- a/src/private/mxtest/api/HarmonyExtrasApiTest.cpp +++ b/src/private/mxtest/api/HarmonyExtrasApiTest.cpp @@ -223,6 +223,52 @@ TEST(harmonyFrameUnplayedRoundTrip, HarmonyExtrasApi) std::optional{"x"}); } +TEST(harmonyFrameFirstFretRoundTrip, HarmonyExtrasApi) +{ + auto score = makeScoreWithChord(); + auto &chord = chordOf(score); + chord.root = Step::c; + chord.chordKind = ChordKind::major; + chord.hasFrameData = true; + chord.frameData.isFirstFretSpecified = true; + chord.frameData.firstFret = 5; + chord.frameData.firstFretText = "5fr."; + chord.frameData.firstFretLocation = FirstFretLocation::right; + + const auto xml = mxtest::toXml(score); + CHECK(xml.find("text=\"5fr.\"") != std::string::npos); + CHECK(xml.find("location=\"right\"") != std::string::npos); + + const auto out = mxtest::roundTrip(score); + const auto &outFrame = firstChord(out).frameData; + CHECK(outFrame.isFirstFretSpecified); + CHECK_EQUAL(5, outFrame.firstFret); + CHECK(outFrame.firstFretText == std::optional{"5fr."}); + CHECK(FirstFretLocation::right == outFrame.firstFretLocation); +} + +T_END; + +TEST(harmonyFrameFirstFretBareRoundTrip, HarmonyExtrasApi) +{ + auto score = makeScoreWithChord(); + auto &chord = chordOf(score); + chord.root = Step::c; + chord.chordKind = ChordKind::major; + chord.hasFrameData = true; + chord.frameData.isFirstFretSpecified = true; + chord.frameData.firstFret = 3; + + const auto xml = mxtest::toXml(score); + CHECK(xml.find("3") != std::string::npos); + + const auto out = mxtest::roundTrip(score); + const auto &outFrame = firstChord(out).frameData; + CHECK_EQUAL(3, outFrame.firstFret); + CHECK(!outFrame.firstFretText.has_value()); + CHECK(FirstFretLocation::unspecified == outFrame.firstFretLocation); +} + T_END; TEST(harmonyFunctionRoundTrip, HarmonyExtrasApi) diff --git a/src/private/mxtest/api/roundtrip-baseline.txt b/src/private/mxtest/api/roundtrip-baseline.txt index 2cbca3dfb..6c66d091a 100644 --- a/src/private/mxtest/api/roundtrip-baseline.txt +++ b/src/private/mxtest/api/roundtrip-baseline.txt @@ -366,6 +366,7 @@ synthetic/beats.3.0.xml synthetic/bracket.3.0.xml synthetic/dashes.3.0.xml synthetic/encoding-description.3.0.xml +synthetic/first-fret.3.0.xml synthetic/first.4.0.xml synthetic/group.3.0.xml synthetic/instrument-abbreviation.3.0.xml