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
2 changes: 1 addition & 1 deletion data/api.features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<missing>other</missing>
</enum>
</enums>
<notes>In &lt;notations&gt;: api::MarkType accidentalMark*. In &lt;ornaments&gt; after an ornament: api::OrnamentAccidental (api::Accidental) in the ornament mark's OrnamentMarkData; after tremolo, other-ornament or wavy-line it falls back to an accidentalMark* mark. All members mapped except AccidentalValue::other (no api member). Only value, position and placement are kept.</notes>
<notes>In &lt;notations&gt;: api::MarkType accidentalMark*. In &lt;ornaments&gt; after an ornament: api::OrnamentAccidental (api::Accidental) in the ornament mark's OrnamentMarkData; after tremolo, other-ornament or wavy-line it falls back to an accidentalMark* mark. All members mapped except AccidentalValue::other (no api member). Every attribute is kept: parentheses, bracket, size (api::SymbolSize), smufl and id in api::AccidentalMarkData (MarkDataChoice) or directly on api::OrnamentAccidental; font and color in PrintData; position and placement in PositionData.</notes>
</feature>
<feature name="accidental-text" support="partial">
<notes>Accidental as text (key/ornament context) (suspected).</notes>
Expand Down
19 changes: 19 additions & 0 deletions src/include/mx/api/ApiCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,25 @@ enum class Enclosure
decagon,
none
};

// How large a symbol is drawn compared with ordinary notation, MusicXML's size attribute.
enum class SymbolSize
{
// Leave the size unstated and let the application choose.
unspecified,

// The normal size, stated explicitly.
full,

// The size of a cue note or a grace note.
cue,

// The size of a grace note that is also a cue note.
graceCue,

// Larger than normal.
large
};
} // namespace api
} // namespace mx

Expand Down
78 changes: 76 additions & 2 deletions src/include/mx/api/MarkDataChoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "mx/api/Id.h"
#include "mx/api/PitchData.h"
#include "mx/api/PositionData.h"
#include "mx/api/PrintData.h"

#include <optional>
#include <string>
Expand Down Expand Up @@ -198,6 +199,42 @@ MXAPI_EQUALS_MEMBER(pitch)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(HarmonicMarkData);

// Payload for the accidental mark types (MarkType::accidentalMarkSharp and the rest): how the
// accidental is drawn. Its font and color are in MarkData::printData, whose printObject is not used.
// Leaving every field at its default is the same as having no payload.
struct AccidentalMarkData
{
// yes: the accidental is drawn in parentheses. Parentheses and square brackets are common ways
// to show an editorial accidental. Leave it unspecified to let the application decide.
Bool parentheses = Bool::unspecified;

// yes: the accidental is drawn in square brackets. Leave it unspecified to let the application
// decide.
Bool bracket = Bool::unspecified;

// How large the accidental is drawn. Like parentheses and brackets, a different size is a common
// way to show an editorial accidental.
SymbolSize size = SymbolSize::unspecified;

// The SMuFL glyph to draw when a specific one is wanted, for example "accidentalSharp". It must
// be the name of a SMuFL accidental, one that starts with acc, medRenFla, medRenNatura,
// medRenShar or kievanAccidental; another name is not written. Leave it absent to draw the
// accidental the MarkType names.
std::optional<std::string> smufl;

// An identifier for this accidental, unique in the document (see Id.h).
std::optional<Id> id;
};

MXAPI_EQUALS_BEGIN(AccidentalMarkData)
MXAPI_EQUALS_MEMBER(parentheses)
MXAPI_EQUALS_MEMBER(bracket)
MXAPI_EQUALS_MEMBER(size)
MXAPI_EQUALS_MEMBER(smufl)
MXAPI_EQUALS_MEMBER(id)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(AccidentalMarkData);

// An accidental printed with an ornament: the sharp over a trill that raises its upper note, or the
// accidentals above and below a turn that alter its upper and lower notes.
struct OrnamentAccidental
Expand All @@ -209,11 +246,35 @@ struct OrnamentAccidental
// above puts the accidental over the ornament, below puts it under. The horizontal and vertical
// alignment fields are not used.
PositionData positionData;

// The accidental's font and color. printObject is not used.
PrintData printData;

// As AccidentalMarkData::parentheses.
Bool parentheses = Bool::unspecified;

// As AccidentalMarkData::bracket.
Bool bracket = Bool::unspecified;

// As AccidentalMarkData::size.
SymbolSize size = SymbolSize::unspecified;

// As AccidentalMarkData::smufl. Leave it absent to draw the accidental named by accidental.
std::optional<std::string> smufl;

// As AccidentalMarkData::id.
std::optional<Id> id;
};

