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
148 changes: 148 additions & 0 deletions src/include/mx/api/ComplexTimeSignature.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// MusicXML Class Library
// Copyright (c) by Matthew James Briggs
// Distributed under the MIT License

#pragma once

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

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

namespace mx
{
namespace api
{

// The full MusicXML time-symbol vocabulary. Used for a complex meter's own symbol and for an
// interchangeable pair's symbol. Unlike the narrow TimeSignatureSymbol (common/cut only), this
// includes the unusual display modes that force a signature into the complex arena.
enum class ComplexTimeSymbol
{
unspecified, // ordinary fractional display
common, // the C glyph
cut, // the slashed-C glyph
singleNumber, // the meter drawn as a single number
note, // the beat-type drawn as a downstem note glyph
dottedNote // the beat-type drawn as a dotted downstem note glyph
};

// The line drawn between beats and beat-type (MusicXML time-separator). 'unspecified' writes no
// attribute (the spec default is "none").
enum class TimeSeparator
{
unspecified,
none,
horizontal,
diagonal,
vertical,
adjacent
};

// How a dual/interchangeable pair is visually joined (MusicXML time-relation).
enum class TimeRelation
{
unspecified,
parentheses,
bracket,
equals,
slash,
space,
hyphen
};

// The alternate meter of an interchangeable dual pair, e.g. the "(6/8)" of "3/4 (6/8)". It decorates
// a primary metered signature and carries its own relation, symbol, and separator, independent of
// the primary's.
struct InterchangeableTimeSignature
{
// The alternate meter's fractions. One = simple, more than one = composite. Nonempty when used.
std::vector<TimeFraction> fractions;

TimeRelation relation{TimeRelation::unspecified};
ComplexTimeSymbol symbol{ComplexTimeSymbol::unspecified};
TimeSeparator separator{TimeSeparator::unspecified};
};

MXAPI_EQUALS_BEGIN(InterchangeableTimeSignature)
MXAPI_EQUALS_MEMBER(fractions)
MXAPI_EQUALS_MEMBER(relation)
MXAPI_EQUALS_MEMBER(symbol)
MXAPI_EQUALS_MEMBER(separator)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(InterchangeableTimeSignature);

// A metered (as opposed to senza-misura) complex time signature: a primary meter of one or more
// fractions, plus its decorations. A single fraction with no symbol, no separator, and no
// interchangeable is simple-equivalent and TimeChoice::complex collapses it back to the simple case;
// so a MeteredTimeSignature that survives as complex always carries at least one of: more than one
// fraction, an unusual symbol, a separator, or an interchangeable alternate.
struct MeteredTimeSignature
{
// The primary meter's fractions. One = a single meter (decorated); more than one = composite
// (additive), e.g. 2/4 + 3/8. Nonempty in a valid state.
std::vector<TimeFraction> fractions{TimeFraction{"4", "4"}};

ComplexTimeSymbol symbol{ComplexTimeSymbol::unspecified};
TimeSeparator separator{TimeSeparator::unspecified};
std::optional<InterchangeableTimeSignature> interchangeable;
};

MXAPI_EQUALS_BEGIN(MeteredTimeSignature)
MXAPI_EQUALS_MEMBER(fractions)
MXAPI_EQUALS_MEMBER(symbol)
MXAPI_EQUALS_MEMBER(separator)
MXAPI_EQUALS_MEMBER(interchangeable)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(MeteredTimeSignature);

// Everything unusual about a time signature, kept out of the way of the simple case. It is itself a
// choice: either a metered signature (composite meters, unusual symbols, separators, interchangeable
// dual meters) or senza-misura (unmetered music). The two are mutually exclusive in MusicXML, which
// this choice makes structurally true: a senza-misura signature cannot carry a symbol or an
// interchangeable, and a metered one cannot be senza-misura.
//
// Reached only through TimeChoice::asComplex(). Constructing one whose metered value is
// simple-equivalent yields a *simple* TimeChoice instead (auto-collapse), so a plain N/D can never
// hide in here.
class ComplexTimeSignature
{
public:
enum class Kind
{
metered,
senzaMisura
};

// Defaults to a metered 4/4 (this default is never simple-equivalent-collapsed on its own; that
// happens only when handed to TimeChoice::complex).
ComplexTimeSignature();

static ComplexTimeSignature metered(MeteredTimeSignature value);

// The string is the senza-misura display glyph (often empty, sometimes "X").
static ComplexTimeSignature senzaMisura(std::string glyph = {});

Kind kind() const;
bool isMetered() const;
bool isSenzaMisura() const;

// Precondition: isMetered().
const MeteredTimeSignature &asMetered() const;

// Precondition: isSenzaMisura(). The returned string is the display glyph (may be empty).
const std::string &asSenzaMisura() const;

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

private:
std::variant<MeteredTimeSignature, std::string> myValue;
};

MXAPI_NOT_EQUALS_AND_VECTORS(ComplexTimeSignature);

} // namespace api
} // namespace mx
13 changes: 11 additions & 2 deletions src/include/mx/api/MeasureData.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include "mx/api/PartSymbolData.h"
#include "mx/api/StaffData.h"
#include "mx/api/TempoData.h"
#include "mx/api/TimeSignatureData.h"
#include "mx/api/TimeChoice.h"
#include "mx/api/TransposeData.h"

