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
20 changes: 8 additions & 12 deletions src/include/mx/api/DirectionData.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "mx/api/MarkData.h"
#include "mx/api/OtherDirectionData.h"
#include "mx/api/OttavaData.h"
#include "mx/api/PedalLineData.h"
#include "mx/api/PercussionData.h"
#include "mx/api/PrincipalVoiceData.h"
#include "mx/api/RehearsalData.h"
Expand Down Expand Up @@ -44,8 +45,7 @@ enum class DirectionComponentKind
bracketStop,
dashesStart,
dashesStop,
pedalStart,
pedalStop,
pedal,
words,
chord,
segno,
Expand Down Expand Up @@ -135,8 +135,7 @@ struct DirectionData
std::vector<SpannerStop> bracketStops;
std::vector<SpannerStart> dashesStarts;
std::vector<SpannerStop> dashesStops;
std::vector<SpannerStart> pedalStarts;
std::vector<SpannerStop> pedalStops;
std::vector<PedalLineData> pedals;
std::vector<WordsData> words;
std::vector<ChordData> chords;
std::vector<FiguredBassData> figuredBasses;
Expand Down Expand Up @@ -165,8 +164,7 @@ struct DirectionData
DirectionData()
: tickTimePosition{0}, placement{Placement::unspecified}, voice{VALUE_UNSPECIFIED}, isStaffValueSpecified{true},
isSoundDataSpecified{false}, soundData{}, marks{}, wedgeStarts{}, wedgeStops{}, ottavaStarts{}, ottavaStops{},
bracketStarts{}, bracketStops{}, dashesStarts{}, dashesStops{}, pedalStarts{}, pedalStops{}, words{},
chords{}, segnos{}
bracketStarts{}, bracketStops{}, dashesStarts{}, dashesStops{}, pedals{}, words{}, chords{}, segnos{}
{
}
};
Expand All @@ -177,10 +175,9 @@ inline bool isDirectionDataEmpty(const DirectionData &directionData)
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.pedals.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.rehearsals.size() == 0 && directionData.damps.size() == 0 &&
directionData.dampAlls.size() == 0 && directionData.eyeglasses.size() == 0 &&
directionData.stringMutes.size() == 0 && directionData.staffDivides.size() == 0 &&
Expand Down Expand Up @@ -208,8 +205,7 @@ 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(pedals)
MXAPI_EQUALS_MEMBER(words)
MXAPI_EQUALS_MEMBER(chords)
MXAPI_EQUALS_MEMBER(figuredBasses)
Expand Down
4 changes: 2 additions & 2 deletions src/include/mx/api/MarkData.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ enum class MarkType
fermataSquareInverted, // <fermata type="inverted">square</fermata>
unknownFermata, // bad state, unknown type, error

pedal, // the typical piano pedal mark
damp, // the typical piano end-pedal mark
pedal, // the typical piano pedal mark. for pedal marks with lines, see PedalLineData
damp, // the typical piano end-pedal mark. for pedal marks with lines, see PedalLineData

// arpeggiate
arpeggiate,
Expand Down
69 changes: 69 additions & 0 deletions src/include/mx/api/PedalLineData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// MusicXML Class Library
// Copyright (c) by Matthew James Briggs
// Distributed under the MIT License

#pragma once

#include "mx/api/ApiCommon.h"
#include "mx/api/PositionData.h"

namespace mx
{
namespace api
{
// The kind of piano-pedal event, MusicXML's pedal/@type. A damper or sostenuto pedal line is
// written as a run of these events on one staff, ordered by tickTimePosition. The three
// "opening" kinds begin a line, the two "closing" kinds end one, and change/continueLine occur
// mid-line:
//
// start Begin a damper-pedal line with the usual downstroke.
// sostenuto Begin a sostenuto-pedal line (the middle piano pedal).
// resume Begin a line with no downstroke -- used to pick a line back up after a
// discontinue, or to start one that a text or symbol already introduced.
// change A pedal lift and immediate retake, drawn as an inverted "V" in the line.
// continueLine A formatting-only continuation point, used to break the line neatly across a
// system break or to shape a complex pedaling line. It changes nothing musically.
// stop End the line with an explicit lift (the upstroke at the end of the line).
// discontinue End the line with no explicit lift -- the counterpart to resume.
//
// mx::api models only pedal lines here (MusicXML pedal/@line="yes"). The alternative sign form
// (Ped. and * marks, pedal/@line="no") is expressed through MarkData as MarkType::pedal and
// MarkType::damp instead.
enum class PedalLineKind
{
unspecified,
start,
stop,
sostenuto,
change,
continueLine,
discontinue,
resume
};

// One event in a piano-pedal line (see PedalKind). Put a sequence of these in
// DirectionData::pedals to describe an entire pedaling; each event stands on its own and is
// placed at its own tickTimePosition.
struct PedalLineData
{
PedalLineKind kind;
int tickTimePosition;
PositionData positionData;

PedalLineData() : kind{PedalLineKind::unspecified}, tickTimePosition{0}, positionData{}
{
}

PedalLineData(PedalLineKind inKind) : kind{inKind}, tickTimePosition{0}, positionData{}
{
}
};

MXAPI_EQUALS_BEGIN(PedalLineData)
MXAPI_EQUALS_MEMBER(kind)
MXAPI_EQUALS_MEMBER(tickTimePosition)
MXAPI_EQUALS_MEMBER(positionData)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(PedalLineData);
} // namespace api
} // namespace mx
79 changes: 42 additions & 37 deletions src/private/mx/impl/DirectionReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,17 +703,32 @@ void DirectionReader::parseBracket(const core::DirectionType &directionType)
}
}