MXAPI_EQUALS_BEGIN(OrnamentAccidental)
MXAPI_EQUALS_MEMBER(accidental)
MXAPI_EQUALS_MEMBER(positionData)
MXAPI_EQUALS_MEMBER(printData)
MXAPI_EQUALS_MEMBER(parentheses)
MXAPI_EQUALS_MEMBER(bracket)
MXAPI_EQUALS_MEMBER(size)
MXAPI_EQUALS_MEMBER(smufl)
MXAPI_EQUALS_MEMBER(id)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(OrnamentAccidental);

Expand Down Expand Up @@ -264,7 +325,8 @@ class MarkDataChoice
compoundDynamics,
otherNotation,
harmonic,
ornament
ornament,
accidentalMark
};

MarkDataChoice();
Expand Down Expand Up @@ -292,6 +354,10 @@ class MarkDataChoice
// an ornament without accidentals needs no payload.
MarkDataChoice(OrnamentMarkData value);

// Builds an accidental mark payload, unless every field is left at its default, in which case
// the result is none.
MarkDataChoice(AccidentalMarkData value);

Kind kind() const;
bool isNone() const;
bool isTremolo() const;
Expand All @@ -303,6 +369,7 @@ class MarkDataChoice
bool isOtherNotation() const;
bool isHarmonic() const;
bool isOrnament() const;
bool isAccidentalMark() const;

// Returns a copy of the internally held TremoloMarkData.
//
Expand Down Expand Up @@ -346,11 +413,18 @@ class MarkDataChoice
// OrnamentMarkData, with no accidentals, is returned.
const OrnamentMarkData ornament() const;

// Returns a copy of the internally held AccidentalMarkData.
//
// Check isAccidentalMark() first. If this is not an accidental mark payload, a default
// constructed AccidentalMarkData is returned.
const AccidentalMarkData accidentalMark() const;

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

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

Expand Down
34 changes: 34 additions & 0 deletions src/private/mx/api/MarkDataChoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ MarkDataChoice::MarkDataChoice(OrnamentMarkData value)
}
}

// An accidental mark with nothing set is stored as none, so a plain accidental mark compares equal
// however it was built.
MarkDataChoice::MarkDataChoice(AccidentalMarkData value)
{
const bool isEmpty = value.parentheses == Bool::unspecified && value.bracket == Bool::unspecified &&
value.size == SymbolSize::unspecified && !value.smufl.has_value() && !value.id.has_value();
if (isEmpty)
{
myValue = std::monostate{};
}
else
{
myValue = std::move(value);
}
}

MarkDataChoice::Kind MarkDataChoice::kind() const
{
if (std::holds_alternative<TremoloMarkData>(myValue))
Expand Down Expand Up @@ -110,6 +126,10 @@ MarkDataChoice::Kind MarkDataChoice::kind() const
{
return Kind::ornament;
}
if (std::holds_alternative<AccidentalMarkData>(myValue))
{
return Kind::accidentalMark;
}
return Kind::none;
}

Expand Down Expand Up @@ -244,6 +264,20 @@ const OrnamentMarkData MarkDataChoice::ornament() const
return OrnamentMarkData{};
}

bool MarkDataChoice::isAccidentalMark() const
{
return std::holds_alternative<AccidentalMarkData>(myValue);
}

const AccidentalMarkData MarkDataChoice::accidentalMark() const
{
if (const auto *value = std::get_if<AccidentalMarkData>(&myValue))
{
return *value;
}
return AccidentalMarkData{};
}

