Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/include/mx/api/DirectionData.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -56,7 +58,9 @@ enum class DirectionComponentKind
principalVoice,
otherDirection,
image,
accordionRegistration
accordionRegistration,
harpPedals,
scordatura
};

struct DirectionComponent
Expand Down Expand Up @@ -146,6 +150,8 @@ struct DirectionData
std::vector<OtherDirectionData> otherDirections;
std::vector<ImageData> images;
std::vector<AccordionRegistrationData> accordionRegistrations;
std::vector<HarpPedalsData> harpPedals;
std::vector<ScordaturaData> 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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
76 changes: 76 additions & 0 deletions src/include/mx/api/HarpPedalsData.h
Original file line number Diff line number Diff line change
@@ -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 <optional>
#include <string>
#include <vector>

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 <harp-pedals> 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 <harp-pedals> has no placement
// attribute (placement lives on the parent <direction>).
class HarpPedalsData
{
public:
std::vector<HarpPedalTuning> pedalTunings;
PositionData positionData;
FontData fontData;
std::optional<ColorData> color;
std::optional<std::string> 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
65 changes: 65 additions & 0 deletions src/include/mx/api/ScordaturaData.h
Original file line number Diff line number Diff line change
@@ -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 <optional>
#include <string>
#include <vector>

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<int> 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 <scordatura> 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<AccordData> accords;
std::optional<std::string> 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
73 changes: 63 additions & 10 deletions src/private/mx/impl/DirectionReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -825,17 +831,33 @@ void DirectionReader::parseOctaveShift(const core::DirectionType &directionType)
static_cast<int>(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
// <direction> 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 <direction> (and its <voice>/<staff>) without
// modeling what it actually says. That is why round-trip discovery reports those files as
// dropping <direction>/<direction-type>/<voice>/<staff> 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<double>(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<int>(myOutDirectionData.harpPedals.size()) - 1);
}

void DirectionReader::parseDamp(const core::DirectionType &directionType)
Expand Down Expand Up @@ -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<double>(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<int>(myOutDirectionData.scordaturas.size()) - 1);
}

void DirectionReader::parseImage(const core::DirectionType &directionType)
Expand Down Expand Up @@ -1061,6 +1109,11 @@ void DirectionReader::parseAccordionRegistration(const core::DirectionType &dire
static_cast<int>(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 <direction> 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 <direction> (and its <voice>/<staff>) without modeling what it actually says.
void DirectionReader::parsePercussion(const core::DirectionType &directionType)
{
MX_UNUSED(directionType);
Expand Down
Loading
Loading