MCC
Loading...
Searching...
No Matches
Interval.h
1#ifndef MCC_INTERVAL_INTERVAL_H
2#define MCC_INTERVAL_INTERVAL_H
3
4#include <MCC_BuildSettings.h>
5
6#include <stdint.h>
7
8#include <MCC/Interval/IntervalDirection.h>
9#include <MCC/Interval/IntervalNumber.h>
10#include <MCC/Interval/IntervalQuality.h>
11
12namespace MCC {
13
30class Interval {
31public:
33 static constexpr int16_t MaximumSteps = IntervalNumber::Maximum - 1;
34
36 static constexpr int16_t MaximumSemitones = 3079;
37
42 static constexpr int16_t InvalidSteps = 32767;
43
44private:
45 int16_t _steps;
46 int16_t _semitones;
47
48 // Semitones of the perfect or major simple interval for 0-6 steps
49 // (0 2 4 5 7 9 11). Computed rather than tabled so AVR keeps no lookup
50 // table in RAM.
51 static constexpr int32_t ReferenceSemitones(int32_t simpleSteps) noexcept {
52 return simpleSteps * 2 - (simpleSteps >= 3 ? 1 : 0);
53 }
54
55 static constexpr bool IsPerfectSteps(int32_t simpleSteps) noexcept {
56 return simpleSteps == 0 || simpleSteps == 3 || simpleSteps == 4;
57 }
58
59 static constexpr int32_t Magnitude(int32_t value) noexcept {
60 return value < 0 ? -value : value;
61 }
62
63 // Deviation of the semitones from the perfect/major reference of the
64 // same number, measured in the interval's direction.
65 MCC_CONSTEXPR14 int32_t Deviation() const noexcept {
66 const int32_t sign = Sign();
67 const int32_t steps = sign * _steps;
68 const int32_t semitones = sign * _semitones;
69 return semitones - (steps / 7) * 12 - ReferenceSemitones(steps % 7);
70 }
71
72 constexpr int32_t Sign() const noexcept {
73 return (_steps != 0)
74 ? (_steps > 0 ? 1 : -1)
75 : (_semitones < 0 ? -1 : 1);
76 }
77
78 static MCC_CONSTEXPR14 IntervalQuality QualityFor(int32_t steps,
79 int32_t deviation) noexcept {
80 if (IsPerfectSteps(steps % 7)) {
81 if (deviation == 0) {
83 }
84 return deviation > 0
86 : IntervalQuality::Diminished(-deviation);
87 }
88 if (deviation == 0) {
90 }
91 if (deviation == -1) {
93 }
94 return deviation > 0
96 : IntervalQuality::Diminished(-deviation - 1);
97 }
98
99public:
101 constexpr Interval() noexcept : _steps(InvalidSteps), _semitones(0) {}
102
111 static MCC_CONSTEXPR14 Interval FromSteps(int32_t diatonicSteps,
112 int32_t semitones) noexcept {
113 // Compare against both bounds: negating INT32_MIN would overflow.
114 if (diatonicSteps < -MaximumSteps || diatonicSteps > MaximumSteps ||
115 semitones < -MaximumSemitones || semitones > MaximumSemitones) {
116 return Interval();
117 }
118 Interval candidate;
119 candidate._steps = static_cast<int16_t>(diatonicSteps);
120 candidate._semitones = static_cast<int16_t>(semitones);
121 return candidate.Quality().IsValid() ? candidate : Interval();
122 }
123
134 MCC_CONSTEXPR14 Interval(IntervalQuality quality, IntervalNumber number,
135 IntervalDirection direction = IntervalDirection::Ascending) noexcept
136 : Interval(Build(quality, number, direction)) {}
137
139 static constexpr Interval Invalid() noexcept { return Interval(); }
140
142 static MCC_CONSTEXPR14 Interval Unison() noexcept { return FromSteps(0, 0); }
143
145 constexpr bool IsValid() const noexcept { return _steps != InvalidSteps; }
146
151 constexpr int16_t DiatonicSteps() const noexcept { return _steps; }
152
157 constexpr int16_t Semitones() const noexcept { return _semitones; }
158
163 MCC_CONSTEXPR14 IntervalDirection Direction() const noexcept {
164 if (!IsValid() || (_steps == 0 && _semitones == 0)) {
165 return IntervalDirection::Unison;
166 }
167 return Sign() > 0 ? IntervalDirection::Ascending
168 : IntervalDirection::Descending;
169 }
170
172 MCC_CONSTEXPR14 IntervalNumber Number() const noexcept {
173 if (!IsValid()) {
175 }
176 return IntervalNumber(Magnitude(_steps) + 1);
177 }
178
180 MCC_CONSTEXPR14 IntervalQuality Quality() const noexcept {
181 if (!IsValid()) {
183 }
184 return QualityFor(Magnitude(_steps), Deviation());
185 }
186
188 MCC_CONSTEXPR14 bool IsSimple() const noexcept { return Number().IsSimple(); }
189
191 MCC_CONSTEXPR14 bool IsCompound() const noexcept { return Number().IsCompound(); }
192
198 MCC_CONSTEXPR14 Interval Simple() const noexcept {
199 if (!IsValid() || Magnitude(_steps) <= 7) {
200 return *this;
201 }
202 const int32_t sign = Sign();
203 const int32_t octaves = (Magnitude(_steps) - 1) / 7;
204 return FromSteps(_steps - sign * octaves * 7,
205 _semitones - sign * octaves * 12);
206 }
207
217 MCC_CONSTEXPR14 Interval Inverted() const noexcept {
218 if (!IsValid()) {
219 return Invalid();
220 }
221 const Interval simple = Simple();
222 const int32_t sign = simple.Sign();
223 return FromSteps(sign * 7 - simple._steps, sign * 12 - simple._semitones);
224 }
225
227 MCC_CONSTEXPR14 Interval Reversed() const noexcept {
228 if (!IsValid()) {
229 return Invalid();
230 }
231 return FromSteps(-static_cast<int32_t>(_steps),
232 -static_cast<int32_t>(_semitones));
233 }
234
240 friend MCC_CONSTEXPR14 Interval operator+(Interval a, Interval b) noexcept {
241 if (!a.IsValid() || !b.IsValid()) {
242 return Invalid();
243 }
244 return FromSteps(static_cast<int32_t>(a._steps) + b._steps,
245 static_cast<int32_t>(a._semitones) + b._semitones);
246 }
247
253 friend constexpr bool operator==(Interval a, Interval b) noexcept {
254 return a._steps == b._steps && a._semitones == b._semitones;
255 }
256
257 friend constexpr bool operator!=(Interval a, Interval b) noexcept {
258 return !(a == b);
259 }
260
261private:
262 static MCC_CONSTEXPR14 Interval Build(IntervalQuality quality, IntervalNumber number,
263 IntervalDirection direction) noexcept {
264 if (!quality.IsValid() || !number.IsValid()) {
265 return Interval();
266 }
267 const int32_t steps = number.Value() - 1;
268 const int32_t simpleSteps = steps % 7;
269 const IntervalQualityKind kind = quality.Kind();
270 const int32_t count = quality.Count();
271
272 int32_t deviation = 0;
273 if (IsPerfectSteps(simpleSteps)) {
274 if (kind == IntervalQualityKind::Major ||
275 kind == IntervalQualityKind::Minor) {
276 return Interval();
277 }
278 deviation = (kind == IntervalQualityKind::Augmented) ? count
279 : (kind == IntervalQualityKind::Diminished) ? -count
280 : 0;
281 } else {
282 if (kind == IntervalQualityKind::Perfect) {
283 return Interval();
284 }
285 deviation = (kind == IntervalQualityKind::Augmented) ? count
286 : (kind == IntervalQualityKind::Minor) ? -1
287 : (kind == IntervalQualityKind::Diminished) ? -count - 1
288 : 0;
289 }
290
291 if (steps == 0) {
292 if (deviation < 0) {
293 return Interval(); // No diminished unison (SPEC-INT-5).
294 }
295 if (deviation == 0) {
296 return Unison();
297 }
298 if (direction == IntervalDirection::Unison) {
299 return Interval();
300 }
301 } else if (direction == IntervalDirection::Unison) {
302 return Interval();
303 }
304
305 const int32_t sign = (direction == IntervalDirection::Descending) ? -1 : 1;
306 const int32_t semitones =
307 (steps / 7) * 12 + ReferenceSemitones(simpleSteps) + deviation;
308 return FromSteps(sign * steps, sign * semitones);
309 }
310};
311
318constexpr bool IsEnharmonic(Interval a, Interval b) noexcept {
319 return a.IsValid() && b.IsValid() && a.Semitones() == b.Semitones();
320}
321
322} // namespace MCC
323
324#endif // MCC_INTERVAL_INTERVAL_H
1-based interval number: 1 unison, 3 third, 8 octave, 10 tenth (SPEC-INT-2, SPEC-INT-3).
Definition IntervalNumber.h:19
constexpr bool IsSimple() const noexcept
Returns true for numbers 1-8 (SPEC-INT-3).
Definition IntervalNumber.h:56
static constexpr uint16_t Maximum
Widest number: Cbbbb-128 to B####127 spans 1791 steps.
Definition IntervalNumber.h:22
constexpr bool IsCompound() const noexcept
Returns true for numbers above 8 (SPEC-INT-3).
Definition IntervalNumber.h:59
static constexpr IntervalNumber Invalid() noexcept
Returns the invalid number.
Definition IntervalNumber.h:47
Interval quality: perfect, major, minor, or augmented/diminished repeated one to four times (SPEC-INT...
Definition IntervalQuality.h:30
static constexpr IntervalQuality Augmented(int32_t count=1) noexcept
Returns the quality augmented count times; a count outside [1, 4] is invalid (SPEC-INT-4).
Definition IntervalQuality.h:73
static constexpr IntervalQuality Minor() noexcept
Returns the minor quality.
Definition IntervalQuality.h:65
constexpr bool IsValid() const noexcept
Returns true unless this is the invalid quality.
Definition IntervalQuality.h:86
static constexpr IntervalQuality Invalid() noexcept
Returns the invalid quality.
Definition IntervalQuality.h:52
static constexpr IntervalQuality Perfect() noexcept
Returns the perfect quality.
Definition IntervalQuality.h:55
static constexpr IntervalQuality Major() noexcept
Returns the major quality.
Definition IntervalQuality.h:60
Directed interval: a diatonic step count and a chromatic semitone count (SPEC-INT-1).
Definition Interval.h:30
MCC_CONSTEXPR14 Interval Reversed() const noexcept
Returns the same interval in the opposite direction.
Definition Interval.h:227
MCC_CONSTEXPR14 IntervalQuality Quality() const noexcept
Returns the quality (SPEC-INT-4); invalid if invalid.
Definition Interval.h:180
constexpr Interval() noexcept
Creates the invalid interval (SPEC-ERR-2).
Definition Interval.h:101
constexpr int16_t Semitones() const noexcept
Returns the signed semitone count, or 0 for the invalid interval (SPEC-ERR-7).
Definition Interval.h:157
MCC_CONSTEXPR14 bool IsCompound() const noexcept
Returns true for numbers above 8 (SPEC-INT-3).
Definition Interval.h:191
MCC_CONSTEXPR14 bool IsSimple() const noexcept
Returns true for numbers 1-8 (SPEC-INT-3).
Definition Interval.h:188
static constexpr int16_t InvalidSteps
Sentinel returned by DiatonicSteps() for the invalid interval (SPEC-ERR-7). Do not rely on it; check ...
Definition Interval.h:42
constexpr int16_t DiatonicSteps() const noexcept
Returns the signed diatonic step count, or InvalidSteps for the invalid interval (SPEC-ERR-7).
Definition Interval.h:151
MCC_CONSTEXPR14 IntervalDirection Direction() const noexcept
Returns the direction (SPEC-INT-2); Unison for the perfect unison and for the invalid interval (SPEC-...
Definition Interval.h:163
static MCC_CONSTEXPR14 Interval FromSteps(int32_t diatonicSteps, int32_t semitones) noexcept
Creates the interval spanning diatonicSteps letters and semitones semitones, both signed (SPEC-INT-1)...
Definition Interval.h:111
static constexpr int16_t MaximumSteps
Largest step magnitude: IntervalNumber::Maximum - 1.
Definition Interval.h:33
MCC_CONSTEXPR14 IntervalNumber Number() const noexcept
Returns the 1-based number (SPEC-INT-2); invalid if invalid.
Definition Interval.h:172
constexpr bool IsValid() const noexcept
Returns true unless this is the invalid interval.
Definition Interval.h:145
MCC_CONSTEXPR14 Interval(IntervalQuality quality, IntervalNumber number, IntervalDirection direction=IntervalDirection::Ascending) noexcept
Creates the interval of quality and number in direction, for example Interval(IntervalQuality::Major(...
Definition Interval.h:134
static MCC_CONSTEXPR14 Interval Unison() noexcept
Returns the perfect unison.
Definition Interval.h:142
MCC_CONSTEXPR14 Interval Simple() const noexcept
Removes whole octaves while keeping direction and quality (SPEC-INT-3): a major tenth becomes a major...
Definition Interval.h:198
static constexpr Interval Invalid() noexcept
Returns the invalid interval.
Definition Interval.h:139
friend constexpr bool operator==(Interval a, Interval b) noexcept
Written equality: same steps and semitones. An augmented fourth and a diminished fifth are different ...
Definition Interval.h:253
MCC_CONSTEXPR14 Interval Inverted() const noexcept
Returns the inversion of the simple interval, in the same direction (SPEC-INT-6): numbers add up to 9...
Definition Interval.h:217
static constexpr int16_t MaximumSemitones
Largest semitone magnitude between two writable pitches.
Definition Interval.h:36
friend MCC_CONSTEXPR14 Interval operator+(Interval a, Interval b) noexcept
Adds two intervals by adding their steps and semitones: major third + minor third = perfect fifth....
Definition Interval.h:240
IntervalDirection
Direction of an interval (SPEC-INT-2).
Definition IntervalDirection.h:17
IntervalQualityKind
Kind of an interval quality.
Definition IntervalQuality.h:14
constexpr bool IsEnharmonic(Pitch a, Pitch b) noexcept
Returns true when a and b are valid and have the same chromatic index (SPEC-EQ-2): B#3 and C4 are enh...
Definition Pitch.h:240
Music Composition Core public namespace.
Definition Version.h:4