api::PedalLineKind pedalKindForTag(core::PedalType::Tag tag)
{
switch (tag)
{
case core::PedalType::Tag::start:
return api::PedalLineKind::start;
case core::PedalType::Tag::stop:
return api::PedalLineKind::stop;
case core::PedalType::Tag::sostenuto:
return api::PedalLineKind::sostenuto;
case core::PedalType::Tag::change:
return api::PedalLineKind::change;
case core::PedalType::Tag::continue_:
return api::PedalLineKind::continueLine;
case core::PedalType::Tag::discontinue:
return api::PedalLineKind::discontinue;
case core::PedalType::Tag::resume:
return api::PedalLineKind::resume;
}
return api::PedalLineKind::unspecified;
}

void DirectionReader::parsePedal(const core::DirectionType &directionType)
{
const auto &pedal = directionType.choice().asPedal();

// sostenuto/change/continue/discontinue/resume are unmodeled (#324) -- pedalStarts/
// pedalStops only model the damper start/stop pair. A <pedal type="change"/> (etc.) is
// dropped here rather than misrepresented as a plain start or stop.
if (pedal.type().tag() != core::PedalType::Tag::start && pedal.type().tag() != core::PedalType::Tag::stop)
{
return;
}
const auto tag = pedal.type().tag();

const auto placement =
(myDirection && myDirection->placement().has_value())
Expand All @@ -723,40 +738,30 @@ void DirectionReader::parsePedal(const core::DirectionType &directionType)

myOutDirectionData.placement = placement;

if (pedal.line().has_value() && pedal.line()->tag() == core::YesNo::Tag::yes)
{
if (pedal.type().tag() == core::PedalType::Tag::start)
{
api::SpannerStart start;
start.tickTimePosition = myOutDirectionData.tickTimePosition;
start.positionData = getPositionData(pedal);
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<int>(myOutDirectionData.pedalStarts.size()) - 1);
}
else if (pedal.type().tag() == core::PedalType::Tag::stop)
{
api::SpannerStop stop;
stop.tickTimePosition = myOutDirectionData.tickTimePosition;
stop.positionData = getPositionData(pedal);
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<int>(myOutDirectionData.pedalStops.size()) - 1);
}
// A pedal line (line="yes"). sostenuto/change/continue/discontinue/resume are inherently
// line-formatting types (the spec only defines them with line="yes"), so they are treated as
// line pedals even when the line attribute is omitted. start/stop with line != "yes" are the
// sign form (Ped./* marks) and fall through to the MarkData path below.
const bool isLine = (pedal.line().has_value() && pedal.line()->tag() == core::YesNo::Tag::yes) ||
tag == core::PedalType::Tag::sostenuto || tag == core::PedalType::Tag::change ||
tag == core::PedalType::Tag::continue_ || tag == core::PedalType::Tag::discontinue ||
tag == core::PedalType::Tag::resume;

if (isLine)
{
api::PedalLineData pedalData{pedalKindForTag(tag)};
pedalData.tickTimePosition = myOutDirectionData.tickTimePosition;
pedalData.positionData = getPositionData(pedal);
pedalData.positionData.placement = placement;
myOutDirectionData.pedals.emplace_back(std::move(pedalData));
appendOrderedComponent(api::DirectionComponentKind::pedal,
static_cast<int>(myOutDirectionData.pedals.size()) - 1);
return;
}

auto pedalType = api::MarkType::pedal;

if (pedal.type().tag() == core::PedalType::Tag::stop)
if (tag == core::PedalType::Tag::stop)
{
pedalType = api::MarkType::damp;
}
Expand Down
65 changes: 35 additions & 30 deletions src/private/mx/impl/DirectionWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,25 +282,42 @@ void DirectionWriter::emitMark(api::MarkData mark, core::Direction &direction)
}
}

void DirectionWriter::emitPedalStart(const api::SpannerStart &pedalStart, core::Direction &direction)
core::PedalType corePedalType(api::PedalLineKind kind)
{
core::Pedal pedal{};
pedal.setType(core::PedalType::start());
pedal.setLine(core::YesNo::yes());
pedal.setSign(core::YesNo::yes());
setAttributesFromPositionData(pedalStart.positionData, pedal);
core::DirectionType dt{};
dt.setChoice(core::DirectionTypeChoice::pedal(pedal));
addDirectionType(std::move(dt), direction);
switch (kind)
{
case api::PedalLineKind::start:
return core::PedalType::start();
case api::PedalLineKind::stop:
return core::PedalType::stop();
case api::PedalLineKind::sostenuto:
return core::PedalType::sostenuto();
case api::PedalLineKind::change:
return core::PedalType::change();
case api::PedalLineKind::continueLine:
return core::PedalType::continue_();
case api::PedalLineKind::discontinue:
return core::PedalType::discontinue();
case api::PedalLineKind::resume:
return core::PedalType::resume();
case api::PedalLineKind::unspecified:
break;
}
return core::PedalType::start();
}

