@pepperhorn/chordl-core 0.4.0 → 0.5.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.
@@ -6,6 +6,17 @@ export interface LayoutOptions {
6
6
  startingNote?: string;
7
7
  spanFrom?: string;
8
8
  spanTo?: string;
9
+ /**
10
+ * Whole octaves above the first note, one per note, when the caller knows
11
+ * where the voicing really sits.
12
+ *
13
+ * Without it the notes are read as an ascending stack — each note that fails
14
+ * to rise wraps into the next octave — which is the only thing bare pitch
15
+ * classes can support. A library voicing declares its octaves, and a
16
+ * declared tenth needs a window two octaves wide where the stack rule would
17
+ * size one for a third and crop the top note off the keyboard.
18
+ */
19
+ octaveOffsets?: number[];
9
20
  }
10
21
  export interface LayoutResult {
11
22
  startFrom: WhiteNote;
@@ -61,7 +61,8 @@ function ensureFullBlackKeyGroups(startIdx, endIdx) {
61
61
  return { startIdx, endIdx };
62
62
  }
63
63
  export function calculateLayout(notes, options = {}) {
64
- const { padding = 1, startingNote, spanFrom, spanTo } = options;
64
+ const { padding = 1, startingNote, spanFrom, spanTo, octaveOffsets } = options;
65
+ const hasOffsets = octaveOffsets != null && octaveOffsets.length === notes.length;
65
66
  // Explicit starting note: anchor the keyboard there
66
67
  if (startingNote) {
67
68
  const anchorKey = nearestWhiteKey(startingNote);
@@ -80,9 +81,19 @@ export function calculateLayout(notes, options = {}) {
80
81
  // the previous wraps into the next octave (matches voicing rotation).
81
82
  const anchorWhiteIdx = WHITE_NOTE_ORDER.indexOf(anchorKey);
82
83
  const indices = [];
83
- for (const w of whiteKeys) {
84
- let idx = WHITE_NOTE_ORDER.indexOf(w);
85
- if (indices.length === 0) {
84
+ for (let i = 0; i < whiteKeys.length; i++) {
85
+ let idx = WHITE_NOTE_ORDER.indexOf(whiteKeys[i]);
86
+ if (hasOffsets) {
87
+ // A declared placement: seven white keys to the octave, counted from
88
+ // the first note, whose own octave is the frame.
89
+ idx += 7 * octaveOffsets[i];
90
+ if (i === 0)
91
+ while (idx < anchorWhiteIdx)
92
+ idx += 7;
93
+ else
94
+ idx += indices[0] - WHITE_NOTE_ORDER.indexOf(whiteKeys[0]);
95
+ }
96
+ else if (indices.length === 0) {
86
97
  // First note: place relative to anchor (must be >= anchor)
87
98
  while (idx < anchorWhiteIdx)
88
99
  idx += 7;
@@ -142,17 +153,22 @@ export function calculateLayout(notes, options = {}) {
142
153
  // the previous one (in white-key index) wraps into the next octave.
143
154
  const whiteKeys = notes.map(nearestWhiteKey);
144
155
  const ascending = [];
145
- for (const w of whiteKeys) {
146
- let idx = WHITE_NOTE_ORDER.indexOf(w);
147
- if (ascending.length > 0) {
156
+ for (let i = 0; i < whiteKeys.length; i++) {
157
+ let idx = WHITE_NOTE_ORDER.indexOf(whiteKeys[i]);
158
+ if (hasOffsets) {
159
+ // A declared placement, so no wrapping is inferred: the offsets already
160
+ // say which octave each note is in, seven white keys apart.
161
+ idx += 7 * octaveOffsets[i];
162
+ }
163
+ else if (ascending.length > 0) {
148
164
  const lastIdx = ascending[ascending.length - 1];
149
165
  while (idx <= lastIdx)
150
166
  idx += 7;
151
167
  }
152
168
  ascending.push(idx);
153
169
  }
154
- const minAsc = ascending[0];
155
- const maxAsc = ascending[ascending.length - 1];
170
+ const minAsc = Math.min(...ascending);
171
+ const maxAsc = Math.max(...ascending);
156
172
  let startIdx = minAsc - padding;
157
173
  let endIdx = maxAsc + padding;
158
174
  // Black-key context padding: if an edge white key has a sharp, extend by 1
@@ -1 +1 @@
1
- {"version":3,"file":"auto-layout.js","sourceRoot":"","sources":["../../src/resolver/auto-layout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auto-layout.js","sourceRoot":"","sources":["../../src/resolver/auto-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Notes → chord-name recognition, and validation of a typed chord name
3
+ * against a set of notes.
4
+ *
5
+ * The opposite direction to `resolveChord`: everything here compares
6
+ * pitch-class sets, so it is enharmonic-agnostic (A–Db–E is an A major triad)
7
+ * and ignores octaves and doubled notes. Chord shapes come from tonal's
8
+ * `ChordType` dictionary, the same one `resolveChord` reads, so a name this
9
+ * module accepts is a name the rest of chordl can draw.
10
+ */
11
+ export interface IdentifyOptions {
12
+ /**
13
+ * Allowed chord-type symbols, tonal style ("M", "m", "dim", "maj7", "m7b5",
14
+ * "add9", ...). Synonyms are accepted ("major", "Δ", "°7"). `undefined`
15
+ * allows every type tonal knows.
16
+ */
17
+ chordTypes?: string[];
18
+ /** Accept slash names — inversions ("C/E") and pedal basses ("C/Ab"). Default true. */
19
+ allowSlash?: boolean;
20
+ /**
21
+ * Accept pedal-bass (foreign-bass) slash names, whose bass is not a tone of
22
+ * the chord above it: "C/Ab" for Ab–C–E–G, "A/C" for E–A–C#–C. Default
23
+ * true. When false, only inversion slash names ("C/E", "C/G" for C–E–G)
24
+ * are allowed. Has no effect when `allowSlash` is false.
25
+ */
26
+ allowPedalBass?: boolean;
27
+ /** Accept a chord whose perfect 5th is missing (C–D–E → "Cadd9"). Default true. */
28
+ allowOmittedFifth?: boolean;
29
+ /**
30
+ * Name an inversion by its root ("C" for E–G–C). Default true. When false,
31
+ * the first note passed is taken as the bass: a chord whose root is not the
32
+ * bass must be written as a slash chord ("C/E").
33
+ */
34
+ inversionAgnostic?: boolean;
35
+ }
36
+ export interface ChordCandidate {
37
+ /** Canonical display symbol, e.g. "Cmaj7", "C/Ab", "Abmaj7#5". */
38
+ symbol: string;
39
+ /** Spelled root, e.g. "C#" or "Db". */
40
+ root: string;
41
+ /** Canonical tonal chord-type symbol ("M" for a major triad). */
42
+ type: string;
43
+ /** Bass note of a slash name, spelled as given. */
44
+ bass?: string;
45
+ /** Chord tones left out of the notes, e.g. ["5"]. */
46
+ omitted?: string[];
47
+ }
48
+ export interface ChordNameMatch {
49
+ ok: boolean;
50
+ /** Canonical spelling of the typed name, whenever it parsed. */
51
+ normalized?: string;
52
+ /** The parsed name as a candidate, when `ok`. */
53
+ matched?: ChordCandidate;
54
+ /** Everything `identifyChords` finds for the notes under the same options. */
55
+ candidates: ChordCandidate[];
56
+ }
57
+ /**
58
+ * Every chord name the given notes spell.
59
+ *
60
+ * Root-position names come first (the name whose root is the first note given
61
+ * leads), then omitted-5th readings, then slash names. Within each of those
62
+ * groups, common chord types come before rare ones (see `TYPE_PRIORITY`). A black-key root is
63
+ * offered in both spellings ("C#" and "Db") as separate candidates. Slash
64
+ * names returned here are pedal basses — a non-chord-tone under a chord
65
+ * ("C/Ab"), unless `allowPedalBass` is false — plus, when `inversionAgnostic`
66
+ * is false, inversions ("C/E").
67
+ * Returns `[]` when any note is unparseable or nothing matches.
68
+ */
69
+ export declare function identifyChords(notes: string[], opts?: IdentifyOptions): ChordCandidate[];
70
+ /**
71
+ * Check a typed chord name against a set of notes.
72
+ *
73
+ * The name is parsed tolerantly (see `normalizeQuality`) and is correct when
74
+ * its pitch-class set equals the notes' set — or equals it with the perfect
75
+ * 5th omitted, when `allowOmittedFifth`. A slash bass must be one of the
76
+ * notes; the chord above it may contain it (an inversion, "C/E") or not (a
77
+ * pedal, "C/Ab"; rejected when `allowPedalBass` is false). Whether the bass is
78
+ * a chord tone is judged against the whole chord, so "Cadd9/E" over E–C–D is
79
+ * an inversion even with its 5th omitted. `normalized` is the canonical spelling of whatever parsed,
80
+ * right or wrong, so a UI can echo it back.
81
+ */
82
+ export declare function matchesChordName(input: string, notes: string[], opts?: IdentifyOptions): ChordNameMatch;
@@ -0,0 +1,340 @@
1
+ import { ChordType, Interval } from "tonal";
2
+ import { PC_SEMITONES, SHARP_TO_FLAT } from "../engine/note-spelling.js";
3
+ import { resolveChord } from "../resolver/chord-resolver.js";
4
+ // ── Pitch-class sets as 12-bit masks ──
5
+ const SHARP_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
6
+ const FIFTH = 7;
7
+ /** An omitted-5th reading must still leave a triad's worth of notes. */
8
+ const MIN_NOTES_AFTER_OMISSION = 3;
9
+ /** A slash chord's upper structure must be at least a triad. */
10
+ const MIN_PEDAL_UPPER_NOTES = 3;
11
+ const bit = (pc) => 1 << pc;
12
+ function popcount(mask) {
13
+ let n = 0;
14
+ for (let m = mask; m; m &= m - 1)
15
+ n++;
16
+ return n;
17
+ }
18
+ function pcsOf(mask) {
19
+ const out = [];
20
+ for (let pc = 0; pc < 12; pc++)
21
+ if (mask & bit(pc))
22
+ out.push(pc);
23
+ return out;
24
+ }
25
+ function transpose(relMask, root) {
26
+ let out = 0;
27
+ for (const pc of pcsOf(relMask))
28
+ out |= bit((pc + root) % 12);
29
+ return out;
30
+ }
31
+ /** Both spellings of a black-key root, one for a white key. */
32
+ function rootSpellings(pc) {
33
+ const sharp = SHARP_NAMES[pc];
34
+ const flat = SHARP_TO_FLAT[sharp];
35
+ return flat ? [sharp, flat] : [sharp];
36
+ }
37
+ // ── Note and chord-type parsing ──
38
+ /** "c#4", " Bb ", "D♭" → "C#", "Bb", "Db". Null when it is not a note name. */
39
+ function spellNote(raw) {
40
+ const m = raw
41
+ .trim()
42
+ .replace(/♯/g, "#")
43
+ .replace(/♭/g, "b")
44
+ .match(/^([A-Ga-g])\s*([#b]?)\s*-?\d*$/);
45
+ if (!m)
46
+ return null;
47
+ const name = m[1].toUpperCase() + m[2];
48
+ return name in PC_SEMITONES ? name : null;
49
+ }
50
+ function parseNotes(notes) {
51
+ if (notes.length === 0)
52
+ return null;
53
+ const spelling = new Map();
54
+ let mask = 0;
55
+ let bass = -1;
56
+ for (const raw of notes) {
57
+ const name = spellNote(raw);
58
+ if (!name)
59
+ return null;
60
+ const pc = PC_SEMITONES[name];
61
+ if (bass < 0)
62
+ bass = pc;
63
+ if (!spelling.has(pc))
64
+ spelling.set(pc, name);
65
+ mask |= bit(pc);
66
+ }
67
+ return { mask, bass, spelling };
68
+ }
69
+ /**
70
+ * Display-type overrides where tonal's first alias is not the symbol people
71
+ * write: tonal lists "Madd9" before "add9", "m/ma7" before "mMaj7".
72
+ */
73
+ const CANONICAL_TYPE = {
74
+ Madd9: "add9",
75
+ "m/ma7": "mMaj7",
76
+ mM9: "mMaj9",
77
+ "maj#4": "maj7#11",
78
+ "M6#11": "6#11",
79
+ };
80
+ function shapeFromIntervals(type, intervals) {
81
+ let mask = 0;
82
+ for (const ivl of intervals) {
83
+ const semis = Interval.semitones(ivl);
84
+ if (Number.isInteger(semis))
85
+ mask |= bit(((semis % 12) + 12) % 12);
86
+ }
87
+ return { type, mask };
88
+ }
89
+ const TONAL_SHAPES = ChordType.all().map((t) => shapeFromIntervals(CANONICAL_TYPE[t.aliases[0]] ?? t.aliases[0], t.intervals));
90
+ /**
91
+ * Rewrite a typed chord quality into a symbol tonal recognises. Tolerates
92
+ * spaces, parentheses, unicode accidentals and the common written synonyms:
93
+ * maj/major → M, Δ → maj7 (Δ9 → maj9), min/minor/- → m, ø → m7b5, ° → dim,
94
+ * + → aug, sus → sus4,
95
+ * add2 → add9. The single letters M and m keep their case-sensitive meaning.
96
+ */
97
+ function normalizeQuality(raw) {
98
+ let s = raw
99
+ .replace(/[\s()]/g, "")
100
+ .replace(/♯/g, "#")
101
+ .replace(/♭/g, "b")
102
+ // A bare Δ is maj7, as in tonal and jazz usage; Δ before a number is
103
+ // "maj" (Δ7, Δ9, Δ13).
104
+ .replace(/Δ(?=\d)/g, "maj")
105
+ .replace(/Δ/g, "maj7");
106
+ s = s.replace(/^major/i, "maj").replace(/^minor/i, "min");
107
+ s = s.replace(/^min/i, "m").replace(/^-/, "m");
108
+ s = s.replace(/^ø7?/, "m7b5").replace(/^°/, "dim");
109
+ s = s.replace(/maj|dim|aug|sus|add|alt|omit|dom/gi, (w) => w.toLowerCase());
110
+ s = s.replace(/^m(?:maj|M)(?=\d|$)/, "mMaj");
111
+ if (s === "mMaj")
112
+ s = "mMaj7";
113
+ if (s === "maj" || s === "")
114
+ s = "M";
115
+ if (s === "+")
116
+ s = "aug";
117
+ s = s.replace(/sus(?![24])/, "sus4").replace(/add2/, "add9");
118
+ return s;
119
+ }
120
+ /** Resolve a typed quality to a chord shape, or null when it is not a chord. */
121
+ function shapeForQuality(raw) {
122
+ const s = normalizeQuality(raw);
123
+ const t = ChordType.get(s);
124
+ if (!t.empty) {
125
+ return shapeFromIntervals(CANONICAL_TYPE[t.aliases[0]] ?? t.aliases[0], t.intervals);
126
+ }
127
+ // Names tonal does not know but `resolveChord` builds (m6/9, 7omit3,
128
+ // compound alterations like maj9#5#13): reuse its fallback rather than
129
+ // re-deriving those chords here.
130
+ try {
131
+ const resolved = resolveChord(`C${s}`);
132
+ return resolved.intervals?.length ? shapeFromIntervals(s, resolved.intervals) : null;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ /**
139
+ * Common chord types first, so that when one set of notes spells several
140
+ * names over the same bass the familiar one leads ("Abmaj7#5" before
141
+ * "AbM7b6"). Types not listed keep tonal's dictionary order after these.
142
+ */
143
+ const TYPE_PRIORITY = [
144
+ "M", "m", "7", "maj7", "m7", "dim", "aug", "sus4", "sus2", "m7b5", "dim7",
145
+ "6", "m6", "add9", "madd9", "7sus4", "9", "maj9", "m9", "maj7#5", "mMaj7",
146
+ ];
147
+ const TONAL_ORDER = new Map(TONAL_SHAPES.map((s, i) => [s.type, i]));
148
+ function typePriority(type) {
149
+ const i = TYPE_PRIORITY.indexOf(type);
150
+ return i >= 0 ? i : TYPE_PRIORITY.length + (TONAL_ORDER.get(type) ?? TONAL_SHAPES.length);
151
+ }
152
+ const displaySuffix = (type) => (type === "M" ? "" : type);
153
+ function allowedTypes(chordTypes) {
154
+ if (!chordTypes)
155
+ return null;
156
+ const set = new Set();
157
+ for (const t of chordTypes) {
158
+ set.add(shapeForQuality(t)?.type ?? normalizeQuality(t));
159
+ }
160
+ return set;
161
+ }
162
+ /** Parse a typed chord symbol: root, quality, optional "/bass". */
163
+ function parseSymbol(input) {
164
+ const m = input
165
+ .trim()
166
+ .replace(/♯/g, "#")
167
+ .replace(/♭/g, "b")
168
+ .match(/^([A-Ga-g])\s*([#b]?)(.*)$/);
169
+ if (!m)
170
+ return null;
171
+ const root = m[1].toUpperCase() + m[2];
172
+ let quality = m[3];
173
+ let bass;
174
+ const slash = quality.match(/^(.*?)\/\s*([A-Ga-g]\s*[#b]?)\s*$/);
175
+ if (slash) {
176
+ quality = slash[1];
177
+ const spelled = spellNote(slash[2]);
178
+ if (!spelled)
179
+ return null;
180
+ bass = spelled;
181
+ }
182
+ const shape = shapeForQuality(quality);
183
+ if (!shape || !(root in PC_SEMITONES))
184
+ return null;
185
+ return {
186
+ root,
187
+ rootPc: PC_SEMITONES[root],
188
+ shape,
189
+ bass,
190
+ bassPc: bass === undefined ? undefined : PC_SEMITONES[bass],
191
+ };
192
+ }
193
+ /**
194
+ * Does `shape` on `root`, with an optional slash bass, spell exactly `target`?
195
+ * Tries the whole chord first, then — if allowed — the chord minus its
196
+ * perfect 5th. A pedal bass (one that is not a chord tone, "C/Ab") requires
197
+ * the whole chord above it: omitting the 5th there yields names like
198
+ * "Abmaj7/E" that describe the notes but no musician would write.
199
+ */
200
+ function spells(shape, root, target, bassPc, allowOmittedFifth) {
201
+ const chord = transpose(shape.mask, root);
202
+ const bassBit = bassPc === undefined ? 0 : bit(bassPc);
203
+ const full = chord | bassBit;
204
+ if (full === target)
205
+ return { ok: true };
206
+ const isPedal = bassBit !== 0 && !(chord & bassBit);
207
+ const fifthPc = (root + FIFTH) % 12;
208
+ // The 5th cannot be "omitted" while it is sounding as the bass.
209
+ if (allowOmittedFifth && !isPedal && shape.mask & bit(FIFTH) && bassPc !== fifthPc) {
210
+ const without = full & ~bit(fifthPc);
211
+ if (without === target && popcount(without) >= MIN_NOTES_AFTER_OMISSION) {
212
+ return { ok: true, omitted: ["5"] };
213
+ }
214
+ }
215
+ return { ok: false };
216
+ }
217
+ function makeCandidate(root, type, bass, omitted) {
218
+ const c = {
219
+ symbol: root + displaySuffix(type) + (bass ? `/${bass}` : ""),
220
+ root,
221
+ type,
222
+ };
223
+ if (bass)
224
+ c.bass = bass;
225
+ if (omitted)
226
+ c.omitted = omitted;
227
+ return c;
228
+ }
229
+ /**
230
+ * Every chord name the given notes spell.
231
+ *
232
+ * Root-position names come first (the name whose root is the first note given
233
+ * leads), then omitted-5th readings, then slash names. Within each of those
234
+ * groups, common chord types come before rare ones (see `TYPE_PRIORITY`). A black-key root is
235
+ * offered in both spellings ("C#" and "Db") as separate candidates. Slash
236
+ * names returned here are pedal basses — a non-chord-tone under a chord
237
+ * ("C/Ab"), unless `allowPedalBass` is false — plus, when `inversionAgnostic`
238
+ * is false, inversions ("C/E").
239
+ * Returns `[]` when any note is unparseable or nothing matches.
240
+ */
241
+ export function identifyChords(notes, opts = {}) {
242
+ const { allowSlash = true, allowPedalBass = true, allowOmittedFifth = true, inversionAgnostic = true, } = opts;
243
+ const parsed = parseNotes(notes);
244
+ if (!parsed)
245
+ return [];
246
+ const allowed = allowedTypes(opts.chordTypes);
247
+ const shapes = allowed ? TONAL_SHAPES.filter((s) => allowed.has(s.type)) : TONAL_SHAPES;
248
+ const found = [];
249
+ const push = (rootPc, type, bassPc, omitted) => {
250
+ const bass = bassPc === undefined ? undefined : parsed.spelling.get(bassPc);
251
+ const rank = (bass ? 4 : 0) + (omitted ? 2 : 0) + (rootPc === parsed.bass && !bass ? 0 : 1);
252
+ for (const root of rootSpellings(rootPc)) {
253
+ found.push({ c: makeCandidate(root, type, bass, omitted), rank, priority: typePriority(type) });
254
+ }
255
+ };
256
+ // Plain names: the notes are exactly the chord (or the chord minus its 5th).
257
+ for (const root of pcsOf(parsed.mask)) {
258
+ for (const shape of shapes) {
259
+ const r = spells(shape, root, parsed.mask, undefined, allowOmittedFifth);
260
+ if (!r.ok)
261
+ continue;
262
+ if (inversionAgnostic || root === parsed.bass)
263
+ push(root, shape.type, undefined, r.omitted);
264
+ else if (allowSlash)
265
+ push(root, shape.type, parsed.bass, r.omitted);
266
+ }
267
+ }
268
+ // Pedal basses: one note under a chord that does not contain it.
269
+ if (allowSlash && allowPedalBass) {
270
+ const basses = inversionAgnostic ? pcsOf(parsed.mask) : [parsed.bass];
271
+ for (const b of basses) {
272
+ const upper = parsed.mask & ~bit(b);
273
+ if (popcount(upper) < MIN_PEDAL_UPPER_NOTES)
274
+ continue;
275
+ for (const root of pcsOf(upper)) {
276
+ for (const shape of shapes) {
277
+ // A chord that contains the bass is an inversion, named above.
278
+ if (transpose(shape.mask, root) & bit(b))
279
+ continue;
280
+ const r = spells(shape, root, parsed.mask, b, allowOmittedFifth);
281
+ if (r.ok)
282
+ push(root, shape.type, b, r.omitted);
283
+ }
284
+ }
285
+ }
286
+ }
287
+ const seen = new Set();
288
+ return found
289
+ .map((f, i) => ({ ...f, i }))
290
+ .sort((a, b) => a.rank - b.rank || a.priority - b.priority || a.i - b.i)
291
+ .map((f) => f.c)
292
+ .filter((c) => (seen.has(c.symbol) ? false : (seen.add(c.symbol), true)));
293
+ }
294
+ /**
295
+ * Check a typed chord name against a set of notes.
296
+ *
297
+ * The name is parsed tolerantly (see `normalizeQuality`) and is correct when
298
+ * its pitch-class set equals the notes' set — or equals it with the perfect
299
+ * 5th omitted, when `allowOmittedFifth`. A slash bass must be one of the
300
+ * notes; the chord above it may contain it (an inversion, "C/E") or not (a
301
+ * pedal, "C/Ab"; rejected when `allowPedalBass` is false). Whether the bass is
302
+ * a chord tone is judged against the whole chord, so "Cadd9/E" over E–C–D is
303
+ * an inversion even with its 5th omitted. `normalized` is the canonical spelling of whatever parsed,
304
+ * right or wrong, so a UI can echo it back.
305
+ */
306
+ export function matchesChordName(input, notes, opts = {}) {
307
+ const { allowSlash = true, allowPedalBass = true, allowOmittedFifth = true, inversionAgnostic = true, } = opts;
308
+ const candidates = identifyChords(notes, opts);
309
+ const sym = parseSymbol(input);
310
+ if (!sym)
311
+ return { ok: false, candidates };
312
+ const normalized = makeCandidate(sym.root, sym.shape.type, sym.bass, undefined).symbol;
313
+ const fail = { ok: false, normalized, candidates };
314
+ const parsed = parseNotes(notes);
315
+ if (!parsed || popcount(parsed.mask) < 2)
316
+ return fail;
317
+ const allowed = allowedTypes(opts.chordTypes);
318
+ if (allowed && !allowed.has(sym.shape.type))
319
+ return fail;
320
+ const lowest = sym.bassPc ?? sym.rootPc;
321
+ if (sym.bassPc !== undefined) {
322
+ if (!allowSlash || !(parsed.mask & bit(sym.bassPc)))
323
+ return fail;
324
+ const isChordTone = transpose(sym.shape.mask, sym.rootPc) & bit(sym.bassPc);
325
+ if (!allowPedalBass && !isChordTone)
326
+ return fail;
327
+ }
328
+ if (!inversionAgnostic && lowest !== parsed.bass)
329
+ return fail;
330
+ const r = spells(sym.shape, sym.rootPc, parsed.mask, sym.bassPc, allowOmittedFifth);
331
+ if (!r.ok)
332
+ return fail;
333
+ return {
334
+ ok: true,
335
+ normalized,
336
+ matched: makeCandidate(sym.root, sym.shape.type, sym.bass, r.omitted),
337
+ candidates,
338
+ };
339
+ }
340
+ //# sourceMappingURL=chord-identify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chord-identify.js","sourceRoot":"","sources":["../../src/theory/chord-identify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * "Starting on X" — the rule that decides which chord tone sits at the bottom.
3
+ *
4
+ * This lives in the theory layer rather than in a renderer because more than
5
+ * one view of a chord has to agree about it. `PianoChord` rotates the voicing
6
+ * it draws; the board's player rotates the voicing it *sounds*. A second copy
7
+ * of the rule in the second caller is the one kind of drift nothing on screen
8
+ * would reveal — the card would draw a first inversion and play root position,
9
+ * silently, so both call this.
10
+ *
11
+ * Degree resolution ("starting on the 3rd") lives here for the same reason: it
12
+ * is the first half of the same clause, and `bassDegree` ("over the 3rd") uses
13
+ * the identical lookup.
14
+ */
15
+ /** Ordinal names for the degrees a "starting on"/"over" clause can name. */
16
+ export declare const DEGREE_NAMES: Record<number, string>;
17
+ /**
18
+ * Find the chord tone matching a musical degree, regardless of quality.
19
+ * e.g. degree 3 finds Eb in Cm7 and E in Cmaj7.
20
+ */
21
+ export declare function degreeToNote(root: string, degree: number, notes: string[]): string | undefined;
22
+ /** Format the available degrees for error messages */
23
+ export declare function describeAvailableDegrees(root: string, notes: string[]): string;
24
+ export interface StartingNoteRotation {
25
+ /**
26
+ * Where the named note sits in the original list, or `-1` when the chord
27
+ * does not contain it. Callers distinguish the two "no rotation" cases from
28
+ * this: `0` is already the bottom note, `-1` is a note that isn't there.
29
+ */
30
+ index: number;
31
+ /** `notes` rotated so the named note is lowest — the input list when `index <= 0`. */
32
+ notes: string[];
33
+ }
34
+ /**
35
+ * Rotate a voicing so `startingNote` is its lowest note.
36
+ *
37
+ * Both sides are normalised before matching. `notes` keeps the chord's own
38
+ * spelling — Bbm is ["Bb", "Db", "F"] — so normalising only the input turned
39
+ * "Bbm starting on Db" into a search for "C#" and reported Db missing from a
40
+ * chord whose own error message listed it as the 3rd; matching literally is
41
+ * the mirror bug, and drops the rotation for "Bbm starting on C#".
42
+ */
43
+ export declare function rotateToStartingNote(notes: string[], startingNote: string): StartingNoteRotation;