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
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
120 changes: 120 additions & 0 deletions src/include/mx/api/DynamicsData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// 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
};

// The letters of the symbol, e.g. "ff" -- also the name of the MusicXML element that carries it.
std::string toString(StandardDynamic value);

// 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);

// The component's letters: the symbol for a standard component, the fallback text for one that has
// no dedicated MusicXML element.
std::string toString(const DynamicsComponent &value);

// 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);

// The letters of the whole mark, its components run together -- "ffz" for ff followed by z.
std::string toString(const CompoundDynamicsData &value);

} // namespace api
} // namespace mx
51 changes: 23 additions & 28 deletions src/include/mx/api/MarkData.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include "mx/api/DynamicsData.h"
#include "mx/api/MarkDataChoice.h"
#include "mx/api/PositionData.h"
#include "mx/api/PrintData.h"
Expand Down Expand Up @@ -46,34 +47,8 @@ enum class MarkType
otherArticulation,

// dynamics
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,
otherDynamics,
unknownDynamics,
dynamics, ///< The symbol itself is in MarkData::choice -- a StandardDynamic such as ff, or a
///< CompoundDynamicsData for marks like ffz that MusicXML spells with several symbols

// ornaments
trillMark,
Expand Down Expand Up @@ -226,6 +201,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 @@ -244,6 +222,7 @@ bool isMarkDynamic(MarkType);
bool isMarkFermata(MarkType);
bool isMarkArpeggiate(MarkType);
bool isMarkNonArpeggiate(MarkType);
bool isMarkOtherNotation(MarkType);

bool isMarkCustom(MarkType);
std::string getCustomMarkName(MarkType);
Expand All @@ -254,6 +233,13 @@ struct MarkData
{
// Fields common to (nearly) every mark, regardless of markType.
MarkType markType;

// The mark's text. For marks whose text is the data -- fingering, pluck, fret, string, and the
// other-* marks -- this is what gets written. For marks that name themselves -- articulations,
// fermatas, dynamics -- it spells the mark out ("ff", "ffz") and the writer ignores it,
// emitting whatever markType and choice say. A self-naming mark fills this in when it is
// constructed, so if you replace choice afterwards, re-derive it: toString() in DynamicsData.h
// spells a dynamic.
std::string name;
int tickTimePosition;
PrintData printData;
Expand Down Expand Up @@ -288,6 +274,15 @@ struct MarkData
MarkData();
MarkData(MarkType inMarkType);
MarkData(Placement inPlacement, MarkType inMarkType);

// Builds a dynamic mark: markType is MarkType::dynamics, choice holds the symbol, and name is
// its letters.
MarkData(StandardDynamic inDynamic);

// Builds a dynamic mark spelled with several symbols, such as ff followed by z for ffz. name
// becomes the letters of the whole mark. A lone standard symbol collapses to the same mark the
// StandardDynamic constructor builds.
MarkData(CompoundDynamicsData inDynamics);
};

MXAPI_EQUALS_BEGIN(MarkData)
Expand Down
76 changes: 74 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 @@ -98,6 +99,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 @@ -120,7 +159,11 @@ class MarkDataChoice
none,
tremolo,
arpeggiate,
nonArpeggiate
nonArpeggiate,
otherMark,
dynamic,
compoundDynamics,
otherNotation
};

MarkDataChoice();
Expand All @@ -131,11 +174,26 @@ class MarkDataChoice

MarkDataChoice(NonArpeggiateMarkData value);

MarkDataChoice(OtherMarkData value);

MarkDataChoice(StandardDynamic value);

// Builds a compound dynamic, unless the value is a single standard symbol, in which case the
// result is a Kind::dynamic choice (auto-collapse). A lone other-dynamics symbol does not
// collapse -- it has no dedicated MusicXML element, so it stays a compound of one.
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 isDynamic() const;
bool isCompoundDynamics() const;
bool isOtherNotation() const;

// Returns a copy of the internally held TremoloMarkData.
//
Expand All @@ -155,10 +213,24 @@ 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 the standard dynamic symbol, or p for another kind.
StandardDynamic dynamic() 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,
StandardDynamic, CompoundDynamicsData, OtherNotationMarkData>
myValue;
};

MXAPI_NOT_EQUALS_AND_VECTORS(MarkDataChoice);
Expand Down
Loading
Loading