void DirectionWriter::emitPedalStop(const api::SpannerStop &pedalStop, core::Direction &direction)
void DirectionWriter::emitPedal(const api::PedalLineData &item, core::Direction &direction)
{
// An unspecified kind describes no pedal event; emit nothing rather than a guessed default.
if (item.kind == api::PedalLineKind::unspecified)
{
return;
}

core::Pedal pedal{};
pedal.setType(core::PedalType::stop());
pedal.setType(corePedalType(item.kind));
pedal.setLine(core::YesNo::yes());
pedal.setSign(core::YesNo::yes());
setAttributesFromPositionData(pedalStop.positionData, pedal);
setAttributesFromPositionData(item.positionData, pedal);
core::DirectionType dt{};
dt.setChoice(core::DirectionTypeChoice::pedal(pedal));
addDirectionType(std::move(dt), direction);
Expand Down Expand Up @@ -1241,14 +1258,9 @@ void DirectionWriter::emitFixedOrder(core::Direction &direction)
emitMark(mark, direction);
}

for (const auto &item : myDirectionData.pedalStarts)
{
emitPedalStart(item, direction);
}

for (const auto &item : myDirectionData.pedalStops)
for (const auto &item : myDirectionData.pedals)
{
emitPedalStop(item, direction);
emitPedal(item, direction);
}

for (const auto &item : myDirectionData.wedgeStops)
Expand Down Expand Up @@ -1391,17 +1403,10 @@ void DirectionWriter::emitOrderedComponents(core::Direction &direction)
}
break;

case api::DirectionComponentKind::pedalStart:
if (i >= 0 && static_cast<size_t>(i) < myDirectionData.pedalStarts.size())
{
emitPedalStart(myDirectionData.pedalStarts.at(i), direction);
}
break;

case api::DirectionComponentKind::pedalStop:
if (i >= 0 && static_cast<size_t>(i) < myDirectionData.pedalStops.size())
case api::DirectionComponentKind::pedal:
if (i >= 0 && static_cast<size_t>(i) < myDirectionData.pedals.size())
{
emitPedalStop(myDirectionData.pedalStops.at(i), direction);
emitPedal(myDirectionData.pedals.at(i), direction);
}
break;

Expand Down
3 changes: 1 addition & 2 deletions src/private/mx/impl/DirectionWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ class DirectionWriter
std::vector<core::MusicDataChoice> &ioOutputSet);

void emitMark(api::MarkData mark, core::Direction &direction);
void emitPedalStart(const api::SpannerStart &pedalStart, core::Direction &direction);
void emitPedalStop(const api::SpannerStop &pedalStop, core::Direction &direction);
void emitPedal(const api::PedalLineData &pedal, core::Direction &direction);
void emitWedgeStop(const api::WedgeStop &wedgeStop, core::Direction &direction);
void emitWedgeStart(const api::WedgeStart &wedgeStart, core::Direction &direction);
void emitOttavaStop(const api::OttavaStop &ottavaStop, core::Direction &direction);
Expand Down
38 changes: 38 additions & 0 deletions src/private/mxtest/api/DirectionMarksRoundTripTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,44 @@ TEST(HarpPedals, DirectionMarksRoundTrip)

T_END;

TEST(PedalAllKinds, DirectionMarksRoundTrip)
{
// Every pedal-line type must survive a round-trip; before #324 only start/stop did and the
// rest (sostenuto/change/continueLine/discontinue/resume) were silently dropped.
const PedalLineKind kinds[] = {
PedalLineKind::start, PedalLineKind::stop, PedalLineKind::sostenuto, PedalLineKind::change,
PedalLineKind::continueLine, PedalLineKind::discontinue, PedalLineKind::resume,
};

for (const auto kind : kinds)
{
DirectionData direction;
direction.pedals.emplace_back(kind);
const auto directions = roundTripDirectionData(direction);
REQUIRE(directions.size() == 1);
REQUIRE(directions.front().pedals.size() == 1);
CHECK(directions.front().pedals.front().kind == kind);
}
}

T_END;

TEST(PedalPlacement, DirectionMarksRoundTrip)
{
DirectionData direction;
direction.placement = Placement::below;
PedalLineData pedal{PedalLineKind::start};
pedal.positionData.placement = Placement::below;
direction.pedals.push_back(pedal);
const auto directions = roundTripDirectionData(direction);
REQUIRE(directions.size() == 1);
REQUIRE(directions.front().pedals.size() == 1);
CHECK(directions.front().pedals.front().kind == PedalLineKind::start);
CHECK(directions.front().placement == Placement::below);
}

T_END;

TEST(Scordatura, DirectionMarksRoundTrip)
{
DirectionData direction;
Expand Down
Loading