tonal-guitar 0.2.0 → 0.4.0

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
@@ -137,7 +137,7 @@ fretboard(STANDARD, [0, 4]); // every note on strings 0-5, frets 0-4
137
137
 
138
138
  ### Shape Registry
139
139
 
140
- Built-in shapes are registered at import time: CAGED scale shapes (5), CAGED chord shapes (5), 3NPS patterns (7), pentatonic boxes (5) -- plus (v0.2.0) 5 minor CAGED scale shapes and 5 minor pentatonic boxes, derived from the major-frame shapes via `relabelShape`. See [Minor-Quality Entries](#minor-quality-entries) below.
140
+ Built-in shapes are registered at import time: CAGED scale shapes (5), CAGED chord shapes (5), 3NPS patterns (7), pentatonic boxes (5) -- plus (v0.2.0) 5 minor CAGED scale shapes and 5 minor pentatonic boxes, derived from the major-frame shapes via `relabelShape`, and 5 minor + 5 major blues boxes, derived via `addPassingTone`. See [Minor-Quality Entries](#minor-quality-entries) and [Blues Boxes](#blues-boxes) below.
141
141
 
142
142
  #### `get(name: string) => ScaleShape | undefined`
143
143
 
@@ -292,6 +292,40 @@ buildFrettedScale(get("G Shape"), "C").notes;
292
292
  buildFrettedScale(get("Em Shape"), "A").notes; // same {string, fret} pairs, A=1P, C=3m
293
293
  ```
294
294
 
295
+ ### Blues Boxes
296
+
297
+ #### `addPassingTone(shape: ScaleShape, tone: string, options?: PassingToneOptions) => ScaleShape | undefined`
298
+
299
+ Adds a chromatic passing tone to any scale shape -- e.g. the b5 (`"5d"`) that turns a minor pentatonic box into a minor blues box, the b3 (`"3m"`) that turns a major pentatonic box into a major blues box, or a `"7M"` on a mixolydian shape for bebop dominant.
300
+
301
+ Placement rule: the tone goes on the same string, one fret above every note a semitone below it (the b5 sits right above each 4P). Tones inside the shape's existing pitch range are always kept, even if they need a one-fret stretch; a tone that would become the shape's new highest note is kept only if it fits within the existing fret span. Fret geometry is evaluated in `options.tuning` (default `STANDARD`). Returns `undefined` for an invalid tone, a shape that doesn't build, or when nothing can be placed (no semitone-below neighbor, or the pitch is already present).
302
+
303
+ ```js
304
+ import { addPassingTone, get } from "tonal-guitar";
305
+
306
+ const blues = addPassingTone(get("Em Shape"), "5d", { name: "Em Blues", quality: "minor-blues" });
307
+
308
+ interface PassingToneOptions {
309
+ name?: string; // override the result name (default: shape.name)
310
+ quality?: string; // value written to result.quality
311
+ parentShape?: string; // value written to result.parentShape (default: shape.name)
312
+ tuning?: string[]; // tuning for the stretch rule (default: STANDARD)
313
+ }
314
+ ```
315
+
316
+ Registered blues boxes (all `system: "pentatonic"`, box numbers shared with the pentatonic boxes):
317
+
318
+ | Registered name | Derived from | `quality` | b5 frets in A minor blues |
319
+ | --- | --- | --- | --- |
320
+ | `"Blues Box 1 Minor"` | `"Pentatonic Box 1 Minor"` + `5d` | `"minor-blues"` | A:6, G:8 |
321
+ | `"Blues Box 2 Minor"` | `"Pentatonic Box 2 Minor"` + `5d` | `"minor-blues"` | low E:11, G:8 |
322
+ | `"Blues Box 3 Minor"` | `"Pentatonic Box 3 Minor"` + `5d` | `"minor-blues"` | low E:11, D:13, high E:11 |
323
+ | `"Blues Box 4 Minor"` | `"Pentatonic Box 4 Minor"` + `5d` | `"minor-blues"` | D:13, B:16 |
324
+ | `"Blues Box 5 Minor"` | `"Pentatonic Box 5 Minor"` + `5d` | `"minor-blues"` | A:6, B:4 |
325
+ | `"Blues Box N Major"` | `"Blues Box N Minor"` relabeled | `"major-blues"` | same frets (C major blues = A minor blues) |
326
+
327
+ They work with Tonal's scale names: `buildFromScale(get("Blues Box 1 Minor"), "E blues")` (`"blues"` is Tonal's alias for `"minor blues"`), `modeShapes("A blues")`, `isShapeCompatible(shape, "C major blues")`, and `relatedScales` (minor blues ↔ relative major blues).
328
+
295
329
  ### Pattern Generators
296
330
 
297
331
  All generators return `number[]` degree sequences.
@@ -733,6 +767,12 @@ The package exports these TypeScript interfaces:
733
767
  - `AsciiTabOptions` — options for `toAsciiTab`
734
768
  - `KeyAnalysis` — result of `analyzeInKey`
735
769
 
770
+ ## Development
771
+
772
+ To author new shapes visually instead of hand-writing `src/data/*.ts`, run `npm run workbench` to start the local Shape Workbench — a Vite + React app (Board → Editor → Export) for building and editing scale/chord shapes on a virtual fretboard. Exporting from the app writes a changeset to `.workbench/changeset.json`.
773
+
774
+ Run `npm run shapes:merge -- .workbench/changeset.json` to land that changeset into `src/data/` and `src/index.ts`. Pass `--dry-run` to preview the resulting diff without writing, or `--check` to validate the changeset (e.g. in CI) without merging.
775
+
736
776
  ## Related
737
777
 
738
778
  - [@tonaljs/scale](https://github.com/tonaljs/tonal/tree/main/packages/scale) — scale definitions and lookups
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Canonical chord-type table.
3
+ *
4
+ * One row per `chordType` key registered in the chord-shape registry
5
+ * (`src/data/*.ts`), carrying everything a consumer needs to name, display
6
+ * and resolve that quality without reaching into the registry itself.
7
+ *
8
+ * ## Purity contract
9
+ *
10
+ * This module has **no imports** — not from `./shape`, `./data`, `./build` or
11
+ * `./index`, and not from Tonal. Importing it (directly, or through the
12
+ * `tonal-guitar/chord-types` package subpath) registers **zero** shapes and
13
+ * runs no side effects, so a consumer that only wants the vocabulary never
14
+ * pays for the 132-shape registry. `src/chord-types.test.ts` enforces this
15
+ * both statically (reading this file's text) and at runtime.
16
+ *
17
+ * ## Row fields
18
+ *
19
+ * | Field | Meaning |
20
+ * | ---------------- | --------------------------------------------------------------------------- |
21
+ * | `id` | The registry `chordType` key, verbatim (`"6/9"`, `"mMaj7"`, `"add9"`). |
22
+ * | `tonalCanonical` | `ChordType.get(id).aliases[0]` — what Tonal canonicalises this quality to. |
23
+ * | `label` | Human label for pickers (`"Major 7"`, `"Sus 4"`, `"Six-nine"`). |
24
+ * | `glyph` | Lead-sheet suffix (`""`, `"-7"`, `"△7"`, `"ø7"`, `"°7"`). |
25
+ * | `intervals` | `ChordType.get(id).intervals`, in Tonal's compound-interval vocabulary. |
26
+ *
27
+ * ## Alias divergences (the reason `tonalCanonical` exists)
28
+ *
29
+ * For 22 of the 25 rows `tonalCanonical === id`. Three diverge, and a
30
+ * consumer that canonicalises a chord symbol through Tonal before querying
31
+ * the registry silently finds nothing for them unless it bridges back:
32
+ *
33
+ * | `id` | `tonalCanonical` |
34
+ * | ------- | ---------------- |
35
+ * | `add9` | `Madd9` |
36
+ * | `6/9` | `6add9` |
37
+ * | `mMaj7` | `m/ma7` |
38
+ *
39
+ * ## `aug7` is deliberately absent
40
+ *
41
+ * `aug7` and `7#5` are the same Tonal chord — identical intervals
42
+ * (`1P 3M 5A 7m`) and overlapping `detect` aliases. The registry indexes
43
+ * chord types by exact string with no alias resolution, and registers `7#5`
44
+ * only (see `src/data/extended-chords.ts`'s "Interop divergence catalog"), so
45
+ * `chordShapes.query({ chordType: "aug7" })` returns nothing by design. Giving
46
+ * `aug7` its own row here would break the table's central invariant — every
47
+ * `id` is voiceable and every registered key is in the table — so callers that
48
+ * see `aug7` should map it onto the `7#5` row.
49
+ */
50
+ /** One canonical chord type. */
51
+ interface ChordTypeEntry {
52
+ /** Registry `chordType` key, verbatim. */
53
+ readonly id: string;
54
+ /** `ChordType.get(id).aliases[0]` — Tonal's canonical spelling. */
55
+ readonly tonalCanonical: string;
56
+ /** Human label for pickers. */
57
+ readonly label: string;
58
+ /** Lead-sheet suffix glyph. */
59
+ readonly glyph: string;
60
+ /** `ChordType.get(id).intervals`, Tonal compound-interval vocabulary. */
61
+ readonly intervals: readonly string[];
62
+ }
63
+ /**
64
+ * The 25 registered chord types, in picker order (triads and sevenths first,
65
+ * then suspensions, altered dominants and extensions).
66
+ */
67
+ declare const CHORD_TYPE_TABLE: readonly [{
68
+ readonly id: "M";
69
+ readonly tonalCanonical: "M";
70
+ readonly label: "Major";
71
+ readonly glyph: "";
72
+ readonly intervals: readonly ["1P", "3M", "5P"];
73
+ }, {
74
+ readonly id: "m";
75
+ readonly tonalCanonical: "m";
76
+ readonly label: "Minor";
77
+ readonly glyph: "-";
78
+ readonly intervals: readonly ["1P", "3m", "5P"];
79
+ }, {
80
+ readonly id: "7";
81
+ readonly tonalCanonical: "7";
82
+ readonly label: "Dominant 7";
83
+ readonly glyph: "7";
84
+ readonly intervals: readonly ["1P", "3M", "5P", "7m"];
85
+ }, {
86
+ readonly id: "maj7";
87
+ readonly tonalCanonical: "maj7";
88
+ readonly label: "Major 7";
89
+ readonly glyph: "△7";
90
+ readonly intervals: readonly ["1P", "3M", "5P", "7M"];
91
+ }, {
92
+ readonly id: "m7";
93
+ readonly tonalCanonical: "m7";
94
+ readonly label: "Minor 7";
95
+ readonly glyph: "-7";
96
+ readonly intervals: readonly ["1P", "3m", "5P", "7m"];
97
+ }, {
98
+ readonly id: "m7b5";
99
+ readonly tonalCanonical: "m7b5";
100
+ readonly label: "Minor 7b5";
101
+ readonly glyph: "ø7";
102
+ readonly intervals: readonly ["1P", "3m", "5d", "7m"];
103
+ }, {
104
+ readonly id: "dim";
105
+ readonly tonalCanonical: "dim";
106
+ readonly label: "Diminished";
107
+ readonly glyph: "°";
108
+ readonly intervals: readonly ["1P", "3m", "5d"];
109
+ }, {
110
+ readonly id: "sus2";
111
+ readonly tonalCanonical: "sus2";
112
+ readonly label: "Sus 2";
113
+ readonly glyph: "sus2";
114
+ readonly intervals: readonly ["1P", "2M", "5P"];
115
+ }, {
116
+ readonly id: "sus4";
117
+ readonly tonalCanonical: "sus4";
118
+ readonly label: "Sus 4";
119
+ readonly glyph: "sus4";
120
+ readonly intervals: readonly ["1P", "4P", "5P"];
121
+ }, {
122
+ readonly id: "aug";
123
+ readonly tonalCanonical: "aug";
124
+ readonly label: "Augmented";
125
+ readonly glyph: "+";
126
+ readonly intervals: readonly ["1P", "3M", "5A"];
127
+ }, {
128
+ readonly id: "7#5";
129
+ readonly tonalCanonical: "7#5";
130
+ readonly label: "Dominant 7#5";
131
+ readonly glyph: "7#5";
132
+ readonly intervals: readonly ["1P", "3M", "5A", "7m"];
133
+ }, {
134
+ readonly id: "7sus4";
135
+ readonly tonalCanonical: "7sus4";
136
+ readonly label: "Dominant 7 Sus 4";
137
+ readonly glyph: "7sus4";
138
+ readonly intervals: readonly ["1P", "4P", "5P", "7m"];
139
+ }, {
140
+ readonly id: "7b5";
141
+ readonly tonalCanonical: "7b5";
142
+ readonly label: "Dominant 7b5";
143
+ readonly glyph: "7b5";
144
+ readonly intervals: readonly ["1P", "3M", "5d", "7m"];
145
+ }, {
146
+ readonly id: "13";
147
+ readonly tonalCanonical: "13";
148
+ readonly label: "Thirteenth";
149
+ readonly glyph: "13";
150
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9M", "13M"];
151
+ }, {
152
+ readonly id: "mMaj7";
153
+ readonly tonalCanonical: "m/ma7";
154
+ readonly label: "Minor Major 7";
155
+ readonly glyph: "-△7";
156
+ readonly intervals: readonly ["1P", "3m", "5P", "7M"];
157
+ }, {
158
+ readonly id: "maj9";
159
+ readonly tonalCanonical: "maj9";
160
+ readonly label: "Major 9";
161
+ readonly glyph: "△9";
162
+ readonly intervals: readonly ["1P", "3M", "5P", "7M", "9M"];
163
+ }, {
164
+ readonly id: "m9";
165
+ readonly tonalCanonical: "m9";
166
+ readonly label: "Minor 9";
167
+ readonly glyph: "-9";
168
+ readonly intervals: readonly ["1P", "3m", "5P", "7m", "9M"];
169
+ }, {
170
+ readonly id: "m6";
171
+ readonly tonalCanonical: "m6";
172
+ readonly label: "Minor 6";
173
+ readonly glyph: "-6";
174
+ readonly intervals: readonly ["1P", "3m", "5P", "6M"];
175
+ }, {
176
+ readonly id: "dim7";
177
+ readonly tonalCanonical: "dim7";
178
+ readonly label: "Diminished 7";
179
+ readonly glyph: "°7";
180
+ readonly intervals: readonly ["1P", "3m", "5d", "7d"];
181
+ }, {
182
+ readonly id: "add9";
183
+ readonly tonalCanonical: "Madd9";
184
+ readonly label: "Add 9";
185
+ readonly glyph: "add9";
186
+ readonly intervals: readonly ["1P", "3M", "5P", "9M"];
187
+ }, {
188
+ readonly id: "9";
189
+ readonly tonalCanonical: "9";
190
+ readonly label: "Dominant 9";
191
+ readonly glyph: "9";
192
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9M"];
193
+ }, {
194
+ readonly id: "7b9";
195
+ readonly tonalCanonical: "7b9";
196
+ readonly label: "Dominant 7b9";
197
+ readonly glyph: "7b9";
198
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9m"];
199
+ }, {
200
+ readonly id: "7#9";
201
+ readonly tonalCanonical: "7#9";
202
+ readonly label: "Dominant 7#9";
203
+ readonly glyph: "7#9";
204
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9A"];
205
+ }, {
206
+ readonly id: "6/9";
207
+ readonly tonalCanonical: "6add9";
208
+ readonly label: "Six-nine";
209
+ readonly glyph: "6/9";
210
+ readonly intervals: readonly ["1P", "3M", "5P", "6M", "9M"];
211
+ }, {
212
+ readonly id: "6";
213
+ readonly tonalCanonical: "6";
214
+ readonly label: "Major 6";
215
+ readonly glyph: "6";
216
+ readonly intervals: readonly ["1P", "3M", "5P", "6M"];
217
+ }];
218
+ /** One row of {@link CHORD_TYPE_TABLE}, with literal field types. */
219
+ type ChordTypeRow = (typeof CHORD_TYPE_TABLE)[number];
220
+ /** Literal union of every registered `chordType` key. */
221
+ type ChordTypeKey = ChordTypeRow["id"];
222
+ /**
223
+ * Every registered `chordType` key, in {@link CHORD_TYPE_TABLE} order, as a
224
+ * literal tuple (so consumers can feed it to `z.enum` or index a
225
+ * `Record<ChordTypeKey, …>` without widening to `string[]`).
226
+ *
227
+ * Written out rather than derived so it stays a tuple; `chord-types.test.ts`
228
+ * asserts it equals `CHORD_TYPE_TABLE.map((row) => row.id)` element for
229
+ * element, so the two can never drift.
230
+ */
231
+ declare const CHORD_TYPE_KEYS: readonly ["M", "m", "7", "maj7", "m7", "m7b5", "dim", "sus2", "sus4", "aug", "7#5", "7sus4", "7b5", "13", "mMaj7", "maj9", "m9", "m6", "dim7", "add9", "9", "7b9", "7#9", "6/9", "6"];
232
+
233
+ export { CHORD_TYPE_KEYS, CHORD_TYPE_TABLE, type ChordTypeEntry, type ChordTypeKey, type ChordTypeRow };
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Canonical chord-type table.
3
+ *
4
+ * One row per `chordType` key registered in the chord-shape registry
5
+ * (`src/data/*.ts`), carrying everything a consumer needs to name, display
6
+ * and resolve that quality without reaching into the registry itself.
7
+ *
8
+ * ## Purity contract
9
+ *
10
+ * This module has **no imports** — not from `./shape`, `./data`, `./build` or
11
+ * `./index`, and not from Tonal. Importing it (directly, or through the
12
+ * `tonal-guitar/chord-types` package subpath) registers **zero** shapes and
13
+ * runs no side effects, so a consumer that only wants the vocabulary never
14
+ * pays for the 132-shape registry. `src/chord-types.test.ts` enforces this
15
+ * both statically (reading this file's text) and at runtime.
16
+ *
17
+ * ## Row fields
18
+ *
19
+ * | Field | Meaning |
20
+ * | ---------------- | --------------------------------------------------------------------------- |
21
+ * | `id` | The registry `chordType` key, verbatim (`"6/9"`, `"mMaj7"`, `"add9"`). |
22
+ * | `tonalCanonical` | `ChordType.get(id).aliases[0]` — what Tonal canonicalises this quality to. |
23
+ * | `label` | Human label for pickers (`"Major 7"`, `"Sus 4"`, `"Six-nine"`). |
24
+ * | `glyph` | Lead-sheet suffix (`""`, `"-7"`, `"△7"`, `"ø7"`, `"°7"`). |
25
+ * | `intervals` | `ChordType.get(id).intervals`, in Tonal's compound-interval vocabulary. |
26
+ *
27
+ * ## Alias divergences (the reason `tonalCanonical` exists)
28
+ *
29
+ * For 22 of the 25 rows `tonalCanonical === id`. Three diverge, and a
30
+ * consumer that canonicalises a chord symbol through Tonal before querying
31
+ * the registry silently finds nothing for them unless it bridges back:
32
+ *
33
+ * | `id` | `tonalCanonical` |
34
+ * | ------- | ---------------- |
35
+ * | `add9` | `Madd9` |
36
+ * | `6/9` | `6add9` |
37
+ * | `mMaj7` | `m/ma7` |
38
+ *
39
+ * ## `aug7` is deliberately absent
40
+ *
41
+ * `aug7` and `7#5` are the same Tonal chord — identical intervals
42
+ * (`1P 3M 5A 7m`) and overlapping `detect` aliases. The registry indexes
43
+ * chord types by exact string with no alias resolution, and registers `7#5`
44
+ * only (see `src/data/extended-chords.ts`'s "Interop divergence catalog"), so
45
+ * `chordShapes.query({ chordType: "aug7" })` returns nothing by design. Giving
46
+ * `aug7` its own row here would break the table's central invariant — every
47
+ * `id` is voiceable and every registered key is in the table — so callers that
48
+ * see `aug7` should map it onto the `7#5` row.
49
+ */
50
+ /** One canonical chord type. */
51
+ interface ChordTypeEntry {
52
+ /** Registry `chordType` key, verbatim. */
53
+ readonly id: string;
54
+ /** `ChordType.get(id).aliases[0]` — Tonal's canonical spelling. */
55
+ readonly tonalCanonical: string;
56
+ /** Human label for pickers. */
57
+ readonly label: string;
58
+ /** Lead-sheet suffix glyph. */
59
+ readonly glyph: string;
60
+ /** `ChordType.get(id).intervals`, Tonal compound-interval vocabulary. */
61
+ readonly intervals: readonly string[];
62
+ }
63
+ /**
64
+ * The 25 registered chord types, in picker order (triads and sevenths first,
65
+ * then suspensions, altered dominants and extensions).
66
+ */
67
+ declare const CHORD_TYPE_TABLE: readonly [{
68
+ readonly id: "M";
69
+ readonly tonalCanonical: "M";
70
+ readonly label: "Major";
71
+ readonly glyph: "";
72
+ readonly intervals: readonly ["1P", "3M", "5P"];
73
+ }, {
74
+ readonly id: "m";
75
+ readonly tonalCanonical: "m";
76
+ readonly label: "Minor";
77
+ readonly glyph: "-";
78
+ readonly intervals: readonly ["1P", "3m", "5P"];
79
+ }, {
80
+ readonly id: "7";
81
+ readonly tonalCanonical: "7";
82
+ readonly label: "Dominant 7";
83
+ readonly glyph: "7";
84
+ readonly intervals: readonly ["1P", "3M", "5P", "7m"];
85
+ }, {
86
+ readonly id: "maj7";
87
+ readonly tonalCanonical: "maj7";
88
+ readonly label: "Major 7";
89
+ readonly glyph: "△7";
90
+ readonly intervals: readonly ["1P", "3M", "5P", "7M"];
91
+ }, {
92
+ readonly id: "m7";
93
+ readonly tonalCanonical: "m7";
94
+ readonly label: "Minor 7";
95
+ readonly glyph: "-7";
96
+ readonly intervals: readonly ["1P", "3m", "5P", "7m"];
97
+ }, {
98
+ readonly id: "m7b5";
99
+ readonly tonalCanonical: "m7b5";
100
+ readonly label: "Minor 7b5";
101
+ readonly glyph: "ø7";
102
+ readonly intervals: readonly ["1P", "3m", "5d", "7m"];
103
+ }, {
104
+ readonly id: "dim";
105
+ readonly tonalCanonical: "dim";
106
+ readonly label: "Diminished";
107
+ readonly glyph: "°";
108
+ readonly intervals: readonly ["1P", "3m", "5d"];
109
+ }, {
110
+ readonly id: "sus2";
111
+ readonly tonalCanonical: "sus2";
112
+ readonly label: "Sus 2";
113
+ readonly glyph: "sus2";
114
+ readonly intervals: readonly ["1P", "2M", "5P"];
115
+ }, {
116
+ readonly id: "sus4";
117
+ readonly tonalCanonical: "sus4";
118
+ readonly label: "Sus 4";
119
+ readonly glyph: "sus4";
120
+ readonly intervals: readonly ["1P", "4P", "5P"];
121
+ }, {
122
+ readonly id: "aug";
123
+ readonly tonalCanonical: "aug";
124
+ readonly label: "Augmented";
125
+ readonly glyph: "+";
126
+ readonly intervals: readonly ["1P", "3M", "5A"];
127
+ }, {
128
+ readonly id: "7#5";
129
+ readonly tonalCanonical: "7#5";
130
+ readonly label: "Dominant 7#5";
131
+ readonly glyph: "7#5";
132
+ readonly intervals: readonly ["1P", "3M", "5A", "7m"];
133
+ }, {
134
+ readonly id: "7sus4";
135
+ readonly tonalCanonical: "7sus4";
136
+ readonly label: "Dominant 7 Sus 4";
137
+ readonly glyph: "7sus4";
138
+ readonly intervals: readonly ["1P", "4P", "5P", "7m"];
139
+ }, {
140
+ readonly id: "7b5";
141
+ readonly tonalCanonical: "7b5";
142
+ readonly label: "Dominant 7b5";
143
+ readonly glyph: "7b5";
144
+ readonly intervals: readonly ["1P", "3M", "5d", "7m"];
145
+ }, {
146
+ readonly id: "13";
147
+ readonly tonalCanonical: "13";
148
+ readonly label: "Thirteenth";
149
+ readonly glyph: "13";
150
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9M", "13M"];
151
+ }, {
152
+ readonly id: "mMaj7";
153
+ readonly tonalCanonical: "m/ma7";
154
+ readonly label: "Minor Major 7";
155
+ readonly glyph: "-△7";
156
+ readonly intervals: readonly ["1P", "3m", "5P", "7M"];
157
+ }, {
158
+ readonly id: "maj9";
159
+ readonly tonalCanonical: "maj9";
160
+ readonly label: "Major 9";
161
+ readonly glyph: "△9";
162
+ readonly intervals: readonly ["1P", "3M", "5P", "7M", "9M"];
163
+ }, {
164
+ readonly id: "m9";
165
+ readonly tonalCanonical: "m9";
166
+ readonly label: "Minor 9";
167
+ readonly glyph: "-9";
168
+ readonly intervals: readonly ["1P", "3m", "5P", "7m", "9M"];
169
+ }, {
170
+ readonly id: "m6";
171
+ readonly tonalCanonical: "m6";
172
+ readonly label: "Minor 6";
173
+ readonly glyph: "-6";
174
+ readonly intervals: readonly ["1P", "3m", "5P", "6M"];
175
+ }, {
176
+ readonly id: "dim7";
177
+ readonly tonalCanonical: "dim7";
178
+ readonly label: "Diminished 7";
179
+ readonly glyph: "°7";
180
+ readonly intervals: readonly ["1P", "3m", "5d", "7d"];
181
+ }, {
182
+ readonly id: "add9";
183
+ readonly tonalCanonical: "Madd9";
184
+ readonly label: "Add 9";
185
+ readonly glyph: "add9";
186
+ readonly intervals: readonly ["1P", "3M", "5P", "9M"];
187
+ }, {
188
+ readonly id: "9";
189
+ readonly tonalCanonical: "9";
190
+ readonly label: "Dominant 9";
191
+ readonly glyph: "9";
192
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9M"];
193
+ }, {
194
+ readonly id: "7b9";
195
+ readonly tonalCanonical: "7b9";
196
+ readonly label: "Dominant 7b9";
197
+ readonly glyph: "7b9";
198
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9m"];
199
+ }, {
200
+ readonly id: "7#9";
201
+ readonly tonalCanonical: "7#9";
202
+ readonly label: "Dominant 7#9";
203
+ readonly glyph: "7#9";
204
+ readonly intervals: readonly ["1P", "3M", "5P", "7m", "9A"];
205
+ }, {
206
+ readonly id: "6/9";
207
+ readonly tonalCanonical: "6add9";
208
+ readonly label: "Six-nine";
209
+ readonly glyph: "6/9";
210
+ readonly intervals: readonly ["1P", "3M", "5P", "6M", "9M"];
211
+ }, {
212
+ readonly id: "6";
213
+ readonly tonalCanonical: "6";
214
+ readonly label: "Major 6";
215
+ readonly glyph: "6";
216
+ readonly intervals: readonly ["1P", "3M", "5P", "6M"];
217
+ }];
218
+ /** One row of {@link CHORD_TYPE_TABLE}, with literal field types. */
219
+ type ChordTypeRow = (typeof CHORD_TYPE_TABLE)[number];
220
+ /** Literal union of every registered `chordType` key. */
221
+ type ChordTypeKey = ChordTypeRow["id"];
222
+ /**
223
+ * Every registered `chordType` key, in {@link CHORD_TYPE_TABLE} order, as a
224
+ * literal tuple (so consumers can feed it to `z.enum` or index a
225
+ * `Record<ChordTypeKey, …>` without widening to `string[]`).
226
+ *
227
+ * Written out rather than derived so it stays a tuple; `chord-types.test.ts`
228
+ * asserts it equals `CHORD_TYPE_TABLE.map((row) => row.id)` element for
229
+ * element, so the two can never drift.
230
+ */
231
+ declare const CHORD_TYPE_KEYS: readonly ["M", "m", "7", "maj7", "m7", "m7b5", "dim", "sus2", "sus4", "aug", "7#5", "7sus4", "7b5", "13", "mMaj7", "maj9", "m9", "m6", "dim7", "add9", "9", "7b9", "7#9", "6/9", "6"];
232
+
233
+ export { CHORD_TYPE_KEYS, CHORD_TYPE_TABLE, type ChordTypeEntry, type ChordTypeKey, type ChordTypeRow };