Skip to content
Open
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
9 changes: 9 additions & 0 deletions docs/ai/design/mx-impl-port-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,15 @@ Open questions for the Phase-3 design session:
both text and attribute), or attribute-only (clean migration, Komp updates in lockstep)?
4. Fate of `customAccentTenuto`/`getMarkTypeFromCustomString` and the `SMUFLKILL` TODOs.

Resolution:

- Exact glyph names live in mark-specific `MarkDataChoice` payloads, not as another common
`MarkData` field.
- A compound dynamic owns its ordered standard and `other-dynamics` components; neighboring marks
are never interpreted as one dynamic.
- Text and `smufl` may coexist. mx does not promote legacy text to a SMuFL name automatically.
- The `customAccentTenuto` compatibility path remains unchanged and can be retired separately.

## Appendix A: port checklist

### A.1 `src/private/mx/api/` (4 of 13 .cpp touch core/ezxml)
Expand Down
110 changes: 110 additions & 0 deletions src/include/mx/api/DynamicsData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// MusicXML Class Library
// Copyright (c) by Matthew James Briggs
// Distributed under the MIT License

#pragma once

#include "mx/api/ApiCommon.h"

#include <optional>
#include <string>
#include <variant>
#include <vector>

namespace mx
{
namespace api
{

// A standard dynamic abbreviation represented by a dedicated MusicXML element.
enum class StandardDynamic
{
p,
pp,
ppp,
pppp,
ppppp,
pppppp,
f,
ff,
fff,
ffff,
fffff,
ffffff,
mp,
mf,
sf,
sfp,
sfpp,
fp,
rf,
rfz,
sfz,
sffz,
fz,
n,
pf,
sfzp
};

// A component of a dynamic mark that has no dedicated MusicXML dynamic element. text is the
// visible fallback; smufl, when present, names the exact glyph to draw.
struct OtherDynamicsData
{
std::string text;
std::optional<std::string> smufl;
};

MXAPI_EQUALS_BEGIN(OtherDynamicsData)
MXAPI_EQUALS_MEMBER(text)
MXAPI_EQUALS_MEMBER(smufl)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(OtherDynamicsData);

// One ordered component of a compound dynamic mark: either a standard abbreviation or a custom
// component with fallback text and an optional SMuFL glyph name.
class DynamicsComponent
{
public:
enum class Kind
{
standard,
other
};

DynamicsComponent();
DynamicsComponent(StandardDynamic value);
DynamicsComponent(OtherDynamicsData value);

Kind kind() const;
bool isStandard() const;
bool isOther() const;

// Returns the standard dynamic, or p when this holds an other-dynamics component.
StandardDynamic standard() const;

// Returns the custom component, or a default value when this holds a standard dynamic.
OtherDynamicsData other() const;

bool operator==(const DynamicsComponent &other) const;

private:
std::variant<StandardDynamic, OtherDynamicsData> myValue;
};

MXAPI_NOT_EQUALS_AND_VECTORS(DynamicsComponent);

// A dynamic mark assembled from multiple symbols in order, such as ff followed by z for ffz.
// MusicXML writes these as children of one <dynamics> element.
struct CompoundDynamicsData
{
std::vector<DynamicsComponent> components;
};

MXAPI_EQUALS_BEGIN(CompoundDynamicsData)
MXAPI_EQUALS_MEMBER(components)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(CompoundDynamicsData);

} // namespace api
} // namespace mx
5 changes: 5 additions & 0 deletions src/include/mx/api/MarkData.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ enum class MarkType
pf,
sfzp,
otherDynamics,
compoundDynamics, ///< A single dynamic mark assembled from ordered components in MarkData::choice
unknownDynamics,

// ornaments
Expand Down Expand Up @@ -218,6 +219,9 @@ enum class MarkType
// nonArpeggiate
nonArpeggiate,

// general notation extension
otherNotation,