bool MarkDataChoice::operator==(const MarkDataChoice &other) const
{
return myValue == other.myValue;
Expand Down
36 changes: 34 additions & 2 deletions src/private/mx/impl/AccidentalMarkFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,43 @@

#include "mx/impl/AccidentalMarkFunctions.h"
#include "mx/core/generated/AccidentalMark.h"
#include "mx/impl/IdFunctions.h"
#include "mx/impl/MarkDataFunctions.h"

namespace mx
{
namespace impl
{

// Reads how an accidental-mark is drawn into either api shape that holds it: a standalone mark's
// AccidentalMarkData, whose position and print data live on the MarkData, or an OrnamentAccidental,
// which has the same fields itself.
template <typename ACCIDENTAL_FIELDS>
void accidentalMarkFunctionsParseAttributes(const core::AccidentalMark &inAccidentalMark, ACCIDENTAL_FIELDS &outFields,
api::PositionData &outPositionData, api::PrintData &outPrintData)
{
Converter converter;
outPositionData = impl::getPositionData(inAccidentalMark);
outPrintData = impl::getPrintData(inAccidentalMark);
if (inAccidentalMark.parentheses().has_value())
{
outFields.parentheses = converter.convert(*inAccidentalMark.parentheses());
}
if (inAccidentalMark.bracket().has_value())
{
outFields.bracket = converter.convert(*inAccidentalMark.bracket());
}
if (inAccidentalMark.size().has_value())
{
outFields.size = converter.convert(*inAccidentalMark.size());
}
if (inAccidentalMark.smufl().has_value())
{
outFields.smufl = inAccidentalMark.smufl()->toString();
}
outFields.id = impl::getId(inAccidentalMark);
}

AccidentalMarkFunctions::AccidentalMarkFunctions(const core::AccidentalMark &inAccidentalMark, impl::Cursor inCursor)
: myAccidentalMark{inAccidentalMark}, myCursor{inCursor}
{
Expand All @@ -26,7 +56,9 @@ api::MarkData AccidentalMarkFunctions::parseAccidentalMark() const
markData.tickTimePosition = myCursor.tickTimePosition;

markData.name = std::string{accidentalValue.toString()};
markData.positionData = impl::getPositionData(myAccidentalMark);
api::AccidentalMarkData payload;
accidentalMarkFunctionsParseAttributes(myAccidentalMark, payload, markData.positionData, markData.printData);
markData.choice = payload;

return markData;
}
Expand All @@ -36,7 +68,7 @@ api::OrnamentAccidental AccidentalMarkFunctions::parseOrnamentAccidental() const
Converter converter;
api::OrnamentAccidental accidental;
accidental.accidental = converter.convert(myAccidentalMark.value());
accidental.positionData = impl::getPositionData(myAccidentalMark);
accidentalMarkFunctionsParseAttributes(myAccidentalMark, accidental, accidental.positionData, accidental.printData);
return accidental;
}
} // namespace impl
Expand Down
17 changes: 17 additions & 0 deletions src/private/mx/impl/Converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,13 @@ const Converter::EnumMap<core::EnclosureShape, api::Enclosure> Converter::enclos
{core::EnclosureShape::none(), api::Enclosure::none},
};

const Converter::EnumMap<core::SymbolSize, api::SymbolSize> Converter::symbolSizeMap = {
{core::SymbolSize::full(), api::SymbolSize::full},
{core::SymbolSize::cue(), api::SymbolSize::cue},
{core::SymbolSize::graceCue(), api::SymbolSize::graceCue},
{core::SymbolSize::large(), api::SymbolSize::large},
};

api::Step Converter::convert(core::Step inStep) const
{
return findApiItem(stepMap, api::Step::c, inStep);
Expand Down Expand Up @@ -2198,6 +2205,16 @@ core::EnclosureShape Converter::convert(api::Enclosure value) const
return findCoreItem(enclosureMap, core::EnclosureShape::none(), value);
}

api::SymbolSize Converter::convert(core::SymbolSize value) const
{
return findApiItem(symbolSizeMap, api::SymbolSize::unspecified, value);
}

core::SymbolSize Converter::convert(api::SymbolSize value) const
{
return findCoreItem(symbolSizeMap, core::SymbolSize::full(), value);
}

double Converter::convertToAlter(int semitones, double cents)
{
double alter = 0.0;
Expand Down
4 changes: 4 additions & 0 deletions src/private/mx/impl/Converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "mx/core/generated/StickLocation.h"
#include "mx/core/generated/StickMaterial.h"
#include "mx/core/generated/StickType.h"
#include "mx/core/generated/SymbolSize.h"
#include "mx/core/generated/SystemRelation.h"
#include "mx/core/generated/SystemRelationNumber.h"
#include "mx/core/generated/TechnicalChoice.h"
Expand Down Expand Up @@ -259,6 +260,8 @@ class Converter
core::TipDirection convert(api::TipDirection value) const;
api::Enclosure convert(core::EnclosureShape value) const;
core::EnclosureShape convert(api::Enclosure value) const;
api::SymbolSize convert(core::SymbolSize value) const;
core::SymbolSize convert(api::SymbolSize value) const;

static double convertToAlter(int semitones, double cents);
static std::pair<int, double> convertToSemitonesAndCents(double alter);
Expand Down Expand Up @@ -327,6 +330,7 @@ class Converter
const static EnumMap<core::StickLocation, api::StickLocation> stickLocationMap;
const static EnumMap<core::TipDirection, api::TipDirection> tipDirectionMap;
const static EnumMap<core::EnclosureShape, api::Enclosure> enclosureMap;
const static EnumMap<core::SymbolSize, api::SymbolSize> symbolSizeMap;

private:
template <typename CORE_TYPE, typename API_TYPE>
Expand Down
Loading
Loading