@pepperhorn/chordl-guitar 0.2.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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Generated (not stored) shapes: bass root-fifth-octave patterns and violin
3
+ * first-position placements.
4
+ *
5
+ * chords-db covers guitar and ukulele only, and neither instrument here wants
6
+ * a database anyway:
7
+ *
8
+ * - A bass line is built from patterns, not strummed chords. The teachable
9
+ * unit is the root-fifth-octave shape, which is the same movable pattern
10
+ * for every root — so it is computed from the root, and the computation IS
11
+ * the lesson.
12
+ * - Violin chord tones in first position are fully determined by the tuning
13
+ * and the first-position reach (open + four fingers ≈ 0-7 semitones above
14
+ * the open string). Given a chord's pitch classes, each string's nearest
15
+ * chord tone is arithmetic, not curation.
16
+ *
17
+ * Both return svguitar `Chord` objects so they render through exactly the
18
+ * same pipeline as the chords-db shapes.
19
+ */
20
+ import type { Chord } from "svguitar";
21
+ export declare function bassShapeFor(root: string): Chord | null;
22
+ /**
23
+ * Violin first-position shape for a chord's pitch classes.
24
+ *
25
+ * The placement is chosen across all four strings at once, not string by
26
+ * string. Picking each string's *nearest* chord tone independently is the
27
+ * obvious approach and it is wrong: on A major the G string's nearest tone is
28
+ * A, the D string's is E, and both open strings are already A and E, so the
29
+ * card sounds A-E-A-E and the third never appears. That greedy version
30
+ * silently dropped a chord tone in 32 of the 60 chords this library generates,
31
+ * including the root of C7, B7 and F#7. A chord card that omits the third is
32
+ * not teaching the chord.
33
+ *
34
+ * So: enumerate every assignment (each string plays one reachable chord tone
35
+ * or is silent) and rank them by
36
+ * 1. how many DISTINCT chord tones sound — the thing that was broken;
37
+ * 2. how many strings sound at all — prefer a fuller chord;
38
+ * 3. total semitone offset — prefer the lower, easier hand shape;
39
+ * 4. the offsets read low-string-first, purely so the result is stable.
40
+ * Four strings with at most five options each is 625 combinations at worst,
41
+ * so the exhaustive search costs nothing and removes the whole class of bug.
42
+ *
43
+ * A tone can still be unreachable — with only 0-7 semitones above four fixed
44
+ * open strings, not every chord fits — so criterion 1 maximises coverage
45
+ * rather than guaranteeing it. `violinCoverage` reports what was achieved.
46
+ *
47
+ * The diagram's "fret" axis is semitones above the open string; markers are
48
+ * labelled with VIOLIN finger numbers via `violinFingerFor`, not guitar
49
+ * fingerings.
50
+ */
51
+ export declare function violinShapeFor(pitchClasses: string[]): Chord | null;
52
+ /**
53
+ * Which of a chord's pitch classes the first-position shape actually sounds.
54
+ *
55
+ * Exists so callers and tests can assert coverage instead of trusting it —
56
+ * the bug this replaced was invisible precisely because nothing measured it.
57
+ */
58
+ export declare function violinCoverage(pitchClasses: string[]): {
59
+ sounded: number[];
60
+ missing: number[];
61
+ } | null;
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Generated (not stored) shapes: bass root-fifth-octave patterns and violin
3
+ * first-position placements.
4
+ *
5
+ * chords-db covers guitar and ukulele only, and neither instrument here wants
6
+ * a database anyway:
7
+ *
8
+ * - A bass line is built from patterns, not strummed chords. The teachable
9
+ * unit is the root-fifth-octave shape, which is the same movable pattern
10
+ * for every root — so it is computed from the root, and the computation IS
11
+ * the lesson.
12
+ * - Violin chord tones in first position are fully determined by the tuning
13
+ * and the first-position reach (open + four fingers ≈ 0-7 semitones above
14
+ * the open string). Given a chord's pitch classes, each string's nearest
15
+ * chord tone is arithmetic, not curation.
16
+ *
17
+ * Both return svguitar `Chord` objects so they render through exactly the
18
+ * same pipeline as the chords-db shapes.
19
+ */
20
+ const PC = {
21
+ C: 0, "C#": 1, Db: 1, D: 2, "D#": 3, Eb: 3, E: 4, F: 5, "F#": 6, Gb: 6,
22
+ G: 7, "G#": 8, Ab: 8, A: 9, "A#": 10, Bb: 10, B: 11,
23
+ };
24
+ /**
25
+ * Bass root-fifth-octave shape for one root, placed where it sounds good.
26
+ *
27
+ * The root always lands FRETTED between the 3rd and 9th fret — never on an
28
+ * open string and never crammed against the nut. Two reasons, both audible:
29
+ * open-position roots boom and mud on a bass, and the whole point of the
30
+ * pattern is that it is movable, which an open string breaks (you cannot
31
+ * slide an open E up a tone). E string is preferred when its root fret sits
32
+ * in the sweet range; otherwise the A string's does (every root lands in
33
+ * 3-9 on one of the two).
34
+ *
35
+ * The window slides to the root (chords-db convention, matching
36
+ * `dbPositionToChord`: `position` is the window's first fret, finger frets
37
+ * window-relative) — D at the 5th fret is the same shape as G at the 3rd.
38
+ *
39
+ * svguitar strings are numbered from the highest pitch: 1=G, 2=D, 3=A, 4=E.
40
+ * Fingers carry the conventional one-finger-per-fret labels: root 1,
41
+ * fifth 3, octave 4.
42
+ */
43
+ const BASS_SWEET_LOW = 3;
44
+ const BASS_SWEET_HIGH = 9;
45
+ export function bassShapeFor(root) {
46
+ const pc = PC[root];
47
+ if (pc === undefined)
48
+ return null;
49
+ const fretOnE = (pc - PC.E + 12) % 12;
50
+ const fretOnA = (pc - PC.A + 12) % 12;
51
+ const sweet = (f) => f >= BASS_SWEET_LOW && f <= BASS_SWEET_HIGH;
52
+ const onEString = sweet(fretOnE) || !sweet(fretOnA);
53
+ const rootFret = onEString ? fretOnE : fretOnA;
54
+ // [rootString, fifthString, octaveString] in svguitar numbering.
55
+ const [rootS, fifthS, octaveS] = onEString ? [4, 3, 2] : [3, 2, 1];
56
+ const mutedS = onEString ? 1 : 4;
57
+ // Slide the window to the root whenever the pattern outgrows the nut view.
58
+ const slide = rootFret + 2 > 6;
59
+ const position = slide ? rootFret : 1;
60
+ const rel = (abs) => (slide ? abs - rootFret + 1 : abs);
61
+ return {
62
+ fingers: [
63
+ [rootS, rel(rootFret), "1"],
64
+ [fifthS, rel(rootFret + 2), "3"],
65
+ [octaveS, rel(rootFret + 2), "4"],
66
+ [mutedS, "x"],
67
+ ],
68
+ barres: [],
69
+ position,
70
+ };
71
+ }
72
+ /**
73
+ * First-position reach: how far above the open string each violin finger
74
+ * falls, in semitones. Low/high placements share a finger — offset 1 or 2 is
75
+ * first finger, 3 or 4 second, and so on — which is exactly how first
76
+ * position is named on a fingering chart.
77
+ */
78
+ function violinFingerFor(offset) {
79
+ if (offset === 0)
80
+ return null; // open string: nut ring, no label
81
+ if (offset <= 2)
82
+ return "1";
83
+ if (offset <= 4)
84
+ return "2";
85
+ if (offset <= 6)
86
+ return "3";
87
+ return "4"; // offset 7 — fourth finger, the octave of the next string down
88
+ }
89
+ const FIRST_POSITION_REACH = 7;
90
+ /**
91
+ * Violin first-position shape for a chord's pitch classes.
92
+ *
93
+ * The placement is chosen across all four strings at once, not string by
94
+ * string. Picking each string's *nearest* chord tone independently is the
95
+ * obvious approach and it is wrong: on A major the G string's nearest tone is
96
+ * A, the D string's is E, and both open strings are already A and E, so the
97
+ * card sounds A-E-A-E and the third never appears. That greedy version
98
+ * silently dropped a chord tone in 32 of the 60 chords this library generates,
99
+ * including the root of C7, B7 and F#7. A chord card that omits the third is
100
+ * not teaching the chord.
101
+ *
102
+ * So: enumerate every assignment (each string plays one reachable chord tone
103
+ * or is silent) and rank them by
104
+ * 1. how many DISTINCT chord tones sound — the thing that was broken;
105
+ * 2. how many strings sound at all — prefer a fuller chord;
106
+ * 3. total semitone offset — prefer the lower, easier hand shape;
107
+ * 4. the offsets read low-string-first, purely so the result is stable.
108
+ * Four strings with at most five options each is 625 combinations at worst,
109
+ * so the exhaustive search costs nothing and removes the whole class of bug.
110
+ *
111
+ * A tone can still be unreachable — with only 0-7 semitones above four fixed
112
+ * open strings, not every chord fits — so criterion 1 maximises coverage
113
+ * rather than guaranteeing it. `violinCoverage` reports what was achieved.
114
+ *
115
+ * The diagram's "fret" axis is semitones above the open string; markers are
116
+ * labelled with VIOLIN finger numbers via `violinFingerFor`, not guitar
117
+ * fingerings.
118
+ */
119
+ export function violinShapeFor(pitchClasses) {
120
+ const tones = pitchClasses.map((p) => PC[p]);
121
+ if (tones.some((t) => t === undefined) || tones.length === 0)
122
+ return null;
123
+ const wanted = [...new Set(tones)];
124
+ // Low to high: G D A E; svguitar numbering: 4=G, 3=D, 2=A, 1=E.
125
+ const strings = [
126
+ { svguitarString: 4, openPc: PC.G },
127
+ { svguitarString: 3, openPc: PC.D },
128
+ { svguitarString: 2, openPc: PC.A },
129
+ { svguitarString: 1, openPc: PC.E },
130
+ ];
131
+ // Per string: every reachable chord tone, plus silence. Within a 0-7 window
132
+ // a pitch class occurs at most once, so one offset per tone per string.
133
+ const choices = strings.map(({ openPc }) => {
134
+ const opts = [];
135
+ for (const tone of wanted) {
136
+ const offset = (tone - openPc + 12) % 12;
137
+ if (offset <= FIRST_POSITION_REACH)
138
+ opts.push({ offset, tone });
139
+ }
140
+ opts.push(null); // silent — better than inventing a placement out of position
141
+ return opts;
142
+ });
143
+ // Cartesian product of the per-string options. Four strings, at most five
144
+ // options each — 625 combinations at the absolute worst.
145
+ let combos = [[]];
146
+ for (const opts of choices) {
147
+ const next = [];
148
+ for (const combo of combos)
149
+ for (const opt of opts)
150
+ next.push([...combo, opt]);
151
+ combos = next;
152
+ }
153
+ let best = null;
154
+ for (const picks of combos) {
155
+ const sounded = picks.filter((p) => p !== null);
156
+ const covered = new Set(sounded.map((p) => p.tone)).size;
157
+ const total = sounded.reduce((n, p) => n + p.offset, 0);
158
+ // Negated where higher is better, so the whole score compares smallest-first.
159
+ const score = [
160
+ -covered,
161
+ -sounded.length,
162
+ total,
163
+ ...picks.map((p) => (p === null ? 99 : p.offset)),
164
+ ];
165
+ if (best === null || lessThan(score, best.score))
166
+ best = { picks, score };
167
+ }
168
+ if (best === null)
169
+ return null;
170
+ const fingers = best.picks.map((pick, i) => {
171
+ const s = strings[i].svguitarString;
172
+ if (pick === null)
173
+ return [s, "x"];
174
+ const label = violinFingerFor(pick.offset);
175
+ return label ? [s, pick.offset, label] : [s, pick.offset];
176
+ });
177
+ return { fingers, barres: [] };
178
+ }
179
+ /**
180
+ * Which of a chord's pitch classes the first-position shape actually sounds.
181
+ *
182
+ * Exists so callers and tests can assert coverage instead of trusting it —
183
+ * the bug this replaced was invisible precisely because nothing measured it.
184
+ */
185
+ export function violinCoverage(pitchClasses) {
186
+ const shape = violinShapeFor(pitchClasses);
187
+ if (shape === null)
188
+ return null;
189
+ const wanted = [...new Set(pitchClasses.map((p) => PC[p]))];
190
+ const openPcs = [PC.E, PC.A, PC.D, PC.G]; // svguitar string 1..4
191
+ const sounded = new Set();
192
+ for (const f of shape.fingers) {
193
+ if (f[1] === "x")
194
+ continue;
195
+ sounded.add((openPcs[Number(f[0]) - 1] + Number(f[1])) % 12);
196
+ }
197
+ return {
198
+ sounded: [...sounded].sort((a, b) => a - b),
199
+ missing: wanted.filter((t) => !sounded.has(t)).sort((a, b) => a - b),
200
+ };
201
+ }
202
+ /** Lexicographic compare for the ranking tuple above. */
203
+ function lessThan(a, b) {
204
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
205
+ const x = a[i] ?? 0;
206
+ const y = b[i] ?? 0;
207
+ if (x !== y)
208
+ return x < y;
209
+ }
210
+ return false;
211
+ }
212
+ //# sourceMappingURL=generatedShapes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generatedShapes.js","sourceRoot":"","sources":["../src/generatedShapes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export { INSTRUMENTS, dbPositionToChord } from "./instruments.js";
2
+ export type { InstrumentId, InstrumentConfig, ChordsDb, ChordsDbEntry, ChordsDbPosition, } from "./instruments.js";
3
+ export { lookupGuitarChord, hasGuitarChord } from "./chordLookup.js";
4
+ export type { GuitarChordResult } from "./chordLookup.js";
5
+ export { GUITAR_TOP3_PRESETS, lookupTop3Chord } from "./staticPresets.js";
6
+ export type { StaticPreset } from "./staticPresets.js";
7
+ export { positionToMidi, rootPitchClass } from "./pitch.js";
8
+ export { positionFacts, duplicateVoicingMap } from "./voicingFacts.js";
9
+ export type { Inversion, PositionFacts } from "./voicingFacts.js";
10
+ export { matchesShapeClass, canonicalPositionIndex, selectVoicings, SHAPE_CLASS_LADDER, } from "./voicingSelect.js";
11
+ export type { ShapeClass, CanonicalOptions, VoicingChoice, SelectVoicingsOptions, SelectVoicingsResult, } from "./voicingSelect.js";
12
+ export { powerChordPosition, powerChordShape } from "./powerChords.js";
13
+ export type { PowerChordStringSet, PowerChordOptions } from "./powerChords.js";
14
+ export { bassShapeFor, violinShapeFor, violinCoverage } from "./generatedShapes.js";
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // @pepperhorn/chordl-guitar — guitar chord-shape lookup and svguitar diagram data.
2
+ //
3
+ // Backed by @tombatossals/chords-db; returns every stored fret position for a
4
+ // chord (alternate placements). Shapes convert to svguitar `Chord` objects for
5
+ // rendering. Based on pepperhorn/frames.
6
+ export { INSTRUMENTS, dbPositionToChord } from "./instruments.js";
7
+ export { lookupGuitarChord, hasGuitarChord } from "./chordLookup.js";
8
+ export { GUITAR_TOP3_PRESETS, lookupTop3Chord } from "./staticPresets.js";
9
+ export { positionToMidi, rootPitchClass } from "./pitch.js";
10
+ export { positionFacts, duplicateVoicingMap } from "./voicingFacts.js";
11
+ export { matchesShapeClass, canonicalPositionIndex, selectVoicings, SHAPE_CLASS_LADDER, } from "./voicingSelect.js";
12
+ export { powerChordPosition, powerChordShape } from "./powerChords.js";
13
+ export { bassShapeFor, violinShapeFor, violinCoverage } from "./generatedShapes.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,yCAAyC;AAEzC,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AASlE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1E,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGvE,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAS5B,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Instrument configs + chords-db types + position→svguitar conversion.
3
+ *
4
+ * Ported/adapted from pepperhorn/frames (src/lib/instruments.ts). chords-db
5
+ * ships guitar and ukulele voicing libraries; we start with guitar and keep the
6
+ * config table so ukulele is a small follow-up.
7
+ */
8
+ import type { Chord } from "svguitar";
9
+ export type InstrumentId = "guitar" | "ukulele" | "guitar-top3" | "bass4" | "bass5" | "violin";
10
+ export interface InstrumentConfig {
11
+ id: InstrumentId;
12
+ label: string;
13
+ description: string;
14
+ strings: number;
15
+ /** Default number of frets to draw. */
16
+ frets: number;
17
+ /** Open-string tuning, low → high. */
18
+ tuning: string[];
19
+ /**
20
+ * Open-string MIDI numbers in **chords-db index order** — `openMidi[i]` is
21
+ * the string that `frets[i]` refers to. This is NOT svguitar's string
22
+ * numbering, which runs highest-pitch-first; `dbPositionToChord` is the one
23
+ * place that inverts. Ukulele's reentrant high-G means the array is not
24
+ * ascending, so the order cannot be recovered by sorting.
25
+ */
26
+ openMidi: number[];
27
+ }
28
+ export declare const INSTRUMENTS: Record<InstrumentId, InstrumentConfig>;
29
+ export interface ChordsDbPosition {
30
+ /** Per-string fret, low→high. -1 = muted, 0 = open, else absolute-ish (see baseFret). */
31
+ frets: number[];
32
+ /** Per-string finger number (0 = none). */
33
+ fingers: number[];
34
+ /** Lowest fret the diagram window starts on (1 = nut). */
35
+ baseFret: number;
36
+ /** Fret numbers that carry a barre. */
37
+ barres: number[];
38
+ capo?: boolean;
39
+ midi?: number[];
40
+ }
41
+ export interface ChordsDbEntry {
42
+ key: string;
43
+ suffix: string;
44
+ positions: ChordsDbPosition[];
45
+ }
46
+ export interface ChordsDb {
47
+ main: {
48
+ strings: number;
49
+ fretsOnChord: number;
50
+ name: string;
51
+ numberOfChords: number;
52
+ };
53
+ tunings: Record<string, string[]>;
54
+ keys: string[];
55
+ suffixes: string[];
56
+ chords: Record<string, ChordsDbEntry[]>;
57
+ }
58
+ /**
59
+ * Convert a chords-db position into an svguitar Chord (fingers + barres +
60
+ * window position). Ported from pepperhorn/frames.
61
+ *
62
+ * @param pos - chords-db position
63
+ * @param stringCount - number of strings (6 guitar, 4 ukulele)
64
+ * @param title - optional diagram title
65
+ */
66
+ export declare function dbPositionToChord(pos: ChordsDbPosition, stringCount: number, title?: string): Chord;
@@ -0,0 +1,114 @@
1
+ export const INSTRUMENTS = {
2
+ guitar: {
3
+ id: "guitar",
4
+ label: "Guitar",
5
+ description: "Standard 6-string guitar (E A D G B E)",
6
+ strings: 6,
7
+ frets: 5,
8
+ tuning: ["E", "A", "D", "G", "B", "E"],
9
+ openMidi: [40, 45, 50, 55, 59, 64],
10
+ },
11
+ ukulele: {
12
+ id: "ukulele",
13
+ label: "Ukulele",
14
+ description: "Standard ukulele (G C E A)",
15
+ strings: 4,
16
+ frets: 5,
17
+ tuning: ["G", "C", "E", "A"],
18
+ openMidi: [67, 60, 64, 69],
19
+ },
20
+ "guitar-top3": {
21
+ id: "guitar-top3",
22
+ label: "Guitar (top 3)",
23
+ description: "Six-string guitar, beginner voicings on the top three strings only (G B E). " +
24
+ "Strings 4-6 are muted at render time.",
25
+ strings: 6,
26
+ frets: 4,
27
+ tuning: ["E", "A", "D", "G", "B", "E"],
28
+ openMidi: [40, 45, 50, 55, 59, 64],
29
+ },
30
+ bass4: {
31
+ id: "bass4",
32
+ label: "Bass (4-string)",
33
+ description: "Standard 4-string bass (E A D G). Also the instrument `bassShapeFor` " +
34
+ "targets: generated root-fifth-octave patterns rather than chords-db " +
35
+ "lookups, because a bass card teaches the pattern, not a strum.",
36
+ strings: 4,
37
+ // Six, not five: the generated root-fifth-octave patterns reach a sixth
38
+ // fret, and this count is what consumers draw the window from. Five clips
39
+ // them. See generatedShapes.bassShapeFor and its test.
40
+ frets: 6,
41
+ tuning: ["E", "A", "D", "G"],
42
+ openMidi: [28, 33, 38, 43],
43
+ },
44
+ bass5: {
45
+ id: "bass5",
46
+ label: "Bass (5-string)",
47
+ description: "5-string bass with low B (B E A D G)",
48
+ strings: 5,
49
+ frets: 5,
50
+ tuning: ["B", "E", "A", "D", "G"],
51
+ openMidi: [23, 28, 33, 38, 43],
52
+ },
53
+ violin: {
54
+ id: "violin",
55
+ label: "Violin",
56
+ description: "Violin in first position (G D A E). The \"fret\" axis is semitones above the " +
57
+ "open string (0-7, the first-position reach); markers carry violin finger " +
58
+ "numbers, not guitar fingerings. Generated from chord tones — see generatedShapes.",
59
+ strings: 4,
60
+ frets: 7,
61
+ tuning: ["G", "D", "A", "E"],
62
+ openMidi: [55, 62, 69, 76],
63
+ },
64
+ };
65
+ /**
66
+ * Convert a chords-db position into an svguitar Chord (fingers + barres +
67
+ * window position). Ported from pepperhorn/frames.
68
+ *
69
+ * @param pos - chords-db position
70
+ * @param stringCount - number of strings (6 guitar, 4 ukulele)
71
+ * @param title - optional diagram title
72
+ */
73
+ export function dbPositionToChord(pos, stringCount, title) {
74
+ const fingers = [];
75
+ const barreFrets = new Set(pos.barres);
76
+ pos.frets.forEach((fret, dbIdx) => {
77
+ // chords-db lists strings low→high; svguitar numbers strings high→low.
78
+ const stringNum = stringCount - dbIdx;
79
+ if (fret === -1) {
80
+ fingers.push([stringNum, "x"]);
81
+ return;
82
+ }
83
+ if (fret === 0) {
84
+ fingers.push([stringNum, 0]);
85
+ return;
86
+ }
87
+ // Skip strings covered by a barre — the barre draws them.
88
+ if (barreFrets.has(fret))
89
+ return;
90
+ const fingerNum = pos.fingers[dbIdx];
91
+ const text = fingerNum && fingerNum > 0 ? String(fingerNum) : undefined;
92
+ fingers.push(text ? [stringNum, fret, text] : [stringNum, fret]);
93
+ });
94
+ const barres = pos.barres.map((fret) => {
95
+ const playingIdxs = pos.frets
96
+ .map((f, i) => (f === fret ? i : -1))
97
+ .filter((i) => i >= 0);
98
+ const stringNums = playingIdxs.map((i) => stringCount - i);
99
+ const fromString = Math.max(...stringNums);
100
+ const toString = Math.min(...stringNums);
101
+ const fingerNums = playingIdxs
102
+ .map((i) => pos.fingers[i])
103
+ .filter((n) => typeof n === "number" && n > 0);
104
+ const fingerLabel = fingerNums.length ? String(Math.min(...fingerNums)) : undefined;
105
+ return { fromString, toString, fret, ...(fingerLabel ? { text: fingerLabel } : {}) };
106
+ });
107
+ return {
108
+ fingers,
109
+ barres,
110
+ position: pos.baseFret,
111
+ ...(title ? { title } : {}),
112
+ };
113
+ }
114
+ //# sourceMappingURL=instruments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instruments.js","sourceRoot":"","sources":["../src/instruments.ts"],"names":[],"mappings":"AA+BA,MAAM,CAAC,MAAM,WAAW,GAA2C;IACjE,MAAM,EAAE;QACN,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;QACtC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KACnC;IACD,OAAO,EAAE;QACP,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,4BAA4B;QACzC,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;QAC5B,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KAC3B;IACD,aAAa,EAAE;QACb,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,8EAA8E;YAC9E,uCAAuC;QACzC,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;QACtC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KACnC;IACD,KAAK,EAAE;QACL,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,uEAAuE;YACvE,sEAAsE;YACtE,gEAAgE;QAClE,OAAO,EAAE,CAAC;QACV,wEAAwE;QACxE,0EAA0E;QAC1E,uDAAuD;QACvD,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;QAC5B,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KAC3B;IACD,KAAK,EAAE;QACL,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,sCAAsC;QACnD,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;QACjC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KAC/B;IACD,MAAM,EAAE;QACN,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,QAAQ;QACf,WAAW,EACT,+EAA+E;YAC/E,2EAA2E;YAC3E,mFAAmF;QACrF,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;QAC5B,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KAC3B;CACF,CAAC;AA+BF;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAqB,EACrB,WAAmB,EACnB,KAAc;IAEd,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEvC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAChC,uEAAuE;QACvE,MAAM,SAAS,GAAG,WAAW,GAAG,KAAK,CAAC;QACtC,IAAI,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,0DAA0D;QAC1D,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACjC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,SAAS,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAoB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACtD,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK;aAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACpC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACzB,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC;QACzC,MAAM,UAAU,GAAG,WAAW;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;aAC1B,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACpF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO;QACP,MAAM;QACN,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Fret → pitch derivation.
3
+ *
4
+ * chords-db ships a `midi` array per position, but scripts/build-data.mjs
5
+ * strips it from the shipped JSON to cut bundle weight. Deriving rather than
6
+ * reviving keeps one pitch code path: the candidate szaza merge source ships
7
+ * no midi field at all, so derivation is required regardless.
8
+ */
9
+ import type { ChordsDbPosition } from "./instruments.js";
10
+ /**
11
+ * Sounding MIDI pitches for a chords-db position.
12
+ *
13
+ * `frets` values are relative to `baseFret`, not absolute fret numbers: a
14
+ * value of 1 at baseFret 3 sounds fret 3. Muted strings (-1) are omitted;
15
+ * open strings (0) sound the open pitch regardless of the diagram window.
16
+ *
17
+ * `capo` is deliberately ignored — it is a rendering hint (draw a bar), not a
18
+ * pitch offset. All 901 capo-flagged guitar positions reproduce chords-db's
19
+ * own `midi` array exactly without it; see test/pitch-golden.test.ts.
20
+ *
21
+ * @param pos - chords-db position
22
+ * @param openMidi - open-string MIDI in chords-db index order (InstrumentConfig.openMidi)
23
+ * @returns sounding pitches only, in chords-db string order
24
+ */
25
+ export declare function positionToMidi(pos: ChordsDbPosition, openMidi: number[]): number[];
26
+ /**
27
+ * Pitch class of a chord label's root, or null when unparseable.
28
+ *
29
+ * Derived from the requested label rather than from a chords-db `key` field:
30
+ * chords-db spells the same root inconsistently ("Csharp" as a container key,
31
+ * "C#" on the entry), and the label is what the caller actually asked for.
32
+ */
33
+ export declare function rootPitchClass(label: string): number | null;
package/dist/pitch.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Sounding MIDI pitches for a chords-db position.
3
+ *
4
+ * `frets` values are relative to `baseFret`, not absolute fret numbers: a
5
+ * value of 1 at baseFret 3 sounds fret 3. Muted strings (-1) are omitted;
6
+ * open strings (0) sound the open pitch regardless of the diagram window.
7
+ *
8
+ * `capo` is deliberately ignored — it is a rendering hint (draw a bar), not a
9
+ * pitch offset. All 901 capo-flagged guitar positions reproduce chords-db's
10
+ * own `midi` array exactly without it; see test/pitch-golden.test.ts.
11
+ *
12
+ * @param pos - chords-db position
13
+ * @param openMidi - open-string MIDI in chords-db index order (InstrumentConfig.openMidi)
14
+ * @returns sounding pitches only, in chords-db string order
15
+ */
16
+ export function positionToMidi(pos, openMidi) {
17
+ const out = [];
18
+ pos.frets.forEach((fret, i) => {
19
+ if (fret === -1)
20
+ return;
21
+ const open = openMidi[i];
22
+ if (open === undefined)
23
+ return;
24
+ out.push(fret === 0 ? open : open + pos.baseFret + fret - 1);
25
+ });
26
+ return out;
27
+ }
28
+ const PITCH_CLASSES = {
29
+ C: 0, "B#": 0,
30
+ "C#": 1, Db: 1,
31
+ D: 2,
32
+ "D#": 3, Eb: 3,
33
+ E: 4, Fb: 4,
34
+ F: 5, "E#": 5,
35
+ "F#": 6, Gb: 6,
36
+ G: 7,
37
+ "G#": 8, Ab: 8,
38
+ A: 9,
39
+ "A#": 10, Bb: 10,
40
+ B: 11, Cb: 11,
41
+ };
42
+ /**
43
+ * Pitch class of a chord label's root, or null when unparseable.
44
+ *
45
+ * Derived from the requested label rather than from a chords-db `key` field:
46
+ * chords-db spells the same root inconsistently ("Csharp" as a container key,
47
+ * "C#" on the entry), and the label is what the caller actually asked for.
48
+ */
49
+ export function rootPitchClass(label) {
50
+ const m = label.trim().match(/^([A-Ga-g][#b]?)/);
51
+ if (!m)
52
+ return null;
53
+ const root = m[1].charAt(0).toUpperCase() + m[1].slice(1);
54
+ const pc = PITCH_CLASSES[root];
55
+ return pc === undefined ? null : pc;
56
+ }
57
+ //# sourceMappingURL=pitch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pitch.js","sourceRoot":"","sources":["../src/pitch.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,GAAqB,EAAE,QAAkB;IACtE,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,IAAI,KAAK,CAAC,CAAC;YAAE,OAAO;QACxB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,aAAa,GAA2B;IAC5C,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;IACb,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACd,CAAC,EAAE,CAAC;IACJ,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACd,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACX,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;IACb,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACd,CAAC,EAAE,CAAC;IACJ,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACd,CAAC,EAAE,CAAC;IACJ,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;IAChB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;CACd,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACjD,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Movable power-chord (root + fifth + octave) shapes.
3
+ *
4
+ * chords-db has no power-chord suffix, so these cannot be looked up. They also
5
+ * need no curation: the shape is perfectly regular, so it derives from the
6
+ * instrument's open-string pitches. Output goes through dbPositionToChord so
7
+ * renderers see the same contract as any other shape.
8
+ */
9
+ import type { Chord } from "svguitar";
10
+ import type { ChordsDbPosition } from "./instruments.js";
11
+ /** Which string carries the root. "E" = 6th string, "A" = 5th string. */
12
+ export type PowerChordStringSet = "E" | "A";
13
+ export interface PowerChordOptions {
14
+ stringSet: PowerChordStringSet;
15
+ /** Include the octave doubling on the next string up. Default true. */
16
+ includeOctave?: boolean;
17
+ title?: string;
18
+ }
19
+ /**
20
+ * Build a chords-db-shaped position for a power chord.
21
+ *
22
+ * @param rootPc - root pitch class, 0 = C
23
+ * @returns null when rootPc is out of range
24
+ */
25
+ export declare function powerChordPosition(rootPc: number, opts: PowerChordOptions): ChordsDbPosition | null;
26
+ /** The same shape as an svguitar Chord, ready to render. */
27
+ export declare function powerChordShape(rootPc: number, opts: PowerChordOptions): Chord | null;