@pepperhorn/chordl-core 0.3.4 → 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/dist/chord-sheet/codec.d.ts +1 -1
- package/dist/chord-sheet/codec.js +1 -1
- package/dist/chord-sheet/codec.js.map +1 -1
- package/dist/chord-sheet/defaults.d.ts +1 -1
- package/dist/chord-sheet/index.d.ts +3 -3
- package/dist/chord-sheet/index.js +3 -3
- package/dist/chord-sheet/index.js.map +1 -1
- package/dist/engine/auto-fingering.js +1 -1
- package/dist/engine/auto-fingering.js.map +1 -1
- package/dist/engine/highlight-mapper.d.ts +1 -1
- package/dist/engine/highlight-mapper.js +2 -2
- package/dist/engine/highlight-mapper.js.map +1 -1
- package/dist/engine/keyboard-layout.d.ts +1 -1
- package/dist/engine/keyboard-layout.js +1 -1
- package/dist/engine/keyboard-layout.js.map +1 -1
- package/dist/engine/mei-builder.d.ts +27 -0
- package/dist/engine/mei-builder.js +138 -0
- package/dist/engine/mei-builder.js.map +1 -0
- package/dist/engine/staff-glyphs.js +5 -10
- package/dist/engine/staff-glyphs.js.map +1 -1
- package/dist/engine/staff-layout.js +2 -2
- package/dist/engine/staff-layout.js.map +1 -1
- package/dist/index.d.ts +48 -42
- package/dist/index.js +32 -28
- package/dist/index.js.map +1 -1
- package/dist/parser/natural-language.d.ts +1 -1
- package/dist/parser/natural-language.js +241 -31
- package/dist/parser/natural-language.js.map +1 -1
- package/dist/parser/progression-parser.d.ts +1 -1
- package/dist/parser/progression-parser.js +1 -1
- package/dist/parser/progression-parser.js.map +1 -1
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +46 -25
- package/dist/pipeline.js.map +1 -1
- package/dist/progression/index.d.ts +5 -5
- package/dist/progression/index.js +3 -3
- package/dist/progression/index.js.map +1 -1
- package/dist/progression/progression-resolver.js +5 -5
- package/dist/progression/progression-resolver.js.map +1 -1
- package/dist/progression/roman-numeral.js +1 -1
- package/dist/progression/roman-numeral.js.map +1 -1
- package/dist/resolver/auto-layout.d.ts +1 -1
- package/dist/resolver/auto-layout.js +2 -2
- package/dist/resolver/auto-layout.js.map +1 -1
- package/dist/resolver/chord-resolver.d.ts +11 -1
- package/dist/resolver/chord-resolver.js +28 -2
- package/dist/resolver/chord-resolver.js.map +1 -1
- package/dist/resolver/scale-resolver.d.ts +5 -2
- package/dist/resolver/scale-resolver.js +28 -8
- package/dist/resolver/scale-resolver.js.map +1 -1
- package/dist/themes/boomwhacker.d.ts +1 -1
- package/dist/themes/boomwhacker.js +1 -1
- package/dist/themes/boomwhacker.js.map +1 -1
- package/dist/themes/crf.d.ts +1 -1
- package/dist/themes/crf.js +1 -1
- package/dist/themes/crf.js.map +1 -1
- package/dist/themes/index.d.ts +1 -1
- package/dist/themes/index.js +3 -3
- package/dist/themes/index.js.map +1 -1
- package/dist/themes/simple.d.ts +1 -1
- package/dist/themes/simple.js +1 -1
- package/dist/themes/simple.js.map +1 -1
- package/dist/theory/constrained-variants.d.ts +46 -0
- package/dist/theory/constrained-variants.js +117 -0
- package/dist/theory/constrained-variants.js.map +1 -0
- package/dist/theory/degree-labels.d.ts +39 -0
- package/dist/theory/degree-labels.js +102 -1
- package/dist/theory/degree-labels.js.map +1 -1
- package/dist/theory/hand-constraints.d.ts +104 -0
- package/dist/theory/hand-constraints.js +214 -0
- package/dist/theory/hand-constraints.js.map +1 -0
- package/dist/types.d.ts +9 -1
- package/package.json +4 -4
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard reach constraints.
|
|
3
|
+
*
|
|
4
|
+
* Two orthogonal limits, both required:
|
|
5
|
+
* - maxSpanPerHand — physical reach, in semitones
|
|
6
|
+
* - maxNotesPerHand — coordination, in note count
|
|
7
|
+
*
|
|
8
|
+
* Count alone does not express reach: a 3-note cap still admits C-E-C', three
|
|
9
|
+
* notes spanning a full octave, which a young player cannot stretch. The
|
|
10
|
+
* converse also holds — a 4-note cluster inside a 5th is an easy reach but
|
|
11
|
+
* harder to coordinate.
|
|
12
|
+
*
|
|
13
|
+
* Deriving a junior cutoff from "no 7ths or octaves": a 7th spans 10-11
|
|
14
|
+
* semitones and an octave 12, so maxSpanPerHand 9 (a major 6th) is binding.
|
|
15
|
+
*/
|
|
16
|
+
import { PC_SEMITONES, assignAscendingOctaves } from "../engine/note-spelling.js";
|
|
17
|
+
/**
|
|
18
|
+
* MIDI number for a pitch class in a given octave, C4 = 60.
|
|
19
|
+
*
|
|
20
|
+
* PC_SEMITONES handles both sharps and flats, so no spelling normalization is
|
|
21
|
+
* needed here.
|
|
22
|
+
*/
|
|
23
|
+
export function midiOf(note, octave) {
|
|
24
|
+
const semi = PC_SEMITONES[note] ?? 0;
|
|
25
|
+
return semi + (octave + 1) * 12;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Stack pitch classes into an ascending voicing.
|
|
29
|
+
*
|
|
30
|
+
* @param notes - pitch classes in voicing order
|
|
31
|
+
* @param baseOctave - octave of the first note
|
|
32
|
+
* @param maxSpan - optional semitone limit; folds notes down where it can
|
|
33
|
+
* (0 = no folding). Folding is preferred over dropping because it keeps
|
|
34
|
+
* every chord tone.
|
|
35
|
+
*/
|
|
36
|
+
export function placeAscending(notes, baseOctave, maxSpan = 0) {
|
|
37
|
+
return assignAscendingOctaves(notes, baseOctave, maxSpan).map(({ note, octave }) => ({
|
|
38
|
+
note,
|
|
39
|
+
octave,
|
|
40
|
+
midi: midiOf(note, octave),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/** Semitones between the lowest and highest pitch. Zero for 0 or 1 notes. */
|
|
44
|
+
export function spanOf(pitches) {
|
|
45
|
+
if (pitches.length < 2)
|
|
46
|
+
return 0;
|
|
47
|
+
return Math.max(...pitches) - Math.min(...pitches);
|
|
48
|
+
}
|
|
49
|
+
const DEFAULT_HAND = "LH";
|
|
50
|
+
/**
|
|
51
|
+
* Fewest notes a hand may be reduced to. A dyad is already a shell voicing —
|
|
52
|
+
* below that there is no chord left to name, so reduction stops and reports
|
|
53
|
+
* failure rather than returning a single note as a satisfied chord.
|
|
54
|
+
*/
|
|
55
|
+
const MIN_NOTES_PER_HAND = 2;
|
|
56
|
+
/**
|
|
57
|
+
* Semitone value for matching pitch classes across spellings.
|
|
58
|
+
*
|
|
59
|
+
* `PC_SEMITONES` maps sharps and flats onto the same number, so "Bb" and "A#"
|
|
60
|
+
* compare equal — necessary because chordl-voicings spells library variants
|
|
61
|
+
* with sharps while core's resolver yields flats. Unknown spellings map to -1
|
|
62
|
+
* rather than 0 so they never collide with C.
|
|
63
|
+
*/
|
|
64
|
+
export function pcSemitone(note) {
|
|
65
|
+
return PC_SEMITONES[note] ?? -1;
|
|
66
|
+
}
|
|
67
|
+
function groupsOf(notes) {
|
|
68
|
+
const byHand = new Map();
|
|
69
|
+
for (const n of notes) {
|
|
70
|
+
const list = byHand.get(n.hand);
|
|
71
|
+
if (list)
|
|
72
|
+
list.push(n);
|
|
73
|
+
else
|
|
74
|
+
byHand.set(n.hand, [n]);
|
|
75
|
+
}
|
|
76
|
+
return [...byHand.values()];
|
|
77
|
+
}
|
|
78
|
+
function withinCaps(groups, c, maxNotes) {
|
|
79
|
+
return groups.every((g) => g.length <= maxNotes && spanOf(g.map((n) => n.midi)) <= c.maxSpanPerHand);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Place a subset of the voicing (by index) and settle each hand's octaves.
|
|
83
|
+
*
|
|
84
|
+
* Step 1 stacks the whole subset ascending with no folding, so the voicing's
|
|
85
|
+
* true shape is preserved. Step 2 groups the result by hand and re-folds
|
|
86
|
+
* only the hands whose span exceeds the cap — folding is per-hand because
|
|
87
|
+
* two hands an octave apart should not be squeezed together just because
|
|
88
|
+
* the whole voicing looks wide. Step 3 collapses duplicate MIDI numbers
|
|
89
|
+
* (same key, one finger) so they cost one slot of the note-count budget.
|
|
90
|
+
*/
|
|
91
|
+
function place(indices, notes, baseOctave, maxSpanPerHand, handOf) {
|
|
92
|
+
const notesForThisCall = indices.map((i) => notes[i]);
|
|
93
|
+
// Step 1: Place ascending with NO folding to preserve the voicing's true shape.
|
|
94
|
+
const unfolded = placeAscending(notesForThisCall, baseOctave, 0).map((p, i) => ({ ...p, originalIndex: indices[i], hand: handOf(indices[i]) }));
|
|
95
|
+
// Step 2: Group by hand and re-fold each hand independently if needed.
|
|
96
|
+
const handToNotes = new Map();
|
|
97
|
+
for (const p of unfolded) {
|
|
98
|
+
const list = handToNotes.get(p.hand);
|
|
99
|
+
if (list)
|
|
100
|
+
list.push(p);
|
|
101
|
+
else
|
|
102
|
+
handToNotes.set(p.hand, [p]);
|
|
103
|
+
}
|
|
104
|
+
const refolded = [];
|
|
105
|
+
for (const [hand, notesInHand] of handToNotes) {
|
|
106
|
+
const span = spanOf(notesInHand.map((n) => n.midi));
|
|
107
|
+
if (span <= maxSpanPerHand) {
|
|
108
|
+
// Already fits, use as-is
|
|
109
|
+
refolded.push(...notesInHand);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Exceeds span, re-fold this hand's notes
|
|
113
|
+
const refoldedHand = placeAscending(notesInHand.map((n) => n.note), notesInHand[0].octave, maxSpanPerHand);
|
|
114
|
+
for (let i = 0; i < refoldedHand.length; i++) {
|
|
115
|
+
refolded.push({
|
|
116
|
+
...refoldedHand[i],
|
|
117
|
+
originalIndex: notesInHand[i].originalIndex,
|
|
118
|
+
hand,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Restore original voicing order by sorting by originalIndex.
|
|
123
|
+
refolded.sort((a, b) => a.originalIndex - b.originalIndex);
|
|
124
|
+
// Step 3: Two notes on the same MIDI number are one key under one finger.
|
|
125
|
+
// Collapse them so they cost one slot of the note-count budget, not two.
|
|
126
|
+
// The first in voicing order wins, so the surviving spelling and hand are
|
|
127
|
+
// the ones the voicing led with.
|
|
128
|
+
const seenMidi = new Set();
|
|
129
|
+
return refolded.filter((p) => {
|
|
130
|
+
if (seenMidi.has(p.midi))
|
|
131
|
+
return false;
|
|
132
|
+
seenMidi.add(p.midi);
|
|
133
|
+
return true;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Fit a voicing inside a hand's reach and note count.
|
|
138
|
+
*
|
|
139
|
+
* Strategy, in order:
|
|
140
|
+
* 1. Fold octaves — keeps every chord tone, so it is always preferred.
|
|
141
|
+
* 2. Drop notes in `dropOrder`, never below `keepAtLeast`, and never below
|
|
142
|
+
* two notes in any one hand.
|
|
143
|
+
*
|
|
144
|
+
* `dropOrder` and `keepAtLeast` are matched by semitone rather than by
|
|
145
|
+
* spelling, so a drop order written in flats still recognises a voicing
|
|
146
|
+
* spelled in sharps. Each returned note keeps its own original spelling —
|
|
147
|
+
* only the matching normalizes.
|
|
148
|
+
*
|
|
149
|
+
* When the constraints still cannot be met without dropping an identity tone
|
|
150
|
+
* or taking a hand below a dyad, returns the closest legal voicing with
|
|
151
|
+
* `satisfied: false` rather than returning nothing or a chord that is no
|
|
152
|
+
* longer the chord asked for. Callers wanting a voicing that genuinely fits
|
|
153
|
+
* should try other variants — an inversion often fits where root position
|
|
154
|
+
* cannot.
|
|
155
|
+
*/
|
|
156
|
+
export function constrainVoicing(input) {
|
|
157
|
+
const baseOctave = input.baseOctave ?? 3;
|
|
158
|
+
const maxNotes = Math.max(MIN_NOTES_PER_HAND, input.constraints.maxNotesPerHand);
|
|
159
|
+
const keepSemitones = new Set(input.keepAtLeast.map(pcSemitone));
|
|
160
|
+
const handOf = (originalIndex) => input.handHints?.[originalIndex] ?? DEFAULT_HAND;
|
|
161
|
+
// Track note indices instead of strings to handle duplicates correctly.
|
|
162
|
+
// Each entry is an index into input.notes.
|
|
163
|
+
let working = input.notes.map((_, i) => i);
|
|
164
|
+
const dropped = [];
|
|
165
|
+
for (;;) {
|
|
166
|
+
const placed = place(working, input.notes, baseOctave, input.constraints.maxSpanPerHand, handOf);
|
|
167
|
+
// A collapsed duplicate is gone from the result, so drop it from `working`
|
|
168
|
+
// too — otherwise the drop loop would keep offering a note the caller can
|
|
169
|
+
// no longer see. `working` only ever shrinks, so this terminates.
|
|
170
|
+
if (placed.length < working.length) {
|
|
171
|
+
working = placed.map((p) => p.originalIndex);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const groups = groupsOf(placed);
|
|
175
|
+
const result = (satisfied) => ({
|
|
176
|
+
notes: placed.map((p) => ({ note: p.note, octave: p.octave, midi: p.midi, hand: p.hand })),
|
|
177
|
+
dropped,
|
|
178
|
+
span: Math.max(0, ...groups.map((g) => spanOf(g.map((n) => n.midi)))),
|
|
179
|
+
satisfied,
|
|
180
|
+
});
|
|
181
|
+
if (withinCaps(groups, input.constraints, maxNotes))
|
|
182
|
+
return result(true);
|
|
183
|
+
const notesPerHand = new Map();
|
|
184
|
+
for (const n of placed)
|
|
185
|
+
notesPerHand.set(n.hand, (notesPerHand.get(n.hand) ?? 0) + 1);
|
|
186
|
+
// Find the next note to drop: earliest in dropOrder, not an identity tone,
|
|
187
|
+
// and not the note that would take its hand below a dyad. Candidates are
|
|
188
|
+
// matched by semitone so spelling differences do not hide them.
|
|
189
|
+
let target = -1;
|
|
190
|
+
for (const pc of input.dropOrder) {
|
|
191
|
+
const semitone = pcSemitone(pc);
|
|
192
|
+
if (keepSemitones.has(semitone))
|
|
193
|
+
continue;
|
|
194
|
+
// Remove the LAST occurrence of this pitch class (conventional choice).
|
|
195
|
+
for (let i = working.length - 1; i >= 0; i--) {
|
|
196
|
+
if (pcSemitone(input.notes[working[i]]) !== semitone)
|
|
197
|
+
continue;
|
|
198
|
+
if ((notesPerHand.get(handOf(working[i])) ?? 0) <= MIN_NOTES_PER_HAND)
|
|
199
|
+
continue;
|
|
200
|
+
target = i;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
if (target >= 0)
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
// Nothing left that may be dropped: every remaining note is an identity
|
|
207
|
+
// tone, or dropping it would leave a hand with fewer than two notes.
|
|
208
|
+
if (target < 0)
|
|
209
|
+
return result(false);
|
|
210
|
+
dropped.push(input.notes[working[target]]);
|
|
211
|
+
working.splice(target, 1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=hand-constraints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hand-constraints.js","sourceRoot":"","sources":["../../src/theory/hand-constraints.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type Format = "compact" | "exact";
|
|
|
2
2
|
/** Tailwind-inspired text size scale for annotations. */
|
|
3
3
|
export type TextSize = "base" | "lg" | "xl" | "2xl";
|
|
4
4
|
/** Note name display mode. */
|
|
5
|
-
export type NoteNameMode = "pitch-class" | "midi" | "degree" | "pitch-class+degree";
|
|
5
|
+
export type NoteNameMode = "pitch-class" | "midi" | "degree" | "pitch-class+degree" | "midi+degree";
|
|
6
6
|
export type WhiteNote = "C" | "D" | "E" | "F" | "G" | "A" | "B";
|
|
7
7
|
export type NoteName = string;
|
|
8
8
|
export interface ColorTheme {
|
|
@@ -34,6 +34,12 @@ export interface ParsedChordRequest {
|
|
|
34
34
|
noteNameSize?: TextSize;
|
|
35
35
|
/** Note name display mode: pitch-class (default) or midi (with octave numbers). */
|
|
36
36
|
noteNameMode?: NoteNameMode;
|
|
37
|
+
/**
|
|
38
|
+
* Text size for degree labels. Independent of `noteNameSize` because the two
|
|
39
|
+
* are asked for separately — "note names in xl with degrees in lg" is one
|
|
40
|
+
* request with two sizes in it. Absent falls back to the note-name size.
|
|
41
|
+
*/
|
|
42
|
+
degreeSize?: TextSize;
|
|
37
43
|
/** Fingering values (aligned with chord notes). Numbers 1–5 plus extra symbols (0, -, x). Invalid entries become "?". */
|
|
38
44
|
fingering?: (number | string)[];
|
|
39
45
|
/** Auto-compute fingering when true (no explicit numbers given). */
|
|
@@ -75,6 +81,8 @@ export interface NotesGroup {
|
|
|
75
81
|
hand?: "lh" | "rh";
|
|
76
82
|
/** Clef context: affects inferred base octave and implies a hand. */
|
|
77
83
|
clef?: "bass" | "treble";
|
|
84
|
+
/** Source chord symbol when the group came from one ("rh Cmaj7"). */
|
|
85
|
+
chord?: string;
|
|
78
86
|
}
|
|
79
87
|
export interface KeyDescriptor {
|
|
80
88
|
note: string;
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pepperhorn/chordl-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Pure TypeScript chord parsing, resolving, layout, and export engine",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
|
-
"
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"tonal": "^6.3.0",
|
|
21
|
-
"@pepperhorn/chordl-voicings": "0.
|
|
21
|
+
"@pepperhorn/chordl-voicings": "0.4.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"typescript": "^5.7.0",
|