97 using U = std::underlying_type_t<ChordTag>;
98 return static_cast<ChordTag>(
static_cast<U
>(lhs) |
static_cast<U
>(rhs));
107 using U = std::underlying_type_t<ChordTag>;
108 return static_cast<ChordTag>(
static_cast<U
>(lhs) &
static_cast<U
>(rhs));
116 using U = std::underlying_type_t<ChordTag>;
117 return (
static_cast<U
>(tags) &
static_cast<U
>(wanted)) ==
static_cast<U
>(wanted);
143 bool b0,
bool b1,
bool b2,
bool b3,
144 bool b4,
bool b5,
bool b6,
bool b7,
145 bool b8,
bool b9,
bool b10,
bool b11)
noexcept
147 return (
static_cast<NoteSet>(b0) << 0) | (
static_cast<NoteSet>(b1) << 1) |
148 (
static_cast<NoteSet>(b2) << 2) | (
static_cast<NoteSet>(b3) << 3) |
149 (
static_cast<NoteSet>(b4) << 4) | (
static_cast<NoteSet>(b5) << 5) |
150 (
static_cast<NoteSet>(b6) << 6) | (
static_cast<NoteSet>(b7) << 7) |
151 (
static_cast<NoteSet>(b8) << 8) | (
static_cast<NoteSet>(b9) << 9) |
152 (
static_cast<NoteSet>(b10) << 10) | (
static_cast<NoteSet>(b11) << 11);
162 for (
int d : degrees) {
163 if (d >= 0) { m |=
static_cast<NoteSet>(1u << (d % 12)); }
173 constexpr void copy(
char* dst, std::string_view src, std::size_t dstCapacity)
noexcept
175 std::size_t toCopy = src.size();
176 if (toCopy + 1u > dstCapacity) { toCopy = (dstCapacity > 0) ? dstCapacity - 1u : 0u; }
177 for (std::size_t i = 0; i < toCopy; ++i) { dst[i] = src[i]; }
178 if (dstCapacity > 0) { dst[toCopy] =
'\0'; }
186 std::array<int, 12> temp{};
188 for (
int i = 0; i < 12; ++i) {
189 if (mask & (1u << i)) temp[tcount++] = i;
192 std::array<int, 7> out{};
194 for (
int i = 0; i < 7; ++i) out[i] = i;
198 for (
int i = 0; i < 7; ++i) {
199 int idx = i % tcount;
200 int octave = i / tcount;
201 out[i] = temp[idx] + octave * 12;
209 [[nodiscard]]
constexpr int interval(
const std::array<int, 7>& deg,
int degIdx,
int skip)
noexcept
212 int b = deg[(degIdx + skip) % 7];
213 while (b <= a) { b += 12; }
222 inline constexpr std::array<Scale, 61>
allScales = [](){
223 std::array<Scale, 61> scales{};
279 scales[40] = {
"Chromatic",
makeMask({0,1,2,3,4,5,6,7,8,9,10,11}),
ChordTag::All};
306 inline constexpr std::array<std::string_view, 12>
sharpNames{
307 "C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B"
309 inline constexpr std::array<std::string_view, 12>
flatNames{
310 "C",
"Db",
"D",
"Eb",
"E",
"F",
"Gb",
"G",
"Ab",
"A",
"Bb",
"B"
316 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1
329 [[nodiscard]]
constexpr std::string_view
noteName(
int midi,
int root = 0) noexcept
331 int idx = (midi % 12 + 12) % 12;
332 int r = (root % 12 + 12) % 12;
341 [[nodiscard]]
constexpr std::optional<int>
parseNote(std::string_view s)
noexcept
346 constexpr std::array<std::pair<std::string_view,int>, 21> lut{{
349 {
"D",2}, {
"D#",3}, {
"Eb",3},
350 {
"E",4}, {
"E#",5}, {
"Fb",4},
351 {
"F",5}, {
"F#",6}, {
"Gb",6},
352 {
"G",7}, {
"G#",8}, {
"Ab",8},
353 {
"A",9}, {
"A#",10},{
"Bb",10},
357 for (
const auto& [name,val] : lut) {
358 if (name.size() == s.size() &&
359 std::equal(name.begin(), name.end(), s.begin(),
360 [](
char a,
char b){ return (a|32) == (b|32); })) {
379 root = (root % 12 + 12) % 12;
380 std::uint32_t b =
static_cast<std::uint32_t
>(base) & 0x0FFFu;
381 std::uint32_t res = ((b << root) | (b >> (12 - root))) & 0x0FFFu;
382 return static_cast<NoteSet>(res);
400 {
"Major", {0, 4, 7, -1, -1, -1, -1}},
401 {
"Minor", {0, 3, 7, -1, -1, -1, -1}},
402 {
"Diminished", {0, 3, 6, -1, -1, -1, -1}},
403 {
"Augmented", {0, 4, 8, -1, -1, -1, -1}},
404 {
"Major7", {0, 4, 7, 11, -1, -1, -1}},
405 {
"Dominant7", {0, 4, 7, 10, -1, -1, -1}},
406 {
"Minor7", {0, 3, 7, 10, -1, -1, -1}},
407 {
"HalfDim7", {0, 3, 6, 10, -1, -1, -1}},
408 {
"Dim7", {0, 3, 6, 9, -1, -1, -1}},
409 {
"Sus2", {0, 2, 7, -1, -1, -1, -1}},
410 {
"Sus4", {0, 5, 7, -1, -1, -1, -1}},
411 {
"Major9", {0, 4, 7, 11, 14, -1, -1}},
412 {
"Dominant9", {0, 4, 7, 10, 14, -1, -1}},
413 {
"Minor9", {0, 3, 7, 10, 14, -1, -1}},
414 {
"Major13", {0, 4, 7, 11, 14, 17, 21}}
428 [[nodiscard]]
constexpr std::array<int, 7>
431 std::array<int, 7> notes{};
434 for (
int i = 0; i < 7; ++i) {
437 notes[count++] = rootMidi + deg;
440 if (count == 0)
return { -1,-1,-1,-1,-1,-1,-1 };
443 inversion = (inversion >= 0) ? (inversion % count) : 0;
444 for (
int i = 0; i < inversion; ++i) { notes[i] += 12; }
445 std::sort(notes.begin(), notes.begin() + count);
448 for (
int i = count; i < 7; ++i) { notes[i] = -1; }
457 [[nodiscard]]
constexpr std::array<const Scale*, 16>
460 std::array<const Scale*, 16> out{};
463 if (((s.mask & chordMask) == chordMask) && idx < out.size()) {
475 [[nodiscard]]
constexpr std::array<const Scale*, 16>
479 for (
int d : chord.intervals) {
480 if (d >= 0) { chordMask |=
static_cast<NoteSet>(1u << (d % 12)); }
516 [[nodiscard]]
constexpr std::string_view
view() const noexcept
519 while (len <
name.size() &&
name[len] !=
'\0') { ++len; }
520 return std::string_view(
name.data(), len);
531 [[nodiscard]]
constexpr DiatonicChord
535 if (degree < 0 || degree >= 7)
return {};
551 auto buildName = [&](std::string_view baseName,
ChordLevel lvl)
556 pos += baseName.size();
562 detail::copy(c.name.data(), std::string_view(buf, pos), c.name.size());
565 std::string_view base;
566 if (third == 4 && fifth == 7) base =
"M";
567 else if (third == 3 && fifth == 7) base =
"m";
568 else if (third == 3 && fifth == 6) base =
"dim";
569 else if (third == 4 && fifth == 8) base =
"aug";
572 std::string_view name7;
573 if (base ==
"dim" && seventh == 10) name7 =
"m7b5";
574 else if (base ==
"dim" && seventh == 9) name7 =
"dim7";
575 else if (base ==
"M" && seventh == 10) name7 =
"7";
576 else if (base ==
"M" && seventh == 11) name7 =
"maj7";
577 else if (base ==
"m" && seventh == 10) name7 =
"m7";
578 else if (base ==
"m" && seventh == 11) name7 =
"m(maj7)";
579 else if (base ==
"aug" && seventh == 10) name7 =
"aug7";
600 [[nodiscard]]
constexpr std::array<int, 7>
603 std::array<int, 7> notes{};
604 for (std::size_t i = 0; i < 7; ++i) {
605 notes[i] = (c.intervals[i] >= 0) ? rootMidi + c.intervals[i] : -1;
621 std::size_t len = note.size();
622 while (len > 0 && note[len - 1] >=
'0' && note[len - 1] <=
'9') { --len; }
623 if (len > 0 && note[len - 1] ==
'-') { --len; }
625 std::string_view baseNote = note.substr(0, len);
636 if (note.empty())
return 4;
637 std::size_t len = note.size();
639 while (len > 0 && note[len - 1] >=
'0' && note[len - 1] <=
'9') { --len; }
641 bool isNegative =
false;
642 if (len > 0 && note[len - 1] ==
'-') {
647 if (len < note.size() && !(isNegative && len + 1 == note.size())) {
649 std::size_t start = isNegative ? len + 1 : len;
650 for (std::size_t i = start; i < note.size(); ++i) {
651 parsed = parsed * 10 + (note[i] -
'0');
653 return isNegative ? -parsed : parsed;
667 return midi + octaveDelta * 12;
constexpr void copy(char *dst, std::string_view src, std::size_t dstCapacity) noexcept
Safe small string copy used only for compile-time-friendly name building.
constexpr int interval(const std::array< int, 7 > °, int degIdx, int skip) noexcept
Calculate semitone interval skipping 'skip' degrees in the scale.
constexpr std::array< int, 7 > activeDegrees(NoteSet mask) noexcept
Extract active scale degrees and expand them sequentially across the octave.
constexpr int getOctaveFromNote(std::string_view note) noexcept
Extract octave number from a note string. Supports negative octaves.
constexpr std::array< const Scale *, 16 > scalesForChordMask(NoteSet chordMask) noexcept
Reverse lookup: find up to 16 scales that fully contain chordMask.
constexpr ChordTag operator&(ChordTag lhs, ChordTag rhs) noexcept
Bitwise AND operator for ChordTag flags (intersection).
constexpr std::array< Scale, 61 > allScales
constexpr std::array< bool, 12 > useSharpsForRoot
std::uint16_t NoteSet
A 12-bit bitmask representing the 12 pitch-classes of the chromatic scale.
constexpr std::array< int, 7 > chordAtRootMidi(const Chord &c, int rootMidi, int inversion=0) noexcept
Build MIDI note numbers for a chord recipe located at a specific root.
constexpr std::optional< int > parseNoteWithOctave(std::string_view note) noexcept
Parse a note string containing an optional octave (e.g. "C#4", "C-1").
int Degree
Integer index used to select a degree inside standard diatonic representations (0-based).
constexpr int transposeByOctaves(int midi, int octaveDelta) noexcept
Transpose a MIDI note by a discrete number of full octaves.
constexpr std::string_view noteName(int midi, int root=0) noexcept
Returns a human-readable note name for the given MIDI note (0..127).
constexpr std::array< Chord, 15 > allChords
constexpr std::array< int, 7 > diatonicChordToMidi(const DiatonicChord &c, int rootMidi) noexcept
Convert a DiatonicChord's interval recipe into absolute MIDI notes.
constexpr std::array< std::string_view, 12 > flatNames
ChordLevel
Complexity level describing which extensions to generate for diatonic chords.
@ Triads791113
Up to 13ths.
@ Triads7
Generate up to 7th chords.
@ TriadsOnly
Generate base triads only (R-3-5).
consteval NoteSet makeMask(bool b0, bool b1, bool b2, bool b3, bool b4, bool b5, bool b6, bool b7, bool b8, bool b9, bool b10, bool b11) noexcept
Build a NoteSet from 12 boolean flags (b0 = C, b1 = C#/Db, ... b11 = B).
constexpr std::optional< int > parseNote(std::string_view s) noexcept
Parse a simple note name (no octave) into a pitch-class (0..11).
constexpr bool hasTags(ChordTag tags, ChordTag wanted) noexcept
Tests whether tags contains ALL flags in wanted.
constexpr NoteSet scaleAtRoot(NoteSet base, int root) noexcept
Circularly rotate a NoteSet so it becomes rooted at a specific key.
constexpr std::array< const Scale *, 16 > scalesForChord(const Chord &chord) noexcept
Reverse lookup wrapper: find scales that can contain a specific Chord recipe.
ChordTag
Bitmask flags describing chord "families" characteristic of a scale.
@ Sus4Triad
Contains a suspended-4 triad.
@ MinorTriad
Contains a minor triad (e.g., C-Eb-G).
@ Dim7
Contains a fully-diminished 7th chord.
@ Major7
Contains a major 7th chord.
@ Dominant9
Contains a dominant 9th chord.
@ Dominant11
Contains a dominant 11th chord.
@ MajorTriad
Contains a major triad (e.g., C-E-G).
@ Sus2Triad
Contains a suspended-2 triad.
@ All
Convenience: all flags set.
@ Dominant7
Contains a dominant 7th chord.
@ DiminishedTriad
Contains a diminished triad (e.g., C-Eb-Gb).
@ Minor7
Contains a minor 7th chord.
@ Major11
Contains a major 11th chord.
@ AugmentedTriad
Contains an augmented triad (e.g., C-E-G#).
@ Minor9
Contains a minor 9th chord.
@ Major9
Contains a major 9th chord.
@ HalfDim7
Contains a half-diminished 7th chord.
constexpr ChordTag operator|(ChordTag lhs, ChordTag rhs) noexcept
Bitwise OR operator for ChordTag flags.
constexpr DiatonicChord diatonicChord(const Scale &sc, Degree degree, ChordLevel level) noexcept
Generates the diatonic chord built upon a specific degree of a scale.
constexpr std::array< std::string_view, 12 > sharpNames
Main namespace for the DSPark framework.
A chord "recipe" defining the required intervals relative to its root.
std::array< int, 7 > intervals
Intervals in semitones from root. -1 = not present.
Contains the structure and generated symbol name of a diatonic chord.
constexpr std::string_view view() const noexcept
Safely obtain a string_view of the internal name buffer.
std::array< int, 7 > intervals
Intervals in semitones (R-3-5-7-9-11-13). -1 = absent.
std::array< char, 24 > name
Descriptor holding the name, pitch mask and chord tags for a musical scale.
NoteSet mask
12-bit mask with set bits for the scale degrees.
std::string_view name
Human-readable scale name (root = C in the database).
ChordTag tags
Flags describing chord families present in the scale.