#include <map>
Expand All @@ -31,7 +31,15 @@ class MeasureData
// this is the notes and other music data
std::vector<StaffData> staves;

TimeSignatureData timeSignature;
// The measure's time signature (applies to all staves). A TimeChoice: simple for the ordinary
// N/D case, complex for anything unusual.
TimeChoice timeSignature;

// Only use this if you need different time signatures on different staves. Normally empty; keyed
// by staff index, one entry per staff-scoped <time number="N"> override. Effective-time-signature
// rule: for a given staff, an entry here whose key matches wins; otherwise fall back to the
// singular timeSignature above.
std::map<int, TimeChoice> staffTimeSignatures;

// an empty measureNumber indicates a normal
// measure number (i.e. the measure's
Expand Down Expand Up @@ -110,6 +118,7 @@ class MeasureData
MXAPI_EQUALS_BEGIN(MeasureData)
MXAPI_EQUALS_MEMBER(staves)
MXAPI_EQUALS_MEMBER(timeSignature)
MXAPI_EQUALS_MEMBER(staffTimeSignatures)
MXAPI_EQUALS_MEMBER(number)
MXAPI_EQUALS_MEMBER(measureNumbering)
MXAPI_EQUALS_MEMBER(multiMeasureRest)
Expand Down
78 changes: 78 additions & 0 deletions src/include/mx/api/TimeChoice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// MusicXML Class Library
// Copyright (c) by Matthew James Briggs
// Distributed under the MIT License

#pragma once

#include "mx/api/ApiCommon.h"
#include "mx/api/ComplexTimeSignature.h"
#include "mx/api/TimeSignatureData.h"

#include <variant>

