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
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 constexpr NumType numerator() const noexcept { return m_num; }
119
121 constexpr NumType denominator() const noexcept { return m_den; }
122
127 constexpr NumType quotient() const
128 {
129 return m_num / m_den;
130 }
131
136 FractionValue constexpr 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
159 {
160 // a/b + c/d = (ad + bc)/bd
161 m_num = m_num * rhs.m_den + rhs.m_num * m_den;
162 m_den = m_den * rhs.m_den;
163 reduce();
164 return *this;
165 }
166
176 {
177 // a/b - c/d = (ad - bc)/bd
178 m_num = m_num * rhs.m_den - rhs.m_num * m_den;
179 m_den = m_den * rhs.m_den;
180 reduce();
181 return *this;
182 }
183
193 {
194 m_num = m_num * rhs.m_num;
195 m_den = m_den * rhs.m_den;
196 reduce();
197 return *this;
198 }
199
211 {
212 if (rhs.m_num == 0) {
213 throw std::invalid_argument("Division by zero FractionValue.");
214 }
215 m_num = m_num * rhs.m_den;
216 m_den = m_den * rhs.m_num;
217 reduce();
218 return *this;
219 }
220
224 constexpr void reduce()
225 {
226 const NumType g = std::gcd(m_num, m_den);
227 if (g > 1) {
228 m_num /= g;
229 m_den /= g;
230 }
231 normalizeSign();
232 }
233
264 constexpr bool expressWithDenominator(NumType targetDenominator)
265 {
266 if (targetDenominator == 0) {
267 // Cannot express anything with denominator 0.
268 return false;
269 }
270
271 // Zero fraction: 0/d is expressible as 0/targetDenominator.
272 if (m_num == 0) {
273 m_den = targetDenominator;
274 return true;
275 }
276
277 // Reduce the fraction logically: m_num/m_den -> (numRed/denRed).
278 // We don't actually have to store the reduced form; just use it to
279 // check compatibility and compute the new numerator.
280 const NumType g = std::gcd(m_num, m_den);
281 const NumType denRed = m_den / g;
282 const NumType numRed = m_num / g; // sign preserved
283
284 // For an integer representation with the requested denominator to exist
285 // (while preserving the value), the reduced denominator must divide
286 // targetDenominator exactly.
287 if (targetDenominator % denRed != 0) {
288 return false; // No integer scaling factor exists.
289 }
290
291 const NumType factor = targetDenominator / denRed;
292
293 // Apply the scaling to the reduced numerator and store the new form.
294 m_num = numRed * factor;
295 m_den = targetDenominator;
296
297 normalizeSign();
298
299 return true;
300 }
301};
302#ifndef DOXYGEN_SHOULD_IGNORE_THIS
303
304// ------------------------------------------------------------
305// Non-member arithmetic operators
306// ------------------------------------------------------------
307
308[[nodiscard]] constexpr FractionValue operator+(FractionValue lhs, const FractionValue& rhs)
309{
310 lhs += rhs;
311 return lhs;
312}
313
314[[nodiscard]] constexpr FractionValue operator-(FractionValue lhs, const FractionValue& rhs)
315{
316 lhs -= rhs;
317 return lhs;
318}
319
320[[nodiscard]] constexpr FractionValue operator*(FractionValue lhs, const FractionValue& rhs)
321{
322 lhs *= rhs;
323 return lhs;
324}
325
326[[nodiscard]] inline FractionValue operator/(FractionValue lhs, const FractionValue& rhs)
327{
328 lhs /= rhs;
329 return lhs;
330}
331
332// ------------------------------------------------------------
333// Comparison operators
334// ------------------------------------------------------------
335
336[[nodiscard]] constexpr bool operator==(const FractionValue& a, const FractionValue& b)
337{
338 return a.numerator() * b.denominator() == b.numerator() * a.denominator();
339}
340
341[[nodiscard]] constexpr bool operator!=(const FractionValue& a, const FractionValue& b)
342{
343 return !(a == b);
344}
345
346[[nodiscard]] constexpr bool operator<(const FractionValue& a, const FractionValue& b)
347{
348 return a.numerator() * b.denominator() < b.numerator() * a.denominator();
349}
350
351[[nodiscard]] constexpr bool operator<=(const FractionValue& a, const FractionValue& b)
352{
353 return !(b < a);
354}
355
356[[nodiscard]] constexpr bool operator>(const FractionValue& a, const FractionValue& b)
357{
358 return b < a;
359}
360
361[[nodiscard]] constexpr bool operator>=(const FractionValue& a, const FractionValue& b)
362{
363 return !(a < b);
364}
365
366#endif // DOXYGEN_SHOULD_IGNORE_THIS
367
370class Fraction : private Array<unsigned>
371{
372private:
373 using NumType = FractionValue::NumType;
375
376 static constexpr size_t NUMERATOR_INDEX = 0;
377 static constexpr size_t DENOMINATOR_INDEX = 1;
378
379public:
381 Fraction(const std::shared_ptr<json>& root, json_pointer pointer)
383 {
384 if (size() != 2) {
385 throw std::invalid_argument("mnx::Fraction must have exactly 2 elements.");
386 }
387 }
388
393 Fraction(Base& parent, std::string_view key, const FractionValue& value)
394 : ArrayType(parent, key)
395 {
396 push_back(value.numerator());
397 push_back(value.denominator());
398 }
399
401 [[nodiscard]] operator FractionValue() const
402 {
403 return FractionValue(numerator(), denominator());
404 }
405
406 MNX_ARRAY_ELEMENT_PROPERTY(NumType, numerator, NUMERATOR_INDEX);
407 MNX_ARRAY_ELEMENT_PROPERTY(NumType, denominator, DENOMINATOR_INDEX);
408
409 friend class Base;
410};
411
417{
418public:
420 RhythmicPosition(const std::shared_ptr<json>& root, json_pointer pointer)
422 {
423 }
424
429 RhythmicPosition(Base& parent, std::string_view key, const FractionValue& position)
430 : Object(parent, key)
431 {
432 create_fraction(position);
433 }
434
436 MNX_OPTIONAL_PROPERTY(unsigned, graceIndex);
438};
439
445{
446public:
448 MeasureRhythmicPosition(const std::shared_ptr<json>& root, json_pointer pointer)
450 {
451 }
452
458 MeasureRhythmicPosition(Base& parent, std::string_view key, int measureId, const FractionValue& position)
459 : Object(parent, key)
460 {
461 set_measure(measureId);
462 create_position(position);
463 }
464
465 MNX_REQUIRED_PROPERTY(int, measure);
467};
468
473class Interval : public Object
474{
475public:
477 Interval(const std::shared_ptr<json>& root, json_pointer pointer)
479 {
480 }
481
487 Interval(Base& parent, std::string_view key, int staffDistance, int halfSteps)
488 : Object(parent, key)
489 {
490 set_halfSteps(halfSteps);
491 set_staffDistance(staffDistance);
492 }
493
494 MNX_REQUIRED_PROPERTY(int, halfSteps);
495 MNX_REQUIRED_PROPERTY(int, staffDistance);
496};
497
502class KeySignature : public Object
503{
504public:
506 KeySignature(const std::shared_ptr<json>& root, json_pointer pointer)
508 {
509 }
510
515 KeySignature(Base& parent, std::string_view key, int fifths)
516 : Object(parent, key)
517 {
518 set_fifths(fifths);
519 }
520
521 MNX_OPTIONAL_PROPERTY(std::string, color);
523};
524
529class NoteValue : public Object
530{
531public:
534 {
535 public:
537 unsigned dots;
538
540 Initializer(NoteValueBase inBase, unsigned inDots = 0) : base(inBase), dots(inDots) {}
541 };
542
544 NoteValue(const std::shared_ptr<json>& root, json_pointer pointer)
546 {
547 }
548
553 NoteValue(Base& parent, std::string_view key, const Initializer& noteValue)
554 : Object(parent, key)
555 {
556 set_base(noteValue.base);
557 if (noteValue.dots) {
558 set_dots(noteValue.dots);
559 }
560 }
561
564
566 [[nodiscard]] unsigned calcNumberOfFlags() const;
567
569 [[nodiscard]] operator FractionValue() const;
570};
571
577{
578public:
580 NoteValueQuantity(const std::shared_ptr<json>& root, json_pointer pointer)
582 {
583 }
584
590 NoteValueQuantity(Base& parent, std::string_view key, unsigned count, const NoteValue::Initializer& noteValue)
591 : Object(parent, key)
592 {
593 set_multiple(count);
594 create_duration(noteValue);
595 }
596
598 MNX_REQUIRED_PROPERTY(unsigned, multiple);
599
600
602 [[nodiscard]] operator FractionValue() const
603 { return multiple() * duration(); }
604};
605
610class TimeSignature : public Object
611{
612public:
614 TimeSignature(const std::shared_ptr<json>& root, json_pointer pointer)
616 {
617 }
618
624 TimeSignature(Base& parent, std::string_view key, int count, TimeSignatureUnit unit)
625 : Object(parent, key)
626 {
627 set_count(count);
628 set_unit(unit);
629 }
630
633
636 [[nodiscard]] operator FractionValue() const
637 { return FractionValue(static_cast<FractionValue::NumType>(count()), static_cast<FractionValue::NumType>(unit())); }
638};
639
640} // namespace mnx
Represents an MNX array, encapsulating property access.
Definition BaseTypes.h:493
size_t size() const
Get the size of the array.
Definition BaseTypes.h:536
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:575
Base class wrapper for all MNX JSON nodes.
Definition BaseTypes.h:212
json_pointer pointer() const
Returns the json_pointer for this node.
Definition BaseTypes.h:274
T parent() const
Returns the parent object for this node.
Definition BaseTypes.h:260
const std::shared_ptr< json > & root() const
Returns the root.
Definition BaseTypes.h:294
Represents a fraction of a whole note, for measuring musical time.
Definition CommonClasses.h:371
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:393
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:381
Represents a musical chromatic interval.
Definition CommonClasses.h:474
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:477
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:487
Represents a key signature.
Definition CommonClasses.h:503
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:506
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:515
Represents a system on a page in a score.
Definition CommonClasses.h:445
MeasureRhythmicPosition(Base &parent, std::string_view key, int measureId, const FractionValue &position)
Creates a new MeasureRhythmicPosition class as a child of a JSON element.
Definition CommonClasses.h:458
MNX_REQUIRED_PROPERTY(int, measure)
The measure id of the measure of this MeasureRhythmicPosition.
MeasureRhythmicPosition(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing rhythmic position instances.
Definition CommonClasses.h:448
MNX_REQUIRED_CHILD(RhythmicPosition, position)
The metric position, where 1/4 is a quarter note.
Represents a quantity of symbolic note values=.
Definition CommonClasses.h:577
MNX_REQUIRED_CHILD(NoteValue, duration)
duration unit
MNX_REQUIRED_PROPERTY(unsigned, multiple)
quantity of duration units
NoteValueQuantity(Base &parent, std::string_view key, unsigned count, const NoteValue::Initializer &noteValue)
Creates a new Barline class as a child of a JSON element.
Definition CommonClasses.h:590
NoteValueQuantity(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing NoteValue instances.
Definition CommonClasses.h:580
initializer class for NoteValue
Definition CommonClasses.h:534
Initializer(NoteValueBase inBase, unsigned inDots=0)
constructor
Definition CommonClasses.h:540
unsigned dots
the number of dots to initialize
Definition CommonClasses.h:537
NoteValueBase base
the note value base to initialize
Definition CommonClasses.h:536
Represents a symbolic note value (not necessarily a duration)
Definition CommonClasses.h:530
NoteValue(Base &parent, std::string_view key, const Initializer &noteValue)
Creates a new Barline class as a child of a JSON element.
Definition CommonClasses.h:553
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:198
NoteValue(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing NoteValue instances.
Definition CommonClasses.h:544
MNX_REQUIRED_PROPERTY(NoteValueBase, base)
the type ("base") of note
Represents an MNX object, encapsulating property access.
Definition BaseTypes.h:426
Represents a system on a page in a score.
Definition CommonClasses.h:417
RhythmicPosition(const std::shared_ptr< json > &root, json_pointer pointer)
Constructor for existing rhythmic position instances.
Definition CommonClasses.h:420
MNX_OPTIONAL_PROPERTY(unsigned, graceIndex)
(0 is the primary, and then count to the left.)
MNX_REQUIRED_CHILD(Fraction, fraction)
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:429
Represents the tempo for a global measure.
Definition CommonClasses.h:611
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:614
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:624
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:198
TimeSignatureUnit
Valid units for the lower numbers of time signatures.
Definition Enumerations.h:287
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:264
constexpr FractionValue()=default
Default constructor initializes the value to 0/1.
FractionValue & operator/=(const FractionValue &rhs)
Divides this fraction by another.
Definition CommonClasses.h:210
constexpr void reduce()
Reduces the fraction to lowest terms using std::gcd.
Definition CommonClasses.h:224
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
FractionValue constexpr 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:158
constexpr FractionValue & operator-=(const FractionValue &rhs)
Subtracts another FractionValue from this one.
Definition CommonClasses.h:175
unsigned NumType
Unsigned integer type used for numerator and denominator.
Definition CommonClasses.h:61
FractionValue(NumType num, NumType den)
Constructs a fraction from a numerator and denominator.
Definition CommonClasses.h:101
constexpr FractionValue & operator*=(const FractionValue &rhs)
Multiplies this fraction by another.
Definition CommonClasses.h:192
constexpr NumType numerator() const noexcept
Returns the numerator.
Definition CommonClasses.h:118