MCC
Loading...
Searching...
No Matches
Key.h
1#ifndef MCC_KEY_KEY_H
2#define MCC_KEY_KEY_H
3
4#include <MCC_BuildSettings.h>
5
6#include <stdint.h>
7
8#include <Foundation/Math/Arithmetic.h>
9
10#include <MCC/Key/KeySignature.h>
11#include <MCC/Pitch/ChromaticIndex.h>
12#include <MCC/Pitch/NoteName.h>
13#include <MCC/Pitch/Pitch.h>
14#include <MCC/Pitch/PitchClass.h>
15#include <MCC/Scale/Scales.h>
16
17namespace MCC {
18
23enum class KeyMode : uint8_t {
24 Lydian = 0,
25 Major,
26 Mixolydian,
27 Dorian,
28 Minor,
29 Phrygian,
30 Locrian
31};
32
46class Key {
47 NoteName _tonic;
48 KeyMode _mode;
49
50 // Circle-of-fifths position of a letter relative to C: F -1 ... B 5.
51 static constexpr int32_t LetterFifths(Letter letter) noexcept {
52 return (DiatonicIndex(letter) * 2 + 1) % 7 - 1;
53 }
54
55 // Signature offset of a mode relative to major on the same tonic.
56 static constexpr int32_t ModeOffset(KeyMode mode) noexcept {
57 return 1 - static_cast<int32_t>(mode);
58 }
59
60 static constexpr int32_t SignatureFifths(NoteName tonic, KeyMode mode) noexcept {
61 return LetterFifths(tonic.Letter()) + 7 * tonic.Accidental().Semitones() + ModeOffset(mode);
62 }
63
64 static constexpr bool IsMode(KeyMode mode) noexcept {
65 return static_cast<uint8_t>(mode) <= static_cast<uint8_t>(KeyMode::Locrian);
66 }
67
68public:
70 constexpr Key() noexcept : _tonic(), _mode(KeyMode::Major) {}
71
73 constexpr Key(NoteName tonic, KeyMode mode) noexcept
74 : _tonic(tonic.IsValid() && IsMode(mode) &&
75 KeySignature(SignatureFifths(tonic, mode)).IsValid()
76 ? tonic : NoteName::Invalid()),
77 _mode(IsMode(mode) ? mode : KeyMode::Major) {}
78
80 static MCC_CONSTEXPR14 Key FromSignature(KeySignature signature, KeyMode mode) noexcept {
81 if (!signature.IsValid() || !IsMode(mode)) {
82 return Key();
83 }
84 // Tonic position on the circle of fifths, then its letter and accidental.
85 const int32_t fifths = signature.Fifths() - ModeOffset(mode);
86 const int32_t order = Foundation::Math::FloorMod(fifths + 1, 7);
87 const int32_t accidental = Foundation::Math::FloorDiv(fifths + 1, 7);
88 const Letter letter = static_cast<Letter>(((order + 6) * 4) % 7);
89 return Key(NoteName(letter, Accidental(accidental)), mode);
90 }
91
93 static constexpr Key Invalid() noexcept { return Key(); }
94
96 constexpr bool IsValid() const noexcept { return _tonic.IsValid(); }
97
99 constexpr NoteName Tonic() const noexcept { return _tonic; }
100
102 constexpr KeyMode Mode() const noexcept { return _mode; }
103
105 constexpr KeySignature Signature() const noexcept {
106 return IsValid() ? KeySignature(SignatureFifths(_tonic, _mode)) : KeySignature();
107 }
108
110 Scales::Id ScaleId() const noexcept;
111
113 MCC::Scale Scale() const noexcept { return Scales::Make(_tonic, ScaleId()); }
114
116 MCC_CONSTEXPR14 NoteName Spell(MCC::PitchClass pitchClass) const noexcept {
117 if (!IsValid() || !pitchClass.IsValid()) {
118 return NoteName::Invalid();
119 }
120 const KeySignature signature = Signature();
121 const int32_t direction = signature.Fifths() < 0 ? -1 : 1;
122 NoteName best;
123 int32_t bestScore = 1000;
124 for (int32_t index = 0; index < 7; ++index) {
125 const Letter letter = static_cast<Letter>(index);
126 const int32_t keyAccidental = signature.AccidentalOf(letter).Semitones();
127 for (int32_t alteration = -1; alteration <= 1; ++alteration) {
128 const NoteName candidate(letter, Accidental(keyAccidental + alteration));
129 if (candidate.IsValid() && candidate.PitchClass() == pitchClass) {
130 // Diatonic first, then the fewest accidentals, then the
131 // key's direction: raised in sharp keys, lowered in flat keys.
132 const int32_t accidental = candidate.Accidental().Semitones();
133 const int32_t score = alteration == 0 ? 0
134 : 10 * (accidental < 0 ? -accidental : accidental) +
135 (alteration == direction ? 1 : 2);
136 if (score < bestScore) {
137 best = candidate;
138 bestScore = score;
139 }
140 }
141 }
142 }
143 return best;
144 }
145
147 MCC_CONSTEXPR14 MCC::Pitch Spell(MCC::ChromaticIndex index) const noexcept {
148 if (!index.IsValid()) {
149 return MCC::Pitch::Invalid();
150 }
151 const NoteName noteName = Spell(index.PitchClass());
152 if (!noteName.IsValid()) {
153 return MCC::Pitch::Invalid();
154 }
155 const int32_t offset =
156 NaturalSemitone(noteName.Letter()) + noteName.Accidental().Semitones();
157 return MCC::Pitch(noteName, Foundation::Math::FloorDiv(index.Value() - offset, 12) - 1);
158 }
159
161 friend constexpr bool operator==(const Key& a, const Key& b) noexcept {
162 return a._tonic == b._tonic && (!a.IsValid() || a._mode == b._mode);
163 }
164
166 friend constexpr bool operator!=(const Key& a, const Key& b) noexcept {
167 return !(a == b);
168 }
169};
170
171} // namespace MCC
172
173#endif // MCC_KEY_KEY_H
Chromatic alteration of a letter, from -4 (quadruple flat) to +4 (quadruple sharp).
Definition Accidental.h:20
constexpr int8_t Semitones() const noexcept
Returns the alteration in semitones, or InvalidValue for the invalid accidental (SPEC-ERR-7).
Definition Accidental.h:89
Absolute semitone position of a pitch, with origin C-1 = 0.
Definition ChromaticIndex.h:26
Key signature: zero to seven sharps or flats (SPEC-KEY-1).
Definition KeySignature.h:21
MCC_CONSTEXPR14 MCC::Accidental AccidentalOf(Letter letter) const noexcept
Returns the accidental the signature applies to letter: sharp, flat or natural; invalid for the inval...
Definition KeySignature.h:62
constexpr int8_t Fifths() const noexcept
Returns sharps as positive and flats as negative; 0 when invalid.
Definition KeySignature.h:46
MCC::Scale Scale() const noexcept
Returns the key's scale spelled from its tonic.
Definition Key.h:113
static MCC_CONSTEXPR14 Key FromSignature(KeySignature signature, KeyMode mode) noexcept
Returns the key with signature in mode (2 sharps + Minor = B minor).
Definition Key.h:80
MCC_CONSTEXPR14 NoteName Spell(MCC::PitchClass pitchClass) const noexcept
Writes pitchClass in this key's context (SPEC-KEY-3).
Definition Key.h:116
constexpr Key(NoteName tonic, KeyMode mode) noexcept
Creates tonic in mode; invalid beyond seven sharps or flats.
Definition Key.h:73
constexpr KeySignature Signature() const noexcept
Returns the key signature, or the invalid signature.
Definition Key.h:105
friend constexpr bool operator==(const Key &a, const Key &b) noexcept
Compares tonic spelling and mode.
Definition Key.h:161
constexpr Key() noexcept
Creates the invalid key (SPEC-ERR-2).
Definition Key.h:70
MCC_CONSTEXPR14 MCC::Pitch Spell(MCC::ChromaticIndex index) const noexcept
Writes a sounding chromatic index as a pitch in this key's context.
Definition Key.h:147
friend constexpr bool operator!=(const Key &a, const Key &b) noexcept
Negation of operator==.
Definition Key.h:166
constexpr NoteName Tonic() const noexcept
Returns the tonic.
Definition Key.h:99
static constexpr Key Invalid() noexcept
Returns the invalid key.
Definition Key.h:93
Scales::Id ScaleId() const noexcept
Returns the catalog scale of the mode, or Scales::Id::Invalid.
constexpr bool IsValid() const noexcept
Returns true unless this is the invalid key.
Definition Key.h:96
constexpr KeyMode Mode() const noexcept
Returns the mode.
Definition Key.h:102
Written note name: a letter plus an accidental, without octave.
Definition NoteName.h:29
constexpr MCC::Accidental Accidental() const noexcept
Returns the written accidental; invalid for the invalid note name.
Definition NoteName.h:76
constexpr MCC::Letter Letter() const noexcept
Returns the written letter, or Letter::C for the invalid note name (SPEC-ERR-7).
Definition NoteName.h:68
static constexpr NoteName Invalid() noexcept
Returns the invalid note name.
Definition NoteName.h:59
constexpr bool IsValid() const noexcept
Returns true unless this is the invalid note name.
Definition NoteName.h:62
MCC_CONSTEXPR14 MCC::PitchClass PitchClass() const noexcept
Returns (NaturalSemitone(letter) + accidental) mod 12, always in [0, 11] (SPEC-ORD-4)....
Definition NoteName.h:83
Pitch class 0-11 with spelling discarded (C = 0).
Definition PitchClass.h:21
Written absolute pitch: a note name plus an octave.
Definition Pitch.h:35
static constexpr Pitch Invalid() noexcept
Returns the invalid pitch.
Definition Pitch.h:80
KeyMode
Diatonic mode of a key, ordered from brightest to darkest.
Definition Key.h:23
MCC_CONSTEXPR14 uint8_t NaturalSemitone(Letter letter) noexcept
Returns the semitone offset of the natural letter above C: 0 2 4 5 7 9 11 (SPEC-ORD-1).
Definition Letter.h:58
constexpr uint8_t DiatonicIndex(Letter letter) noexcept
Returns the diatonic index of letter: C = 0 ... B = 6.
Definition Letter.h:49
Letter
Diatonic letter name.
Definition Letter.h:22
Scale Make(NoteName root, Id id) noexcept
Spells catalog entry id from root.
Definition Scales.h:107
Id
Stable catalog identifier.
Definition Scales.h:23
Music Composition Core public namespace.
Definition Version.h:4