Zrythm v2.0.0-alpha.1+31.4967fd053471
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
chord_descriptor.h
1// SPDX-FileCopyrightText: © 2018-2022, 2024-2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <algorithm>
7#include <array>
8#include <cstdint>
9#include <optional>
10
11#include <QObject>
12#include <QtQmlIntegration/qqmlintegration.h>
13
14#include <boost/container/static_vector.hpp>
15#include <nlohmann/json_fwd.hpp>
16
18{
19class Utf8String;
20}
21
22namespace zrythm::dsp::chords
23{
24Q_NAMESPACE
25QML_ELEMENT
26
27enum class MusicalNote : std::uint8_t
28{
29 C = 0,
30 CSharp,
31 D,
32 DSharp,
33 E,
34 F,
35 FSharp,
36 G,
37 GSharp,
38 A,
39 ASharp,
40 B
41};
42Q_ENUM_NS (MusicalNote)
43
44
47enum class ChordType : std::uint8_t
48{
49 None,
50 Major,
51 Minor,
52 Diminished,
53 SuspendedFourth,
54 SuspendedSecond,
55 Augmented,
56 Custom,
57};
58Q_ENUM_NS (ChordType)
59
60
63enum class ChordAccent : std::uint8_t
64{
65 None,
68 Seventh,
70 MajorSeventh,
71 /* NOTE: all accents below assume Seventh */
73 FlatNinth,
75 Ninth,
77 SharpNinth,
79 Eleventh,
81 FlatFifthSharpEleventh,
83 SharpFifthFlatThirteenth,
85 SixthThirteenth,
86};
87Q_ENUM_NS (ChordAccent)
88
89
96constexpr MusicalNote
97transpose_note (MusicalNote note, bool up) noexcept
98{
99 return static_cast<MusicalNote> (
100 (static_cast<int> (note) + (up ? 1 : 11)) % 12);
101}
102
103} // namespace zrythm::dsp::chords
104
105namespace zrythm::dsp
106{
107
108using MusicalNote = chords::MusicalNote;
109using ChordType = chords::ChordType;
110using ChordAccent = chords::ChordAccent;
111
119class ChordDescriptor : public QObject
120{
121 Q_OBJECT
122 QML_ELEMENT
123
124public:
125 static constexpr std::uint8_t kDefaultBasePitch = 36; // C2
126
127 ChordDescriptor () = default;
128
129 ChordDescriptor (
130 MusicalNote root,
131 ChordType type,
132 ChordAccent accent = ChordAccent::None,
133 int inversion = 0,
134 std::optional<MusicalNote> bass = std::nullopt,
135 QObject * parent = nullptr)
136 : QObject (parent), root_note_ (root), type_ (type), accent_ (accent),
137 inversion_ (std::clamp (inversion, minInversion (), maxInversion ())),
138 bass_note_ (bass)
139 {
140 }
141
142 // ========================================================================
143 // QML Interface
144 // ========================================================================
145
146 Q_PROPERTY (
147 zrythm::dsp::chords::MusicalNote rootNote READ rootNote WRITE setRootNote
148 NOTIFY rootNoteChanged)
149 Q_PROPERTY (bool hasBass READ hasBass WRITE setHasBass NOTIFY hasBassChanged)
150 Q_PROPERTY (
151 zrythm::dsp::chords::MusicalNote bassNote READ bassNote WRITE setBassNote
152 NOTIFY bassNoteChanged)
153 Q_PROPERTY (
154 zrythm::dsp::chords::ChordType chordType READ chordType WRITE setChordType
155 NOTIFY chordTypeChanged)
156 Q_PROPERTY (
157 zrythm::dsp::chords::ChordAccent chordAccent READ chordAccent WRITE
158 setChordAccent NOTIFY chordAccentChanged)
159 Q_PROPERTY (
160 int inversion READ inversion WRITE setInversion NOTIFY inversionChanged)
161 Q_PROPERTY (QString displayName READ displayName NOTIFY displayNameChanged)
162
163 MusicalNote rootNote () const { return root_note_; }
164 void setRootNote (MusicalNote v)
165 {
166 if (root_note_ != v)
167 {
168 root_note_ = v;
169 Q_EMIT rootNoteChanged (v);
170 Q_EMIT displayNameChanged ();
171 Q_EMIT changed ();
172 }
173 }
174
175 bool hasBass () const { return bass_note_.has_value (); }
176 void setHasBass (bool v)
177 {
178 if (hasBass () != v)
179 {
180 bass_note_ = v ? std::optional<MusicalNote> (root_note_) : std::nullopt;
181 Q_EMIT hasBassChanged (v);
182 Q_EMIT displayNameChanged ();
183 Q_EMIT changed ();
184 }
185 }
186
187 MusicalNote bassNote () const { return bass_note_.value_or (MusicalNote::C); }
188 void setBassNote (MusicalNote v)
189 {
190 bool had = hasBass ();
191 if (!bass_note_.has_value () || *bass_note_ != v)
192 {
193 bass_note_ = v;
194 if (!had)
195 Q_EMIT hasBassChanged (true);
196 Q_EMIT bassNoteChanged (v);
197 Q_EMIT displayNameChanged ();
198 Q_EMIT changed ();
199 }
200 }
201
202 ChordType chordType () const { return type_; }
203 void setChordType (ChordType v)
204 {
205 if (type_ != v)
206 {
207 type_ = v;
208 Q_EMIT chordTypeChanged (v);
209 Q_EMIT displayNameChanged ();
210 Q_EMIT changed ();
211 }
212 }
213
214 ChordAccent chordAccent () const { return accent_; }
215 void setChordAccent (ChordAccent v)
216 {
217 if (accent_ != v)
218 {
219 accent_ = v;
220 Q_EMIT chordAccentChanged (v);
221 Q_EMIT displayNameChanged ();
222 Q_EMIT changed ();
223 }
224 }
225
226 int inversion () const { return inversion_; }
227 void setInversion (int v)
228 {
229 v = std::clamp (v, minInversion (), maxInversion ());
230 if (inversion_ != v)
231 {
232 inversion_ = v;
233 Q_EMIT inversionChanged (v);
234 Q_EMIT displayNameChanged ();
235 Q_EMIT changed ();
236 }
237 }
238
239 QString displayName () const;
240
241 Q_SIGNAL void rootNoteChanged (zrythm::dsp::chords::MusicalNote note);
242 Q_SIGNAL void hasBassChanged (bool has);
243 Q_SIGNAL void bassNoteChanged (zrythm::dsp::chords::MusicalNote note);
244 Q_SIGNAL void chordTypeChanged (zrythm::dsp::chords::ChordType type);
245 Q_SIGNAL void chordAccentChanged (zrythm::dsp::chords::ChordAccent accent);
246 Q_SIGNAL void inversionChanged (int inversion);
247 Q_SIGNAL void displayNameChanged ();
248 Q_SIGNAL void changed ();
249
250 // ========================================================================
251 // Queries
252 // ========================================================================
253
254 Q_INVOKABLE bool isKeyInChord (MusicalNote key) const;
255 Q_INVOKABLE bool isKeyBass (MusicalNote key) const;
256
257 static constexpr size_t kMaxIntervals = 12;
258
263 static boost::container::static_vector<int, kMaxIntervals>
264 get_intervals_for_type_and_accent (ChordType type, ChordAccent accent);
265
267 * Returns the semitone intervals from root for the current type and accent.
268 * e.g., Major = {0, 4, 7}, Minor7 = {0, 3, 7, 10}
269 */
270 boost::container::static_vector<int, kMaxIntervals> getIntervals () const;
271
276 struct ChordPitches
277 {
278 static constexpr size_t kMaxPitches = 8;
279 std::array<std::uint8_t, kMaxPitches> data{};
280 size_t count = 0;
281
282 auto begin () const { return data.begin (); }
283 auto end () const { return data.begin () + count; }
284 bool empty () const { return count == 0; }
285 size_t size () const { return count; }
286 std::uint8_t operator[] (size_t i) const { return data[i]; }
287
288 friend bool operator== (const ChordPitches &, const ChordPitches &);
289 };
290
296 ChordPitches
297 getMidiPitches (std::uint8_t base_pitch = kDefaultBasePitch) const;
298
299 int maxInversion () const;
300 int minInversion () const { return -maxInversion (); }
301
302 // ========================================================================
303 // String utilities
304 // ========================================================================
305
306 static utils::Utf8String chord_type_to_string (ChordType type);
307 static utils::Utf8String chord_accent_to_string (ChordAccent accent);
308 static utils::Utf8String note_to_string (MusicalNote note);
309 utils::Utf8String to_string () const;
310
311 bool isEquivalent (const ChordDescriptor &other) const;
312
313private:
314 friend void to_json (nlohmann::json &j, const ChordDescriptor &c);
315 friend void from_json (const nlohmann::json &j, ChordDescriptor &c);
316
318 MusicalNote root_note_ = MusicalNote::C;
319
321 ChordType type_ = ChordType::None;
322
328 ChordAccent accent_ = ChordAccent::None;
329
335 int inversion_ = 0;
336
338 std::optional<MusicalNote> bass_note_;
339
347 std::optional<boost::container::static_vector<int, kMaxIntervals>>
348 custom_intervals_;
349};
350
351} // namespace zrythm::dsp
static boost::container::static_vector< int, kMaxIntervals > get_intervals_for_type_and_accent(ChordType type, ChordAccent accent)
Returns the semitone intervals from root for the given type and accent.
boost::container::static_vector< int, kMaxIntervals > getIntervals() const
Returns the semitone intervals from root for the current type and accent.
ChordPitches getMidiPitches(std::uint8_t base_pitch=kDefaultBasePitch) const
Returns the MIDI pitches for this chord's voicing.
Lightweight UTF-8 string wrapper with safe conversions.
Definition utf8_string.h:37
String utilities.
Stack-allocated container for chord MIDI pitches.