MNX Document Model
Loading...
Searching...
No Matches
CommonClasses.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include <utility>
25#include <numeric>
26#include <stdexcept>
27#include <limits>
28#include <type_traits>
29
30#include "BaseTypes.h"
31#include "Enumerations.h"
32
33namespace mnx {
34
58struct [[nodiscard]] FractionValue
59{
61 using NumType = unsigned;
62 static_assert(std::is_integral<NumType>::value, "FractionValue::NumType must be an integral type");
63
64private:
65 NumType m_num = 0;
66 NumType m_den = 1;
67
68 template<typename T = NumType,
69 typename std::enable_if_t<std::is_signed<T>::value, int> = 0>
70 constexpr void normalizeSign()
71 {
72 if (m_den < 0) {
73 m_den = -m_den;
74 m_num = -m_num;
75 }
76 }
77
78 // Unsigned case: no-op, never even sees m_den < 0
79 template<typename T = NumType,
80 typename std::enable_if_t<!std::is_signed<T>::value, int> = 0>
81 constexpr void normalizeSign() {}
82
83public:
87 constexpr FractionValue() = default;
88
101 constexpr FractionValue(NumType num, NumType den)
102 : m_num(num), m_den(den)
103 {
104 if (m_den == 0) {
105 throw std::invalid_argument("FractionValue: denominator must not be zero.");
106 }
107 normalizeSign();
108 }
109
115 constexpr FractionValue(NumType value) : m_num(value), m_den(1) {}
116
118 [[nodiscard]] constexpr NumType numerator() const noexcept { return m_num; }
119
121 [[nodiscard]] constexpr NumType denominator() const noexcept { return m_den; }
122
127 [[nodiscard]] constexpr NumType quotient() const
128 {
129 return m_num / m_den;
130 }
131
136 constexpr FractionValue remainder() const
137 {
138 FractionValue result;
139 result.m_num = m_num % m_den;
140 result.m_den = m_den;
141 return result;
142 }
143
145 static constexpr FractionValue max() noexcept
146 {
147 return FractionValue((std::numeric_limits<NumType>::max)());
148 }
149
151 [[nodiscard]] constexpr double toDouble() const {
152 return double(m_num) / double(m_den);
153 }
154
164 {
165 // a/b + c/d = (ad + bc)/bd
166 m_num = m_num * rhs.m_den + rhs.m_num * m_den;
167 m_den = m_den * rhs.m_den;
168 reduce();
169 return *this;
170 }
171
181 {
182 // a/b - c/d = (ad - bc)/bd
183 m_num = m_num * rhs.m_den - rhs.m_num * m_den;
184 m_den = m_den * rhs.m_den;
185 reduce();
186 return *this;
187 }
188
198 {
199 m_num = m_num * rhs.m_num;
200 m_den = m_den * rhs.m_den;
201 reduce();
202 return *this;
203 }
204
216 {
217 if (rhs.m_num == 0) {
218 throw std::invalid_argument("Division by zero FractionValue.");
219 }
220 m_num = m_num * rhs.m_den;
221 m_den = m_den * rhs.m_num;
222 reduce();
223 return *this;
224 }
225
229 constexpr void reduce()
230 {
231 const NumType g = std::gcd(m_num, m_den);
232 if (g > 1) {
233 m_num /= g;
234 m_den /= g;
235 }
236 normalizeSign();
237 }
238
242 constexpr FractionValue reduced() const
243 {
244 auto result = *this;
245 result.reduce();
246 return result;
247 }
248
279 constexpr bool expressWithDenominator(NumType targetDenominator)
280 {
281 if (targetDenominator == 0) {
282 // Cannot express anything with denominator 0.
283 return false;
284 }
285
286 // Zero fraction: 0/d is expressible as 0/targetDenominator.
287 if (m_num == 0) {
288 m_den = targetDenominator;
289 return true;
290 }
291
292 // Reduce the fraction logically: m_num/m_den -> (numRed/denRed).
293 // We don't actually have to store the reduced form; just use it to
294 // check compatibility and compute the new numerator.
295 const NumType g = std::gcd(m_num, m_den);
296 const NumType denRed = m_den / g;
297 const NumType numRed = m_num / g; // sign preserved
298
299 // For an integer representation with the requested denominator to exist
300 // (while preserving the value), the reduced denominator must divide
301 // targetDenominator exactly.
302 if (targetDenominator % denRed != 0) {
303 return false; // No integer scaling factor exists.
304 }
305
306 const NumType factor = targetDenominator / denRed;
307
308 // Apply the scaling to the reduced numerator and store the new form.
309 m_num = numRed * factor;
310 m_den = targetDenominator;
311
312 normalizeSign();
313
314 return true;
315 }
316};
317#ifndef DOXYGEN_SHOULD_IGNORE_THIS
318
319// ------------------------------------------------------------
320// Non-member arithmetic operators
321// ------------------------------------------------------------
322
323[[nodiscard]] constexpr FractionValue operator+(FractionValue lhs, const FractionValue& rhs)
324{
325 lhs += rhs;
326 return lhs;
327}
328
329[[nodiscard]] constexpr FractionValue operator-(FractionValue lhs, const FractionValue& rhs)
330{
331 lhs -= rhs;
332 return lhs;
333}
334
335[[nodiscard]] constexpr FractionValue operator*(FractionValue lhs, const FractionValue& rhs)
336{
337 lhs *= rhs;
338 return lhs;
339}
340
341[[nodiscard]] constexpr FractionValue operator/(FractionValue lhs, const FractionValue& rhs)
342{
343 lhs /= rhs;
344 return lhs;
345}
346
347// ------------------------------------------------------------
348// Comparison operators
349// ------------------------------------------------------------
350
351[[nodiscard]] constexpr bool operator==(const FractionValue& a, const FractionValue& b)
352{
353 return a.numerator() * b.denominator() == b.numerator() * a.denominator();
354}
355
356[[nodiscard]] constexpr bool operator!=(const FractionValue& a, const FractionValue& b)
357{
358 return !(a == b);
359}
360
361[[nodiscard]] constexpr bool operator<(const FractionValue& a, const FractionValue& b)
362{
363 return a.numerator() * b.denominator() < b.numerator() * a.denominator();
364}
365
366[[nodiscard]] constexpr bool operator<=(const FractionValue& a, const FractionValue& b)
367{
368 return !(b < a);
369}
370
371[[nodiscard]] constexpr bool operator>(const FractionValue& a, const FractionValue& b)
372{
373 return b < a;
374}
375
376[[nodiscard]] constexpr bool operator>=(const FractionValue& a, const FractionValue& b)
377{
378 return !(a < b);
379}
380
381#endif // DOXYGEN_SHOULD_IGNORE_THIS
382
385class Fraction : private Array<unsigned>
386{
387private:
388 using NumType = FractionValue::NumType;
390
391 static constexpr size_t NUMERATOR_INDEX = 0;
392 static constexpr size_t DENOMINATOR_INDEX = 1;
393
394public:
396 Fraction(const std::shared_ptr<json>& root, json_pointer pointer)
398 {
399 if (size() != 2) {
400 throw std::invalid_argument("mnx::Fraction must have exactly 2 elements.");
401 }
402 }
403
408 Fraction(Base& parent, std::string_view key, const FractionValue& value)
409 : ArrayType(parent, key)
410 {
411 push_back(value.numerator());
412 push_back(value.denominator());
413 }
414
416 [[nodiscard]] operator FractionValue() const
417 {
418 return FractionValue(numerator(), denominator());
419 }
420
421 MNX_ARRAY_ELEMENT_PROPERTY(NumType, numerator, NUMERATOR_INDEX);
422 MNX_ARRAY_ELEMENT_PROPERTY(NumType, denominator, DENOMINATOR_INDEX);
423
424 friend class Base;
425};
426
432{
433public:
435 struct Required
436 {
438 };
439
441 RhythmicPosition(const std::shared_ptr<json>& root, json_pointer pointer)
443 {
444 }
445
450 RhythmicPosition(Base& parent, std::string_view key, const FractionValue& position)
451 : Object(parent, key)
452 {
453 create_fraction(position);
454 }
455
457 operator Required() const { return { fraction() }; }
458
460 static Required make(const FractionValue& position) { return { position }; }
461
463 Fraction, fraction,
464 (const FractionValue&, value));
465 MNX_OPTIONAL_PROPERTY(unsigned, graceIndex);
467};
468
474{
475public:
477 struct Required
478 {
479 std::string measureId;
481 };
482
484 MeasureRhythmicPosition(const std::shared_ptr<json>& root, json_pointer pointer)
486 {
487 }
488
494 MeasureRhythmicPosition(Base& parent, std::string_view key, const std::string& measureId, const FractionValue& position)
495 : Object(parent, key)
496 {
497 set_measure(measureId);
498 create_position(position);
499 }
500
502 operator Required() const { return { measure(), position().fraction() }; }
503
505 static Required make(const std::string& measureId, const FractionValue& position) { return { measureId, position }; }
506
507 MNX_REQUIRED_PROPERTY(std::string, measure);
509 RhythmicPosition, position,
510 (const FractionValue&, position));
511};
512
517class Interval : public Object
518{
519public:
521 struct Required
522 {
524 int halfSteps{};
525 };
526
528 Interval(const std::shared_ptr<json>& root, json_pointer pointer)
530 {
531 }
532
538 Interval(Base& parent, std::string_view key, int staffDistance, int halfSteps)
539 : Object(parent, key)
540 {
541 set_halfSteps(halfSteps);
542 set_staffDistance(staffDistance);
543 }
544
546 operator Required() const { return { staffDistance(), halfSteps() }; }
547
549 static Required make(int staffDistance, int halfSteps) { return { staffDistance, halfSteps }; }
550
551 MNX_REQUIRED_PROPERTY(int, halfSteps);
552 MNX_REQUIRED_PROPERTY(int, staffDistance);
553};
554
559class KeySignature : public Object
560{
561public:
563 struct Required
564 {
565 int fifths{};
566 };
567
569 KeySignature(const std::shared_ptr<json>& root, json_pointer pointer)
571 {
572 }
573
575 operator Required() const { return { fifths() }; }
576
578 static Required make(int fifths) { return { fifths }; }
579
584 KeySignature(Base& parent, std::string_view key, int fifths)
585 : Object(parent, key)
586 {
587 set_fifths(fifths);
588 }
589
590 MNX_OPTIONAL_PROPERTY(std::string, color);
592};
593
598class NoteValue : public Object
599{
600public:
602 struct Required
603 {
605 unsigned dots{};
606 };
607
609 NoteValue(const std::shared_ptr<json>& root, json_pointer pointer)
611 {
612 }
613
619 NoteValue(Base& parent, std::string_view key, NoteValueBase base, unsigned dots = 0)
620 : Object(parent, key)
621 {
622 set_base(base);
623 if (dots) {
624 set_dots(dots);
625 }
626 }
627
629 operator Required() const { return { base(), dots() }; }
630
632 static Required make(NoteValueBase base, unsigned dots = 0) { return { base, dots }; }
633
635 [[nodiscard]] operator FractionValue() const;
636
639
641 [[nodiscard]] unsigned calcNumberOfFlags() const;
642};
643
649{
650public:
652 struct Required
653 {
654 unsigned count{};
656 };
657
659 NoteValueQuantity(const std::shared_ptr<json>& root, json_pointer pointer)
661 {
662 }
663
669 NoteValueQuantity(Base& parent, std::string_view key, unsigned count, const NoteValue::Required& noteValue)
670 : Object(parent, key)
671 {
672 set_multiple(count);
673 create_duration(noteValue.base, noteValue.dots);
674 }
675
677 operator Required() const { return { multiple(), duration() }; }
678
680 static Required make(unsigned count, const NoteValue::Required& noteValue) { return { count, noteValue }; }
681
683 [[nodiscard]] operator FractionValue() const
684 { return multiple() * duration(); }
685
687 (NoteValueBase, base), (unsigned, dots));
688 MNX_REQUIRED_PROPERTY(unsigned, multiple);
689};
690
695class TimeSignature : public Object
696{
697public:
699 struct Required
700 {
701 int count{};
703 };
704
706 TimeSignature(const std::shared_ptr<json>& root, json_pointer pointer)
708 {
709 }
710
716 TimeSignature(Base& parent, std::string_view key, int count, TimeSignatureUnit unit)
717 : Object(parent, key)
718 {
719 set_count(count);
720 set_unit(unit);
721 }
722
724 operator Required() const { return { count(), unit() }; }
725
727 static Required make(int count, TimeSignatureUnit unit) { return { count, unit }; }
728
731 [[nodiscard]] operator FractionValue() const
732 { return FractionValue(static_cast<FractionValue::NumType>(count()), static_cast<FractionValue::NumType>(unit())); }
733
736};
737
738} // namespace mnx
Represents an MNX array, encapsulating property access.
Definition BaseTypes.h:499
size_t size() const
Get the size of the array.
Definition BaseTypes.h:548
std::enable_if_t<!std::is_base_of_v< Base, U >, void > push_back(const U &value)
Append a new value to the array. (Available only for primitive types)
Definition BaseTypes.h:597
Base class wrapper for all MNX JSON nodes.
Definition BaseTypes.h:198
json_pointer pointer() const
Returns the json_pointer for this node.
Definition BaseTypes.h:260
T parent() const
Returns the parent object for this node.
Definition BaseTypes.h:246
const std::shared_ptr< json > & root() const
Returns the root.
Definition BaseTypes.h:280
Represents a fraction of a whole note, for measuring musical time.
Definition CommonClasses.h:386
MNX_ARRAY_ELEMENT_PROPERTY(NumType, denominator, DENOMINATOR_INDEX)
the denominator of the fraction
Fraction(Base &parent, std::string_view key, const FractionValue &value)
Creates a new Array class as a child of a JSON element.
Definition CommonClasses.h:408
MNX_ARRAY_ELEMENT_PROPERTY(NumType, numerator, NUMERATOR_INDEX)
the numerator of the fraction
Fraction(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor to wrap a Fraction instance around existing JSON.
Definition CommonClasses.h:396
Represents a musical chromatic interval.
Definition CommonClasses.h:518
static Required make(int staffDistance, int halfSteps)
Create a Required instance for Interval.
Definition CommonClasses.h:549
MNX_REQUIRED_PROPERTY(int, halfSteps)
the number of 12-EDO chromatic halfsteps in the interval (negative is down)
Interval(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing NoteValue instances.
Definition CommonClasses.h:528
MNX_REQUIRED_PROPERTY(int, staffDistance)
the number of diatonic steps in the interval (negative is down)
Interval(Base &parent, std::string_view key, int staffDistance, int halfSteps)
Creates a new Barline class as a child of a JSON element.
Definition CommonClasses.h:538
Represents a key signature.
Definition CommonClasses.h:560
MNX_OPTIONAL_PROPERTY(std::string, color)
color to use when rendering the key signature
KeySignature(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing KeySignature objects.
Definition CommonClasses.h:569
MNX_REQUIRED_PROPERTY(int, fifths)
offset from signature with no accidentals
KeySignature(Base &parent, std::string_view key, int fifths)
Creates a new KeySignature class as a child of a JSON element.
Definition CommonClasses.h:584
static Required make(int fifths)
Create a Required instance for KeySignature.
Definition CommonClasses.h:578
Represents a system on a page in a score.
Definition CommonClasses.h:474
static Required make(const std::string &measureId, const FractionValue &position)
Create a Required instance for MeasureRhythmicPosition.
Definition CommonClasses.h:505
MeasureRhythmicPosition(Base &parent, std::string_view key, const std::string &measureId, const FractionValue &position)
Creates a new MeasureRhythmicPosition class as a child of a JSON element.
Definition CommonClasses.h:494
MeasureRhythmicPosition(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing rhythmic position instances.
Definition CommonClasses.h:484
MNX_REQUIRED_PROPERTY(std::string, measure)
The id of the global measure of this MeasureRhythmicPosition.
MNX_REQUIRED_CHILD(RhythmicPosition, position,(const FractionValue &, position))
The metric position, where 1/4 is a quarter note.
Represents a quantity of symbolic note values=.
Definition CommonClasses.h:649
static Required make(unsigned count, const NoteValue::Required &noteValue)
Create a Required instance for NoteValueQuantity.
Definition CommonClasses.h:680
MNX_REQUIRED_PROPERTY(unsigned, multiple)
quantity of duration units
MNX_REQUIRED_CHILD(NoteValue, duration,(NoteValueBase, base),(unsigned, dots))
duration unit
NoteValueQuantity(Base &parent, std::string_view key, unsigned count, const NoteValue::Required &noteValue)
Creates a new Barline class as a child of a JSON element.
Definition CommonClasses.h:669
NoteValueQuantity(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing NoteValue instances.
Definition CommonClasses.h:659
Represents a symbolic note value (not necessarily a duration)
Definition CommonClasses.h:599
NoteValue(Base &parent, std::string_view key, NoteValueBase base, unsigned dots=0)
Creates a new Barline class as a child of a JSON element.
Definition CommonClasses.h:619
MNX_OPTIONAL_PROPERTY_WITH_DEFAULT(unsigned, dots, 0)
the number of dots
unsigned calcNumberOfFlags() const
Calculates the number of flags or beams required by this note value.
Definition Implementations.cpp:475
NoteValue(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing NoteValue instances.
Definition CommonClasses.h:609
MNX_REQUIRED_PROPERTY(NoteValueBase, base)
the type ("base") of note
static Required make(NoteValueBase base, unsigned dots=0)
Create a Required instance for NoteValue.
Definition CommonClasses.h:632
Represents an MNX object, encapsulating property access.
Definition BaseTypes.h:412
Represents a system on a page in a score.
Definition CommonClasses.h:432
RhythmicPosition(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing rhythmic position instances.
Definition CommonClasses.h:441
MNX_OPTIONAL_PROPERTY(unsigned, graceIndex)
MNX_REQUIRED_CHILD(Fraction, fraction,(const FractionValue &, value))
The metric position, where 1/4 is a quarter note.
RhythmicPosition(Base &parent, std::string_view key, const FractionValue &position)
Creates a new RhythmicPosition class as a child of a JSON element.
Definition CommonClasses.h:450
static Required make(const FractionValue &position)
Create a Required instance for RhythmicPosition.
Definition CommonClasses.h:460
Represents the tempo for a global measure.
Definition CommonClasses.h:696
MNX_REQUIRED_PROPERTY(TimeSignatureUnit, unit)
the unit value (bottom number)
TimeSignature(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing NoteValue instances.
Definition CommonClasses.h:706
static Required make(int count, TimeSignatureUnit unit)
Create a Required instance for TimeSignature.
Definition CommonClasses.h:727
TimeSignature(Base &parent, std::string_view key, int count, TimeSignatureUnit unit)
Creates a new Barline class as a child of a JSON element.
Definition CommonClasses.h:716
MNX_REQUIRED_PROPERTY(int, count)
the number of beats (top number)
object model for MNX format
json::json_pointer json_pointer
JSON pointer class for MNX.
Definition BaseTypes.h:68
TimeSignatureUnit
Valid units for the lower numbers of time signatures.
Definition Enumerations.h:317
NoteValueBase
The note values allowed in MNX.
Definition Enumerations.h:198
Represents a detached arithmetic fraction with normalization.
Definition CommonClasses.h:59
constexpr NumType denominator() const noexcept
Returns the denominator.
Definition CommonClasses.h:121
constexpr bool expressWithDenominator(NumType targetDenominator)
Attempts to express this fraction with the given denominator.
Definition CommonClasses.h:279
constexpr FractionValue()=default
Default constructor initializes the value to 0/1.
constexpr void reduce()
Reduces the fraction to lowest terms using std::gcd.
Definition CommonClasses.h:229
constexpr FractionValue(NumType num, NumType den)
Constructs a fraction from a numerator and denominator.
Definition CommonClasses.h:101
constexpr NumType quotient() const
Returns the integer (whole number) part of the fraction.
Definition CommonClasses.h:127
constexpr FractionValue(NumType value)
Constructs a Fraction object from an integer.
Definition CommonClasses.h:115
static constexpr FractionValue max() noexcept
Constructs the max fractional value.
Definition CommonClasses.h:145
constexpr FractionValue remainder() const
Returns the fractional part of the fraction.
Definition CommonClasses.h:136
constexpr FractionValue & operator+=(const FractionValue &rhs)
Adds another FractionValue to this one.
Definition CommonClasses.h:163
constexpr FractionValue & operator-=(const FractionValue &rhs)
Subtracts another FractionValue from this one.
Definition CommonClasses.h:180
unsigned NumType
Unsigned integer type used for numerator and denominator.
Definition CommonClasses.h:61
constexpr double toDouble() const
Converts the fraction to floating point double.
Definition CommonClasses.h:151
constexpr FractionValue reduced() const
Returns a copy of the fraction reduced to lowest terms using std::gcd.
Definition CommonClasses.h:242
constexpr FractionValue & operator*=(const FractionValue &rhs)
Multiplies this fraction by another.
Definition CommonClasses.h:197
constexpr NumType numerator() const noexcept
Returns the numerator.
Definition CommonClasses.h:118
constexpr FractionValue & operator/=(const FractionValue &rhs)
Divides this fraction by another.
Definition CommonClasses.h:215
initializer class for Interval
Definition CommonClasses.h:522
int halfSteps
the number of 12-EDO chromatic halfsteps in the interval (negative is down)
Definition CommonClasses.h:524
int staffDistance
the number of diatonic steps in the interval (negative is down)
Definition CommonClasses.h:523
initializer class for KeySignature
Definition CommonClasses.h:564
int fifths
offset from signature with no accidentals
Definition CommonClasses.h:565
initializer class for MeasureRhythmicPosition
Definition CommonClasses.h:478
std::string measureId
the global measure id of the position
Definition CommonClasses.h:479
FractionValue position
the position within the measure
Definition CommonClasses.h:480
initializer class for NoteValueQuantity
Definition CommonClasses.h:653
unsigned count
The quantity of note values.
Definition CommonClasses.h:654
NoteValue::Required noteValue
the note value base to initialize
Definition CommonClasses.h:655
initializer class for NoteValue
Definition CommonClasses.h:603
NoteValueBase base
the note value base to initialize
Definition CommonClasses.h:604
unsigned dots
the number of dots to initialize
Definition CommonClasses.h:605
initializer class for RhythmicPosition
Definition CommonClasses.h:436
FractionValue position
position within a measure
Definition CommonClasses.h:437
initializer class for TimeSignature
Definition CommonClasses.h:700
int count
the number of beats (top number)
Definition CommonClasses.h:701
TimeSignatureUnit unit
the unit value (bottom number)
Definition CommonClasses.h:702