Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
chord.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <array>
7#include <compare>
8#include <cstddef>
9#include <cstdint>
10#include <optional>
11#include <span>
12#include <string_view>
13
14namespace pulp::music {
15
16enum class ChordQuality : std::uint8_t {
17 major = 0,
18 minor = 1,
22 major7 = 5,
23 minor7 = 6,
27 power = 10,
28 octave = 11,
29};
30
31inline constexpr std::size_t kChordQualityCount = 12;
32
33enum class ChordExtension : std::uint8_t {
34 ninth = 0,
37};
38
39enum class ChordSuspension : std::uint8_t {
40 second = 0,
41 fourth = 1,
42};
43
52
57
58// These indices match timeline::ChordQuality and its persisted spelling.
59inline constexpr std::array<ChordCompatibilityEntry, 10> kPulpTimelineChordQualities{{
60 {"major", ChordQuality::major},
61 {"minor", ChordQuality::minor},
62 {"diminished", ChordQuality::diminished},
63 {"augmented", ChordQuality::augmented},
64 {"dominant7", ChordQuality::dominant7},
65 {"major7", ChordQuality::major7},
66 {"minor7", ChordQuality::minor7},
67 {"half_diminished7", ChordQuality::half_diminished7},
68 {"suspended2", ChordQuality::suspended2},
69 {"suspended4", ChordQuality::suspended4},
70}};
71
72// These indices and short names match Forge's chord transform table.
73inline constexpr std::array<ChordCompatibilityEntry, 11> kForgeChordQualities{{
74 {"maj", ChordQuality::major},
75 {"min", ChordQuality::minor},
80 {"maj7", ChordQuality::major7},
81 {"min7", ChordQuality::minor7},
83 {"power", ChordQuality::power},
84 {"octave", ChordQuality::octave},
85}};
86
88 public:
89 static constexpr std::size_t kMaxNotes = 8;
90 static constexpr int kMaxInterval = 48;
91
92 static constexpr std::optional<ChordFormula>
93 from_intervals(std::span<const int> intervals) noexcept {
94 if (intervals.empty() || intervals.size() > kMaxNotes || intervals.front() != 0)
95 return std::nullopt;
96
97 ChordFormula result;
98 int previous = -1;
99 for (std::size_t i = 0; i < intervals.size(); ++i) {
100 const int interval = intervals[i];
102 return std::nullopt;
103 result.intervals_[i] = static_cast<std::int8_t>(interval);
104 previous = interval;
105 }
106 result.count_ = static_cast<std::uint8_t>(intervals.size());
107 return result;
108 }
109
110 static constexpr std::optional<ChordFormula> for_quality(ChordQuality quality) noexcept {
111 constexpr std::array<std::array<std::int8_t, kMaxNotes>, kChordQualityCount> intervals{{
112 {{0, 4, 7}},
113 {{0, 3, 7}},
114 {{0, 3, 6}},
115 {{0, 4, 8}},
116 {{0, 4, 7, 10}},
117 {{0, 4, 7, 11}},
118 {{0, 3, 7, 10}},
119 {{0, 3, 6, 10}},
120 {{0, 2, 7}},
121 {{0, 5, 7}},
122 {{0, 7}},
123 {{0, 12}},
124 }};
125 constexpr std::array<std::uint8_t, kChordQualityCount> counts{{
126 3,
127 3,
128 3,
129 3,
130 4,
131 4,
132 4,
133 4,
134 3,
135 3,
136 2,
137 2,
138 }};
139 const auto index = static_cast<std::size_t>(quality);
140 if (index >= intervals.size())
141 return std::nullopt;
142 ChordFormula result;
143 result.intervals_ = intervals[index];
144 result.count_ = counts[index];
145 result.quality_ = quality;
146 return result;
147 }
148
149 constexpr std::size_t size() const noexcept {
150 return count_;
151 }
152 constexpr std::optional<int> interval(std::size_t index) const noexcept {
153 if (index >= count_)
154 return std::nullopt;
155 return static_cast<int>(intervals_[index]);
156 }
157 constexpr std::optional<ChordQuality> quality() const noexcept {
158 return quality_;
159 }
160
161 constexpr std::optional<ChordFormula> with_extension(ChordExtension extension) const noexcept {
162 constexpr std::array<int, 3> intervals{14, 17, 21};
163 const auto index = static_cast<std::size_t>(extension);
164 if (index >= intervals.size())
165 return std::nullopt;
166 return with_replacement(-1, intervals[index]);
167 }
168
169 constexpr std::optional<ChordFormula>
170 with_suspension(ChordSuspension suspension) const noexcept {
171 const auto index = static_cast<std::size_t>(suspension);
172 if (index > 1)
173 return std::nullopt;
174 const int replacement = index == 0 ? 2 : 5;
175 for (std::size_t note = 1; note < count_; ++note) {
176 const int interval_value = intervals_[note];
177 const auto pitch_class = wrap_pitch_class(interval_value);
178 if (pitch_class == PitchClass::d_sharp || pitch_class == PitchClass::e) {
179 const int replacement_interval =
180 interval_value - static_cast<int>(pitch_class) + replacement;
181 return with_replacement(interval_value, replacement_interval);
182 }
183 }
184 for (std::size_t note = 1; note < count_; ++note) {
185 const int interval_value = intervals_[note];
186 const auto pitch_class = wrap_pitch_class(interval_value);
187 if ((pitch_class == PitchClass::d || pitch_class == PitchClass::f) &&
188 static_cast<int>(pitch_class) != replacement) {
189 const int replacement_interval =
190 interval_value - static_cast<int>(pitch_class) + replacement;
191 return with_replacement(interval_value, replacement_interval);
192 }
193 }
194 for (std::size_t note = 1; note < count_; ++note)
195 if (intervals_[note] == replacement)
196 return *this;
197 return with_replacement(-1, replacement);
198 }
199
200 constexpr std::optional<ChordFormula>
201 with_alteration(ChordAlteration alteration) const noexcept {
202 constexpr std::array<int, 6> natural_intervals{7, 7, 14, 14, 17, 21};
203 constexpr std::array<int, 6> altered_intervals{6, 8, 13, 15, 18, 20};
204 const auto index = static_cast<std::size_t>(alteration);
205 if (index >= natural_intervals.size())
206 return std::nullopt;
207 return with_replacement(natural_intervals[index], altered_intervals[index]);
208 }
209
210 constexpr PitchClassSet pitch_classes() const noexcept {
211 std::uint16_t mask = 0;
212 for (std::size_t i = 0; i < count_; ++i)
213 mask = static_cast<std::uint16_t>(
214 mask | (1u << static_cast<unsigned>(wrap_pitch_class(intervals_[i]))));
215 return *PitchClassSet::from_mask(mask);
216 }
217
218 constexpr auto operator<=>(const ChordFormula&) const = default;
219
220 private:
221 constexpr std::optional<ChordFormula> with_replacement(int removed, int added) const noexcept {
222 std::array<int, kMaxNotes> values{};
223 std::size_t size = 0;
224 bool removed_existing = false;
225 bool added_existing = false;
226 for (std::size_t note = 0; note < count_; ++note) {
227 const int interval_value = intervals_[note];
228 if (!removed_existing && interval_value == removed) {
229 removed_existing = true;
230 continue;
231 }
232 if (interval_value == added)
233 added_existing = true;
234 values[size++] = interval_value;
235 }
236 if (!added_existing) {
237 if (size >= kMaxNotes)
238 return std::nullopt;
239 values[size++] = added;
240 } else if (!removed_existing) {
241 return *this;
242 }
243 std::sort(values.begin(), values.begin() + static_cast<std::ptrdiff_t>(size));
244 return from_intervals(std::span<const int>(values.data(), size));
245 }
246
247 std::array<std::int8_t, kMaxNotes> intervals_{};
248 std::uint8_t count_ = 0;
249 std::optional<ChordQuality> quality_;
250};
251
252class Chord {
253 public:
254 static constexpr std::optional<Chord> construct(int root_midi, const ChordFormula& formula,
255 std::size_t inversion = 0) noexcept {
256 if (root_midi < 0 || root_midi > 127 || formula.size() == 0 || inversion >= formula.size())
257 return std::nullopt;
258
259 Chord result;
260 result.root_midi_ = static_cast<std::uint8_t>(root_midi);
261 result.formula_ = formula;
262 result.inversion_ = static_cast<std::uint8_t>(inversion);
263 result.count_ = static_cast<std::uint8_t>(formula.size());
264
265 int previous = -1;
266 for (std::size_t output = 0; output < formula.size(); ++output) {
267 const std::size_t source = (output + inversion) % formula.size();
268 int pitch = root_midi + *formula.interval(source);
269 if (source < inversion)
270 while (pitch <= previous)
272 if (pitch > 127)
273 return std::nullopt;
274 result.pitches_[output] = static_cast<std::uint8_t>(pitch);
275 previous = pitch;
276 }
277 return result;
278 }
279
280 constexpr int root_midi() const noexcept {
281 return root_midi_;
282 }
283 constexpr PitchClass root_pitch_class() const noexcept {
284 return wrap_pitch_class(root_midi_);
285 }
286 constexpr const ChordFormula& formula() const noexcept {
287 return formula_;
288 }
289 constexpr std::size_t inversion() const noexcept {
290 return inversion_;
291 }
292 constexpr std::size_t size() const noexcept {
293 return count_;
294 }
295 constexpr std::optional<int> pitch(std::size_t index) const noexcept {
296 if (index >= count_)
297 return std::nullopt;
298 return pitches_[index];
299 }
300
301 constexpr PitchClassSet pitch_classes() const noexcept {
302 std::uint16_t mask = 0;
303 for (std::size_t i = 0; i < count_; ++i)
304 mask = static_cast<std::uint16_t>(
305 mask | (1u << static_cast<unsigned>(wrap_pitch_class(pitches_[i]))));
306 return *PitchClassSet::from_mask(mask);
307 }
308
309 constexpr std::optional<Chord> transposed(int semitones) const noexcept {
310 const auto transposed_root = static_cast<std::int64_t>(root_midi_) + semitones;
311 if (transposed_root < 0 || transposed_root > 127)
312 return std::nullopt;
313 return construct(static_cast<int>(transposed_root), formula_, inversion_);
314 }
315
316 constexpr auto operator<=>(const Chord&) const = default;
317
318 private:
319 ChordFormula formula_{};
320 std::array<std::uint8_t, ChordFormula::kMaxNotes> pitches_{};
321 std::uint8_t root_midi_ = 0;
322 std::uint8_t count_ = 0;
323 std::uint8_t inversion_ = 0;
324};
325
326} // namespace pulp::music
Definition chord.hpp:87
constexpr std::optional< int > interval(std::size_t index) const noexcept
Definition chord.hpp:152
constexpr std::optional< ChordFormula > with_alteration(ChordAlteration alteration) const noexcept
Definition chord.hpp:201
static constexpr std::size_t kMaxNotes
Definition chord.hpp:89
static constexpr std::optional< ChordFormula > for_quality(ChordQuality quality) noexcept
Definition chord.hpp:110
constexpr PitchClassSet pitch_classes() const noexcept
Definition chord.hpp:210
static constexpr int kMaxInterval
Definition chord.hpp:90
constexpr std::optional< ChordFormula > with_suspension(ChordSuspension suspension) const noexcept
Definition chord.hpp:170
constexpr std::size_t size() const noexcept
Definition chord.hpp:149
constexpr auto operator<=>(const ChordFormula &) const =default
static constexpr std::optional< ChordFormula > from_intervals(std::span< const int > intervals) noexcept
Definition chord.hpp:93
constexpr std::optional< ChordFormula > with_extension(ChordExtension extension) const noexcept
Definition chord.hpp:161
constexpr std::optional< ChordQuality > quality() const noexcept
Definition chord.hpp:157
Definition chord.hpp:252
constexpr const ChordFormula & formula() const noexcept
Definition chord.hpp:286
constexpr PitchClassSet pitch_classes() const noexcept
Definition chord.hpp:301
constexpr std::optional< int > pitch(std::size_t index) const noexcept
Definition chord.hpp:295
constexpr PitchClass root_pitch_class() const noexcept
Definition chord.hpp:283
constexpr auto operator<=>(const Chord &) const =default
static constexpr std::optional< Chord > construct(int root_midi, const ChordFormula &formula, std::size_t inversion=0) noexcept
Definition chord.hpp:254
constexpr std::size_t size() const noexcept
Definition chord.hpp:292
constexpr std::optional< Chord > transposed(int semitones) const noexcept
Definition chord.hpp:309
constexpr int root_midi() const noexcept
Definition chord.hpp:280
constexpr std::size_t inversion() const noexcept
Definition chord.hpp:289
Definition pitch.hpp:56
static constexpr std::optional< PitchClassSet > from_mask(std::uint16_t mask) noexcept
Definition pitch.hpp:60
Definition chord.hpp:14
constexpr std::size_t kChordQualityCount
Definition chord.hpp:31
constexpr PitchClass wrap_pitch_class(int index) noexcept
Definition pitch.hpp:44
ChordExtension
Definition chord.hpp:33
@ thirteenth
Definition chord.hpp:36
@ ninth
Definition chord.hpp:34
@ eleventh
Definition chord.hpp:35
constexpr std::array< ChordCompatibilityEntry, 11 > kForgeChordQualities
Definition chord.hpp:73
ChordSuspension
Definition chord.hpp:39
@ second
Definition chord.hpp:40
@ fourth
Definition chord.hpp:41
constexpr int kPitchClassesPerOctave
Definition pitch.hpp:16
PitchClass
Definition pitch.hpp:19
@ d
Definition pitch.hpp:22
@ f
Definition pitch.hpp:25
@ d_sharp
Definition pitch.hpp:23
@ e
Definition pitch.hpp:24
ChordAlteration
Definition chord.hpp:44
@ sharp_ninth
Definition chord.hpp:48
@ flat_thirteenth
Definition chord.hpp:50
@ flat_fifth
Definition chord.hpp:45
@ sharp_fifth
Definition chord.hpp:46
@ sharp_eleventh
Definition chord.hpp:49
@ flat_ninth
Definition chord.hpp:47
constexpr std::array< ChordCompatibilityEntry, 10 > kPulpTimelineChordQualities
Definition chord.hpp:59
ChordQuality
Definition chord.hpp:16
@ diminished
Definition chord.hpp:19
@ suspended2
Definition chord.hpp:25
@ suspended4
Definition chord.hpp:26
@ half_diminished7
Definition chord.hpp:24
@ power
Definition chord.hpp:27
@ augmented
Definition chord.hpp:20
@ octave
Definition chord.hpp:28
@ minor7
Definition chord.hpp:23
@ dominant7
Definition chord.hpp:21
@ minor
Definition chord.hpp:18
@ major7
Definition chord.hpp:22
@ major
Definition chord.hpp:17
ChordQuality quality
Definition chord.hpp:55
std::string_view stored_name
Definition chord.hpp:54