@musodojo/music-theory-data 17.0.0 → 19.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,9 +13,9 @@ more.**
13
13
  ## Verified Note Collections
14
14
 
15
15
  - ✅ Diatonic Modes
16
- - 🛠️ Pentatonic Variants
17
- - 🛠️ Major Variants
18
- - 🛠️ Minor Variants
16
+ - Pentatonic Variants
17
+ - Major Variants
18
+ - Minor Variants
19
19
  - 🛠️ Dominant Variants
20
20
  - ✅ Harmonic Minor Modes
21
21
  - ✅ Melodic Minor Modes
@@ -38,16 +38,16 @@ more.**
38
38
 
39
39
  ### Deno / JSR
40
40
 
41
- Import directly from JSR in your Deno project:
41
+ Install the package from the JSR registry:
42
42
 
43
- ```ts
44
- import * as music_theory_data from "@musodojo/music-theory-data";
43
+ ```bash
44
+ deno add jsr:@musodojo/music-theory-data
45
45
  ```
46
46
 
47
- or
47
+ Then import it into your project:
48
48
 
49
49
  ```ts
50
- import * as music_theory_data from "jsr:@musodojo/music-theory-data";
50
+ import * as music_theory_data from "@musodojo/music-theory-data";
51
51
  ```
52
52
 
53
53
  ### Node.js / npm
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@musodojo/music-theory-data",
3
- "version": "17.0.0",
3
+ "version": "19.0.1",
4
4
  "description": "The musician-friendly TypeScript library for modes, scales, chords, and more.",
5
5
  "keywords": [
6
6
  "music",
@@ -1,4 +1,4 @@
1
- export type NoteInteger = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
1
+ export type RootNoteInteger = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
2
2
 
3
3
  const _noteLetters = [
4
4
  "C",
@@ -53,15 +53,16 @@ export const noteNames: readonly NoteName[] = enharmonicNoteNameGroups.flat();
53
53
 
54
54
  export const noteNamesSet: ReadonlySet<NoteName> = new Set(noteNames);
55
55
 
56
- export const noteNameToIntegerMap: ReadonlyMap<NoteName, NoteInteger> = (() => {
57
- const map = new Map<NoteName, NoteInteger>();
58
- _enharmonicNoteNameGroups.forEach((group, index) => {
59
- group.forEach((note) => {
60
- map.set(note, index as NoteInteger);
56
+ export const noteNameToIntegerMap: ReadonlyMap<NoteName, RootNoteInteger> =
57
+ (() => {
58
+ const map = new Map<NoteName, RootNoteInteger>();
59
+ _enharmonicNoteNameGroups.forEach((group, index) => {
60
+ group.forEach((note) => {
61
+ map.set(note, index as RootNoteInteger);
62
+ });
61
63
  });
62
- });
63
- return map;
64
- })();
64
+ return map;
65
+ })();
65
66
 
66
67
  const _enharmonicRootNoteGroups = [
67
68
  ["C", "B♯"],
@@ -87,15 +88,16 @@ export const rootNotes: readonly RootNote[] = enharmonicRootNoteGroups.flat();
87
88
 
88
89
  export const rootNotesSet: ReadonlySet<RootNote> = new Set(rootNotes);
89
90
 
90
- export const rootNoteToIntegerMap: ReadonlyMap<RootNote, NoteInteger> = (() => {
91
- const map = new Map<RootNote, NoteInteger>();
92
- enharmonicRootNoteGroups.forEach((group, index) => {
93
- group.forEach((note) => {
94
- map.set(note, index as NoteInteger);
91
+ export const rootNoteToIntegerMap: ReadonlyMap<RootNote, RootNoteInteger> =
92
+ (() => {
93
+ const map = new Map<RootNote, RootNoteInteger>();
94
+ enharmonicRootNoteGroups.forEach((group, index) => {
95
+ group.forEach((note) => {
96
+ map.set(note, index as RootNoteInteger);
97
+ });
95
98
  });
96
- });
97
- return map;
98
- })();
99
+ return map;
100
+ })();
99
101
 
100
102
  export type SimpleIntervalNumber =
101
103
  | "1"
@@ -1,8 +1,9 @@
1
- import type { ScaleCollection } from "../../types/note-collections.d.ts";
1
+ import type { ModalScaleCollection } from "../../types/note-collections.d.ts";
2
2
 
3
- const ionian: ScaleCollection = {
3
+ const ionian: ModalScaleCollection = {
4
4
  category: "scale",
5
5
  rotation: 0,
6
+ parentScale: "ionian",
6
7
  primaryName: "Major",
7
8
  names: [
8
9
  "Major",
@@ -41,9 +42,10 @@ const ionian: ScaleCollection = {
41
42
  patternShort: ["W", "W", "H", "W", "W", "W", "H"],
42
43
  } as const;
43
44
 
44
- const dorian: ScaleCollection = {
45
+ const dorian: ModalScaleCollection = {
45
46
  category: "scale",
46
47
  rotation: 1,
48
+ parentScale: "ionian",
47
49
  primaryName: "Dorian",
48
50
  names: [
49
51
  "Dorian",
@@ -80,9 +82,10 @@ const dorian: ScaleCollection = {
80
82
  patternShort: ["W", "H", "W", "W", "W", "H", "W"],
81
83
  } as const;
82
84
 
83
- const phrygian: ScaleCollection = {
85
+ const phrygian: ModalScaleCollection = {
84
86
  category: "scale",
85
87
  rotation: 2,
88
+ parentScale: "ionian",
86
89
  primaryName: "Phrygian",
87
90
  names: [
88
91
  "Phrygian",
@@ -117,9 +120,10 @@ const phrygian: ScaleCollection = {
117
120
  patternShort: ["H", "W", "W", "W", "H", "W", "W"],
118
121
  } as const;
119
122
 
120
- const lydian: ScaleCollection = {
123
+ const lydian: ModalScaleCollection = {
121
124
  category: "scale",
122
125
  rotation: 3,
126
+ parentScale: "ionian",
123
127
  primaryName: "Lydian",
124
128
  names: ["Lydian", "Major ♯4", "Lydian Mode"],
125
129
  intervals: ["1", "2", "3", "♯4", "5", "6", "7", "8"],
@@ -148,9 +152,10 @@ const lydian: ScaleCollection = {
148
152
  patternShort: ["W", "W", "W", "H", "W", "W", "H"],
149
153
  } as const;
150
154
 
151
- const mixolydian: ScaleCollection = {
155
+ const mixolydian: ModalScaleCollection = {
152
156
  category: "scale",
153
157
  rotation: 4,
158
+ parentScale: "ionian",
154
159
  primaryName: "Mixolydian",
155
160
  names: [
156
161
  "Mixolydian",
@@ -185,9 +190,10 @@ const mixolydian: ScaleCollection = {
185
190
  patternShort: ["W", "W", "H", "W", "W", "H", "W"],
186
191
  } as const;
187
192
 
188
- const aeolian: ScaleCollection = {
193
+ const aeolian: ModalScaleCollection = {
189
194
  category: "scale",
190
195
  rotation: 5,
196
+ parentScale: "ionian",
191
197
  primaryName: "Minor",
192
198
  names: [
193
199
  "Minor",
@@ -223,9 +229,10 @@ const aeolian: ScaleCollection = {
223
229
  patternShort: ["W", "H", "W", "W", "H", "W", "W"],
224
230
  } as const;
225
231
 
226
- const locrian: ScaleCollection = {
232
+ const locrian: ModalScaleCollection = {
227
233
  category: "scale",
228
234
  rotation: 6,
235
+ parentScale: "ionian",
229
236
  primaryName: "Locrian",
230
237
  names: [
231
238
  "Locrian",
@@ -270,5 +277,5 @@ const _diatonicModes = {
270
277
 
271
278
  export type DiatonicModeKey = keyof typeof _diatonicModes;
272
279
 
273
- export const diatonicModes: Record<DiatonicModeKey, ScaleCollection> =
280
+ export const diatonicModes: Record<DiatonicModeKey, ModalScaleCollection> =
274
281
  _diatonicModes;
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ChordCollection,
3
+ ModalScaleCollection,
3
4
  NoteCollection,
4
- ScaleCollection,
5
5
  } from "../../types/note-collections.d.ts";
6
6
 
7
7
  const diminishedTriad: ChordCollection = {
@@ -40,9 +40,10 @@ const halfDiminished7: ChordCollection = {
40
40
  patternShort: ["m3", "m3", "M3"],
41
41
  } as const;
42
42
 
43
- const wholeHalfDiminished: ScaleCollection = {
43
+ const wholeHalfDiminished: ModalScaleCollection = {
44
44
  category: "scale",
45
45
  rotation: 0,
46
+ parentScale: "wholeHalfDiminished",
46
47
  primaryName: "Whole Half Diminished",
47
48
  names: ["Whole Half Diminished"],
48
49
  intervals: ["1", "2", "♭3", "4", "♭5", "♭6", "6", "7", "8"],
@@ -53,9 +54,10 @@ const wholeHalfDiminished: ScaleCollection = {
53
54
  patternShort: ["W", "H", "W", "H", "W", "H", "W", "H"],
54
55
  } as const;
55
56
 
56
- const halfWholeDiminished: ScaleCollection = {
57
+ const halfWholeDiminished: ModalScaleCollection = {
57
58
  category: "scale",
58
59
  rotation: 1,
60
+ parentScale: "wholeHalfDiminished",
59
61
  primaryName: "Half Whole Diminished",
60
62
  names: ["Half Whole Diminished", "Dominant Diminished"],
61
63
  intervals: ["1", "♭2", "♭3", "3", "♯4", "5", "6", "♭7", "8"],
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ChordCollection,
3
+ NonModalScaleCollection,
3
4
  NoteCollection,
4
- ScaleCollection,
5
5
  } from "../../types/note-collections.d.ts";
6
6
 
7
7
  const dominant7: ChordCollection = {
@@ -105,7 +105,7 @@ const dominant13: ChordCollection = {
105
105
  patternShort: ["M3", "m3", "m3", "M3", "m3", "m3"],
106
106
  } as const;
107
107
 
108
- const dominantPentatonic: ScaleCollection = {
108
+ const dominantPentatonic: NonModalScaleCollection = {
109
109
  category: "scale",
110
110
  primaryName: "Dominant Pentatonic",
111
111
  names: ["Dominant Pentatonic"],
@@ -1,8 +1,9 @@
1
- import type { ScaleCollection } from "../../types/note-collections.d.ts";
1
+ import type { ModalScaleCollection } from "../../types/note-collections.d.ts";
2
2
 
3
- const harmonicMinor: ScaleCollection = {
3
+ const harmonicMinor: ModalScaleCollection = {
4
4
  category: "scale",
5
5
  rotation: 0,
6
+ parentScale: "harmonicMinor",
6
7
  primaryName: "Harmonic Minor",
7
8
  names: [
8
9
  "Harmonic Minor",
@@ -34,9 +35,10 @@ const harmonicMinor: ScaleCollection = {
34
35
  patternShort: ["W", "H", "W", "W", "H", "A2", "H"],
35
36
  } as const;
36
37
 
37
- const locrianNatural6: ScaleCollection = {
38
+ const locrianNatural6: ModalScaleCollection = {
38
39
  category: "scale",
39
40
  rotation: 1,
41
+ parentScale: "harmonicMinor",
40
42
  primaryName: "Locrian ♮6",
41
43
  names: ["Locrian ♮6", "Locrian Natural Sixth", "Locrian Raised Sixth"],
42
44
  intervals: ["1", "♭2", "♭3", "4", "♭5", "6", "♭7", "8"],
@@ -61,9 +63,10 @@ const locrianNatural6: ScaleCollection = {
61
63
  patternShort: ["H", "W", "W", "H", "A2", "H", "W"],
62
64
  } as const;
63
65
 
64
- const ionianSharp5: ScaleCollection = {
66
+ const ionianSharp5: ModalScaleCollection = {
65
67
  category: "scale",
66
68
  rotation: 2,
69
+ parentScale: "harmonicMinor",
67
70
  primaryName: "Ionian ♯5",
68
71
  names: [
69
72
  "Ionian ♯5",
@@ -100,9 +103,10 @@ const ionianSharp5: ScaleCollection = {
100
103
  patternShort: ["W", "W", "H", "A2", "H", "W", "H"],
101
104
  } as const;
102
105
 
103
- const dorianSharp4: ScaleCollection = {
106
+ const dorianSharp4: ModalScaleCollection = {
104
107
  category: "scale",
105
108
  rotation: 3,
109
+ parentScale: "harmonicMinor",
106
110
  primaryName: "Dorian ♯4",
107
111
  names: [
108
112
  "Dorian ♯4",
@@ -133,9 +137,10 @@ const dorianSharp4: ScaleCollection = {
133
137
  patternShort: ["W", "H", "A2", "H", "W", "H", "W"],
134
138
  } as const;
135
139
 
136
- const phrygianDominant: ScaleCollection = {
140
+ const phrygianDominant: ModalScaleCollection = {
137
141
  category: "scale",
138
142
  rotation: 4,
143
+ parentScale: "harmonicMinor",
139
144
  primaryName: "Phrygian Dominant",
140
145
  names: [
141
146
  "Phrygian Dominant",
@@ -172,9 +177,10 @@ const phrygianDominant: ScaleCollection = {
172
177
  patternShort: ["H", "A2", "H", "W", "H", "W", "W"],
173
178
  } as const;
174
179
 
175
- const lydianSharp2: ScaleCollection = {
180
+ const lydianSharp2: ModalScaleCollection = {
176
181
  category: "scale",
177
182
  rotation: 5,
183
+ parentScale: "harmonicMinor",
178
184
  primaryName: "Lydian ♯2",
179
185
  names: [
180
186
  "Lydian ♯2",
@@ -204,9 +210,10 @@ const lydianSharp2: ScaleCollection = {
204
210
  patternShort: ["A2", "H", "W", "H", "W", "W", "H"],
205
211
  } as const;
206
212
 
207
- const superLocrianDoubleFlat7: ScaleCollection = {
213
+ const superLocrianDoubleFlat7: ModalScaleCollection = {
208
214
  category: "scale",
209
215
  rotation: 6,
216
+ parentScale: "harmonicMinor",
210
217
  primaryName: "Super Locrian 𝄫7",
211
218
  names: [
212
219
  "Super Locrian 𝄫7",
@@ -248,5 +255,7 @@ export const _harmonicMinorModes = {
248
255
 
249
256
  export type HarmonicMinorModeKey = keyof typeof _harmonicMinorModes;
250
257
 
251
- export const harmonicMinorModes: Record<HarmonicMinorModeKey, ScaleCollection> =
252
- _harmonicMinorModes;
258
+ export const harmonicMinorModes: Record<
259
+ HarmonicMinorModeKey,
260
+ ModalScaleCollection
261
+ > = _harmonicMinorModes;
@@ -61,7 +61,7 @@ const major9: ChordCollection = {
61
61
  primaryName: "M9",
62
62
  names: ["M9", "maj9", "Major 9th", "Major Ninth", "Δ9"],
63
63
  intervals: ["1", "3", "5", "7", "9"],
64
- integers: [0, 2, 4, 7, 11],
64
+ integers: [0, 4, 7, 11, 14],
65
65
  type: ["major", "chord", "arpeggio", "pentad"],
66
66
  characteristics: [
67
67
  "stable",
@@ -81,7 +81,7 @@ const majorAdd9: ChordCollection = {
81
81
  primaryName: "add9",
82
82
  names: ["add9", "maj(add9)", "M(add9)", "Major add 9"],
83
83
  intervals: ["1", "3", "5", "9"],
84
- integers: [0, 2, 4, 7],
84
+ integers: [0, 4, 7, 14],
85
85
  type: ["major", "chord", "arpeggio", "tetrad"],
86
86
  characteristics: [
87
87
  "stable",
@@ -101,7 +101,7 @@ const major6Add9: ChordCollection = {
101
101
  primaryName: "6/9",
102
102
  names: ["6/9", "M6/9", "maj6/9", "Major 6/9", "6add9", "Major add 6 add 9"],
103
103
  intervals: ["1", "3", "5", "6", "9"],
104
- integers: [0, 2, 4, 7, 9],
104
+ integers: [0, 4, 7, 9, 14],
105
105
  type: ["major", "chord", "arpeggio", "pentad"],
106
106
  characteristics: [
107
107
  "stable",
@@ -1,8 +1,9 @@
1
- import type { ScaleCollection } from "../../types/note-collections.d.ts";
1
+ import type { ModalScaleCollection } from "../../types/note-collections.d.ts";
2
2
 
3
- const melodicMinor: ScaleCollection = {
3
+ const melodicMinor: ModalScaleCollection = {
4
4
  category: "scale",
5
5
  rotation: 0,
6
+ parentScale: "melodicMinor",
6
7
  primaryName: "Melodic Minor",
7
8
  names: [
8
9
  "Melodic Minor",
@@ -33,9 +34,10 @@ const melodicMinor: ScaleCollection = {
33
34
  patternShort: ["W", "H", "W", "W", "W", "W", "H"],
34
35
  } as const;
35
36
 
36
- const dorianFlat2: ScaleCollection = {
37
+ const dorianFlat2: ModalScaleCollection = {
37
38
  category: "scale",
38
39
  rotation: 1,
40
+ parentScale: "melodicMinor",
39
41
  primaryName: "Dorian ♭2",
40
42
  names: [
41
43
  "Dorian ♭2",
@@ -59,9 +61,10 @@ const dorianFlat2: ScaleCollection = {
59
61
  patternShort: ["H", "W", "W", "W", "W", "H", "W"],
60
62
  } as const;
61
63
 
62
- const lydianAugmented: ScaleCollection = {
64
+ const lydianAugmented: ModalScaleCollection = {
63
65
  category: "scale",
64
66
  rotation: 2,
67
+ parentScale: "melodicMinor",
65
68
  primaryName: "Lydian Augmented",
66
69
  names: ["Lydian Augmented", "Lydian ♯5", "Lydian Sharp Fifth"],
67
70
  intervals: ["1", "2", "3", "♯4", "♯5", "6", "7", "8"],
@@ -86,9 +89,10 @@ const lydianAugmented: ScaleCollection = {
86
89
  patternShort: ["W", "W", "W", "W", "H", "W", "H"],
87
90
  } as const;
88
91
 
89
- const lydianDominant: ScaleCollection = {
92
+ const lydianDominant: ModalScaleCollection = {
90
93
  category: "scale",
91
94
  rotation: 3,
95
+ parentScale: "melodicMinor",
92
96
  primaryName: "Lydian Dominant",
93
97
  names: [
94
98
  "Lydian Dominant",
@@ -116,9 +120,10 @@ const lydianDominant: ScaleCollection = {
116
120
  patternShort: ["W", "W", "W", "H", "W", "H", "W"],
117
121
  } as const;
118
122
 
119
- const mixolydianFlat6: ScaleCollection = {
123
+ const mixolydianFlat6: ModalScaleCollection = {
120
124
  category: "scale",
121
125
  rotation: 4,
126
+ parentScale: "melodicMinor",
122
127
  primaryName: "Mixolydian ♭6",
123
128
  names: [
124
129
  "Mixolydian ♭6",
@@ -143,9 +148,10 @@ const mixolydianFlat6: ScaleCollection = {
143
148
  patternShort: ["W", "W", "H", "W", "H", "W", "W"],
144
149
  } as const;
145
150
 
146
- const aeolianFlat5: ScaleCollection = {
151
+ const aeolianFlat5: ModalScaleCollection = {
147
152
  category: "scale",
148
153
  rotation: 5,
154
+ parentScale: "melodicMinor",
149
155
  primaryName: "Aeolian ♭5",
150
156
  names: [
151
157
  "Aeolian ♭5",
@@ -170,9 +176,10 @@ const aeolianFlat5: ScaleCollection = {
170
176
  patternShort: ["W", "H", "W", "H", "W", "W", "W"],
171
177
  } as const;
172
178
 
173
- const altered: ScaleCollection = {
179
+ const altered: ModalScaleCollection = {
174
180
  category: "scale",
175
181
  rotation: 6,
182
+ parentScale: "melodicMinor",
176
183
  primaryName: "Altered Scale",
177
184
  names: [
178
185
  "Altered Scale",
@@ -208,5 +215,7 @@ export const _melodicMinorModes = {
208
215
 
209
216
  export type MelodicMinorModeKey = keyof typeof _melodicMinorModes;
210
217
 
211
- export const melodicMinorModes: Record<MelodicMinorModeKey, ScaleCollection> =
212
- _melodicMinorModes;
218
+ export const melodicMinorModes: Record<
219
+ MelodicMinorModeKey,
220
+ ModalScaleCollection
221
+ > = _melodicMinorModes;
@@ -46,12 +46,35 @@ const minor7: ChordCollection = {
46
46
  patternShort: ["m3", "M3", "m3"],
47
47
  } as const;
48
48
 
49
+ const minorMajor7: ChordCollection = {
50
+ category: "chord",
51
+ primaryName: "m(M7)",
52
+ names: [
53
+ "m(M7)",
54
+ "min(M7)",
55
+ "Minor (Major 7th)",
56
+ "Minor Major Seventh",
57
+ "mM7",
58
+ "-M7",
59
+ "-(maj7)",
60
+ ],
61
+ intervals: ["1", "♭3", "5", "7"],
62
+ integers: [0, 3, 7, 11],
63
+ type: ["minor", "chord", "arpeggio", "tetrad"],
64
+ characteristics: [
65
+ "smooth",
66
+ "jazzy",
67
+ ],
68
+ pattern: ["minor third", "major third", "major third"],
69
+ patternShort: ["m3", "M3", "M3"],
70
+ };
71
+
49
72
  const minor9: ChordCollection = {
50
73
  category: "chord",
51
74
  primaryName: "m9",
52
75
  names: ["m9", "min9", "Minor 9th", "Minor Ninth", "-9"],
53
76
  intervals: ["1", "♭3", "5", "♭7", "9"],
54
- integers: [0, 2, 3, 7, 10],
77
+ integers: [0, 3, 7, 10, 14],
55
78
  type: ["minor", "chord", "arpeggio", "pentad"],
56
79
  characteristics: ["rich", "lush", "sophisticated", "common in jazz"],
57
80
  pattern: ["minor third", "major third", "minor third", "major third"],
@@ -63,7 +86,7 @@ const minorAdd9: ChordCollection = {
63
86
  primaryName: "m(add9)",
64
87
  names: ["m(add9)", "min(add9)", "Minor add 9"],
65
88
  intervals: ["1", "♭3", "5", "9"],
66
- integers: [0, 2, 3, 7],
89
+ integers: [0, 3, 7, 14],
67
90
  type: ["minor", "chord", "arpeggio", "tetrad"],
68
91
  characteristics: [
69
92
  "open",
@@ -78,9 +101,9 @@ const minorAdd9: ChordCollection = {
78
101
  const minor6Add9: ChordCollection = {
79
102
  category: "chord",
80
103
  primaryName: "m6/9",
81
- names: ["m6/9", "min6/9", "Minor 6/9"],
104
+ names: ["m6/9", "min6/9", "Minor 6/9", "-6/9"],
82
105
  intervals: ["1", "♭3", "5", "6", "9"],
83
- integers: [0, 2, 3, 7, 9],
106
+ integers: [0, 3, 7, 9, 14],
84
107
  type: ["minor", "chord", "arpeggio", "pentad"],
85
108
  characteristics: [
86
109
  "rich",
@@ -96,6 +119,7 @@ export const _minorVariants = {
96
119
  minor,
97
120
  minor6,
98
121
  minor7,
122
+ minorMajor7,
99
123
  minor9,
100
124
  minorAdd9,
101
125
  minor6Add9,
@@ -49,11 +49,8 @@ const chromatic: ScaleCollection = {
49
49
 
50
50
  const wholeTone: ScaleCollection = {
51
51
  category: "scale",
52
- rotation: 0,
53
52
  primaryName: "Whole Tone Scale",
54
- names: [
55
- "Whole Tone Scale",
56
- ],
53
+ names: ["Whole Tone Scale"],
57
54
  intervals: ["1", "2", "3", "♯4", "♯5", "♯6", "8"],
58
55
  integers: [0, 2, 4, 6, 8, 10],
59
56
  type: ["whole tone", "scale", "symmetrical", "hexatonic"],
@@ -1,8 +1,9 @@
1
- import type { ScaleCollection } from "../../types/note-collections.d.ts";
1
+ import type { ModalScaleCollection } from "../../types/note-collections.d.ts";
2
2
 
3
- const majorPentatonic: ScaleCollection = {
3
+ const majorPentatonic: ModalScaleCollection = {
4
4
  category: "scale",
5
5
  rotation: 0,
6
+ parentScale: "majorPentatonic",
6
7
  primaryName: "Major Pentatonic",
7
8
  names: ["Major Pentatonic"],
8
9
  intervals: ["1", "2", "3", "5", "6", "8"],
@@ -20,48 +21,52 @@ const majorPentatonic: ScaleCollection = {
20
21
  patternShort: ["W", "W", "m3", "W", "m3"],
21
22
  } as const;
22
23
 
23
- const suspendedPentatonic: ScaleCollection = {
24
+ const suspendedPentatonic: ModalScaleCollection = {
24
25
  category: "scale",
25
26
  rotation: 1,
27
+ parentScale: "majorPentatonic",
26
28
  primaryName: "Suspended Pentatonic",
27
29
  names: ["Suspended Pentatonic", "Egyptian Pentatonic"],
28
30
  intervals: ["1", "2", "4", "5", "♭7", "8"],
29
31
  integers: [0, 2, 5, 7, 10],
30
32
  type: ["suspended", "pentatonic", "scale", "gapped scale"],
31
33
  characteristics: ["open", "stable", "neutral", "neither major nor minor"],
32
- pattern: ["whole", "minor third", "whole", "minor third"],
33
- patternShort: ["W", "m3", "W", "m3"],
34
+ pattern: ["whole", "minor third", "whole", "minor third", "whole"],
35
+ patternShort: ["W", "m3", "W", "m3", "W"],
34
36
  } as const;
35
37
 
36
- const bluesMinorPentatonic: ScaleCollection = {
38
+ const bluesMinorPentatonic: ModalScaleCollection = {
37
39
  category: "scale",
38
40
  rotation: 2,
41
+ parentScale: "majorPentatonic",
39
42
  primaryName: "Blues Minor Pentatonic",
40
43
  names: ["Blues Minor Pentatonic"],
41
- intervals: ["1", "♭3", "4", "♭5", "♭7", "8"],
44
+ intervals: ["1", "♭3", "4", "♭6", "♭7", "8"],
42
45
  integers: [0, 3, 5, 8, 10],
43
46
  type: ["minor", "pentatonic", "scale", "gapped scale", "blues"],
44
- characteristics: ["bluesy", "tense", "minor with a blue note"],
45
- pattern: ["minor third", "whole", "half", "minor third"],
46
- patternShort: ["m3", "W", "H", "m3"],
47
+ characteristics: ["bluesy", "tense"],
48
+ pattern: ["minor third", "whole", "minor third", "whole", "whole"],
49
+ patternShort: ["m3", "W", "m3", "W", "W"],
47
50
  } as const;
48
51
 
49
- const bluesMajorPentatonic: ScaleCollection = {
52
+ const bluesMajorPentatonic: ModalScaleCollection = {
50
53
  category: "scale",
51
54
  rotation: 3,
55
+ parentScale: "majorPentatonic",
52
56
  primaryName: "Blues Major Pentatonic",
53
57
  names: ["Blues Major Pentatonic"],
54
- intervals: ["1", "2", "♭3", "5", "6", "8"],
58
+ intervals: ["1", "2", "4", "5", "6", "8"],
55
59
  integers: [0, 2, 5, 7, 9],
56
60
  type: ["major", "pentatonic", "scale", "gapped scale", "blues"],
57
- characteristics: ["bluesy", "country", "major with a blue note"],
58
- pattern: ["whole", "half", "major third", "whole"],
59
- patternShort: ["W", "H", "M3", "W"],
61
+ characteristics: ["bluesy", "country"],
62
+ pattern: ["whole", "minor third", "whole", "whole", "minor third"],
63
+ patternShort: ["W", "m3", "W", "W", "m3"],
60
64
  } as const;
61
65
 
62
- const minorPentatonic: ScaleCollection = {
66
+ const minorPentatonic: ModalScaleCollection = {
63
67
  category: "scale",
64
68
  rotation: 4,
69
+ parentScale: "majorPentatonic",
65
70
  primaryName: "Minor Pentatonic",
66
71
  names: ["Minor Pentatonic"],
67
72
  intervals: ["1", "♭3", "4", "5", "♭7", "8"],
@@ -88,5 +93,7 @@ export const _pentatonicVariants = {
88
93
 
89
94
  export type PentatonicVariantKey = keyof typeof _pentatonicVariants;
90
95
 
91
- export const pentatonicVariants: Record<PentatonicVariantKey, ScaleCollection> =
92
- _pentatonicVariants;
96
+ export const pentatonicVariants: Record<
97
+ PentatonicVariantKey,
98
+ ModalScaleCollection
99
+ > = _pentatonicVariants;
@@ -1,5 +1,3 @@
1
- export type OctaveNumber = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
2
-
3
1
  export type MidiNoteNumber =
4
2
  | 0
5
3
  | 1
@@ -1,4 +1,4 @@
1
- import type { Interval, NoteInteger } from "../data/labels/note-labels.ts";
1
+ import type { Interval } from "../data/labels/note-labels.ts";
2
2
 
3
3
  export type CollectionCategory = "scale" | "chord";
4
4
 
@@ -20,10 +20,11 @@ interface NoteCollectionBase {
20
20
  */
21
21
  readonly intervals: readonly Interval[];
22
22
  /**
23
- * The set of pitch classes as integers (0-11), representing semitones from the root.
24
- * This set never includes the octave.
23
+ * The set of semitone values from the root.
24
+ * - For **scales**, this is a pitch class set (0-11) and does not include the octave.
25
+ * - For **chords**, this can include values > 11 to represent extensions (e.g., 14 for a 9th).
25
26
  */
26
- readonly integers: readonly NoteInteger[];
27
+ readonly integers: readonly number[];
27
28
  /**
28
29
  * An array of tags used for classification and filtering.
29
30
  * e.g., ["major", "scale", "heptatonic", "diatonic mode"]
@@ -48,20 +49,29 @@ interface NoteCollectionBase {
48
49
  readonly patternShort: readonly string[];
49
50
  }
50
51
 
51
- export interface ScaleCollection extends NoteCollectionBase {
52
+ /** A scale that can be thought of as a mode of a parent scale (e.g., Ionian, Dorian, Lydian Augmented). */
53
+ interface ModalScaleCollection extends NoteCollectionBase {
52
54
  /**
53
55
  * The fundamental classification of the collection. For scales, this is always "scale".
54
- * This influences the interpretation of other properties:
55
- * - `intervals` conventionally includes the octave ("8").
56
- * - `pattern` represents steps between consecutive notes.
57
56
  */
58
57
  readonly category: "scale";
59
58
  /**
60
- * The rotation index relative to a parent scale (e.g., for diatonic modes).
61
- * Ionian is 0, Dorian is 1, etc. This property is optional and only applies
62
- * to collections that are modes of another scale.
59
+ * The rotation index for a parent scale is always 0.
60
+ * The rotation index relative to a parent scale. e.g., Dorian is 1.
63
61
  */
64
- readonly rotation?: number;
62
+ readonly rotation: number;
63
+ /**
64
+ * The key-name (e.g., "ionian") of the parent scale from which this mode is derived.
65
+ * The parent scale has itself as parentScale!
66
+ */
67
+ readonly parentScale: string;
68
+ }
69
+
70
+ /** A scale that is not a mode of another scale in this collection. */
71
+ interface NonModalScaleCollection extends NoteCollectionBase {
72
+ readonly category: "scale";
73
+ readonly rotation?: never;
74
+ readonly parentScale?: never;
65
75
  }
66
76
 
67
77
  export interface ChordCollection extends NoteCollectionBase {
@@ -69,4 +79,8 @@ export interface ChordCollection extends NoteCollectionBase {
69
79
  readonly rotation?: never;
70
80
  }
71
81
 
82
+ export type ScaleCollection =
83
+ | ModalScaleCollection
84
+ | NonModalScaleCollection;
85
+
72
86
  export type NoteCollection = ScaleCollection | ChordCollection;
@@ -100,11 +100,6 @@ export function getChordDetailsForDiatonicMode(
100
100
  diatonicModeKey: DiatonicModeKey,
101
101
  ): ChordDetails[] {
102
102
  const mode = diatonicModes[diatonicModeKey];
103
- if (mode.rotation === undefined) {
104
- throw new Error(
105
- `Mode "${diatonicModeKey}" is missing the 'rotation' property.`,
106
- );
107
- }
108
103
  const rotation = mode.rotation;
109
104
  const rotatedTriads = rotateArray(diatonicTriads, rotation);
110
105
  const rotatedSeventhChords = rotateArray(diatonicSeventhChords, rotation);
@@ -121,11 +116,6 @@ export function getChordDetailsForHarmonicMinorMode(
121
116
  harmonicMinorModeKey: HarmonicMinorModeKey,
122
117
  ): ChordDetails[] {
123
118
  const mode = harmonicMinorModes[harmonicMinorModeKey];
124
- if (mode.rotation === undefined) {
125
- throw new Error(
126
- `Mode "${harmonicMinorModeKey}" is missing the 'rotation' property.`,
127
- );
128
- }
129
119
  const rotation = mode.rotation;
130
120
  const rotatedTriads = rotateArray(harmonicMinorTriads, rotation);
131
121
  const rotatedSeventhChords = rotateArray(
@@ -145,11 +135,6 @@ export function getChordDetailsForMelodicMinorMode(
145
135
  melodicMinorModeKey: MelodicMinorModeKey,
146
136
  ): ChordDetails[] {
147
137
  const mode = melodicMinorModes[melodicMinorModeKey];
148
- if (mode.rotation === undefined) {
149
- throw new Error(
150
- `Mode "${melodicMinorModeKey}" is missing the 'rotation' property.`,
151
- );
152
- }
153
138
  const rotation = mode.rotation;
154
139
  const rotatedTriads = rotateArray(melodicMinorTriads, rotation);
155
140
  const rotatedSeventhChords = rotateArray(melodicMinorSeventhChords, rotation);
@@ -1,6 +1,6 @@
1
1
  import type { Interval } from "../data/labels/note-labels.ts";
2
2
  import type { MidiNoteNumber, MidiNoteSequence } from "../types/midi.d.ts";
3
- import { rootMidiAndIntervalToMidi } from "./midi.ts";
3
+ import { noteMidiAndIntervalToMidi } from "./midi.ts";
4
4
 
5
5
  export type MidiNoteSequenceDirection =
6
6
  | "ascending"
@@ -50,7 +50,7 @@ function getMonotonicMidiNoteSequence(
50
50
  intervalIndex = (startFromIndex + i) % intervalsLength;
51
51
  octaveOffset = Math.floor((startFromIndex + i) / intervalsLength) * 12;
52
52
  const interval = intervals[intervalIndex];
53
- const note = rootMidiAndIntervalToMidi(rootNoteMidi, interval);
53
+ const note = noteMidiAndIntervalToMidi(rootNoteMidi, interval);
54
54
  if (note === undefined) {
55
55
  throw new Error(
56
56
  `Could not calculate MIDI note for interval ${interval} at index ${intervalIndex}`,
@@ -75,7 +75,7 @@ function getMonotonicMidiNoteSequence(
75
75
  Math.max(0, Math.ceil((i - startFromIndex) / intervalsLength)) * 12;
76
76
  const interval = intervals[intervalIndex];
77
77
  // Subtract octaveOffset from root before applying interval
78
- const note = rootMidiAndIntervalToMidi(
78
+ const note = noteMidiAndIntervalToMidi(
79
79
  (rootNoteMidi - octaveOffset) as MidiNoteNumber,
80
80
  interval,
81
81
  );
package/src/utils/midi.ts CHANGED
@@ -1,50 +1,51 @@
1
1
  import {
2
2
  type Interval,
3
3
  intervalToIntegerMap,
4
- type NoteInteger,
5
4
  type NoteName,
6
5
  noteNameToIntegerMap,
7
6
  } from "../data/labels/note-labels.ts";
8
7
 
9
- import type { MidiNoteNumber, OctaveNumber } from "../types/midi.d.ts";
8
+ import type { MidiNoteNumber } from "../types/midi.d.ts";
10
9
 
11
- export function rootIntegerAndIntervalToMidi(
12
- rootNoteInteger: NoteInteger,
10
+ export function noteIntegerAndIntervalToMidi(
11
+ noteInteger: number,
12
+ noteOctaveNumber: number,
13
13
  interval: Interval,
14
- rootNoteOctaveNumber: OctaveNumber = 4,
15
14
  ): MidiNoteNumber | undefined {
16
- const intervalValue = intervalToIntegerMap.get(interval);
17
- if (intervalValue === undefined) return undefined;
18
- return (rootNoteOctaveNumber + 1) * 12 + rootNoteInteger +
19
- intervalValue as MidiNoteNumber;
15
+ const intervalInteger = intervalToIntegerMap.get(interval);
16
+ if (intervalInteger === undefined) return undefined;
17
+ return (noteOctaveNumber + 1) * 12 + noteInteger +
18
+ intervalInteger as MidiNoteNumber;
20
19
  }
21
20
 
22
- export function rootMidiAndIntervalToMidi(
23
- rootNoteMidi: MidiNoteNumber,
21
+ export function noteNameAndIntervalToMidi(
22
+ noteName: NoteName,
23
+ noteOctaveNumber: number,
24
24
  interval: Interval,
25
25
  ): MidiNoteNumber | undefined {
26
- const intervalValue = intervalToIntegerMap.get(interval);
27
- if (intervalValue === undefined) return undefined;
28
- return rootNoteMidi + intervalValue as MidiNoteNumber;
26
+ const noteInteger = noteNameToIntegerMap.get(noteName);
27
+ if (noteInteger === undefined) return undefined;
28
+ return noteIntegerAndIntervalToMidi(
29
+ noteInteger,
30
+ noteOctaveNumber,
31
+ interval,
32
+ );
29
33
  }
30
34
 
31
- export function noteNameToMidi(
32
- noteName: NoteName,
33
- octaveNumber: OctaveNumber = 4,
35
+ export function noteMidiAndIntervalToMidi(
36
+ noteMidi: MidiNoteNumber,
37
+ interval: Interval,
34
38
  ): MidiNoteNumber | undefined {
35
- const noteValue = noteNameToIntegerMap.get(noteName);
36
- if (noteValue === undefined) return undefined;
37
- return noteValue + (octaveNumber + 1) * 12 as MidiNoteNumber;
39
+ const intervalValue = intervalToIntegerMap.get(interval);
40
+ if (intervalValue === undefined) return undefined;
41
+ return noteMidi + intervalValue as MidiNoteNumber;
38
42
  }
39
43
 
40
- export function noteNameAndIntervalToMidi(
44
+ export function noteNameToMidi(
41
45
  noteName: NoteName,
42
- interval: Interval,
43
- octaveNumber: OctaveNumber = 4,
46
+ noteOctaveNumber: number,
44
47
  ): MidiNoteNumber | undefined {
45
48
  const noteValue = noteNameToIntegerMap.get(noteName);
46
49
  if (noteValue === undefined) return undefined;
47
- const intervalValue = intervalToIntegerMap.get(interval);
48
- if (intervalValue === undefined) return undefined;
49
- return noteValue + (octaveNumber + 1) * 12 as MidiNoteNumber;
50
+ return noteValue + (noteOctaveNumber + 1) * 12 as MidiNoteNumber;
50
51
  }
@@ -2,13 +2,13 @@ import {
2
2
  enharmonicNoteNameGroups,
3
3
  type Interval,
4
4
  intervalToIntegerMap,
5
- type NoteInteger,
6
5
  type NoteLetter,
7
6
  noteLetters,
8
7
  type NoteName,
9
8
  noteNamesSet,
10
9
  noteNameToIntegerMap,
11
10
  type RootNote,
11
+ type RootNoteInteger,
12
12
  rootNotesSet,
13
13
  } from "../data/labels/note-labels.ts";
14
14
  import {
@@ -122,18 +122,12 @@ export function normalizeRootNoteString(name: string): RootNote | undefined {
122
122
  return undefined;
123
123
  }
124
124
 
125
- export function noteNameToInteger(
126
- noteName: NoteName,
127
- ): NoteInteger | undefined {
128
- return noteNameToIntegerMap.get(noteName);
129
- }
130
-
131
- export function noteNameStringToInteger(
125
+ export function getNoteIntegerFromString(
132
126
  noteName: string,
133
- ): NoteInteger | undefined {
127
+ ): RootNoteInteger | undefined {
134
128
  const normalized = normalizeNoteNameString(noteName);
135
129
  if (!normalized) return undefined;
136
- return noteNameToInteger(normalized);
130
+ return noteNameToIntegerMap.get(normalized);
137
131
  }
138
132
 
139
133
  export function getNoteNamesFromRootAndIntervals(
@@ -141,7 +135,7 @@ export function getNoteNamesFromRootAndIntervals(
141
135
  intervals: readonly Interval[],
142
136
  options: TransformIntervalsOptions = {},
143
137
  ): NoteName[] {
144
- const rootNoteInteger = noteNameToInteger(rootNote);
138
+ const rootNoteInteger = noteNameToIntegerMap.get(rootNote);
145
139
  if (rootNoteInteger === undefined) return [];
146
140
  const rootNoteLetter = rootNote.charAt(0).toUpperCase();
147
141
  const rootNoteLetterIndex = noteLetters.indexOf(rootNoteLetter as NoteLetter);