namespace mx
{
namespace api
{

// A measure's (or a staff's) time signature. It switches between the simple case -- the ordinary
// N/D you want 99% of the time -- and the complex case, which quarantines everything unusual
// (composite meters, senza-misura, interchangeable, unusual display symbols). Read the kind, then
// reach for asSimple() or asComplex().
//
// The whole-<time> attributes that apply regardless of shape live here, not on either leaf:
// isImplicit (was the signature restated in this measure or carried forward) and display
// (print-object show/hide).
//
// Invariant: a simple-equivalent signature is always held as simple. TimeChoice::complex collapses a
// complex value whose meter is really just a plain fraction (one fraction, no interchangeable, no
// separator, and a symbol the simple case can express) down to simple, so there is exactly one
// canonical representation and the simple case can never hide inside the complex one.
//
// Defaults to a simple, implicit 4/4.
class TimeChoice
{
public:
enum class Kind
{
simple,
complex
};

// True when the source did not restate the time signature here (it was carried forward). The
// writer emits a <time> only for entries where this is false. Denormalized onto every measure.
bool isImplicit{true};

// Print/hide the time signature (<time print-object=...>). Ignored when isImplicit.
Bool display{Bool::unspecified};

// Defaults to a simple, implicit 4/4.
TimeChoice();

static TimeChoice simple(TimeSignatureData value);

// Builds a complex time signature, unless the value is simple-equivalent, in which case the
// result is a *simple* TimeChoice (auto-collapse). isImplicit/display are left at their defaults;
// set them on the returned value.
static TimeChoice complex(ComplexTimeSignature value);

Kind kind() const;
bool isSimple() const;
bool isComplex() const;

// Precondition: isSimple().
const TimeSignatureData &asSimple() const;

// Precondition: isComplex().
const ComplexTimeSignature &asComplex() const;

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

private:
std::variant<TimeSignatureData, ComplexTimeSignature> myValue;
};

MXAPI_NOT_EQUALS_AND_VECTORS(TimeChoice);

} // namespace api
} // namespace mx
66 changes: 35 additions & 31 deletions src/include/mx/api/TimeSignatureData.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,56 @@ namespace mx
{
namespace api
{

// The symbol drawn in place of, or alongside, a simple time signature. This is the narrow vocabulary
// that belongs to the simple case: an ordinary fraction, optionally shown with the C (common) or the
// slashed-C (cut) glyph. The weirder display modes (single-number, note, dotted-note) live only in
// the complex arena (see ComplexTimeSignature.h) and are not expressible here on purpose.
enum class TimeSignatureSymbol
{
unspecified,
common,
cut,
singleNumber
unspecified, // ordinary fractional display, e.g. 4/4 drawn as two numbers
common, // the C glyph (4/4)
cut // the slashed-C glyph (2/2)
};

class TimeSignatureData
// One beats/beat-type pair. The strings are free-form by design: they may hold compound values
// ("3+2"), decimals ("1 + 2.5"), or compound denominators ("8 + 16"). This is the shared leaf reused
// by the simple TimeSignatureData and by the composite/interchangeable meters in the complex arena;
// it carries no symbol, which is what lets it be shared across arenas whose symbol vocabularies differ.
struct TimeFraction
{
public:
// common, cut
TimeSignatureSymbol symbol;

// the top number of the time signature, e.g. "5" in a "5/4" or "3+2" in a "(3+2)/8"
// the top number of the time signature, e.g. "5" in "5/4" or "3+2" in "(3+2)/8"
std::string beats;

// the bottom number of the time signature, e.g. "4" in a "5/4"
// the bottom number of the time signature, e.g. "4" in "5/4"
std::string beatType;
};

// a time signature is implicit when it is not specified by the musicxml
bool isImplicit;

// use this to hide a time sigature with Bool::no. If a time signature is
// implicit, the 'display' field will be ignored
Bool display;

inline bool isEqualTo(const TimeSignatureData &other) const
{
return (beats == other.beats) && (beatType == other.beatType) && (symbol == other.symbol);
}
MXAPI_EQUALS_BEGIN(TimeFraction)
MXAPI_EQUALS_MEMBER(beats)
MXAPI_EQUALS_MEMBER(beatType)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(TimeFraction);

TimeSignatureData()
: symbol{TimeSignatureSymbol::unspecified}, beats{"4"}, beatType{"4"}, isImplicit{true},
display{Bool::unspecified}
{
}
// The simple time signature: the ordinary N/D that you reach for 99% of the time, optionally shown
// with the common or cut glyph. Everything unusual (composite meters, senza-misura, interchangeable
// dual meters, and the single-number/note/dotted-note display modes) is quarantined in
// ComplexTimeSignature. Access a measure's time signature through TimeChoice (TimeChoice.h), which
// switches between this simple case and the complex one and holds the whole-<time> attributes
// (isImplicit, display).
//
// Defaults to 4/4 with no symbol.
struct TimeSignatureData
{
TimeSignatureSymbol symbol{TimeSignatureSymbol::unspecified};
TimeFraction fraction{"4", "4"};
};

MXAPI_EQUALS_BEGIN(TimeSignatureData)
MXAPI_EQUALS_MEMBER(symbol)
MXAPI_EQUALS_MEMBER(beats)
MXAPI_EQUALS_MEMBER(beatType)
MXAPI_EQUALS_MEMBER(isImplicit)
MXAPI_EQUALS_MEMBER(display)
MXAPI_EQUALS_MEMBER(fraction)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(TimeSignatureData);

} // namespace api
} // namespace mx
Loading
Loading