@pepperhorn/chordl-core 0.3.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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/dist/audio/midi-export.d.ts +23 -0
  3. package/dist/audio/midi-export.js +155 -0
  4. package/dist/audio/midi-export.js.map +1 -0
  5. package/dist/chord-sheet/codec.d.ts +14 -0
  6. package/dist/chord-sheet/codec.js +119 -0
  7. package/dist/chord-sheet/codec.js.map +1 -0
  8. package/dist/chord-sheet/defaults.d.ts +13 -0
  9. package/dist/chord-sheet/defaults.js +48 -0
  10. package/dist/chord-sheet/defaults.js.map +1 -0
  11. package/dist/chord-sheet/index.d.ts +3 -0
  12. package/dist/chord-sheet/index.js +4 -0
  13. package/dist/chord-sheet/index.js.map +1 -0
  14. package/dist/chord-sheet/schema.d.ts +6 -0
  15. package/dist/chord-sheet/schema.js +17 -0
  16. package/dist/chord-sheet/schema.js.map +1 -0
  17. package/dist/config.d.ts +2 -0
  18. package/dist/config.js +15 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/engine/auto-fingering.d.ts +36 -0
  21. package/dist/engine/auto-fingering.js +344 -0
  22. package/dist/engine/auto-fingering.js.map +1 -0
  23. package/dist/engine/highlight-mapper.d.ts +3 -0
  24. package/dist/engine/highlight-mapper.js +46 -0
  25. package/dist/engine/highlight-mapper.js.map +1 -0
  26. package/dist/engine/keyboard-layout.d.ts +6 -0
  27. package/dist/engine/keyboard-layout.js +65 -0
  28. package/dist/engine/keyboard-layout.js.map +1 -0
  29. package/dist/engine/note-spelling.d.ts +54 -0
  30. package/dist/engine/note-spelling.js +103 -0
  31. package/dist/engine/note-spelling.js.map +1 -0
  32. package/dist/engine/scale-fingering.d.ts +21 -0
  33. package/dist/engine/scale-fingering.js +130 -0
  34. package/dist/engine/scale-fingering.js.map +1 -0
  35. package/dist/engine/staff-constants.d.ts +16 -0
  36. package/dist/engine/staff-constants.js +20 -0
  37. package/dist/engine/staff-constants.js.map +1 -0
  38. package/dist/engine/staff-glyphs.d.ts +17 -0
  39. package/dist/engine/staff-glyphs.js +59 -0
  40. package/dist/engine/staff-glyphs.js.map +1 -0
  41. package/dist/engine/staff-layout.d.ts +28 -0
  42. package/dist/engine/staff-layout.js +238 -0
  43. package/dist/engine/staff-layout.js.map +1 -0
  44. package/dist/engine/svg-constants.d.ts +19 -0
  45. package/dist/engine/svg-constants.js +40 -0
  46. package/dist/engine/svg-constants.js.map +1 -0
  47. package/dist/index.d.ts +42 -0
  48. package/dist/index.js +37 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/logging.d.ts +70 -0
  51. package/dist/logging.js +24 -0
  52. package/dist/logging.js.map +1 -0
  53. package/dist/parser/natural-language.d.ts +9 -0
  54. package/dist/parser/natural-language.js +406 -0
  55. package/dist/parser/natural-language.js.map +1 -0
  56. package/dist/parser/progression-parser.d.ts +16 -0
  57. package/dist/parser/progression-parser.js +97 -0
  58. package/dist/parser/progression-parser.js.map +1 -0
  59. package/dist/pipeline.d.ts +33 -0
  60. package/dist/pipeline.js +203 -0
  61. package/dist/pipeline.js.map +1 -0
  62. package/dist/progression/form-templates.d.ts +11 -0
  63. package/dist/progression/form-templates.js +105 -0
  64. package/dist/progression/form-templates.js.map +1 -0
  65. package/dist/progression/index.d.ts +5 -0
  66. package/dist/progression/index.js +4 -0
  67. package/dist/progression/index.js.map +1 -0
  68. package/dist/progression/progression-resolver.d.ts +29 -0
  69. package/dist/progression/progression-resolver.js +135 -0
  70. package/dist/progression/progression-resolver.js.map +1 -0
  71. package/dist/progression/roman-numeral.d.ts +14 -0
  72. package/dist/progression/roman-numeral.js +78 -0
  73. package/dist/progression/roman-numeral.js.map +1 -0
  74. package/dist/resolver/auto-layout.d.ts +20 -0
  75. package/dist/resolver/auto-layout.js +195 -0
  76. package/dist/resolver/auto-layout.js.map +1 -0
  77. package/dist/resolver/chord-resolver.d.ts +8 -0
  78. package/dist/resolver/chord-resolver.js +172 -0
  79. package/dist/resolver/chord-resolver.js.map +1 -0
  80. package/dist/resolver/scale-resolver.d.ts +9 -0
  81. package/dist/resolver/scale-resolver.js +45 -0
  82. package/dist/resolver/scale-resolver.js.map +1 -0
  83. package/dist/themes/boomwhacker.d.ts +2 -0
  84. package/dist/themes/boomwhacker.js +35 -0
  85. package/dist/themes/boomwhacker.js.map +1 -0
  86. package/dist/themes/crf.d.ts +19 -0
  87. package/dist/themes/crf.js +34 -0
  88. package/dist/themes/crf.js.map +1 -0
  89. package/dist/themes/index.d.ts +3 -0
  90. package/dist/themes/index.js +25 -0
  91. package/dist/themes/index.js.map +1 -0
  92. package/dist/themes/simple.d.ts +2 -0
  93. package/dist/themes/simple.js +13 -0
  94. package/dist/themes/simple.js.map +1 -0
  95. package/dist/theory/chord-tones.d.ts +56 -0
  96. package/dist/theory/chord-tones.js +215 -0
  97. package/dist/theory/chord-tones.js.map +1 -0
  98. package/dist/theory/degree-labels.d.ts +31 -0
  99. package/dist/theory/degree-labels.js +74 -0
  100. package/dist/theory/degree-labels.js.map +1 -0
  101. package/dist/types.d.ts +123 -0
  102. package/dist/types.js +2 -0
  103. package/dist/types.js.map +1 -0
  104. package/package.json +37 -0