// these are cust additions that will be written to, and read from, the
// other-articulations (or other-*) elements.
customErrorUnknown, // used to represent an error when parsing from a string
Expand All @@ -236,6 +240,7 @@ bool isMarkDynamic(MarkType);
bool isMarkFermata(MarkType);
bool isMarkArpeggiate(MarkType);
bool isMarkNonArpeggiate(MarkType);
bool isMarkOtherNotation(MarkType);

bool isMarkCustom(MarkType);
std::string getCustomMarkName(MarkType);
Expand Down
66 changes: 64 additions & 2 deletions src/include/mx/api/MarkDataChoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

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

#include <optional>
#include <string>
Expand Down Expand Up @@ -83,6 +84,44 @@ MXAPI_EQUALS_MEMBER(id)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(NonArpeggiateMarkData);

// The exact glyph used by an other-articulation, other-dynamics, other-ornament, or
// other-technical mark. The mark's visible fallback text remains in MarkData::name.
struct OtherMarkData
{
std::optional<std::string> smufl;
};

MXAPI_EQUALS_BEGIN(OtherMarkData)
MXAPI_EQUALS_MEMBER(smufl)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(OtherMarkData);

// Whether an other-notation is a standalone symbol or one end of a multi-note notation.
enum class OtherNotationType
{
start,
stop,
single
};

// Payload for MusicXML's general other-notation extension. The visible fallback text, position,
// and print appearance use MarkData's common fields.
struct OtherNotationMarkData
{
OtherNotationType type = OtherNotationType::single;
std::optional<int> number;
std::optional<std::string> smufl;
std::optional<std::string> id;
};

MXAPI_EQUALS_BEGIN(OtherNotationMarkData)
MXAPI_EQUALS_MEMBER(type)
MXAPI_EQUALS_MEMBER(number)
MXAPI_EQUALS_MEMBER(smufl)
MXAPI_EQUALS_MEMBER(id)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(OtherNotationMarkData);

// A variant class that carries data for MarkType values whose payload does not fit MarkData's
// common fields.
//
Expand All @@ -105,7 +144,10 @@ class MarkDataChoice
none,
tremolo,
arpeggiate,
nonArpeggiate
nonArpeggiate,
otherMark,
compoundDynamics,
otherNotation
};

MarkDataChoice();
Expand All @@ -116,11 +158,20 @@ class MarkDataChoice

MarkDataChoice(NonArpeggiateMarkData value);

MarkDataChoice(OtherMarkData value);

MarkDataChoice(CompoundDynamicsData value);

MarkDataChoice(OtherNotationMarkData value);

Kind kind() const;
bool isNone() const;
bool isTremolo() const;
bool isArpeggiate() const;
bool isNonArpeggiate() const;
bool isOtherMark() const;
bool isCompoundDynamics() const;
bool isOtherNotation() const;

// Returns a copy of the internally held TremoloMarkData.
//
Expand All @@ -140,10 +191,21 @@ class MarkDataChoice
// constructed NonArpeggiateMarkData is returned.
const NonArpeggiateMarkData nonArpeggiate() const;

// Returns a copy of the internally held OtherMarkData, or a default value for another kind.
const OtherMarkData otherMark() const;

// Returns a copy of the internally held CompoundDynamicsData, or a default value for another kind.
const CompoundDynamicsData compoundDynamics() const;

// Returns a copy of the internally held OtherNotationMarkData, or a default value for another kind.
const OtherNotationMarkData otherNotation() const;

bool operator==(const MarkDataChoice &other) const;

private:
std::variant<std::monostate, TremoloMarkData, ArpeggiateMarkData, NonArpeggiateMarkData> myValue;
std::variant<std::monostate, TremoloMarkData, ArpeggiateMarkData, NonArpeggiateMarkData, OtherMarkData,
CompoundDynamicsData, OtherNotationMarkData>
myValue;
};

MXAPI_NOT_EQUALS_AND_VECTORS(MarkDataChoice);
Expand Down
65 changes: 65 additions & 0 deletions src/private/mx/api/DynamicsData.cpp
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

#include "mx/api/DynamicsData.h"

#include <utility>

