Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spelling.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <array>
6#include <compare>
7#include <cstddef>
8#include <cstdint>
9#include <optional>
10#include <string_view>
11
12namespace pulp::music {
13
14enum class NoteLetter : std::uint8_t {
15 c = 0,
16 d = 1,
17 e = 2,
18 f = 3,
19 g = 4,
20 a = 5,
21 b = 6,
22};
23
24enum class AccidentalPolicy : std::uint8_t {
27 // Natural spellings have zero cost. A chromatic pitch has equal-cost
28 // one-accidental spellings, so this policy resolves that tie toward sharps.
30};
31
34 std::int8_t accidental = 0;
36
37 constexpr auto operator<=>(const SpelledPitchClass&) const = default;
38};
39
40constexpr std::string_view spelling_name(SpelledPitchClass spelling) noexcept {
41 constexpr std::array<std::array<std::string_view, 5>, 7> names{{
42 {{"Cbb", "Cb", "C", "C#", "Cx"}},
43 {{"Dbb", "Db", "D", "D#", "Dx"}},
44 {{"Ebb", "Eb", "E", "E#", "Ex"}},
45 {{"Fbb", "Fb", "F", "F#", "Fx"}},
46 {{"Gbb", "Gb", "G", "G#", "Gx"}},
47 {{"Abb", "Ab", "A", "A#", "Ax"}},
48 {{"Bbb", "Bb", "B", "B#", "Bx"}},
49 }};
50 const auto letter = static_cast<std::size_t>(spelling.letter);
51 const int accidental = spelling.accidental;
52 if (letter >= names.size() || accidental < -2 || accidental > 2)
53 return {};
54 return names[letter][static_cast<std::size_t>(accidental + 2)];
55}
56
57constexpr std::optional<SpelledPitchClass> spell_pitch_class(PitchClass pitch_class,
58 AccidentalPolicy policy) noexcept {
59 if (!is_valid(pitch_class) || static_cast<unsigned>(policy) > 2)
60 return std::nullopt;
61 constexpr std::array<NoteLetter, 12> sharp_letters{
64 constexpr std::array<std::int8_t, 12> sharp_accidentals{0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0};
65 constexpr std::array<NoteLetter, 12> flat_letters{
68 constexpr std::array<std::int8_t, 12> flat_accidentals{0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1, 0};
69 const auto index = static_cast<std::size_t>(pitch_class);
70 const bool flats = policy == AccidentalPolicy::prefer_flats;
71 return SpelledPitchClass{flats ? flat_letters[index] : sharp_letters[index],
72 flats ? flat_accidentals[index] : sharp_accidentals[index],
73 pitch_class};
74}
75
77 public:
78 constexpr std::size_t size() const noexcept {
79 return count_;
80 }
81
82 constexpr std::optional<SpelledPitchClass> note(std::size_t index) const noexcept {
83 if (index >= count_)
84 return std::nullopt;
85 return notes_[index];
86 }
87
88 constexpr auto operator<=>(const SpelledChord&) const = default;
89
90 private:
91 friend constexpr std::optional<SpelledChord> spell_chord(PitchClass, const ChordFormula&,
92 AccidentalPolicy) noexcept;
93
94 std::array<SpelledPitchClass, ChordFormula::kMaxNotes> notes_{};
95 std::uint8_t count_ = 0;
96};
97
98constexpr std::optional<SpelledChord> spell_chord(PitchClass root, const ChordFormula& formula,
99 AccidentalPolicy policy) noexcept {
100 const auto spelled_root = spell_pitch_class(root, policy);
101 if (!spelled_root || formula.size() == 0)
102 return std::nullopt;
103
104 constexpr std::array<int, 7> natural_pitch_classes{0, 2, 4, 5, 7, 9, 11};
105 constexpr std::array<int, 12> diatonic_steps{0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 6, 6};
106 SpelledChord result;
107 result.count_ = static_cast<std::uint8_t>(formula.size());
108 const int root_letter = static_cast<int>(spelled_root->letter);
109
110 for (std::size_t note = 0; note < formula.size(); ++note) {
111 const int interval = *formula.interval(note);
112 const int octaves = interval / kPitchClassesPerOctave;
113 const int within_octave = interval % kPitchClassesPerOctave;
114 int letter_steps = octaves * 7 + diatonic_steps[within_octave];
115 if (interval == 15)
116 letter_steps = 8; // sharp ninth
117 else if (interval == 18)
118 letter_steps = 10; // sharp eleventh
119 else if (interval == 20)
120 letter_steps = 12; // flat thirteenth
121 const auto letter = static_cast<NoteLetter>((root_letter + letter_steps) % 7);
122 const auto pitch_class = transpose(root, interval);
123 int accidental =
124 static_cast<int>(pitch_class) - natural_pitch_classes[static_cast<std::size_t>(letter)];
125 while (accidental > 6)
126 accidental -= 12;
127 while (accidental < -6)
128 accidental += 12;
129 if (accidental < -2 || accidental > 2)
130 return std::nullopt;
131 result.notes_[note] =
132 SpelledPitchClass{letter, static_cast<std::int8_t>(accidental), pitch_class};
133 }
134 return result;
135}
136
137constexpr std::optional<SpelledChord> spell_chord(const Chord& chord,
138 AccidentalPolicy policy) noexcept {
139 return spell_chord(chord.root_pitch_class(), chord.formula(), policy);
140}
141
142} // namespace pulp::music
Definition chord.hpp:87
Definition chord.hpp:252
Definition spelling.hpp:76
constexpr std::size_t size() const noexcept
Definition spelling.hpp:78
constexpr std::optional< SpelledPitchClass > note(std::size_t index) const noexcept
Definition spelling.hpp:82
constexpr auto operator<=>(const SpelledChord &) const =default
friend constexpr std::optional< SpelledChord > spell_chord(PitchClass, const ChordFormula &, AccidentalPolicy) noexcept
Definition spelling.hpp:98
Definition chord.hpp:14
constexpr bool is_valid(PitchClass pitch_class) noexcept
Definition pitch.hpp:34
constexpr std::optional< SpelledChord > spell_chord(PitchClass root, const ChordFormula &formula, AccidentalPolicy policy) noexcept
Definition spelling.hpp:98
NoteLetter
Definition spelling.hpp:14
@ a
Definition spelling.hpp:20
@ c
Definition spelling.hpp:15
@ d
Definition spelling.hpp:16
@ f
Definition spelling.hpp:18
@ b
Definition spelling.hpp:21
@ g
Definition spelling.hpp:19
@ e
Definition spelling.hpp:17
constexpr PitchClass transpose(PitchClass pitch_class, int semitones) noexcept
Definition pitch.hpp:51
constexpr int kPitchClassesPerOctave
Definition pitch.hpp:16
PitchClass
Definition pitch.hpp:19
@ a
Definition pitch.hpp:29
@ c
Definition pitch.hpp:20
@ d
Definition pitch.hpp:22
@ f
Definition pitch.hpp:25
@ b
Definition pitch.hpp:31
@ g
Definition pitch.hpp:27
@ e
Definition pitch.hpp:24
AccidentalPolicy
Definition spelling.hpp:24
@ prefer_flats
Definition spelling.hpp:26
@ minimize_accidentals
Definition spelling.hpp:29
@ prefer_sharps
Definition spelling.hpp:25
constexpr std::string_view spelling_name(SpelledPitchClass spelling) noexcept
Definition spelling.hpp:40
constexpr std::optional< SpelledPitchClass > spell_pitch_class(PitchClass pitch_class, AccidentalPolicy policy) noexcept
Definition spelling.hpp:57
Definition spelling.hpp:32
NoteLetter letter
Definition spelling.hpp:33
constexpr auto operator<=>(const SpelledPitchClass &) const =default
std::int8_t accidental
Definition spelling.hpp:34
PitchClass pitch_class
Definition spelling.hpp:35