@@ -0,0 +1,135 @@
1
+ import { MAX_EXAMPLES } from "../config";
2
+ import { resolveChord } from "../resolver/chord-resolver";
3
+ import { resolveProgression, tokenizeProgression } from "./roman-numeral";
4
+ import { findTemplate } from "./form-templates";
5
+ import { findVoicing, voicingPitchClasses, mapToVoicingQuality, queryVoicings, inferStyle, } from "@pepperhorn/chordl-voicings";
6
+ /**
7
+ * A set of contrasting styles to auto-cycle through when no style hint is given.
8
+ * Ordered by musical diversity.
9
+ */
10
+ const CONTRASTING_STYLES = [
11
+ "Rootless Type A",
12
+ "Drop 2",
13
+ "Shell",
14
+ "Quartal",
15
+ "Spread",
16
+ "4-Note Closed",
17
+ "Rootless Type B",
18
+ "Upper Structure",
19
+ "Drop 2+4",
20
+ ];
21
+ /**
22
+ * Given a style hint, return variations within that family.
23
+ * e.g. "Bill Evans" → [Rootless Type A, Rootless Type B, Shell]
24
+ */
25
+ function getStyleVariations(styleHint) {
26
+ const primary = inferStyle(styleHint);
27
+ if (!primary)
28
+ return CONTRASTING_STYLES.slice(0, MAX_EXAMPLES);
29
+ const variations = [primary];
30
+ // Add related styles
31
+ if (primary === "Rootless Type A") {
32
+ variations.push("Rootless Type B", "Shell");
33
+ }
34
+ else if (primary === "Rootless Type B") {
35
+ variations.push("Rootless Type A", "Shell");
36
+ }
37
+ else if (primary === "Shell") {
38
+ variations.push("Rootless Type A", "Drop 2");
39
+ }
40
+ else if (primary === "Drop 2") {
41
+ variations.push("Drop 2+4", "Shell");
42
+ }
43
+ else if (primary === "Drop 2+4") {
44
+ variations.push("Drop 2", "Spread");
45
+ }
46
+ else if (primary === "Quartal") {
47
+ variations.push("Upper Structure", "Rootless Type A");
48
+ }
49
+ else if (primary === "Upper Structure") {
50
+ variations.push("Quartal", "Spread");
51
+ }
52
+ else if (primary === "Spread") {
53
+ variations.push("Drop 2", "4-Note Closed");
54
+ }
55
+ else if (primary === "4-Note Closed") {
56
+ variations.push("Spread", "Drop 2");
57
+ }
58
+ return variations.slice(0, MAX_EXAMPLES);
59
+ }
60
+ /**
61
+ * Voice a single chord symbol using a specific voicing style.
62
+ * Falls back to plain resolution if no voicing matches.
63
+ */
64
+ function voiceChord(symbol, style) {
65
+ const resolved = resolveChord(symbol);
66
+ const quality = mapToVoicingQuality(resolved.type, resolved.notes);
67
+ let notes = resolved.notes;
68
+ let voicingStyle;
69
+ if (quality && style) {
70
+ const matches = queryVoicings({ quality, style });
71
+ if (matches.length > 0) {
72
+ const pcs = voicingPitchClasses(resolved.root, matches[0]);
73
+ if (pcs.length > 0) {
74
+ notes = pcs;
75
+ voicingStyle = style;
76
+ }
77
+ }
78
+ }
79
+ // Fallback: try any voicing for this quality
80
+ if (!voicingStyle && quality) {
81
+ const voicing = findVoicing(quality);
82
+ if (voicing) {
83
+ const pcs = voicingPitchClasses(resolved.root, voicing);
84
+ if (pcs.length > 0) {
85
+ notes = pcs;
86
+ voicingStyle = voicing.tags.style;
87
+ }
88
+ }
89
+ }
90
+ return {
91
+ symbol,
92
+ root: resolved.root,
93
+ notes,
94
+ voicingStyle,
95
+ };
96
+ }
97
+ /**
98
+ * Resolve a progression request into multiple voicing examples.
99
+ */
100
+ export function resolveProgressionRequest(req) {
101
+ const numExamples = Math.min(req.numExamples ?? MAX_EXAMPLES, MAX_EXAMPLES);
102
+ // Try form template first
103
+ const template = findTemplate(req.progression);
104
+ let numerals;
105
+ let progressionName;
106
+ if (template) {
107
+ numerals = template.numerals;
108
+ progressionName = template.name;
109
+ }
110
+ else {
111
+ numerals = tokenizeProgression(req.progression);
112
+ progressionName = numerals.join("-");
113
+ }
114
+ // Resolve roman numerals to chord symbols in the key
115
+ const chords = resolveProgression(numerals, req.key);
116
+ // Determine voicing styles for each example
117
+ const styles = req.styleHint
118
+ ? getStyleVariations(req.styleHint)
119
+ : CONTRASTING_STYLES.slice(0, numExamples);
120
+ const examples = [];
121
+ for (let i = 0; i < numExamples; i++) {
122
+ const style = styles[i % styles.length];
123
+ const voicedChords = chords.map((c) => voiceChord(c.symbol, style));
124
+ examples.push({
125
+ label: `Example ${i + 1} — ${style}`,
126
+ chords: voicedChords,
127
+ });
128
+ }
129
+ return {
130
+ progressionName,
131
+ key: req.key,
132
+ examples,
133
+ };
134
+ }
135
+ //# sourceMappingURL=progression-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progression-resolver.js","sourceRoot":"","sources":["../../src/progression/progression-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ interface RomanNumeralChord {
2
+ root: string;
3
+ symbol: string;
4
+ degree: number;
5
+ }
6
+ /**
7
+ * Resolve a roman numeral progression in a given key.
8
+ */
9
+ export declare function resolveProgression(numerals: string[], key: string): RomanNumeralChord[];
10
+ /**
11
+ * Parse a progression string like "ii-V-I" or "ii V7 Imaj7" into tokens.
12
+ */
13
+ export declare function tokenizeProgression(input: string): string[];
14
+ export {};
@@ -0,0 +1,78 @@
1
+ import { Note } from "tonal";
2
+ import { spellWithPreference } from "../engine/note-spelling";
3
+ const ROMAN_TO_SEMITONE = {
4
+ i: 0, ii: 2, iii: 4, iv: 5, v: 7, vi: 9, vii: 11,
5
+ };
6
+ /**
7
+ * Parse a single roman numeral token like "ii", "V7", "bIII", "#ivø7", "viio7"
8
+ */
9
+ function parseRomanToken(token) {
10
+ // Alternation order matters: "iv" before "i" (otherwise "i" matches first
11
+ // in "iv"), "vi{1,2}" before "v" (otherwise "v" matches first in "vi").
12
+ // "i{1,3}" handles i, ii, iii. Suffix group (.*) captures quality markers.
13
+ const re = /^([#b]?)(iv|vi{1,2}|i{1,3}|v)(.*)/i;
14
+ const m = token.match(re);
15
+ if (!m)
16
+ return null;
17
+ const accidental = m[1];
18
+ const numeral = m[2];
19
+ const suffix = m[3];
20
+ const lower = numeral.toLowerCase();
21
+ const base = ROMAN_TO_SEMITONE[lower];
22
+ if (base === undefined)
23
+ return null;
24
+ let semitones = base;
25
+ if (accidental === "b")
26
+ semitones -= 1;
27
+ if (accidental === "#")
28
+ semitones += 1;
29
+ semitones = ((semitones % 12) + 12) % 12;
30
+ const isMinor = numeral === numeral.toLowerCase();
31
+ let quality = "";
32
+ if (suffix) {
33
+ let norm = suffix
34
+ .replace(/^°7/, "dim7")
35
+ .replace(/^°/, "dim")
36
+ .replace(/^ø7?/, "m7b5")
37
+ .replace(/^Δ7?/, "maj7");
38
+ if (isMinor && !norm.startsWith("m") && !norm.startsWith("dim") && !norm.startsWith("M")) {
39
+ quality = "m" + norm;
40
+ }
41
+ else {
42
+ quality = norm;
43
+ }
44
+ }
45
+ else {
46
+ quality = isMinor ? "m" : "";
47
+ }
48
+ return { semitones, quality, hasFlat: accidental === "b" };
49
+ }
50
+ /**
51
+ * Resolve a roman numeral progression in a given key.
52
+ */
53
+ export function resolveProgression(numerals, key) {
54
+ const keyMidi = Note.midi(`${key}4`);
55
+ if (keyMidi == null)
56
+ throw new Error(`Invalid key: ${key}`);
57
+ return numerals.map((token) => {
58
+ const parsed = parseRomanToken(token);
59
+ if (!parsed)
60
+ throw new Error(`Can't parse roman numeral: "${token}"`);
61
+ const rootMidi = keyMidi + parsed.semitones;
62
+ const rootNote = Note.pitchClass(Note.fromMidi(rootMidi));
63
+ const root = spellWithPreference(rootNote, key, parsed.hasFlat);
64
+ const symbol = root + parsed.quality;
65
+ return { root, symbol, degree: parsed.semitones };
66
+ });
67
+ }
68
+ /**
69
+ * Parse a progression string like "ii-V-I" or "ii V7 Imaj7" into tokens.
70
+ */
71
+ export function tokenizeProgression(input) {
72
+ return input
73
+ .split(/[\s\-–—→>]+/)
74
+ .map((t) => t.trim())
75
+ .filter((t) => t.length > 0);
76
+ }
77
+ // preferredEnharmonic is now spellWithPreference from note-spelling.ts
78
+ //# sourceMappingURL=roman-numeral.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roman-numeral.js","sourceRoot":"","sources":["../../src/progression/roman-numeral.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { WhiteNote } from "../types";
2
+ /** Check if a white-key index (mod 7) has a sharp. */
3
+ export declare function whiteIdxHasSharp(idx: number): boolean;
4
+ export interface LayoutOptions {
5
+ padding?: number;
6
+ startingNote?: string;
7
+ spanFrom?: string;
8
+ spanTo?: string;
9
+ }
10
+ export interface LayoutResult {
11
+ startFrom: WhiteNote;
12
+ size: number;
13
+ /** Relative octave where the lowest chord note sits (for octave-qualified highlights). */
14
+ chordOctave: number;
15
+ /** True when the left edge was extended for black-key context (crop half a key). */
16
+ clipLeft?: boolean;
17
+ /** True when the right edge was extended for black-key context (crop half a key). */
18
+ clipRight?: boolean;
19
+ }
20
+ export declare function calculateLayout(notes: string[], options?: LayoutOptions): LayoutResult;
@@ -0,0 +1,195 @@
1
+ import { WHITE_NOTE_ORDER } from "../engine/svg-constants";
2
+ import { normalizeToSharps } from "../engine/note-spelling";
3
+ // White notes that have a sharp (black key to their right)
4
+ const HAS_SHARP = new Set(["C", "D", "F", "G", "A"]);
5
+ /** Check if a white-key index (mod 7) has a sharp. */
6
+ export function whiteIdxHasSharp(idx) {
7
+ return HAS_SHARP.has(WHITE_NOTE_ORDER[((idx % 7) + 7) % 7]);
8
+ }
9
+ // Map a note to its nearest white key (the white key it sits on or just below)
10
+ function nearestWhiteKey(note) {
11
+ const normalized = normalizeToSharps(note);
12
+ const base = normalized.replace("#", "");
13
+ return base;
14
+ }
15
+ // Count white keys from one white note to another (inclusive of start, exclusive of end)
16
+ function whiteKeySpan(from, to) {
17
+ const fromIdx = WHITE_NOTE_ORDER.indexOf(from);
18
+ let toIdx = WHITE_NOTE_ORDER.indexOf(to);
19
+ if (toIdx < fromIdx)
20
+ toIdx += 7;
21
+ return toIdx - fromIdx;
22
+ }
23
+ /**
24
+ * Ensure the visible keyboard range doesn't cut through the middle of a
25
+ * black-key group, making orientation difficult.
26
+ *
27
+ * Piano keys form repeating groups:
28
+ * - 2-group: C(0), D(1), E(2) — black keys C#, D#
29
+ * - 3-group: F(3), G(4), A(5), B(6) — black keys F#, G#, A#
30
+ *
31
+ * Rule: if the keyboard starts or ends in the INTERIOR of a group
32
+ * (not at its boundary), extend outward to the nearest group boundary.
33
+ * Starting/ending at a group boundary (C, F, E, B) is fine — the edge
34
+ * itself provides enough orientation context.
35
+ */
36
+ function ensureFullBlackKeyGroups(startIdx, endIdx) {
37
+ // Determine which position within a group the start/end falls
38
+ // Group boundaries: C(0)=start of 2-group, F(3)=start of 3-group
39
+ // Interior: D(1), E(2) are interior of 2-group; G(4), A(5) are interior of 3-group
40
+ // B(6) is the end of the 3-group — a boundary, fine to start/end on
41
+ const startMod = ((startIdx % 7) + 7) % 7;
42
+ const endMod = ((endIdx % 7) + 7) % 7;
43
+ // Fix left edge: if we start in the interior of a group, extend left to group start.
44
+ // Group boundaries (OK to start on): C(0), F(3), E(2)→end of 2-group, B(6)→end of 3-group
45
+ // Interior (confusing): D(1), G(4), A(5)
46
+ if (startMod === 1)
47
+ startIdx -= 1; // D → extend to C
48
+ if (startMod === 4)
49
+ startIdx -= 1; // G → extend to F
50
+ if (startMod === 5)
51
+ startIdx -= 2; // A → extend to F
52
+ // Fix right edge: if we end in the interior of a group, extend right to group end.
53
+ // Group boundaries (OK to end on): E(2), B(6), C(0)→start of 2-group, F(3)→start of 3-group
54
+ // Interior (confusing): D(1), G(4), A(5)
55
+ if (endMod === 1)
56
+ endIdx += 1; // D → extend to E
57
+ if (endMod === 4)
58
+ endIdx += 2; // G → extend to B
59
+ if (endMod === 5)
60
+ endIdx += 1; // A → extend to B
61
+ return { startIdx, endIdx };
62
+ }
63
+ export function calculateLayout(notes, options = {}) {
64
+ const { padding = 1, startingNote, spanFrom, spanTo } = options;
65
+ // Explicit starting note: anchor the keyboard there
66
+ if (startingNote) {
67
+ const anchorKey = nearestWhiteKey(startingNote);
68
+ let startIdx = WHITE_NOTE_ORDER.indexOf(anchorKey) - padding;
69
+ // Black-key context: extend left edge if it has a sharp
70
+ let clipLeft = false;
71
+ if (whiteIdxHasSharp(startIdx)) {
72
+ startIdx -= 1;
73
+ clipLeft = true;
74
+ }
75
+ const start = WHITE_NOTE_ORDER[((startIdx % 7) + 7) % 7];
76
+ // Size the keyboard to fit all notes with padding on the right
77
+ if (notes.length > 0) {
78
+ const whiteKeys = notes.map(nearestWhiteKey);
79
+ // Treat notes as ascending from the anchor: each note at or below
80
+ // the previous wraps into the next octave (matches voicing rotation).
81
+ const anchorWhiteIdx = WHITE_NOTE_ORDER.indexOf(anchorKey);
82
+ const indices = [];
83
+ for (const w of whiteKeys) {
84
+ let idx = WHITE_NOTE_ORDER.indexOf(w);
85
+ if (indices.length === 0) {
86
+ // First note: place relative to anchor (must be >= anchor)
87
+ while (idx < anchorWhiteIdx)
88
+ idx += 7;
89
+ }
90
+ else {
91
+ // Subsequent notes: must be above the previous
92
+ const lastIdx = indices[indices.length - 1];
93
+ while (idx <= lastIdx)
94
+ idx += 7;
95
+ }
96
+ indices.push(idx);
97
+ }
98
+ const maxIdx = Math.max(...indices);
99
+ let endIdx = maxIdx + padding;
100
+ // Black-key context: extend right edge if it lands on a sharp key
101
+ let clipRight = false;
102
+ if (whiteIdxHasSharp(endIdx)) {
103
+ endIdx += 1;
104
+ clipRight = true;
105
+ }
106
+ // Ensure full black-key groups are visible
107
+ const expanded = ensureFullBlackKeyGroups(startIdx, endIdx);
108
+ startIdx = expanded.startIdx;
109
+ endIdx = expanded.endIdx;
110
+ const finalStart = WHITE_NOTE_ORDER[((startIdx % 7) + 7) % 7];
111
+ let span = endIdx - startIdx + 1;
112
+ // chordOctave: count C crossings from start to the anchor note
113
+ const anchorIdx = WHITE_NOTE_ORDER.indexOf(anchorKey);
114
+ const startNoteIdx = WHITE_NOTE_ORDER.indexOf(finalStart);
115
+ const rawSteps = ((anchorIdx - startNoteIdx) % 7 + 7) % 7;
116
+ // Only add a full octave if the modular distance is 0 (anchor == start)
117
+ // AND there is padding (meaning the anchor is actually one octave away)
118
+ const stepsToAnchor = rawSteps === 0 && padding > 0 ? 7 : rawSteps;
119
+ let chordOctave = 0;
120
+ for (let i = 1; i <= stepsToAnchor; i++) {
121
+ if (WHITE_NOTE_ORDER[(startNoteIdx + i) % 7] === "C")
122
+ chordOctave++;
123
+ }
124
+ return { startFrom: finalStart, size: Math.max(span, notes.length + 2), chordOctave, clipLeft, clipRight };
125
+ }
126
+ return { startFrom: start, size: 8, chordOctave: 0, clipLeft };
127
+ }
128
+ if (spanFrom && spanTo) {
129
+ const from = nearestWhiteKey(spanFrom);
130
+ const to = nearestWhiteKey(spanTo);
131
+ const span = whiteKeySpan(from, to);
132
+ return {
133
+ startFrom: from,
134
+ size: span === 0 ? 8 : span + 1,
135
+ chordOctave: 0,
136
+ };
137
+ }
138
+ if (notes.length === 0) {
139
+ return { startFrom: "C", size: 8, chordOctave: 0 };
140
+ }
141
+ // Treat notes as an ascending sequence: each note that is at or below
142
+ // the previous one (in white-key index) wraps into the next octave.
143
+ const whiteKeys = notes.map(nearestWhiteKey);
144
+ const ascending = [];
145
+ for (const w of whiteKeys) {
146
+ let idx = WHITE_NOTE_ORDER.indexOf(w);
147
+ if (ascending.length > 0) {
148
+ const lastIdx = ascending[ascending.length - 1];
149
+ while (idx <= lastIdx)
150
+ idx += 7;
151
+ }
152
+ ascending.push(idx);
153
+ }
154
+ const minAsc = ascending[0];
155
+ const maxAsc = ascending[ascending.length - 1];
156
+ let startIdx = minAsc - padding;
157
+ let endIdx = maxAsc + padding;
158
+ // Black-key context padding: if an edge white key has a sharp, extend by 1
159
+ // so the neighboring black key is visible, giving orientation landmarks.
160
+ let clipLeft = false;
161
+ let clipRight = false;
162
+ if (whiteIdxHasSharp(startIdx)) {
163
+ startIdx -= 1;
164
+ clipLeft = true;
165
+ }
166
+ if (whiteIdxHasSharp(endIdx)) {
167
+ endIdx += 1;
168
+ clipRight = true;
169
+ }
170
+ // Ensure full black-key groups are visible for keyboard orientation.
171
+ // If we can see part of the 3-group (F-B) or 2-group (C-E), show all of it.
172
+ const expanded = ensureFullBlackKeyGroups(startIdx, endIdx);
173
+ startIdx = expanded.startIdx;
174
+ endIdx = expanded.endIdx;
175
+ const startNote = WHITE_NOTE_ORDER[((startIdx % 7) + 7) % 7];
176
+ const keyCount = endIdx - startIdx + 1;
177
+ // Calculate which relative octave the chord notes live in.
178
+ // Count how many times we cross C going from startNote to the first chord note.
179
+ const actualPadding = minAsc - startIdx;
180
+ const startNoteIdx = WHITE_NOTE_ORDER.indexOf(startNote);
181
+ let chordOctave = 0;
182
+ for (let i = 1; i <= actualPadding; i++) {
183
+ const noteIdx = (startNoteIdx + i) % 7;
184
+ if (WHITE_NOTE_ORDER[noteIdx] === "C")
185
+ chordOctave++;
186
+ }
187
+ return {
188
+ startFrom: startNote,
189
+ size: Math.max(keyCount, notes.length + 2),
190
+ chordOctave,
191
+ clipLeft,
192
+ clipRight,
193
+ };
194
+ }
195
+ //# sourceMappingURL=auto-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-layout.js","sourceRoot":"","sources":["../../src/resolver/auto-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface ResolvedChord {
2
+ notes: string[];
3
+ root: string;
4
+ type: string;
5
+ /** Tonal interval names when available (e.g. ["1P", "3M", "5P", "7M"]) */
6
+ intervals?: string[];
7
+ }
8
+ export declare function resolveChord(chordName: string, inversion?: number): ResolvedChord;
@@ -0,0 +1,172 @@
1
+ import { Chord, Note } from "tonal";
2
+ /**
3
+ * Simplify a note name via Tonal without forcing sharp spelling.
4
+ * Tonal's simplify already respects musical context (e.g. Bb stays Bb).
5
+ */
6
+ function simplifyNote(note) {
7
+ const simplified = Note.simplify(note);
8
+ return simplified || note;
9
+ }
10
+ // Alteration tokens we can strip and re-apply as intervals
11
+ const ALTERATION_RE = /([#b](?:5|9|11|13)|add(?:9|11|13|b9|#9|b13|#11))$/;
12
+ const ALTERATION_TO_INTERVAL = {
13
+ "#5": "5A",
14
+ "b5": "5d",
15
+ "#9": "2A",
16
+ "b9": "2m",
17
+ "#11": "4A",
18
+ "b11": "4d",
19
+ "#13": "6A",
20
+ "b13": "6m",
21
+ "add9": "2M",
22
+ "add11": "4P",
23
+ "add13": "6M",
24
+ "addb9": "2m",
25
+ "add#9": "2A",
26
+ "add#11": "4A",
27
+ "addb13": "6m",
28
+ };
29
+ // Try to resolve by stripping trailing alterations one at a time,
30
+ // then transposing the root by those intervals to get extra notes
31
+ function resolveWithFallback(chordName) {
32
+ // Extract root note
33
+ const rootMatch = chordName.match(/^([A-G][#b]?)/);
34
+ if (!rootMatch)
35
+ return null;
36
+ const root = rootMatch[1];
37
+ let suffix = chordName.slice(root.length);
38
+ const extraIntervals = [];
39
+ // Strip alterations from the end until Tonal recognizes the base
40
+ let attempts = 0;
41
+ while (suffix && attempts < 5) {
42
+ const match = suffix.match(ALTERATION_RE);
43
+ if (!match)
44
+ break;
45
+ const token = match[1];
46
+ const interval = ALTERATION_TO_INTERVAL[token];
47
+ if (!interval)
48
+ break;
49
+ extraIntervals.unshift(interval);
50
+ suffix = suffix.slice(0, -token.length);
51
+ attempts++;
52
+ const chord = Chord.get(root + suffix);
53
+ if (!chord.empty) {
54
+ let notes = chord.notes.map(simplifyNote);
55
+ // Add extra notes from stripped alterations
56
+ for (const ivl of extraIntervals) {
57
+ const extra = Note.transpose(root, ivl);
58
+ if (extra) {
59
+ const normalized = simplifyNote(extra);
60
+ // Replace if same degree already exists, otherwise add
61
+ if (!notes.includes(normalized)) {
62
+ notes.push(normalized);
63
+ }
64
+ }
65
+ }
66
+ return { notes, root: simplifyNote(root), type: chord.type + " (extended)" };
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ // Normalize chord symbols that Tonal doesn't recognize into equivalents
72
+ function normalizeChordName(name) {
73
+ // Extract root note first
74
+ const rootMatch = name.match(/^([A-G][#b]?)/i);
75
+ if (!rootMatch)
76
+ return name;
77
+ const root = rootMatch[1];
78
+ let suffix = name.slice(root.length);
79
+ // m7sus is special — handled by buildSpecialChord, don't normalize it away
80
+ // Cmaug → Cm#5 (minor augmented)
81
+ suffix = suffix.replace(/^maug$/i, "m#5");
82
+ // Cm6/9 → Cm6add9 (Tonal doesn't know m6/9 but knows the notes)
83
+ // Actually build it manually below via special handling
84
+ // CmMaj → CmM (Tonal recognizes mM but not mmaj for some roots)
85
+ // Dbmmaj7 → DbmMaj7 — ensure proper casing
86
+ suffix = suffix.replace(/mmaj/i, "mMaj");
87
+ // omit3 → remove, we'll handle it post-resolution
88
+ // Keep it for now, handle in resolveChord
89
+ return root + suffix;
90
+ }
91
+ // Special chord builders for symbols Tonal can't handle at all
92
+ function buildSpecialChord(name) {
93
+ const rootMatch = name.match(/^([A-G][#b]?)/i);
94
+ if (!rootMatch)
95
+ return null;
96
+ const root = rootMatch[1];
97
+ const suffix = name.slice(root.length);
98
+ // X7omit3 → root, 5th, b7 (no 3rd)
99
+ if (/^7omit3$/i.test(suffix)) {
100
+ return {
101
+ notes: [root, Note.transpose(root, "5P"), Note.transpose(root, "7m")].map(simplifyNote),
102
+ root: simplifyNote(root),
103
+ type: "7omit3",
104
+ };
105
+ }
106
+ // Xm7sus / Xm7sus4 → root, m3, P4, m7
107
+ if (/^m7sus4?$/i.test(suffix)) {
108
+ return {
109
+ notes: [
110
+ root,
111
+ Note.transpose(root, "3m"),
112
+ Note.transpose(root, "4P"),
113
+ Note.transpose(root, "7m"),
114
+ ].map(simplifyNote),
115
+ root: simplifyNote(root),
116
+ type: "m7sus4",
117
+ };
118
+ }
119
+ // Xm6/9 → root, m3, 5, 6, 9
120
+ if (/^m6\/9$/i.test(suffix)) {
121
+ return {
122
+ notes: [
123
+ root,
124
+ Note.transpose(root, "3m"),
125
+ Note.transpose(root, "5P"),
126
+ Note.transpose(root, "6M"),
127
+ Note.transpose(root, "2M"),
128
+ ].map(simplifyNote),
129
+ root: simplifyNote(root),
130
+ type: "m6/9",
131
+ };
132
+ }
133
+ return null;
134
+ }
135
+ export function resolveChord(chordName, inversion) {
136
+ chordName = normalizeChordName(chordName);
137
+ const chord = Chord.get(chordName);
138
+ let notes;
139
+ let root;
140
+ let type;
141
+ let intervals;
142
+ // Try special chord builders first (omit3, m6/9, etc.)
143
+ const special = buildSpecialChord(chordName);
144
+ if (special) {
145
+ notes = special.notes;
146
+ root = special.root;
147
+ type = special.type;
148
+ }
149
+ else if (!chord.empty) {
150
+ notes = chord.notes.map(simplifyNote);
151
+ root = simplifyNote(chord.tonic ?? notes[0]);
152
+ type = chord.type;
153
+ intervals = chord.intervals;
154
+ }
155
+ else {
156
+ // Fallback: strip trailing alterations and reapply as intervals
157
+ const fallback = resolveWithFallback(chordName);
158
+ if (!fallback) {
159
+ throw new Error(`Unknown chord: "${chordName}"`);
160
+ }
161
+ notes = fallback.notes;
162
+ root = fallback.root;
163
+ type = fallback.type;
164
+ }
165
+ // Apply inversion: rotate notes array
166
+ if (inversion && inversion > 0) {
167
+ const rotation = inversion % notes.length;
168
+ notes = [...notes.slice(rotation), ...notes.slice(0, rotation)];
169
+ }
170
+ return { notes, root, type, intervals };
171
+ }
172
+ //# sourceMappingURL=chord-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chord-resolver.js","sourceRoot":"","sources":["../../src/resolver/chord-resolver.ts"],"names":[],"mappings":"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"}