33#include <initializer_list>
101 using U = std::underlying_type_t<ChordTag>;
102 return static_cast<ChordTag>(
static_cast<U
>(lhs) |
static_cast<U
>(rhs));
111 using U = std::underlying_type_t<ChordTag>;
112 return static_cast<ChordTag>(
static_cast<U
>(lhs) &
static_cast<U
>(rhs));
120 using U = std::underlying_type_t<ChordTag>;
121 return (
static_cast<U
>(tags) &
static_cast<U
>(wanted)) ==
static_cast<U
>(wanted);
147 bool b0,
bool b1,
bool b2,
bool b3,
148 bool b4,
bool b5,
bool b6,
bool b7,
149 bool b8,
bool b9,
bool b10,
bool b11)
noexcept
151 return (
static_cast<NoteSet>(b0) << 0) | (
static_cast<NoteSet>(b1) << 1) |
152 (
static_cast<NoteSet>(b2) << 2) | (
static_cast<NoteSet>(b3) << 3) |
153 (
static_cast<NoteSet>(b4) << 4) | (
static_cast<NoteSet>(b5) << 5) |
154 (
static_cast<NoteSet>(b6) << 6) | (
static_cast<NoteSet>(b7) << 7) |
155 (
static_cast<NoteSet>(b8) << 8) | (
static_cast<NoteSet>(b9) << 9) |
156 (
static_cast<NoteSet>(b10) << 10) | (
static_cast<NoteSet>(b11) << 11);
166 for (
int d : degrees) {
167 if (d >= 0) { m |=
static_cast<NoteSet>(1u << (d % 12)); }
177 constexpr void copy(
char* dst, std::string_view src, std::size_t dstCapacity)
noexcept
179 std::size_t toCopy = src.size();
180 if (toCopy + 1u > dstCapacity) { toCopy = (dstCapacity > 0) ? dstCapacity - 1u : 0u; }
181 for (std::size_t i = 0; i < toCopy; ++i) { dst[i] = src[i]; }
182 if (dstCapacity > 0) { dst[toCopy] =
'\0'; }
200 std::array<int, 12> temp{};
202 for (
int i = 0; i < 12; ++i) {
203 if (mask & (1u << i)) temp[tcount++] = i;
206 std::array<int, 7> out{};
208 for (
int i = 0; i < 7; ++i) out[i] = i;
212 for (
int i = 0; i < 7; ++i) {
213 int idx = i % tcount;
214 int octave = i / tcount;
215 out[i] = temp[idx] + octave * 12;
223 [[nodiscard]]
constexpr int interval(
const std::array<int, 7>& deg,
int degIdx,
int skip)
noexcept
226 int b = deg[(degIdx + skip) % 7];
227 while (b <= a) { b += 12; }
236 inline constexpr std::array<Scale, 61>
allScales = [](){
237 std::array<Scale, 61> scales{};
293 scales[40] = {
"Chromatic",
makeMask({0,1,2,3,4,5,6,7,8,9,10,11}),
ChordTag::All};
320 inline constexpr std::array<std::string_view, 12>
sharpNames{
321 "C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B"
323 inline constexpr std::array<std::string_view, 12>
flatNames{
324 "C",
"Db",
"D",
"Eb",
"E",
"F",
"Gb",
"G",
"Ab",
"A",
"Bb",
"B"
330 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1
343 [[nodiscard]]
constexpr std::string_view
noteName(
int midi,
int root = 0) noexcept
345 int idx = (midi % 12 + 12) % 12;
346 int r = (root % 12 + 12) % 12;
355 [[nodiscard]]
constexpr std::optional<int>
parseNote(std::string_view s)
noexcept
360 constexpr std::array<std::pair<std::string_view,int>, 21> lut{{
363 {
"D",2}, {
"D#",3}, {
"Eb",3},
364 {
"E",4}, {
"E#",5}, {
"Fb",4},
365 {
"F",5}, {
"F#",6}, {
"Gb",6},
366 {
"G",7}, {
"G#",8}, {
"Ab",8},
367 {
"A",9}, {
"A#",10},{
"Bb",10},
371 for (
const auto& [name,val] : lut) {
372 if (name.size() == s.size() &&
373 std::equal(name.begin(), name.end(), s.begin(),
374 [](
char a,
char b){ return (a|32) == (b|32); })) {
393 root = (root % 12 + 12) % 12;
394 std::uint32_t b =
static_cast<std::uint32_t
>(base) & 0x0FFFu;
395 std::uint32_t res = ((b << root) | (b >> (12 - root))) & 0x0FFFu;
396 return static_cast<NoteSet>(res);
414 {
"Major", {0, 4, 7, -1, -1, -1, -1}},
415 {
"Minor", {0, 3, 7, -1, -1, -1, -1}},
416 {
"Diminished", {0, 3, 6, -1, -1, -1, -1}},
417 {
"Augmented", {0, 4, 8, -1, -1, -1, -1}},
418 {
"Major7", {0, 4, 7, 11, -1, -1, -1}},
419 {
"Dominant7", {0, 4, 7, 10, -1, -1, -1}},
420 {
"Minor7", {0, 3, 7, 10, -1, -1, -1}},
421 {
"HalfDim7", {0, 3, 6, 10, -1, -1, -1}},
422 {
"Dim7", {0, 3, 6, 9, -1, -1, -1}},
423 {
"Sus2", {0, 2, 7, -1, -1, -1, -1}},
424 {
"Sus4", {0, 5, 7, -1, -1, -1, -1}},
425 {
"Major9", {0, 4, 7, 11, 14, -1, -1}},
426 {
"Dominant9", {0, 4, 7, 10, 14, -1, -1}},
427 {
"Minor9", {0, 3, 7, 10, 14, -1, -1}},
428 {
"Major13", {0, 4, 7, 11, 14, 17, 21}}
442 [[nodiscard]]
constexpr std::array<int, 7>
445 std::array<int, 7> notes{};
448 for (
int i = 0; i < 7; ++i) {
451 notes[count++] = rootMidi + deg;
454 if (count == 0)
return { -1,-1,-1,-1,-1,-1,-1 };
457 inversion = (inversion >= 0) ? (inversion % count) : 0;
458 for (
int i = 0; i < inversion; ++i) { notes[i] += 12; }
459 std::sort(notes.begin(), notes.begin() + count);
462 for (
int i = count; i < 7; ++i) { notes[i] = -1; }
471 [[nodiscard]]
constexpr std::array<const Scale*, 16>
474 std::array<const Scale*, 16> out{};
477 if (((s.mask & chordMask) == chordMask) && idx < out.size()) {
489 [[nodiscard]]
constexpr std::array<const Scale*, 16>
493 for (
int d : chord.intervals) {
494 if (d >= 0) { chordMask |=
static_cast<NoteSet>(1u << (d % 12)); }
530 [[nodiscard]]
constexpr std::string_view
view() const noexcept
533 while (len <
name.size() &&
name[len] !=
'\0') { ++len; }
534 return std::string_view(
name.data(), len);
559 [[nodiscard]]
constexpr DiatonicChord
563 if (degree < 0 || degree >= 7)
return {};
579 auto buildName = [&](std::string_view baseName,
ChordLevel lvl)
584 pos += baseName.size();
590 detail::copy(c.name.data(), std::string_view(buf, pos), c.name.size());
593 std::string_view base;
594 if (third == 4 && fifth == 7) base =
"M";
595 else if (third == 3 && fifth == 7) base =
"m";
596 else if (third == 3 && fifth == 6) base =
"dim";
597 else if (third == 4 && fifth == 8) base =
"aug";
600 std::string_view name7;
601 if (base ==
"dim" && seventh == 10) name7 =
"m7b5";
602 else if (base ==
"dim" && seventh == 9) name7 =
"dim7";
603 else if (base ==
"M" && seventh == 10) name7 =
"7";
604 else if (base ==
"M" && seventh == 11) name7 =
"maj7";
605 else if (base ==
"m" && seventh == 10) name7 =
"m7";
606 else if (base ==
"m" && seventh == 11) name7 =
"m(maj7)";
607 else if (base ==
"aug" && seventh == 10) name7 =
"aug7";
628 [[nodiscard]]
constexpr std::array<int, 7>
631 std::array<int, 7> notes{};
632 for (std::size_t i = 0; i < 7; ++i) {
633 notes[i] = (c.intervals[i] >= 0) ? rootMidi + c.intervals[i] : -1;
649 std::size_t len = note.size();
650 while (len > 0 && note[len - 1] >=
'0' && note[len - 1] <=
'9') { --len; }
651 if (len > 0 && note[len - 1] ==
'-') { --len; }
653 std::string_view baseNote = note.substr(0, len);
664 if (note.empty())
return 4;
665 std::size_t len = note.size();
667 while (len > 0 && note[len - 1] >=
'0' && note[len - 1] <=
'9') { --len; }
669 bool isNegative =
false;
670 if (len > 0 && note[len - 1] ==
'-') {
675 if (len < note.size() && !(isNegative && len + 1 == note.size())) {
677 std::size_t start = isNegative ? len + 1 : len;
678 for (std::size_t i = start; i < note.size(); ++i) {
679 parsed = parsed * 10 + (note[i] -
'0');
681 return isNegative ? -parsed : parsed;
695 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.