diff --git a/src/include/mx/api/DirectionData.h b/src/include/mx/api/DirectionData.h index eca6d6dde..b96e0f938 100644 --- a/src/include/mx/api/DirectionData.h +++ b/src/include/mx/api/DirectionData.h @@ -11,12 +11,14 @@ #include "mx/api/DampData.h" #include "mx/api/EyeglassesData.h" #include "mx/api/FiguredBassData.h" +#include "mx/api/HarpPedalsData.h" #include "mx/api/ImageData.h" #include "mx/api/MarkData.h" #include "mx/api/OtherDirectionData.h" #include "mx/api/OttavaData.h" #include "mx/api/PrincipalVoiceData.h" #include "mx/api/RehearsalData.h" +#include "mx/api/ScordaturaData.h" #include "mx/api/SegnoData.h" #include "mx/api/SoundData.h" #include "mx/api/StaffDivideData.h" @@ -56,7 +58,9 @@ enum class DirectionComponentKind principalVoice, otherDirection, image, - accordionRegistration + accordionRegistration, + harpPedals, + scordatura }; struct DirectionComponent @@ -146,6 +150,8 @@ struct DirectionData std::vector otherDirections; std::vector images; std::vector accordionRegistrations; + std::vector harpPedals; + std::vector scordaturas; // Preserves the original order of direction-type children from parsed XML // for round-trip fidelity. Do NOT populate this when constructing // DirectionData programmatically. If empty, the writer uses a default @@ -177,6 +183,7 @@ inline bool isDirectionDataEmpty(const DirectionData &directionData) directionData.stringMutes.size() == 0 && directionData.staffDivides.size() == 0 && directionData.principalVoices.size() == 0 && directionData.otherDirections.size() == 0 && directionData.images.size() == 0 && directionData.accordionRegistrations.size() == 0 && + directionData.harpPedals.size() == 0 && directionData.scordaturas.size() == 0 && directionData.figuredBasses.size() == 0 && !directionData.isSoundDataSpecified && directionData.orderedComponents.size() == 0; } @@ -215,6 +222,8 @@ MXAPI_EQUALS_MEMBER(principalVoices) MXAPI_EQUALS_MEMBER(otherDirections) MXAPI_EQUALS_MEMBER(images) MXAPI_EQUALS_MEMBER(accordionRegistrations) +MXAPI_EQUALS_MEMBER(harpPedals) +MXAPI_EQUALS_MEMBER(scordaturas) MXAPI_EQUALS_MEMBER(orderedComponents) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(DirectionData); diff --git a/src/include/mx/api/HarpPedalsData.h b/src/include/mx/api/HarpPedalsData.h new file mode 100644 index 000000000..3b00837e3 --- /dev/null +++ b/src/include/mx/api/HarpPedalsData.h @@ -0,0 +1,76 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +#pragma once + +#include "mx/api/ApiCommon.h" +#include "mx/api/ColorData.h" +#include "mx/api/FontData.h" +#include "mx/api/PitchData.h" +#include "mx/api/PositionData.h" + +#include +#include +#include + +namespace mx +{ +namespace api +{ +// One pedal's setting inside a harp pedal diagram: which pedal (named by the pitch step it +// controls) and its alteration. alter is -1 for the flat (pedal up), 0 for natural (pedal +// centered), and 1 for the sharp (pedal down) position; cents carries any microtonal part of +// the alteration (in hundredths of a semitone), as in PitchData. +struct HarpPedalTuning +{ + Step step; + int alter; + double cents; + + HarpPedalTuning() : step{Step::c}, alter{0}, cents{0.0} + { + } + + HarpPedalTuning(Step inStep, int inAlter) : step{inStep}, alter{inAlter}, cents{0.0} + { + } +}; + +MXAPI_EQUALS_BEGIN(HarpPedalTuning) +MXAPI_EQUALS_MEMBER(step) +MXAPI_EQUALS_MEMBER(alter) +MXAPI_DOUBLES_EQUALS_MEMBER(cents) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(HarpPedalTuning); + +// A harp pedal diagram, MusicXML's element: shows the position of each of the +// harp's seven pedals. A complete diagram lists all seven in the standard order of the harp's +// pedal mechanism (D, C, B on the left foot; E, F, G, A on the right); pedalTunings preserves +// the order given. positionData captures default/relative x-y plus the horizontal and vertical +// alignment; its placement member is unused here because has no placement +// attribute (placement lives on the parent ). +class HarpPedalsData +{ + public: + std::vector pedalTunings; + PositionData positionData; + FontData fontData; + std::optional color; + std::optional id; + + HarpPedalsData() : pedalTunings{}, positionData{}, fontData{}, color{}, id{} + { + } +}; + +MXAPI_EQUALS_BEGIN(HarpPedalsData) +MXAPI_EQUALS_MEMBER(pedalTunings) +MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(fontData) +MXAPI_EQUALS_MEMBER(color) +MXAPI_EQUALS_MEMBER(id) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(HarpPedalsData); +} // namespace api +} // namespace mx diff --git a/src/include/mx/api/ScordaturaData.h b/src/include/mx/api/ScordaturaData.h new file mode 100644 index 000000000..e0d35240a --- /dev/null +++ b/src/include/mx/api/ScordaturaData.h @@ -0,0 +1,65 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +#pragma once + +#include "mx/api/ApiCommon.h" +#include "mx/api/PitchData.h" + +#include +#include +#include + +namespace mx +{ +namespace api +{ +// One retuned string in a scordatura: the pitch the string is tuned to, and optionally which +// string it is (1 is the highest-pitched string). When stringNumber is absent the accords are +// understood to run through the strings in order. tuningAlter/tuningCents alter the step in +// semitones and hundredths of a semitone, as in PitchData; tuningOctave is the octave number +// where 4 is the octave starting at middle C. +struct AccordData +{ + std::optional stringNumber; + Step tuningStep; + int tuningAlter; + double tuningCents; + int tuningOctave; + + AccordData() : stringNumber{}, tuningStep{Step::c}, tuningAlter{0}, tuningCents{0.0}, tuningOctave{4} + { + } +}; + +MXAPI_EQUALS_BEGIN(AccordData) +MXAPI_EQUALS_MEMBER(stringNumber) +MXAPI_EQUALS_MEMBER(tuningStep) +MXAPI_EQUALS_MEMBER(tuningAlter) +MXAPI_DOUBLES_EQUALS_MEMBER(tuningCents) +MXAPI_EQUALS_MEMBER(tuningOctave) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(AccordData); + +// A scordatura, MusicXML's element: tells a string player to retune, listing the +// altered tuning of each affected string. Common in Baroque violin music and in guitar +// notation (drop-D and similar tunings). +class ScordaturaData +{ + public: + std::vector accords; + std::optional id; + + ScordaturaData() : accords{}, id{} + { + } +}; + +MXAPI_EQUALS_BEGIN(ScordaturaData) +MXAPI_EQUALS_MEMBER(accords) +MXAPI_EQUALS_MEMBER(id) +MXAPI_EQUALS_END; +MXAPI_NOT_EQUALS_AND_VECTORS(ScordaturaData); +} // namespace api +} // namespace mx diff --git a/src/private/mx/impl/DirectionReader.cpp b/src/private/mx/impl/DirectionReader.cpp index 0165424ea..a46792642 100644 --- a/src/private/mx/impl/DirectionReader.cpp +++ b/src/private/mx/impl/DirectionReader.cpp @@ -4,6 +4,7 @@ #include "mx/impl/DirectionReader.h" #include "mx/api/WedgeData.h" +#include "mx/core/generated/Accord.h" #include "mx/core/generated/AccordionRegistration.h" #include "mx/core/generated/Barre.h" #include "mx/core/generated/Bass.h" @@ -40,11 +41,13 @@ #include "mx/core/generated/NumeralMode.h" #include "mx/core/generated/NumeralRoot.h" #include "mx/core/generated/NumeralValue.h" +#include "mx/core/generated/Octave.h" #include "mx/core/generated/OctaveShift.h" #include "mx/core/generated/Offset.h" #include "mx/core/generated/OnOff.h" #include "mx/core/generated/OtherDirection.h" #include "mx/core/generated/Pedal.h" +#include "mx/core/generated/PedalTuning.h" #include "mx/core/generated/PedalType.h" #include "mx/core/generated/Percussion.h" #include "mx/core/generated/PrincipalVoice.h" @@ -53,6 +56,7 @@ #include "mx/core/generated/RootStep.h" #include "mx/core/generated/Scordatura.h" #include "mx/core/generated/Segno.h" +#include "mx/core/generated/Semitones.h" #include "mx/core/generated/Sound.h" #include "mx/core/generated/StaffDivide.h" #include "mx/core/generated/StaffDivideSymbol.h" @@ -61,7 +65,9 @@ #include "mx/core/generated/Step.h" #include "mx/core/generated/String.h" #include "mx/core/generated/StringMute.h" +#include "mx/core/generated/StringNumber.h" #include "mx/core/generated/StyleText.h" +#include "mx/core/generated/TuningGroup.h" #include "mx/core/generated/UpDownStopContinue.h" #include "mx/core/generated/ValignImage.h" #include "mx/core/generated/Wedge.h" @@ -825,17 +831,33 @@ void DirectionReader::parseOctaveShift(const core::DirectionType &directionType) static_cast(myOutDirectionData.ottavaStarts.size()) - 1); } -// The stubs below (harp-pedals, scordatura, percussion) are genuinely unmodeled -// direction-type choices, tracked at #324, not a bug in the surrounding dispatch. When a -// contains only one of these, the resulting DirectionData carries no content and -// is correctly left unwritten -- MusicXML requires at least one direction-type child, so -// there is no schema-valid way to keep the (and its /) without -// modeling what it actually says. That is why round-trip discovery reports those files as -// dropping /// together: it is one gap (the -// unmodeled content), not four. void DirectionReader::parseHarpPedals(const core::DirectionType &directionType) { - MX_UNUSED(directionType); + const auto &harpPedals = directionType.choice().asHarpPedals(); + api::HarpPedalsData outHarpPedals; + for (const auto &pedalTuning : harpPedals.pedalTuning()) + { + api::HarpPedalTuning outTuning; + outTuning.step = myConverter.convert(pedalTuning.pedalStep()); + const auto semitonesAndCents = + Converter::convertToSemitonesAndCents(static_cast(pedalTuning.pedalAlter().value().value())); + outTuning.alter = semitonesAndCents.first; + outTuning.cents = semitonesAndCents.second; + outHarpPedals.pedalTunings.emplace_back(outTuning); + } + outHarpPedals.positionData = getPositionData(harpPedals); + outHarpPedals.fontData = getFontData(harpPedals); + if (harpPedals.color().has_value()) + { + outHarpPedals.color = getColor(harpPedals); + } + if (harpPedals.id().has_value()) + { + outHarpPedals.id = harpPedals.id()->value(); + } + myOutDirectionData.harpPedals.emplace_back(std::move(outHarpPedals)); + appendOrderedComponent(api::DirectionComponentKind::harpPedals, + static_cast(myOutDirectionData.harpPedals.size()) - 1); } void DirectionReader::parseDamp(const core::DirectionType &directionType) @@ -949,7 +971,33 @@ void DirectionReader::parseStaffDivide(const core::DirectionType &directionType) void DirectionReader::parseScordatura(const core::DirectionType &directionType) { - MX_UNUSED(directionType); + const auto &scordatura = directionType.choice().asScordatura(); + api::ScordaturaData outScordatura; + for (const auto &accord : scordatura.accord()) + { + api::AccordData outAccord; + if (accord.string().has_value()) + { + outAccord.stringNumber = accord.string()->value(); + } + outAccord.tuningStep = myConverter.convert(accord.tuning().tuningStep()); + if (accord.tuning().tuningAlter().has_value()) + { + const auto semitonesAndCents = Converter::convertToSemitonesAndCents( + static_cast(accord.tuning().tuningAlter()->value().value())); + outAccord.tuningAlter = semitonesAndCents.first; + outAccord.tuningCents = semitonesAndCents.second; + } + outAccord.tuningOctave = accord.tuning().tuningOctave().value(); + outScordatura.accords.emplace_back(outAccord); + } + if (scordatura.id().has_value()) + { + outScordatura.id = scordatura.id()->value(); + } + myOutDirectionData.scordaturas.emplace_back(std::move(outScordatura)); + appendOrderedComponent(api::DirectionComponentKind::scordatura, + static_cast(myOutDirectionData.scordaturas.size()) - 1); } void DirectionReader::parseImage(const core::DirectionType &directionType) @@ -1061,6 +1109,11 @@ void DirectionReader::parseAccordionRegistration(const core::DirectionType &dire static_cast(myOutDirectionData.accordionRegistrations.size()) - 1); } +// The percussion stub below is the last genuinely unmodeled direction-type choice, tracked at +// #324, not a bug in the surrounding dispatch. When a contains only percussion +// content, the resulting DirectionData carries no content and is correctly left unwritten -- +// MusicXML requires at least one direction-type child, so there is no schema-valid way to +// keep the (and its /) without modeling what it actually says. void DirectionReader::parsePercussion(const core::DirectionType &directionType) { MX_UNUSED(directionType); diff --git a/src/private/mx/impl/DirectionWriter.cpp b/src/private/mx/impl/DirectionWriter.cpp index 16a4a5fb9..daa47033b 100644 --- a/src/private/mx/impl/DirectionWriter.cpp +++ b/src/private/mx/impl/DirectionWriter.cpp @@ -4,6 +4,7 @@ #include "mx/impl/DirectionWriter.h" #include "mx/api/BarlineData.h" +#include "mx/core/generated/Accord.h" #include "mx/core/generated/AccordionMiddle.h" #include "mx/core/generated/AccordionRegistration.h" #include "mx/core/generated/Bass.h" @@ -37,6 +38,7 @@ #include "mx/core/generated/HarmonyAlter.h" #include "mx/core/generated/HarmonyChordGroup.h" #include "mx/core/generated/HarmonyChordGroupChoice.h" +#include "mx/core/generated/HarpPedals.h" #include "mx/core/generated/Image.h" #include "mx/core/generated/Inversion.h" #include "mx/core/generated/Kind.h" @@ -51,11 +53,13 @@ #include "mx/core/generated/NumeralMode.h" #include "mx/core/generated/NumeralRoot.h" #include "mx/core/generated/NumeralValue.h" +#include "mx/core/generated/Octave.h" #include "mx/core/generated/OctaveShift.h" #include "mx/core/generated/Offset.h" #include "mx/core/generated/OnOff.h" #include "mx/core/generated/OtherDirection.h" #include "mx/core/generated/Pedal.h" +#include "mx/core/generated/PedalTuning.h" #include "mx/core/generated/PedalType.h" #include "mx/core/generated/PerMinute.h" #include "mx/core/generated/PositiveDivisions.h" @@ -63,6 +67,7 @@ #include "mx/core/generated/PrincipalVoiceSymbol.h" #include "mx/core/generated/Root.h" #include "mx/core/generated/RootStep.h" +#include "mx/core/generated/Scordatura.h" #include "mx/core/generated/Segno.h" #include "mx/core/generated/Semitones.h" #include "mx/core/generated/SmuflGlyphName.h" @@ -75,6 +80,7 @@ #include "mx/core/generated/StringMute.h" #include "mx/core/generated/StringNumber.h" #include "mx/core/generated/StyleText.h" +#include "mx/core/generated/TuningGroup.h" #include "mx/core/generated/ValignImage.h" #include "mx/core/generated/Wedge.h" #include "mx/core/generated/WedgeType.h" @@ -845,6 +851,92 @@ void DirectionWriter::emitAccordionRegistration(const api::AccordionRegistration addDirectionType(std::move(dt), direction); } +void DirectionWriter::emitHarpPedals(const api::HarpPedalsData &item, core::Direction &direction) +{ + // MusicXML requires at least one pedal-tuning; a diagram with none cannot be expressed + // and is not written. + if (item.pedalTunings.empty()) + { + return; + } + core::HarpPedals harpPedals{}; + bool isFirstTuningAdded = false; + for (const auto &tuning : item.pedalTunings) + { + core::PedalTuning pedalTuning{}; + pedalTuning.setPedalStep(myConverter.convert(tuning.step)); + pedalTuning.setPedalAlter( + core::Semitones{core::Decimal{Converter::convertToAlter(tuning.alter, tuning.cents)}}); + if (!isFirstTuningAdded) + { + harpPedals.setPedalTuning(core::OneOrMore{std::move(pedalTuning)}); + isFirstTuningAdded = true; + } + else + { + harpPedals.addPedalTuning(std::move(pedalTuning)); + } + } + setAttributesFromPositionData(item.positionData, harpPedals); + setAttributesFromFontData(item.fontData, harpPedals); + if (item.color.has_value()) + { + setAttributesFromColorData(*item.color, harpPedals); + } + if (item.id.has_value()) + { + harpPedals.setID(core::Token{*item.id}); + } + core::DirectionType dt{}; + dt.setChoice(core::DirectionTypeChoice::harpPedals(std::move(harpPedals))); + addDirectionType(std::move(dt), direction); +} + +void DirectionWriter::emitScordatura(const api::ScordaturaData &item, core::Direction &direction) +{ + // MusicXML requires at least one accord; a scordatura with none cannot be expressed and + // is not written. + if (item.accords.empty()) + { + return; + } + core::Scordatura scordatura{}; + bool isFirstAccordAdded = false; + for (const auto &accordData : item.accords) + { + core::Accord accord{}; + if (accordData.stringNumber.has_value()) + { + accord.setString(core::StringNumber{*accordData.stringNumber}); + } + core::TuningGroup tuning{}; + tuning.setTuningStep(myConverter.convert(accordData.tuningStep)); + if (accordData.tuningAlter != 0 || accordData.tuningCents != 0.0) + { + tuning.setTuningAlter(core::Semitones{ + core::Decimal{Converter::convertToAlter(accordData.tuningAlter, accordData.tuningCents)}}); + } + tuning.setTuningOctave(core::Octave{accordData.tuningOctave}); + accord.setTuning(std::move(tuning)); + if (!isFirstAccordAdded) + { + scordatura.setAccord(core::OneOrMore{std::move(accord)}); + isFirstAccordAdded = true; + } + else + { + scordatura.addAccord(std::move(accord)); + } + } + if (item.id.has_value()) + { + scordatura.setID(core::Token{*item.id}); + } + core::DirectionType dt{}; + dt.setChoice(core::DirectionTypeChoice::scordatura(std::move(scordatura))); + addDirectionType(std::move(dt), direction); +} + void DirectionWriter::emitFixedOrder(core::Direction &direction) { for (const auto &mark : myDirectionData.marks) @@ -968,6 +1060,16 @@ void DirectionWriter::emitFixedOrder(core::Direction &direction) { emitAccordionRegistration(item, direction); } + + for (const auto &item : myDirectionData.harpPedals) + { + emitHarpPedals(item, direction); + } + + for (const auto &item : myDirectionData.scordaturas) + { + emitScordatura(item, direction); + } } void DirectionWriter::emitOrderedComponents(core::Direction &direction) @@ -1159,6 +1261,20 @@ void DirectionWriter::emitOrderedComponents(core::Direction &direction) } break; + case api::DirectionComponentKind::harpPedals: + if (i >= 0 && static_cast(i) < myDirectionData.harpPedals.size()) + { + emitHarpPedals(myDirectionData.harpPedals.at(i), direction); + } + break; + + case api::DirectionComponentKind::scordatura: + if (i >= 0 && static_cast(i) < myDirectionData.scordaturas.size()) + { + emitScordatura(myDirectionData.scordaturas.at(i), direction); + } + break; + case api::DirectionComponentKind::chord: // Chords are emitted via createHarmonyElements, not as direction-types. break; diff --git a/src/private/mx/impl/DirectionWriter.h b/src/private/mx/impl/DirectionWriter.h index 3b6860b03..563388754 100644 --- a/src/private/mx/impl/DirectionWriter.h +++ b/src/private/mx/impl/DirectionWriter.h @@ -65,6 +65,8 @@ class DirectionWriter void emitOtherDirection(const api::OtherDirectionData &item, core::Direction &direction); void emitImage(const api::ImageData &item, core::Direction &direction); void emitAccordionRegistration(const api::AccordionRegistrationData &item, core::Direction &direction); + void emitHarpPedals(const api::HarpPedalsData &item, core::Direction &direction); + void emitScordatura(const api::ScordaturaData &item, core::Direction &direction); void emitFixedOrder(core::Direction &direction); void emitOrderedComponents(core::Direction &direction); diff --git a/src/private/mxtest/api/DirectionMarksRoundTripTest.cpp b/src/private/mxtest/api/DirectionMarksRoundTripTest.cpp index b5fd2a860..0e0402f86 100644 --- a/src/private/mxtest/api/DirectionMarksRoundTripTest.cpp +++ b/src/private/mxtest/api/DirectionMarksRoundTripTest.cpp @@ -215,6 +215,77 @@ TEST(AccordionRegistrationEmpty, DirectionMarksRoundTrip) T_END; +TEST(HarpPedals, DirectionMarksRoundTrip) +{ + DirectionData direction; + HarpPedalsData harpPedals; + harpPedals.pedalTunings.emplace_back(Step::d, 0); + harpPedals.pedalTunings.emplace_back(Step::c, -1); + harpPedals.pedalTunings.emplace_back(Step::b, 1); + direction.harpPedals.push_back(harpPedals); + const auto directions = roundTripDirectionData(direction); + REQUIRE(directions.size() == 1); + REQUIRE(directions.front().harpPedals.size() == 1); + const auto &out = directions.front().harpPedals.front(); + REQUIRE(out.pedalTunings.size() == 3); + CHECK(out.pedalTunings.at(0).step == Step::d); + CHECK_EQUAL(0, out.pedalTunings.at(0).alter); + CHECK(out.pedalTunings.at(1).step == Step::c); + CHECK_EQUAL(-1, out.pedalTunings.at(1).alter); + CHECK(out.pedalTunings.at(2).step == Step::b); + CHECK_EQUAL(1, out.pedalTunings.at(2).alter); +} + +T_END; + +TEST(Scordatura, DirectionMarksRoundTrip) +{ + DirectionData direction; + ScordaturaData scordatura; + AccordData accord; + accord.stringNumber = 6; + accord.tuningStep = Step::d; + accord.tuningOctave = 2; + scordatura.accords.push_back(accord); + direction.scordaturas.push_back(scordatura); + const auto directions = roundTripDirectionData(direction); + REQUIRE(directions.size() == 1); + REQUIRE(directions.front().scordaturas.size() == 1); + const auto &out = directions.front().scordaturas.front(); + REQUIRE(out.accords.size() == 1); + REQUIRE(out.accords.front().stringNumber.has_value()); + CHECK_EQUAL(6, *out.accords.front().stringNumber); + CHECK(out.accords.front().tuningStep == Step::d); + CHECK_EQUAL(0, out.accords.front().tuningAlter); + CHECK_EQUAL(2, out.accords.front().tuningOctave); +} + +T_END; + +TEST(ScordaturaNoStringNumbers, DirectionMarksRoundTrip) +{ + // When stringNumber is absent the accords run through the strings in order. + DirectionData direction; + ScordaturaData scordatura; + AccordData accord; + accord.tuningStep = Step::a; + accord.tuningAlter = -1; + accord.tuningOctave = 3; + scordatura.accords.push_back(accord); + direction.scordaturas.push_back(scordatura); + const auto directions = roundTripDirectionData(direction); + REQUIRE(directions.size() == 1); + REQUIRE(directions.front().scordaturas.size() == 1); + const auto &out = directions.front().scordaturas.front(); + REQUIRE(out.accords.size() == 1); + CHECK(!out.accords.front().stringNumber.has_value()); + CHECK(out.accords.front().tuningStep == Step::a); + CHECK_EQUAL(-1, out.accords.front().tuningAlter); + CHECK_EQUAL(3, out.accords.front().tuningOctave); +} + +T_END; + TEST(DampFormatting, DirectionMarksRoundTrip) { DirectionData direction; diff --git a/src/private/mxtest/api/roundtrip-baseline.txt b/src/private/mxtest/api/roundtrip-baseline.txt index 590de4e80..2048ca33c 100644 --- a/src/private/mxtest/api/roundtrip-baseline.txt +++ b/src/private/mxtest/api/roundtrip-baseline.txt @@ -456,3 +456,8 @@ synthetic/other-direction.3.0.xml synthetic/other-direction.3.1.xml synthetic/principal-voice.3.0.xml synthetic/principal-voice.3.1.xml + +# Unblocked by modeling harp-pedals and scordatura (#324). +synthetic/harp-pedals.3.0.xml +synthetic/harp-pedals.3.1.xml +synthetic/scordatura.3.1.xml