namespace mx
{
namespace api
{

DynamicsComponent::DynamicsComponent() : myValue{StandardDynamic::p}
{
}

DynamicsComponent::DynamicsComponent(StandardDynamic value) : myValue{value}
{
}

DynamicsComponent::DynamicsComponent(OtherDynamicsData value) : myValue{std::move(value)}
{
}

DynamicsComponent::Kind DynamicsComponent::kind() const
{
return isOther() ? Kind::other : Kind::standard;
}

bool DynamicsComponent::isStandard() const
{
return std::holds_alternative<StandardDynamic>(myValue);
}

bool DynamicsComponent::isOther() const
{
return std::holds_alternative<OtherDynamicsData>(myValue);
}

StandardDynamic DynamicsComponent::standard() const
{
if (const auto *value = std::get_if<StandardDynamic>(&myValue))
{
return *value;
}
return StandardDynamic::p;
}

OtherDynamicsData DynamicsComponent::other() const
{
if (const auto *value = std::get_if<OtherDynamicsData>(&myValue))
{
return *value;
}
return OtherDynamicsData{};
}

bool DynamicsComponent::operator==(const DynamicsComponent &other) const
{
return myValue == other.myValue;
}

} // namespace api
} // namespace mx
20 changes: 11 additions & 9 deletions src/private/mx/api/MarkData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ namespace mx
{
namespace api
{
namespace
{
// The wire literal of a dynamics alternative (the old core's
// toString(DynamicsEnum); the new variant Kind carries no string).
std::string dynamicsKindToString(core::DynamicsChoice::Kind kind)
std::string markDataDynamicsKindToString(core::DynamicsChoice::Kind kind)
{
switch (kind)
{
Expand Down Expand Up @@ -75,7 +73,6 @@ std::string dynamicsKindToString(core::DynamicsChoice::Kind kind)
return "other-dynamics";
}
}
} // namespace

bool isMarkDynamic(MarkType markType)
{
Expand All @@ -88,7 +85,7 @@ bool isMarkDynamic(MarkType markType)
(markType == MarkType::fp) || (markType == MarkType::rf) || (markType == MarkType::rfz) ||
(markType == MarkType::sfz) || (markType == MarkType::sffz) || (markType == MarkType::fz) ||
(markType == MarkType::n) || (markType == MarkType::pf) || (markType == MarkType::sfzp) ||
(markType == MarkType::otherDynamics);
(markType == MarkType::otherDynamics) || (markType == MarkType::compoundDynamics);
}

bool isMarkArpeggiate(MarkType markType)
Expand Down Expand Up @@ -144,6 +141,11 @@ bool isMarkNonArpeggiate(MarkType markType)
return (markType == MarkType::nonArpeggiate);
}

bool isMarkOtherNotation(MarkType markType)
{
return markType == MarkType::otherNotation;
}

bool isMarkPedal(MarkType markType)
{
return (markType == MarkType::pedal) || (markType == MarkType::damp);
Expand Down Expand Up @@ -244,9 +246,9 @@ MarkData::MarkData(MarkType inMarkType)
fingeringAlternate{Bool::unspecified}, choice{}
{
impl::Converter converter;
if (isMarkDynamic(markType))
if (isMarkDynamic(markType) && markType != MarkType::compoundDynamics)
{
name = dynamicsKindToString(converter.convertDynamic(markType));
name = markDataDynamicsKindToString(converter.convertDynamic(markType));
}
else if (isMarkArticulation(markType))
{
Expand All @@ -266,9 +268,9 @@ MarkData::MarkData(Placement inPlacement, MarkType inMarkType)
{
positionData.placement = inPlacement;
impl::Converter converter;
if (isMarkDynamic(markType))
if (isMarkDynamic(markType) && markType != MarkType::compoundDynamics)
{
name = dynamicsKindToString(converter.convertDynamic(markType));
name = markDataDynamicsKindToString(converter.convertDynamic(markType));
}
else if (isMarkArticulation(markType))
{
Expand Down
Loading
Loading