@hosanna/chordpro 1.1.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,1143 @@
1
+ // src/parser/parser.ts
2
+ var TIMING_REGEX = /^(.+?)@([0-9]*\.?[0-9]+)x$/;
3
+ function parseChordTiming(rawChord) {
4
+ const match = rawChord.match(TIMING_REGEX);
5
+ if (match) return { chord: match[1], timing: parseFloat(match[2]) };
6
+ return { chord: rawChord };
7
+ }
8
+ function parseLineSegments(lineText) {
9
+ const segments = [];
10
+ const regex = /\[([^\]]+)\]/g;
11
+ let match;
12
+ let lastIndex = 0;
13
+ let currentChord = "";
14
+ let currentTiming;
15
+ while ((match = regex.exec(lineText)) !== null) {
16
+ const rawChord = match[1];
17
+ const { chord, timing } = parseChordTiming(rawChord);
18
+ const textBefore = lineText.slice(lastIndex, match.index);
19
+ if (lastIndex === 0 && textBefore === "") {
20
+ currentChord = chord;
21
+ currentTiming = timing;
22
+ } else {
23
+ segments.push({
24
+ chord: currentChord,
25
+ text: textBefore,
26
+ timing: currentTiming
27
+ });
28
+ currentChord = chord;
29
+ currentTiming = timing;
30
+ }
31
+ lastIndex = regex.lastIndex;
32
+ }
33
+ const remainingText = lineText.slice(lastIndex);
34
+ segments.push({
35
+ chord: currentChord,
36
+ text: remainingText,
37
+ timing: currentTiming
38
+ });
39
+ return segments;
40
+ }
41
+ function parseChordPro(content) {
42
+ const lines = content.split(/\r?\n/);
43
+ const metadata = {};
44
+ const sections = [];
45
+ let currentSection = null;
46
+ let isTab = false;
47
+ let isGrid = false;
48
+ let lastChorusLines = [];
49
+ const commitSection = () => {
50
+ if (currentSection) {
51
+ sections.push(currentSection);
52
+ if (currentSection.type === "chorus") {
53
+ lastChorusLines = [...currentSection.lines];
54
+ }
55
+ currentSection = null;
56
+ }
57
+ };
58
+ const aliasMap = {
59
+ t: "title",
60
+ st: "subtitle",
61
+ a: "artist",
62
+ k: "key",
63
+ c: "comment",
64
+ ci: "comment_italic",
65
+ cb: "comment_box",
66
+ soc: "start_of_chorus",
67
+ eoc: "end_of_chorus",
68
+ sov: "start_of_verse",
69
+ eov: "end_of_verse",
70
+ sob: "start_of_bridge",
71
+ eob: "end_of_bridge",
72
+ sot: "start_of_tab",
73
+ eot: "end_of_tab",
74
+ sog: "start_of_grid",
75
+ eog: "end_of_grid",
76
+ ch: "chorus",
77
+ v: "verse",
78
+ b: "bridge",
79
+ re: "repeat",
80
+ ns: "new_song",
81
+ time_signature: "time",
82
+ timesignature: "time",
83
+ "time signature": "time",
84
+ original_key: "original_key",
85
+ "original key": "original_key"
86
+ };
87
+ for (let line of lines) {
88
+ const trimmed = line.trim();
89
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
90
+ const directive = trimmed.slice(1, -1).trim();
91
+ const colonIndex = directive.indexOf(":");
92
+ let rawName = directive;
93
+ let value = "";
94
+ if (colonIndex !== -1) {
95
+ rawName = directive.substring(0, colonIndex).trim();
96
+ value = directive.substring(colonIndex + 1).trim();
97
+ }
98
+ const lowerName = rawName.toLowerCase();
99
+ const name = aliasMap[lowerName] || lowerName;
100
+ switch (name) {
101
+ case "start_of_chorus":
102
+ commitSection();
103
+ currentSection = {
104
+ type: "chorus",
105
+ label: value || "Refr\xE3o",
106
+ lines: []
107
+ };
108
+ break;
109
+ case "start_of_verse":
110
+ commitSection();
111
+ currentSection = {
112
+ type: "verse",
113
+ label: value || "Verso",
114
+ lines: []
115
+ };
116
+ break;
117
+ case "start_of_bridge":
118
+ commitSection();
119
+ currentSection = {
120
+ type: "bridge",
121
+ label: value || "Ponte",
122
+ lines: []
123
+ };
124
+ break;
125
+ case "start_of_tab":
126
+ commitSection();
127
+ isTab = true;
128
+ currentSection = {
129
+ type: "tab",
130
+ label: value || "Tablatura",
131
+ lines: []
132
+ };
133
+ break;
134
+ case "start_of_grid":
135
+ commitSection();
136
+ isGrid = true;
137
+ currentSection = { type: "grid", label: value || "Grid", lines: [] };
138
+ break;
139
+ case "end_of_chorus":
140
+ if (currentSection?.type === "chorus") commitSection();
141
+ break;
142
+ case "end_of_verse":
143
+ if (currentSection?.type === "verse") commitSection();
144
+ break;
145
+ case "end_of_bridge":
146
+ if (currentSection?.type === "bridge") commitSection();
147
+ break;
148
+ case "end_of_tab":
149
+ isTab = false;
150
+ if (currentSection?.type === "tab") commitSection();
151
+ break;
152
+ case "end_of_grid":
153
+ isGrid = false;
154
+ if (currentSection?.type === "grid") commitSection();
155
+ break;
156
+ case "chorus":
157
+ commitSection();
158
+ sections.push({
159
+ type: "chorus",
160
+ label: value || "Refr\xE3o",
161
+ lines: [...lastChorusLines]
162
+ });
163
+ break;
164
+ case "verse":
165
+ commitSection();
166
+ currentSection = {
167
+ type: "verse",
168
+ label: value || "Verso",
169
+ lines: []
170
+ };
171
+ break;
172
+ case "bridge":
173
+ commitSection();
174
+ currentSection = {
175
+ type: "bridge",
176
+ label: value || "Ponte",
177
+ lines: []
178
+ };
179
+ break;
180
+ case "comment":
181
+ case "comment_italic":
182
+ const commentLine = { type: "comment", text: value };
183
+ if (currentSection) currentSection.lines.push(commentLine);
184
+ else sections.push({ type: "comment", lines: [commentLine] });
185
+ break;
186
+ case "comment_box":
187
+ const cbLine = { type: "comment_box", text: value };
188
+ if (currentSection) currentSection.lines.push(cbLine);
189
+ else sections.push({ type: "comment", lines: [cbLine] });
190
+ break;
191
+ case "repeat":
192
+ if (currentSection) {
193
+ currentSection.repeat = value || "2";
194
+ } else {
195
+ sections.push({
196
+ type: "comment",
197
+ lines: [
198
+ { type: "comment_box", text: `Repetir: ${value || "2"}` }
199
+ ]
200
+ });
201
+ }
202
+ break;
203
+ case "new_song":
204
+ commitSection();
205
+ sections.push({ type: "new_song", lines: [] });
206
+ break;
207
+ case "duration":
208
+ if (/^\d{1,2}:\d{2}$/.test(value)) {
209
+ const [minutes, seconds] = value.split(":").map(Number);
210
+ metadata["duration"] = (minutes * 60 + seconds).toString();
211
+ } else {
212
+ metadata["duration"] = value;
213
+ }
214
+ break;
215
+ default:
216
+ if (value) {
217
+ const metaKey = name.replace(
218
+ /[-_\s]+([a-zA-Z])/g,
219
+ (_, letter) => letter.toUpperCase()
220
+ ).replace(/\s+/g, "");
221
+ metadata[metaKey] = value;
222
+ }
223
+ break;
224
+ }
225
+ continue;
226
+ }
227
+ if (trimmed === "") {
228
+ if (currentSection) currentSection.lines.push({ type: "empty" });
229
+ continue;
230
+ }
231
+ if (trimmed.startsWith("#") && !isTab) continue;
232
+ let lineType = "lyrics";
233
+ let parsedSegments = [];
234
+ if (isTab) {
235
+ lineType = "tab";
236
+ } else {
237
+ parsedSegments = parseLineSegments(line);
238
+ const textContent = parsedSegments.map((s) => s.text).join("");
239
+ const onlyBarsAndSpaces = /^[\s|:\-.%]*$/.test(textContent);
240
+ const hasBars = textContent.includes("|");
241
+ if (isGrid || onlyBarsAndSpaces && hasBars) {
242
+ lineType = "chord-section";
243
+ }
244
+ }
245
+ const parsedLine = { type: lineType };
246
+ if (lineType === "tab") {
247
+ parsedLine.text = line;
248
+ } else if (lineType === "lyrics") {
249
+ parsedLine.segments = parsedSegments;
250
+ } else if (lineType === "chord-section") {
251
+ parsedLine.segments = parsedSegments;
252
+ const measures = [];
253
+ let currentChords = [];
254
+ let startBarline = "";
255
+ let hasSeenChord = false;
256
+ let startBarlineFound = false;
257
+ for (let i = 0; i < parsedSegments.length; i++) {
258
+ const seg = parsedSegments[i];
259
+ if (seg.chord) {
260
+ currentChords.push({
261
+ chord: seg.chord,
262
+ text: "",
263
+ timing: seg.timing
264
+ });
265
+ hasSeenChord = true;
266
+ }
267
+ const barlineMatches = seg.text.match(/\|\||:\||\|:|\|/g);
268
+ if (barlineMatches) {
269
+ for (let j = 0; j < barlineMatches.length; j++) {
270
+ const b = barlineMatches[j];
271
+ if (!hasSeenChord && !startBarlineFound) {
272
+ startBarline = b;
273
+ startBarlineFound = true;
274
+ } else {
275
+ measures.push({ chords: currentChords, endBarline: b });
276
+ currentChords = [];
277
+ }
278
+ }
279
+ }
280
+ }
281
+ if (currentChords.length > 0) {
282
+ measures.push({ chords: currentChords, endBarline: "" });
283
+ }
284
+ parsedLine.measures = measures;
285
+ parsedLine.startBarline = startBarline;
286
+ }
287
+ if (!currentSection) currentSection = { type: "verse", lines: [] };
288
+ currentSection.lines.push(parsedLine);
289
+ }
290
+ commitSection();
291
+ if (!metadata.title) metadata.title = "Sem T\xEDtulo";
292
+ return { metadata, sections };
293
+ }
294
+ function buildChordProText(metadata, bodyContent) {
295
+ const lines = [];
296
+ const primaryKeys = [
297
+ "title",
298
+ "subtitle",
299
+ "artist",
300
+ "composer",
301
+ "album",
302
+ "copyright",
303
+ "key",
304
+ "originalKey",
305
+ "capo",
306
+ "tempo",
307
+ "time",
308
+ "duration",
309
+ "songNumber",
310
+ "ccli",
311
+ "youtube"
312
+ ];
313
+ for (const k of primaryKeys) {
314
+ if (metadata[k]) {
315
+ const directiveName = k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
316
+ lines.push(`{${directiveName}: ${metadata[k]}}`);
317
+ }
318
+ }
319
+ for (const k in metadata) {
320
+ if (!primaryKeys.includes(k) && metadata[k] && k !== "title") {
321
+ const directiveName = k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
322
+ lines.push(`{${directiveName}: ${metadata[k]}}`);
323
+ }
324
+ }
325
+ lines.push("");
326
+ lines.push(bodyContent.trim());
327
+ return lines.join("\n");
328
+ }
329
+
330
+ // src/parser/transpose.ts
331
+ var NOTE_TO_VAL = {
332
+ C: 0,
333
+ "C#": 1,
334
+ DB: 1,
335
+ D: 2,
336
+ "D#": 3,
337
+ EB: 3,
338
+ E: 4,
339
+ F: 5,
340
+ "F#": 6,
341
+ GB: 6,
342
+ G: 7,
343
+ "G#": 8,
344
+ AB: 8,
345
+ A: 9,
346
+ "A#": 10,
347
+ BB: 10,
348
+ B: 11,
349
+ DO: 0,
350
+ RE: 2,
351
+ R\u00C9: 2,
352
+ MI: 4,
353
+ FA: 5,
354
+ F\u00C1: 5,
355
+ SOL: 7,
356
+ LA: 9,
357
+ L\u00C1: 9,
358
+ SI: 11
359
+ };
360
+ var SHARPS = [
361
+ "C",
362
+ "C#",
363
+ "D",
364
+ "D#",
365
+ "E",
366
+ "F",
367
+ "F#",
368
+ "G",
369
+ "G#",
370
+ "A",
371
+ "A#",
372
+ "B"
373
+ ];
374
+ var FLATS = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
375
+ function getNoteValue(note) {
376
+ if (!note) return void 0;
377
+ const match = note.match(
378
+ /^([A-G][#b]?|Do|Ré|Mi|Fá|Sol|Lá|Si|DO|RE|RÉ|MI|FA|FÁ|SOL|LA|LÁ|SI)/i
379
+ );
380
+ if (!match) return void 0;
381
+ return NOTE_TO_VAL[match[1].toUpperCase()];
382
+ }
383
+ function getSuggestedCapo(originalKey, transposeVal) {
384
+ const baseKeyVal = originalKey ? getNoteValue(originalKey) : 0;
385
+ const keyVal = baseKeyVal !== void 0 ? baseKeyVal : 0;
386
+ const soundingVal = (keyVal + transposeVal + 240) % 12;
387
+ const EASY_SHAPES = {
388
+ 0: "C",
389
+ 7: "G",
390
+ 2: "D",
391
+ 9: "A",
392
+ 4: "E"
393
+ };
394
+ const PREFERRED_OPEN_VALS = [0, 7, 2, 9, 4];
395
+ for (const targetShapeVal of PREFERRED_OPEN_VALS) {
396
+ const neededCapo = (soundingVal - targetShapeVal + 12) % 12;
397
+ if (neededCapo >= 1 && neededCapo <= 7) {
398
+ return {
399
+ capo: neededCapo,
400
+ chordShape: EASY_SHAPES[targetShapeVal]
401
+ };
402
+ }
403
+ }
404
+ return null;
405
+ }
406
+ function transposeNote(note, semitones, preferFlats = false) {
407
+ const upper = note.toUpperCase();
408
+ if (NOTE_TO_VAL[upper] === void 0) return note;
409
+ const val = NOTE_TO_VAL[upper];
410
+ const newVal = (val + semitones + 24) % 12;
411
+ const targetScale = preferFlats ? FLATS : SHARPS;
412
+ let transposed = targetScale[newVal];
413
+ if (note[0] === note[0].toLowerCase()) {
414
+ transposed = transposed.toLowerCase();
415
+ }
416
+ return transposed;
417
+ }
418
+ function transposeChord(chord, semitones) {
419
+ if (!chord || semitones === 0) return chord;
420
+ if (chord.includes("/")) {
421
+ return chord.split("/").map((part) => transposeChord(part.trim(), semitones)).join("/");
422
+ }
423
+ const noteRegex = /^([A-G][#b]?|Do|Ré|Mi|Fá|Sol|Lá|Si|DO|RE|RÉ|MI|FA|FÁ|SOL|LA|LÁ|SI)/;
424
+ const match = chord.match(noteRegex);
425
+ if (!match) return chord;
426
+ const note = match[1];
427
+ const suffix = chord.slice(note.length);
428
+ const preferFlats = chord.includes("b") || chord.includes("B");
429
+ const transposedNote = transposeNote(note, semitones, preferFlats);
430
+ return transposedNote + suffix;
431
+ }
432
+
433
+ // src/parser/chordDictionary.ts
434
+ var NOTE_ALIASES = {
435
+ C: 0,
436
+ "B#": 0,
437
+ Do: 0,
438
+ DO: 0,
439
+ "C#": 1,
440
+ Db: 1,
441
+ D: 2,
442
+ Re: 2,
443
+ RE: 2,
444
+ R\u00E9: 2,
445
+ R\u00C9: 2,
446
+ "D#": 3,
447
+ Eb: 3,
448
+ E: 4,
449
+ Fb: 4,
450
+ Mi: 4,
451
+ MI: 4,
452
+ F: 5,
453
+ "E#": 5,
454
+ Fa: 5,
455
+ FA: 5,
456
+ F\u00E1: 5,
457
+ F\u00C1: 5,
458
+ "F#": 6,
459
+ Gb: 6,
460
+ G: 7,
461
+ Sol: 7,
462
+ SOL: 7,
463
+ "G#": 8,
464
+ Ab: 8,
465
+ A: 9,
466
+ La: 9,
467
+ LA: 9,
468
+ L\u00E1: 9,
469
+ L\u00C1: 9,
470
+ "A#": 10,
471
+ Bb: 10,
472
+ B: 11,
473
+ Cb: 11,
474
+ Si: 11,
475
+ SI: 11
476
+ };
477
+ var ROOT_KEYS = Object.keys(NOTE_ALIASES).sort((a, b) => b.length - a.length);
478
+ var ROOT_PATTERN = new RegExp(`^(${ROOT_KEYS.join("|")})`, "i");
479
+ var SEMITONE_NAMES = [
480
+ "C",
481
+ "C#",
482
+ "D",
483
+ "D#",
484
+ "E",
485
+ "F",
486
+ "F#",
487
+ "G",
488
+ "G#",
489
+ "A",
490
+ "A#",
491
+ "B"
492
+ ];
493
+ function resolveRootSemitone(raw) {
494
+ if (raw in NOTE_ALIASES) return NOTE_ALIASES[raw];
495
+ const titleCase = raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase();
496
+ if (titleCase in NOTE_ALIASES) return NOTE_ALIASES[titleCase];
497
+ const upper = raw.toUpperCase();
498
+ if (upper in NOTE_ALIASES) return NOTE_ALIASES[upper];
499
+ return null;
500
+ }
501
+ function pitchClassName(semitone) {
502
+ return SEMITONE_NAMES[(semitone % 12 + 12) % 12];
503
+ }
504
+ var CHORD_QUALITIES = [
505
+ {
506
+ id: "major",
507
+ label: "Major",
508
+ intervals: [0, 4, 7],
509
+ aliases: ["", "M", "maj", "Maj", "MAJ"]
510
+ },
511
+ {
512
+ id: "minor",
513
+ label: "Minor",
514
+ intervals: [0, 3, 7],
515
+ aliases: ["m", "min", "Min", "MIN", "-"]
516
+ },
517
+ {
518
+ id: "dim",
519
+ label: "Diminished",
520
+ intervals: [0, 3, 6],
521
+ aliases: ["dim", "o", "\xB0"]
522
+ },
523
+ {
524
+ id: "aug",
525
+ label: "Augmented",
526
+ intervals: [0, 4, 8],
527
+ aliases: ["aug", "+"]
528
+ },
529
+ {
530
+ id: "sus2",
531
+ label: "Suspended 2nd",
532
+ intervals: [0, 2, 7],
533
+ aliases: ["sus2"]
534
+ },
535
+ {
536
+ id: "sus4",
537
+ label: "Suspended 4th",
538
+ intervals: [0, 5, 7],
539
+ aliases: ["sus4", "sus"]
540
+ },
541
+ { id: "five", label: "Power chord", intervals: [0, 7], aliases: ["5"] },
542
+ { id: "six", label: "6th", intervals: [0, 4, 7, 9], aliases: ["6"] },
543
+ {
544
+ id: "m6",
545
+ label: "Minor 6th",
546
+ intervals: [0, 3, 7, 9],
547
+ aliases: ["m6", "min6"]
548
+ },
549
+ {
550
+ id: "six9",
551
+ label: "6/9",
552
+ intervals: [0, 4, 7, 9, 14],
553
+ aliases: ["6/9", "69"]
554
+ },
555
+ {
556
+ id: "dom7",
557
+ label: "Dominant 7th",
558
+ intervals: [0, 4, 7, 10],
559
+ aliases: ["7"]
560
+ },
561
+ {
562
+ id: "maj7",
563
+ label: "Major 7th",
564
+ intervals: [0, 4, 7, 11],
565
+ aliases: ["maj7", "Maj7", "MAJ7", "M7", "\u0394", "\u03947"]
566
+ },
567
+ {
568
+ id: "m7",
569
+ label: "Minor 7th",
570
+ intervals: [0, 3, 7, 10],
571
+ aliases: ["m7", "min7", "Min7"]
572
+ },
573
+ {
574
+ id: "mMaj7",
575
+ label: "Minor Major 7th",
576
+ intervals: [0, 3, 7, 11],
577
+ aliases: ["mMaj7", "m(maj7)", "mM7", "minMaj7"]
578
+ },
579
+ {
580
+ id: "m7b5",
581
+ label: "Half-diminished 7th",
582
+ intervals: [0, 3, 6, 10],
583
+ aliases: ["m7b5", "m7-5", "\xF8", "\xF87"]
584
+ },
585
+ {
586
+ id: "dim7",
587
+ label: "Diminished 7th",
588
+ intervals: [0, 3, 6, 9],
589
+ aliases: ["dim7", "o7", "\xB07"]
590
+ },
591
+ {
592
+ id: "aug7",
593
+ label: "7#5",
594
+ intervals: [0, 4, 8, 10],
595
+ aliases: ["7#5", "aug7"]
596
+ },
597
+ { id: "dom7b5", label: "7b5", intervals: [0, 4, 6, 10], aliases: ["7b5"] },
598
+ {
599
+ id: "dom7sus4",
600
+ label: "7sus4",
601
+ intervals: [0, 5, 7, 10],
602
+ aliases: ["7sus4"]
603
+ },
604
+ {
605
+ id: "dom7sus2",
606
+ label: "7sus2",
607
+ intervals: [0, 2, 7, 10],
608
+ aliases: ["7sus2"]
609
+ },
610
+ { id: "nine", label: "9th", intervals: [0, 4, 7, 10, 14], aliases: ["9"] },
611
+ {
612
+ id: "maj9",
613
+ label: "Major 9th",
614
+ intervals: [0, 4, 7, 11, 14],
615
+ aliases: ["maj9", "Maj9", "M9"]
616
+ },
617
+ {
618
+ id: "m9",
619
+ label: "Minor 9th",
620
+ intervals: [0, 3, 7, 10, 14],
621
+ aliases: ["m9", "min9"]
622
+ },
623
+ { id: "add9", label: "Add 9", intervals: [0, 4, 7, 14], aliases: ["add9"] },
624
+ {
625
+ id: "madd9",
626
+ label: "Minor Add 9",
627
+ intervals: [0, 3, 7, 14],
628
+ aliases: ["madd9", "minAdd9"]
629
+ },
630
+ {
631
+ id: "eleven",
632
+ label: "11th",
633
+ intervals: [0, 4, 7, 10, 14, 17],
634
+ aliases: ["11"]
635
+ },
636
+ {
637
+ id: "m11",
638
+ label: "Minor 11th",
639
+ intervals: [0, 3, 7, 10, 14, 17],
640
+ aliases: ["m11"]
641
+ },
642
+ {
643
+ id: "thirteen",
644
+ label: "13th",
645
+ intervals: [0, 4, 7, 10, 14, 17, 21],
646
+ aliases: ["13"]
647
+ },
648
+ {
649
+ id: "m13",
650
+ label: "Minor 13th",
651
+ intervals: [0, 3, 7, 10, 14, 17, 21],
652
+ aliases: ["m13"]
653
+ },
654
+ {
655
+ id: "dom7sharp9",
656
+ label: "7#9",
657
+ intervals: [0, 4, 7, 10, 15],
658
+ aliases: ["7#9"]
659
+ },
660
+ {
661
+ id: "dom7flat9",
662
+ label: "7b9",
663
+ intervals: [0, 4, 7, 10, 13],
664
+ aliases: ["7b9"]
665
+ },
666
+ {
667
+ id: "maj7sharp5",
668
+ label: "maj7#5",
669
+ intervals: [0, 4, 8, 11],
670
+ aliases: ["maj7#5"]
671
+ },
672
+ {
673
+ id: "maj7flat5",
674
+ label: "maj7b5",
675
+ intervals: [0, 4, 6, 11],
676
+ aliases: ["maj7b5"]
677
+ }
678
+ ];
679
+ var QUALITY_BY_ID = Object.fromEntries(
680
+ CHORD_QUALITIES.map((q) => [q.id, q])
681
+ );
682
+ var QUALITY_ALIAS_TABLE = CHORD_QUALITIES.flatMap(
683
+ (quality) => quality.aliases.map((alias) => ({ alias, quality }))
684
+ ).sort((a, b) => b.alias.length - a.alias.length);
685
+ function resolveQuality(qualitySymbol) {
686
+ if (qualitySymbol === "") return QUALITY_BY_ID.major;
687
+ const exact = QUALITY_ALIAS_TABLE.find(
688
+ (e) => e.alias !== "" && e.alias === qualitySymbol
689
+ );
690
+ if (exact) return exact.quality;
691
+ const lower = qualitySymbol.toLowerCase();
692
+ const loose = QUALITY_ALIAS_TABLE.find(
693
+ (e) => e.alias !== "" && e.alias.toLowerCase() === lower
694
+ );
695
+ if (loose) return loose.quality;
696
+ return QUALITY_BY_ID.major;
697
+ }
698
+ var QUALITY_SIMPLIFICATION = {
699
+ dim: "minor",
700
+ dim7: "minor",
701
+ aug: "major",
702
+ six: "major",
703
+ m6: "minor",
704
+ six9: "major",
705
+ mMaj7: "m7",
706
+ m7b5: "m7",
707
+ aug7: "dom7",
708
+ dom7b5: "dom7",
709
+ dom7sus4: "sus4",
710
+ dom7sus2: "sus2",
711
+ nine: "dom7",
712
+ maj9: "maj7",
713
+ m9: "m7",
714
+ add9: "major",
715
+ madd9: "minor",
716
+ eleven: "dom7",
717
+ m11: "m7",
718
+ thirteen: "dom7",
719
+ m13: "m7",
720
+ dom7sharp9: "dom7",
721
+ dom7flat9: "dom7",
722
+ maj7sharp5: "maj7",
723
+ maj7flat5: "maj7"
724
+ };
725
+ var SLASH_CONTAINING_ALIASES = QUALITY_ALIAS_TABLE.filter(
726
+ (e) => e.alias.includes("/")
727
+ ).sort((a, b) => b.alias.length - a.alias.length);
728
+ function parseChordSymbol(chord) {
729
+ const cleaned = chord.replace(/[()]/g, "").trim();
730
+ if (!cleaned) return null;
731
+ const rootMatch = cleaned.match(ROOT_PATTERN);
732
+ if (!rootMatch) return null;
733
+ const rootText = rootMatch[1];
734
+ const rootSemitone = resolveRootSemitone(rootText);
735
+ if (rootSemitone === null) return null;
736
+ const remainder = cleaned.slice(rootText.length);
737
+ const slashAlias = SLASH_CONTAINING_ALIASES.find(
738
+ (e) => remainder.startsWith(e.alias)
739
+ );
740
+ let qualitySymbol;
741
+ let bassPart;
742
+ if (slashAlias) {
743
+ qualitySymbol = slashAlias.alias;
744
+ const rest = remainder.slice(slashAlias.alias.length);
745
+ bassPart = rest.startsWith("/") ? rest.slice(1).trim() : void 0;
746
+ } else {
747
+ const slashIndex = remainder.indexOf("/");
748
+ if (slashIndex === -1) {
749
+ qualitySymbol = remainder;
750
+ } else {
751
+ qualitySymbol = remainder.slice(0, slashIndex);
752
+ bassPart = remainder.slice(slashIndex + 1).trim();
753
+ }
754
+ }
755
+ const quality = resolveQuality(qualitySymbol);
756
+ let bassSemitone;
757
+ if (bassPart) {
758
+ const bassMatch = bassPart.match(ROOT_PATTERN);
759
+ if (bassMatch) {
760
+ const resolved = resolveRootSemitone(bassMatch[1]);
761
+ if (resolved !== null) bassSemitone = resolved;
762
+ }
763
+ }
764
+ return {
765
+ raw: cleaned,
766
+ rootSemitone,
767
+ rootDisplay: pitchClassName(rootSemitone),
768
+ quality,
769
+ bassSemitone
770
+ };
771
+ }
772
+ function computePianoVoicing(rootSemitone, intervals, bassSemitone) {
773
+ const rootPc = (rootSemitone % 12 + 12) % 12;
774
+ if (bassSemitone !== void 0) {
775
+ const bassPc = (bassSemitone % 12 + 12) % 12;
776
+ const notes2 = [pitchClassName(bassPc)];
777
+ const highlightKeys2 = [bassPc];
778
+ for (const iv of intervals) {
779
+ const key = (rootPc + iv) % 12 + 12;
780
+ const name = pitchClassName(rootPc + iv);
781
+ highlightKeys2.push(key);
782
+ if (!notes2.includes(name)) notes2.push(name);
783
+ }
784
+ return { notes: notes2, highlightKeys: highlightKeys2 };
785
+ }
786
+ const notes = intervals.map((iv) => pitchClassName(rootPc + iv));
787
+ const highlightKeys = intervals.map((iv) => (rootPc + iv) % 24);
788
+ return { notes, highlightKeys };
789
+ }
790
+ var OPEN_CHORD_SHAPES = {
791
+ C: { frets: [-1, 3, 2, 0, 1, 0], fingers: [0, 3, 2, 0, 1, 0] },
792
+ Cm: { frets: [-1, 3, 5, 5, 4, 3], fingers: [0, 1, 3, 4, 2, 1], barre: 3 },
793
+ C7: { frets: [-1, 3, 2, 3, 1, 0], fingers: [0, 3, 2, 4, 1, 0] },
794
+ Cmaj7: { frets: [-1, 3, 2, 0, 0, 0], fingers: [0, 3, 2, 0, 0, 0] },
795
+ Cm7: { frets: [-1, 3, 5, 3, 4, 3], fingers: [0, 1, 3, 1, 2, 1], barre: 3 },
796
+ Csus4: { frets: [-1, 3, 3, 0, 1, 1], fingers: [0, 3, 4, 0, 1, 1] },
797
+ Csus2: { frets: [-1, 3, 0, 0, 1, 3], fingers: [0, 2, 0, 0, 1, 4] },
798
+ Cadd9: { frets: [-1, 3, 2, 0, 3, 0], fingers: [0, 2, 1, 0, 3, 0] },
799
+ C9: { frets: [-1, 3, 2, 3, 3, 3], fingers: [0, 2, 1, 3, 3, 3], barre: 3 },
800
+ C6: { frets: [-1, 3, 2, 2, 1, 0], fingers: [0, 4, 2, 3, 1, 0] },
801
+ "C#": { frets: [-1, 4, 6, 6, 6, 4], fingers: [0, 1, 2, 3, 4, 1], barre: 4 },
802
+ "C#m": {
803
+ frets: [-1, 4, 6, 6, 5, 4],
804
+ fingers: [0, 1, 3, 4, 2, 1],
805
+ barre: 4
806
+ },
807
+ "C#7": { frets: [-1, 4, 3, 4, 2, -1], fingers: [0, 3, 2, 4, 1, 0] },
808
+ "C#maj7": {
809
+ frets: [-1, 4, 6, 5, 6, 4],
810
+ fingers: [0, 1, 3, 2, 4, 1],
811
+ barre: 4
812
+ },
813
+ "C#m7": {
814
+ frets: [-1, 4, 6, 4, 5, 4],
815
+ fingers: [0, 1, 3, 1, 2, 1],
816
+ barre: 4
817
+ },
818
+ D: { frets: [-1, -1, 0, 2, 3, 2], fingers: [0, 0, 0, 1, 3, 2] },
819
+ Dm: { frets: [-1, -1, 0, 2, 3, 1], fingers: [0, 0, 0, 2, 3, 1] },
820
+ D7: { frets: [-1, -1, 0, 2, 1, 2], fingers: [0, 0, 0, 2, 1, 3] },
821
+ Dmaj7: {
822
+ frets: [-1, -1, 0, 2, 2, 2],
823
+ fingers: [0, 0, 0, 1, 1, 1],
824
+ barre: 2
825
+ },
826
+ Dm7: { frets: [-1, -1, 0, 2, 1, 1], fingers: [0, 0, 0, 2, 1, 1], barre: 1 },
827
+ Dsus4: { frets: [-1, -1, 0, 2, 3, 3], fingers: [0, 0, 0, 1, 2, 3] },
828
+ Dsus2: { frets: [-1, -1, 0, 2, 3, 0], fingers: [0, 0, 0, 1, 2, 0] },
829
+ Dadd9: { frets: [-1, -1, 0, 2, 5, 2], fingers: [0, 0, 0, 1, 4, 2] },
830
+ D6: { frets: [-1, -1, 0, 2, 0, 2], fingers: [0, 0, 0, 2, 0, 3] },
831
+ D9: { frets: [-1, -1, 0, 2, 1, 0], fingers: [0, 0, 0, 2, 1, 0] },
832
+ Eb: { frets: [-1, 6, 8, 8, 8, 6], fingers: [0, 1, 2, 3, 4, 1], barre: 6 },
833
+ Ebm: { frets: [-1, 6, 8, 8, 7, 6], fingers: [0, 1, 3, 4, 2, 1], barre: 6 },
834
+ Eb7: { frets: [-1, 6, 5, 6, 4, -1], fingers: [0, 3, 2, 4, 1, 0] },
835
+ E: { frets: [0, 2, 2, 1, 0, 0], fingers: [0, 2, 3, 1, 0, 0] },
836
+ Em: { frets: [0, 2, 2, 0, 0, 0], fingers: [0, 2, 3, 0, 0, 0] },
837
+ E7: { frets: [0, 2, 0, 1, 0, 0], fingers: [0, 2, 0, 1, 0, 0] },
838
+ Emaj7: { frets: [0, 2, 1, 1, 0, 0], fingers: [0, 3, 1, 2, 0, 0] },
839
+ Em7: { frets: [0, 2, 0, 0, 0, 0], fingers: [0, 2, 0, 0, 0, 0] },
840
+ Esus4: { frets: [0, 2, 2, 2, 0, 0], fingers: [0, 2, 3, 4, 0, 0] },
841
+ Eadd9: { frets: [0, 2, 4, 1, 0, 0], fingers: [0, 2, 4, 1, 0, 0] },
842
+ E6: { frets: [0, 2, 2, 1, 2, 0], fingers: [0, 2, 3, 1, 4, 0] },
843
+ E9: { frets: [0, 2, 0, 1, 3, 0], fingers: [0, 2, 0, 1, 4, 0] },
844
+ F: { frets: [1, 3, 3, 2, 1, 1], fingers: [1, 3, 4, 2, 1, 1], barre: 1 },
845
+ Fm: { frets: [1, 3, 3, 1, 1, 1], fingers: [1, 3, 4, 1, 1, 1], barre: 1 },
846
+ F7: { frets: [1, 3, 1, 2, 1, 1], fingers: [1, 3, 1, 2, 1, 1], barre: 1 },
847
+ Fmaj7: { frets: [-1, 3, 3, 2, 1, 0], fingers: [0, 3, 4, 2, 1, 0] },
848
+ Fm7: { frets: [1, 3, 1, 1, 1, 1], fingers: [1, 3, 1, 1, 1, 1], barre: 1 },
849
+ "F#": { frets: [2, 4, 4, 3, 2, 2], fingers: [1, 3, 4, 2, 1, 1], barre: 2 },
850
+ "F#m": { frets: [2, 4, 4, 2, 2, 2], fingers: [1, 3, 4, 1, 1, 1], barre: 2 },
851
+ "F#7": { frets: [2, 4, 2, 3, 2, 2], fingers: [1, 3, 1, 2, 1, 1], barre: 2 },
852
+ "F#m7": {
853
+ frets: [2, 4, 2, 2, 2, 2],
854
+ fingers: [1, 3, 1, 1, 1, 1],
855
+ barre: 2
856
+ },
857
+ G: { frets: [3, 2, 0, 0, 3, 3], fingers: [2, 1, 0, 0, 3, 4] },
858
+ Gm: { frets: [3, 5, 5, 3, 3, 3], fingers: [1, 3, 4, 1, 1, 1], barre: 3 },
859
+ G7: { frets: [3, 2, 0, 0, 0, 1], fingers: [3, 2, 0, 0, 0, 1] },
860
+ Gmaj7: { frets: [3, 2, 0, 0, 0, 2], fingers: [2, 1, 0, 0, 0, 3] },
861
+ Gm7: { frets: [3, 5, 3, 3, 3, 3], fingers: [1, 3, 1, 1, 1, 1], barre: 3 },
862
+ Gsus4: { frets: [3, 3, 0, 0, 3, 3], fingers: [2, 3, 0, 0, 1, 4] },
863
+ Gadd9: { frets: [3, 2, 0, 2, 0, 3], fingers: [2, 1, 0, 3, 0, 4] },
864
+ G6: { frets: [3, 2, 0, 0, 0, 0], fingers: [3, 2, 0, 0, 0, 0] },
865
+ Ab: { frets: [4, 6, 6, 5, 4, 4], fingers: [1, 3, 4, 2, 1, 1], barre: 4 },
866
+ Abm: { frets: [4, 6, 6, 4, 4, 4], fingers: [1, 3, 4, 1, 1, 1], barre: 4 },
867
+ A: { frets: [-1, 0, 2, 2, 2, 0], fingers: [0, 0, 1, 2, 3, 0] },
868
+ Am: { frets: [-1, 0, 2, 2, 1, 0], fingers: [0, 0, 2, 3, 1, 0] },
869
+ A7: { frets: [-1, 0, 2, 0, 2, 0], fingers: [0, 0, 1, 0, 2, 0] },
870
+ Amaj7: { frets: [-1, 0, 2, 1, 2, 0], fingers: [0, 0, 2, 1, 3, 0] },
871
+ Am7: { frets: [-1, 0, 2, 0, 1, 0], fingers: [0, 0, 2, 0, 1, 0] },
872
+ Asus4: { frets: [-1, 0, 2, 2, 3, 0], fingers: [0, 0, 1, 2, 4, 0] },
873
+ Asus2: { frets: [-1, 0, 2, 2, 0, 0], fingers: [0, 0, 1, 2, 0, 0] },
874
+ Aadd9: { frets: [-1, 0, 2, 4, 2, 0], fingers: [0, 0, 1, 3, 2, 0] },
875
+ A6: { frets: [-1, 0, 2, 2, 2, 2], fingers: [0, 0, 1, 1, 1, 1], barre: 2 },
876
+ A9: { frets: [-1, 0, 2, 4, 2, 3], fingers: [0, 0, 1, 3, 2, 4] },
877
+ Bb: { frets: [-1, 1, 3, 3, 3, 1], fingers: [0, 1, 2, 3, 4, 1], barre: 1 },
878
+ Bbm: { frets: [-1, 1, 3, 3, 2, 1], fingers: [0, 1, 3, 4, 2, 1], barre: 1 },
879
+ Bb7: { frets: [-1, 1, 3, 1, 3, 1], fingers: [0, 1, 3, 1, 4, 1], barre: 1 },
880
+ B: { frets: [-1, 2, 4, 4, 4, 2], fingers: [0, 1, 2, 3, 4, 1], barre: 2 },
881
+ Bm: { frets: [-1, 2, 4, 4, 3, 2], fingers: [0, 1, 3, 4, 2, 1], barre: 2 },
882
+ B7: { frets: [-1, 2, 1, 2, 0, 2], fingers: [0, 2, 1, 3, 0, 4] },
883
+ Bmaj7: {
884
+ frets: [-1, 2, 4, 3, 4, 2],
885
+ fingers: [0, 1, 3, 2, 4, 1],
886
+ barre: 2
887
+ },
888
+ Bm7: { frets: [-1, 2, 4, 2, 3, 2], fingers: [0, 1, 3, 1, 2, 1], barre: 2 }
889
+ };
890
+ var SLASH_CHORD_SHAPES = {
891
+ "C/E": { frets: [0, 3, 2, 0, 1, 0], fingers: [0, 3, 2, 0, 1, 0] },
892
+ "C/G": { frets: [3, 3, 2, 0, 1, 0], fingers: [3, 4, 2, 0, 1, 0] },
893
+ "C/Bb": { frets: [-1, 3, 2, 3, 1, 0], fingers: [0, 3, 2, 4, 1, 0] },
894
+ "D/F#": { frets: [2, 0, 0, 2, 3, 2], fingers: [1, 0, 0, 2, 4, 3] },
895
+ "D/A": { frets: [-1, 0, 0, 2, 3, 2], fingers: [0, 0, 0, 1, 3, 2] },
896
+ "E/G#": { frets: [4, 2, 2, 1, 0, 0], fingers: [4, 2, 3, 1, 0, 0] },
897
+ "E/B": { frets: [0, 2, 2, 1, 0, 0], fingers: [0, 2, 3, 1, 0, 0] },
898
+ "F/A": { frets: [-1, 0, 3, 2, 1, 1], fingers: [0, 0, 3, 2, 1, 1] },
899
+ "F/C": {
900
+ frets: [8, 8, 10, 10, 10, 8],
901
+ fingers: [1, 1, 2, 3, 4, 1],
902
+ barre: 8
903
+ },
904
+ "G/B": { frets: [-1, 2, 0, 0, 3, 3], fingers: [0, 1, 0, 0, 3, 4] },
905
+ "G/D": { frets: [-1, -1, 0, 0, 3, 3], fingers: [0, 0, 0, 0, 3, 4] },
906
+ "G/F": { frets: [3, 2, 0, 0, 0, 1], fingers: [3, 2, 0, 0, 0, 1] },
907
+ "A/C#": { frets: [-1, 4, 2, 2, 2, -1], fingers: [0, 4, 1, 1, 1, 0] },
908
+ "A/E": { frets: [0, 0, 2, 2, 2, 0], fingers: [0, 0, 1, 2, 3, 0] },
909
+ "A/G": { frets: [3, 0, 2, 2, 2, 0], fingers: [4, 0, 1, 2, 3, 0] },
910
+ "B/D#": { frets: [-1, 6, 4, 4, 4, -1], fingers: [0, 3, 1, 1, 1, 0] },
911
+ "B/F#": {
912
+ frets: [2, 2, 4, 4, 4, 2],
913
+ fingers: [1, 1, 2, 3, 4, 1],
914
+ barre: 2
915
+ },
916
+ "Am/G": { frets: [3, 0, 2, 2, 1, 0], fingers: [4, 0, 2, 3, 1, 0] },
917
+ "Am/F#": { frets: [2, 0, 2, 2, 1, 0], fingers: [2, 0, 3, 4, 1, 0] },
918
+ "Am/E": { frets: [0, 0, 2, 2, 1, 0], fingers: [0, 0, 2, 3, 1, 0] },
919
+ "Dm/C": { frets: [-1, 3, 0, 2, 3, 1], fingers: [0, 3, 0, 2, 4, 1] },
920
+ "Dm/B": { frets: [-1, 2, 0, 2, 3, 1], fingers: [0, 2, 0, 3, 4, 1] },
921
+ "Dm/A": { frets: [-1, 0, 0, 2, 3, 1], fingers: [0, 0, 0, 2, 3, 1] },
922
+ "Dm/F": { frets: [1, -1, 0, 2, 3, 1], fingers: [1, 0, 0, 2, 4, 3] },
923
+ "Em/D": { frets: [0, 2, 0, 0, 0, 0], fingers: [0, 2, 0, 0, 0, 0] },
924
+ "Em/C#": { frets: [0, 4, 2, 0, 0, 0], fingers: [0, 3, 1, 0, 0, 0] },
925
+ "Em/B": {
926
+ frets: [7, 7, 9, 9, 8, 7],
927
+ fingers: [1, 1, 3, 4, 2, 1],
928
+ barre: 7
929
+ },
930
+ "Em/G": { frets: [3, 2, 2, 0, 0, 0], fingers: [3, 1, 2, 0, 0, 0] },
931
+ "Fm/Eb": { frets: [-1, 6, 6, 5, 6, -1], fingers: [0, 2, 3, 1, 4, 0] },
932
+ "Gm/F": {
933
+ frets: [3, 5, 3, 3, 3, 3],
934
+ fingers: [1, 3, 1, 1, 1, 1],
935
+ barre: 3
936
+ },
937
+ "Bm/A": { frets: [-1, 0, 4, 4, 3, 2], fingers: [0, 0, 3, 4, 2, 1] }
938
+ };
939
+ var CANONICAL_SUFFIX = {
940
+ major: "",
941
+ minor: "m",
942
+ dom7: "7",
943
+ maj7: "maj7",
944
+ m7: "m7",
945
+ sus4: "sus4",
946
+ sus2: "sus2",
947
+ six: "6",
948
+ add9: "add9",
949
+ nine: "9"
950
+ };
951
+ var E_FORM = {
952
+ major: {
953
+ formRootSemitone: 4,
954
+ frets: [0, 2, 2, 1, 0, 0],
955
+ openFingers: [0, 2, 3, 1, 0, 0],
956
+ barreFingers: [1, 3, 4, 2, 1, 1]
957
+ },
958
+ minor: {
959
+ formRootSemitone: 4,
960
+ frets: [0, 2, 2, 0, 0, 0],
961
+ openFingers: [0, 2, 3, 0, 0, 0],
962
+ barreFingers: [1, 3, 4, 1, 1, 1]
963
+ },
964
+ dom7: {
965
+ formRootSemitone: 4,
966
+ frets: [0, 2, 0, 1, 0, 0],
967
+ openFingers: [0, 2, 0, 1, 0, 0],
968
+ barreFingers: [1, 3, 1, 2, 1, 1]
969
+ },
970
+ m7: {
971
+ formRootSemitone: 4,
972
+ frets: [0, 2, 0, 0, 0, 0],
973
+ openFingers: [0, 2, 0, 0, 0, 0],
974
+ barreFingers: [1, 3, 1, 1, 1, 1]
975
+ },
976
+ maj7: {
977
+ formRootSemitone: 4,
978
+ frets: [0, 2, 1, 1, 0, 0],
979
+ openFingers: [0, 3, 1, 2, 0, 0],
980
+ barreFingers: [1, 3, 2, 2, 1, 1]
981
+ },
982
+ sus4: {
983
+ formRootSemitone: 4,
984
+ frets: [0, 2, 2, 2, 0, 0],
985
+ openFingers: [0, 2, 3, 4, 0, 0],
986
+ barreFingers: [1, 3, 4, 4, 1, 1]
987
+ }
988
+ };
989
+ var A_FORM = {
990
+ major: {
991
+ formRootSemitone: 9,
992
+ frets: [-1, 0, 2, 2, 2, 0],
993
+ openFingers: [0, 0, 1, 2, 3, 0],
994
+ barreFingers: [0, 1, 3, 4, 4, 1]
995
+ },
996
+ minor: {
997
+ formRootSemitone: 9,
998
+ frets: [-1, 0, 2, 2, 1, 0],
999
+ openFingers: [0, 0, 2, 3, 1, 0],
1000
+ barreFingers: [0, 1, 3, 4, 2, 1]
1001
+ },
1002
+ dom7: {
1003
+ formRootSemitone: 9,
1004
+ frets: [-1, 0, 2, 0, 2, 0],
1005
+ openFingers: [0, 0, 1, 0, 2, 0],
1006
+ barreFingers: [0, 1, 3, 1, 4, 1]
1007
+ },
1008
+ m7: {
1009
+ formRootSemitone: 9,
1010
+ frets: [-1, 0, 2, 0, 1, 0],
1011
+ openFingers: [0, 0, 2, 0, 1, 0],
1012
+ barreFingers: [0, 1, 3, 1, 2, 1]
1013
+ },
1014
+ maj7: {
1015
+ formRootSemitone: 9,
1016
+ frets: [-1, 0, 2, 1, 2, 0],
1017
+ openFingers: [0, 0, 2, 1, 3, 0],
1018
+ barreFingers: [0, 1, 3, 2, 4, 1]
1019
+ },
1020
+ sus4: {
1021
+ formRootSemitone: 9,
1022
+ frets: [-1, 0, 2, 2, 3, 0],
1023
+ openFingers: [0, 0, 1, 2, 4, 0],
1024
+ barreFingers: [0, 1, 3, 3, 4, 1]
1025
+ },
1026
+ sus2: {
1027
+ formRootSemitone: 9,
1028
+ frets: [-1, 0, 2, 2, 0, 0],
1029
+ openFingers: [0, 0, 1, 2, 0, 0],
1030
+ barreFingers: [0, 1, 3, 4, 1, 1]
1031
+ }
1032
+ };
1033
+ function realizeTemplate(t, targetSemitone) {
1034
+ const shift = ((targetSemitone - t.formRootSemitone) % 12 + 12) % 12;
1035
+ const frets = t.frets.map((f) => f < 0 ? f : f + shift);
1036
+ if (shift === 0) return { frets, fingers: t.openFingers ?? t.barreFingers };
1037
+ return { frets, fingers: t.barreFingers, barre: shift };
1038
+ }
1039
+ function maxFret(shape) {
1040
+ return Math.max(0, ...shape.frets.filter((f) => f >= 0));
1041
+ }
1042
+ function templateFingering(qualityId, targetSemitone) {
1043
+ const e = E_FORM[qualityId];
1044
+ const a = A_FORM[qualityId];
1045
+ if (!e && !a) return null;
1046
+ if (e && !a) return realizeTemplate(e, targetSemitone);
1047
+ if (a && !e) return realizeTemplate(a, targetSemitone);
1048
+ const eShape = realizeTemplate(e, targetSemitone);
1049
+ const aShape = realizeTemplate(a, targetSemitone);
1050
+ return maxFret(aShape) <= maxFret(eShape) ? aShape : eShape;
1051
+ }
1052
+ function powerChordShape(targetSemitone) {
1053
+ const shift = ((targetSemitone - 4) % 12 + 12) % 12;
1054
+ return {
1055
+ frets: [shift, shift + 2, shift + 2, -1, -1, -1],
1056
+ fingers: [1, 3, 4, 0, 0, 0]
1057
+ };
1058
+ }
1059
+ var TEMPLATE_QUALITIES = /* @__PURE__ */ new Set([
1060
+ "major",
1061
+ "minor",
1062
+ "dom7",
1063
+ "m7",
1064
+ "maj7",
1065
+ "sus4",
1066
+ "sus2"
1067
+ ]);
1068
+ function getGuitarFingering(parsed) {
1069
+ const { rootSemitone, quality, raw, rootDisplay } = parsed;
1070
+ if (SLASH_CHORD_SHAPES[raw])
1071
+ return { shape: SLASH_CHORD_SHAPES[raw], approximate: false };
1072
+ if (OPEN_CHORD_SHAPES[raw])
1073
+ return { shape: OPEN_CHORD_SHAPES[raw], approximate: false };
1074
+ const suffix = CANONICAL_SUFFIX[quality.id];
1075
+ if (suffix !== void 0) {
1076
+ const canonical = rootDisplay + suffix;
1077
+ if (OPEN_CHORD_SHAPES[canonical])
1078
+ return { shape: OPEN_CHORD_SHAPES[canonical], approximate: false };
1079
+ }
1080
+ if (quality.id === "five")
1081
+ return { shape: powerChordShape(rootSemitone), approximate: false };
1082
+ if (TEMPLATE_QUALITIES.has(quality.id)) {
1083
+ const shape = templateFingering(quality.id, rootSemitone);
1084
+ if (shape) return { shape, approximate: false };
1085
+ }
1086
+ let fallbackId = QUALITY_SIMPLIFICATION[quality.id];
1087
+ let hops = 0;
1088
+ while (fallbackId && hops < 4) {
1089
+ if (TEMPLATE_QUALITIES.has(fallbackId)) {
1090
+ const shape = templateFingering(fallbackId, rootSemitone);
1091
+ if (shape) return { shape, approximate: true };
1092
+ }
1093
+ const suf = CANONICAL_SUFFIX[fallbackId];
1094
+ if (suf !== void 0) {
1095
+ const canonical = rootDisplay + suf;
1096
+ if (OPEN_CHORD_SHAPES[canonical])
1097
+ return {
1098
+ shape: OPEN_CHORD_SHAPES[canonical],
1099
+ approximate: true
1100
+ };
1101
+ }
1102
+ fallbackId = QUALITY_SIMPLIFICATION[fallbackId];
1103
+ hops += 1;
1104
+ }
1105
+ return null;
1106
+ }
1107
+ var DefaultChordDictionary = class {
1108
+ getFingering(chord) {
1109
+ const parsed = parseChordSymbol(chord);
1110
+ if (!parsed) return null;
1111
+ const piano = computePianoVoicing(
1112
+ parsed.rootSemitone,
1113
+ parsed.quality.intervals,
1114
+ parsed.bassSemitone
1115
+ );
1116
+ const guitar = getGuitarFingering(parsed);
1117
+ return {
1118
+ chord: parsed.raw,
1119
+ qualityId: parsed.quality.id,
1120
+ qualityLabel: parsed.quality.label,
1121
+ piano,
1122
+ guitar: guitar ? {
1123
+ frets: guitar.shape.frets,
1124
+ fingers: guitar.shape.fingers,
1125
+ barre: guitar.shape.barre,
1126
+ approximate: guitar.approximate || void 0
1127
+ } : void 0
1128
+ };
1129
+ }
1130
+ };
1131
+ var chordDictionary = new DefaultChordDictionary();
1132
+
1133
+ export {
1134
+ parseLineSegments,
1135
+ parseChordPro,
1136
+ buildChordProText,
1137
+ getNoteValue,
1138
+ getSuggestedCapo,
1139
+ transposeNote,
1140
+ transposeChord,
1141
+ DefaultChordDictionary,
1142
+ chordDictionary
1143
+ };