@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,1567 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/parser/index.ts
21
+ var parser_exports = {};
22
+ __export(parser_exports, {
23
+ DefaultChordDictionary: () => DefaultChordDictionary,
24
+ buildChordProText: () => buildChordProText,
25
+ chordDictionary: () => chordDictionary,
26
+ convertToChordProDetailed: () => convertToChordProDetailed,
27
+ detectSourceFormat: () => detectSourceFormat,
28
+ getNoteValue: () => getNoteValue,
29
+ getSuggestedCapo: () => getSuggestedCapo,
30
+ parseChordPro: () => parseChordPro,
31
+ parseLineSegments: () => parseLineSegments,
32
+ slugifyTitle: () => slugifyTitle,
33
+ toChordPro: () => toChordPro,
34
+ transposeChord: () => transposeChord,
35
+ transposeNote: () => transposeNote
36
+ });
37
+ module.exports = __toCommonJS(parser_exports);
38
+
39
+ // src/parser/parser.ts
40
+ var TIMING_REGEX = /^(.+?)@([0-9]*\.?[0-9]+)x$/;
41
+ function parseChordTiming(rawChord) {
42
+ const match = rawChord.match(TIMING_REGEX);
43
+ if (match) return { chord: match[1], timing: parseFloat(match[2]) };
44
+ return { chord: rawChord };
45
+ }
46
+ function parseLineSegments(lineText) {
47
+ const segments = [];
48
+ const regex = /\[([^\]]+)\]/g;
49
+ let match;
50
+ let lastIndex = 0;
51
+ let currentChord = "";
52
+ let currentTiming;
53
+ while ((match = regex.exec(lineText)) !== null) {
54
+ const rawChord = match[1];
55
+ const { chord, timing } = parseChordTiming(rawChord);
56
+ const textBefore = lineText.slice(lastIndex, match.index);
57
+ if (lastIndex === 0 && textBefore === "") {
58
+ currentChord = chord;
59
+ currentTiming = timing;
60
+ } else {
61
+ segments.push({
62
+ chord: currentChord,
63
+ text: textBefore,
64
+ timing: currentTiming
65
+ });
66
+ currentChord = chord;
67
+ currentTiming = timing;
68
+ }
69
+ lastIndex = regex.lastIndex;
70
+ }
71
+ const remainingText = lineText.slice(lastIndex);
72
+ segments.push({
73
+ chord: currentChord,
74
+ text: remainingText,
75
+ timing: currentTiming
76
+ });
77
+ return segments;
78
+ }
79
+ function parseChordPro(content) {
80
+ const lines = content.split(/\r?\n/);
81
+ const metadata = {};
82
+ const sections = [];
83
+ let currentSection = null;
84
+ let isTab = false;
85
+ let isGrid = false;
86
+ let lastChorusLines = [];
87
+ const commitSection = () => {
88
+ if (currentSection) {
89
+ sections.push(currentSection);
90
+ if (currentSection.type === "chorus") {
91
+ lastChorusLines = [...currentSection.lines];
92
+ }
93
+ currentSection = null;
94
+ }
95
+ };
96
+ const aliasMap = {
97
+ t: "title",
98
+ st: "subtitle",
99
+ a: "artist",
100
+ k: "key",
101
+ c: "comment",
102
+ ci: "comment_italic",
103
+ cb: "comment_box",
104
+ soc: "start_of_chorus",
105
+ eoc: "end_of_chorus",
106
+ sov: "start_of_verse",
107
+ eov: "end_of_verse",
108
+ sob: "start_of_bridge",
109
+ eob: "end_of_bridge",
110
+ sot: "start_of_tab",
111
+ eot: "end_of_tab",
112
+ sog: "start_of_grid",
113
+ eog: "end_of_grid",
114
+ ch: "chorus",
115
+ v: "verse",
116
+ b: "bridge",
117
+ re: "repeat",
118
+ ns: "new_song",
119
+ time_signature: "time",
120
+ timesignature: "time",
121
+ "time signature": "time",
122
+ original_key: "original_key",
123
+ "original key": "original_key"
124
+ };
125
+ for (let line of lines) {
126
+ const trimmed = line.trim();
127
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
128
+ const directive = trimmed.slice(1, -1).trim();
129
+ const colonIndex = directive.indexOf(":");
130
+ let rawName = directive;
131
+ let value = "";
132
+ if (colonIndex !== -1) {
133
+ rawName = directive.substring(0, colonIndex).trim();
134
+ value = directive.substring(colonIndex + 1).trim();
135
+ }
136
+ const lowerName = rawName.toLowerCase();
137
+ const name = aliasMap[lowerName] || lowerName;
138
+ switch (name) {
139
+ case "start_of_chorus":
140
+ commitSection();
141
+ currentSection = {
142
+ type: "chorus",
143
+ label: value || "Refr\xE3o",
144
+ lines: []
145
+ };
146
+ break;
147
+ case "start_of_verse":
148
+ commitSection();
149
+ currentSection = {
150
+ type: "verse",
151
+ label: value || "Verso",
152
+ lines: []
153
+ };
154
+ break;
155
+ case "start_of_bridge":
156
+ commitSection();
157
+ currentSection = {
158
+ type: "bridge",
159
+ label: value || "Ponte",
160
+ lines: []
161
+ };
162
+ break;
163
+ case "start_of_tab":
164
+ commitSection();
165
+ isTab = true;
166
+ currentSection = {
167
+ type: "tab",
168
+ label: value || "Tablatura",
169
+ lines: []
170
+ };
171
+ break;
172
+ case "start_of_grid":
173
+ commitSection();
174
+ isGrid = true;
175
+ currentSection = { type: "grid", label: value || "Grid", lines: [] };
176
+ break;
177
+ case "end_of_chorus":
178
+ if (currentSection?.type === "chorus") commitSection();
179
+ break;
180
+ case "end_of_verse":
181
+ if (currentSection?.type === "verse") commitSection();
182
+ break;
183
+ case "end_of_bridge":
184
+ if (currentSection?.type === "bridge") commitSection();
185
+ break;
186
+ case "end_of_tab":
187
+ isTab = false;
188
+ if (currentSection?.type === "tab") commitSection();
189
+ break;
190
+ case "end_of_grid":
191
+ isGrid = false;
192
+ if (currentSection?.type === "grid") commitSection();
193
+ break;
194
+ case "chorus":
195
+ commitSection();
196
+ sections.push({
197
+ type: "chorus",
198
+ label: value || "Refr\xE3o",
199
+ lines: [...lastChorusLines]
200
+ });
201
+ break;
202
+ case "verse":
203
+ commitSection();
204
+ currentSection = {
205
+ type: "verse",
206
+ label: value || "Verso",
207
+ lines: []
208
+ };
209
+ break;
210
+ case "bridge":
211
+ commitSection();
212
+ currentSection = {
213
+ type: "bridge",
214
+ label: value || "Ponte",
215
+ lines: []
216
+ };
217
+ break;
218
+ case "comment":
219
+ case "comment_italic":
220
+ const commentLine = { type: "comment", text: value };
221
+ if (currentSection) currentSection.lines.push(commentLine);
222
+ else sections.push({ type: "comment", lines: [commentLine] });
223
+ break;
224
+ case "comment_box":
225
+ const cbLine = { type: "comment_box", text: value };
226
+ if (currentSection) currentSection.lines.push(cbLine);
227
+ else sections.push({ type: "comment", lines: [cbLine] });
228
+ break;
229
+ case "repeat":
230
+ if (currentSection) {
231
+ currentSection.repeat = value || "2";
232
+ } else {
233
+ sections.push({
234
+ type: "comment",
235
+ lines: [
236
+ { type: "comment_box", text: `Repetir: ${value || "2"}` }
237
+ ]
238
+ });
239
+ }
240
+ break;
241
+ case "new_song":
242
+ commitSection();
243
+ sections.push({ type: "new_song", lines: [] });
244
+ break;
245
+ case "duration":
246
+ if (/^\d{1,2}:\d{2}$/.test(value)) {
247
+ const [minutes, seconds] = value.split(":").map(Number);
248
+ metadata["duration"] = (minutes * 60 + seconds).toString();
249
+ } else {
250
+ metadata["duration"] = value;
251
+ }
252
+ break;
253
+ default:
254
+ if (value) {
255
+ const metaKey = name.replace(
256
+ /[-_\s]+([a-zA-Z])/g,
257
+ (_, letter) => letter.toUpperCase()
258
+ ).replace(/\s+/g, "");
259
+ metadata[metaKey] = value;
260
+ }
261
+ break;
262
+ }
263
+ continue;
264
+ }
265
+ if (trimmed === "") {
266
+ if (currentSection) currentSection.lines.push({ type: "empty" });
267
+ continue;
268
+ }
269
+ if (trimmed.startsWith("#") && !isTab) continue;
270
+ let lineType = "lyrics";
271
+ let parsedSegments = [];
272
+ if (isTab) {
273
+ lineType = "tab";
274
+ } else {
275
+ parsedSegments = parseLineSegments(line);
276
+ const textContent = parsedSegments.map((s) => s.text).join("");
277
+ const onlyBarsAndSpaces = /^[\s|:\-.%]*$/.test(textContent);
278
+ const hasBars = textContent.includes("|");
279
+ if (isGrid || onlyBarsAndSpaces && hasBars) {
280
+ lineType = "chord-section";
281
+ }
282
+ }
283
+ const parsedLine = { type: lineType };
284
+ if (lineType === "tab") {
285
+ parsedLine.text = line;
286
+ } else if (lineType === "lyrics") {
287
+ parsedLine.segments = parsedSegments;
288
+ } else if (lineType === "chord-section") {
289
+ parsedLine.segments = parsedSegments;
290
+ const measures = [];
291
+ let currentChords = [];
292
+ let startBarline = "";
293
+ let hasSeenChord = false;
294
+ let startBarlineFound = false;
295
+ for (let i = 0; i < parsedSegments.length; i++) {
296
+ const seg = parsedSegments[i];
297
+ if (seg.chord) {
298
+ currentChords.push({
299
+ chord: seg.chord,
300
+ text: "",
301
+ timing: seg.timing
302
+ });
303
+ hasSeenChord = true;
304
+ }
305
+ const barlineMatches = seg.text.match(/\|\||:\||\|:|\|/g);
306
+ if (barlineMatches) {
307
+ for (let j = 0; j < barlineMatches.length; j++) {
308
+ const b = barlineMatches[j];
309
+ if (!hasSeenChord && !startBarlineFound) {
310
+ startBarline = b;
311
+ startBarlineFound = true;
312
+ } else {
313
+ measures.push({ chords: currentChords, endBarline: b });
314
+ currentChords = [];
315
+ }
316
+ }
317
+ }
318
+ }
319
+ if (currentChords.length > 0) {
320
+ measures.push({ chords: currentChords, endBarline: "" });
321
+ }
322
+ parsedLine.measures = measures;
323
+ parsedLine.startBarline = startBarline;
324
+ }
325
+ if (!currentSection) currentSection = { type: "verse", lines: [] };
326
+ currentSection.lines.push(parsedLine);
327
+ }
328
+ commitSection();
329
+ if (!metadata.title) metadata.title = "Sem T\xEDtulo";
330
+ return { metadata, sections };
331
+ }
332
+ function buildChordProText(metadata, bodyContent) {
333
+ const lines = [];
334
+ const primaryKeys = [
335
+ "title",
336
+ "subtitle",
337
+ "artist",
338
+ "composer",
339
+ "album",
340
+ "copyright",
341
+ "key",
342
+ "originalKey",
343
+ "capo",
344
+ "tempo",
345
+ "time",
346
+ "duration",
347
+ "songNumber",
348
+ "ccli",
349
+ "youtube"
350
+ ];
351
+ for (const k of primaryKeys) {
352
+ if (metadata[k]) {
353
+ const directiveName = k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
354
+ lines.push(`{${directiveName}: ${metadata[k]}}`);
355
+ }
356
+ }
357
+ for (const k in metadata) {
358
+ if (!primaryKeys.includes(k) && metadata[k] && k !== "title") {
359
+ const directiveName = k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
360
+ lines.push(`{${directiveName}: ${metadata[k]}}`);
361
+ }
362
+ }
363
+ lines.push("");
364
+ lines.push(bodyContent.trim());
365
+ return lines.join("\n");
366
+ }
367
+
368
+ // src/parser/transpose.ts
369
+ var NOTE_TO_VAL = {
370
+ C: 0,
371
+ "C#": 1,
372
+ DB: 1,
373
+ D: 2,
374
+ "D#": 3,
375
+ EB: 3,
376
+ E: 4,
377
+ F: 5,
378
+ "F#": 6,
379
+ GB: 6,
380
+ G: 7,
381
+ "G#": 8,
382
+ AB: 8,
383
+ A: 9,
384
+ "A#": 10,
385
+ BB: 10,
386
+ B: 11,
387
+ DO: 0,
388
+ RE: 2,
389
+ R\u00C9: 2,
390
+ MI: 4,
391
+ FA: 5,
392
+ F\u00C1: 5,
393
+ SOL: 7,
394
+ LA: 9,
395
+ L\u00C1: 9,
396
+ SI: 11
397
+ };
398
+ var SHARPS = [
399
+ "C",
400
+ "C#",
401
+ "D",
402
+ "D#",
403
+ "E",
404
+ "F",
405
+ "F#",
406
+ "G",
407
+ "G#",
408
+ "A",
409
+ "A#",
410
+ "B"
411
+ ];
412
+ var FLATS = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
413
+ function getNoteValue(note) {
414
+ if (!note) return void 0;
415
+ const match = note.match(
416
+ /^([A-G][#b]?|Do|Ré|Mi|Fá|Sol|Lá|Si|DO|RE|RÉ|MI|FA|FÁ|SOL|LA|LÁ|SI)/i
417
+ );
418
+ if (!match) return void 0;
419
+ return NOTE_TO_VAL[match[1].toUpperCase()];
420
+ }
421
+ function getSuggestedCapo(originalKey, transposeVal) {
422
+ const baseKeyVal = originalKey ? getNoteValue(originalKey) : 0;
423
+ const keyVal = baseKeyVal !== void 0 ? baseKeyVal : 0;
424
+ const soundingVal = (keyVal + transposeVal + 240) % 12;
425
+ const EASY_SHAPES = {
426
+ 0: "C",
427
+ 7: "G",
428
+ 2: "D",
429
+ 9: "A",
430
+ 4: "E"
431
+ };
432
+ const PREFERRED_OPEN_VALS = [0, 7, 2, 9, 4];
433
+ for (const targetShapeVal of PREFERRED_OPEN_VALS) {
434
+ const neededCapo = (soundingVal - targetShapeVal + 12) % 12;
435
+ if (neededCapo >= 1 && neededCapo <= 7) {
436
+ return {
437
+ capo: neededCapo,
438
+ chordShape: EASY_SHAPES[targetShapeVal]
439
+ };
440
+ }
441
+ }
442
+ return null;
443
+ }
444
+ function transposeNote(note, semitones, preferFlats = false) {
445
+ const upper = note.toUpperCase();
446
+ if (NOTE_TO_VAL[upper] === void 0) return note;
447
+ const val = NOTE_TO_VAL[upper];
448
+ const newVal = (val + semitones + 24) % 12;
449
+ const targetScale = preferFlats ? FLATS : SHARPS;
450
+ let transposed = targetScale[newVal];
451
+ if (note[0] === note[0].toLowerCase()) {
452
+ transposed = transposed.toLowerCase();
453
+ }
454
+ return transposed;
455
+ }
456
+ function transposeChord(chord, semitones) {
457
+ if (!chord || semitones === 0) return chord;
458
+ if (chord.includes("/")) {
459
+ return chord.split("/").map((part) => transposeChord(part.trim(), semitones)).join("/");
460
+ }
461
+ const noteRegex = /^([A-G][#b]?|Do|Ré|Mi|Fá|Sol|Lá|Si|DO|RE|RÉ|MI|FA|FÁ|SOL|LA|LÁ|SI)/;
462
+ const match = chord.match(noteRegex);
463
+ if (!match) return chord;
464
+ const note = match[1];
465
+ const suffix = chord.slice(note.length);
466
+ const preferFlats = chord.includes("b") || chord.includes("B");
467
+ const transposedNote = transposeNote(note, semitones, preferFlats);
468
+ return transposedNote + suffix;
469
+ }
470
+
471
+ // src/parser/chordDictionary.ts
472
+ var NOTE_ALIASES = {
473
+ C: 0,
474
+ "B#": 0,
475
+ Do: 0,
476
+ DO: 0,
477
+ "C#": 1,
478
+ Db: 1,
479
+ D: 2,
480
+ Re: 2,
481
+ RE: 2,
482
+ R\u00E9: 2,
483
+ R\u00C9: 2,
484
+ "D#": 3,
485
+ Eb: 3,
486
+ E: 4,
487
+ Fb: 4,
488
+ Mi: 4,
489
+ MI: 4,
490
+ F: 5,
491
+ "E#": 5,
492
+ Fa: 5,
493
+ FA: 5,
494
+ F\u00E1: 5,
495
+ F\u00C1: 5,
496
+ "F#": 6,
497
+ Gb: 6,
498
+ G: 7,
499
+ Sol: 7,
500
+ SOL: 7,
501
+ "G#": 8,
502
+ Ab: 8,
503
+ A: 9,
504
+ La: 9,
505
+ LA: 9,
506
+ L\u00E1: 9,
507
+ L\u00C1: 9,
508
+ "A#": 10,
509
+ Bb: 10,
510
+ B: 11,
511
+ Cb: 11,
512
+ Si: 11,
513
+ SI: 11
514
+ };
515
+ var ROOT_KEYS = Object.keys(NOTE_ALIASES).sort((a, b) => b.length - a.length);
516
+ var ROOT_PATTERN = new RegExp(`^(${ROOT_KEYS.join("|")})`, "i");
517
+ var SEMITONE_NAMES = [
518
+ "C",
519
+ "C#",
520
+ "D",
521
+ "D#",
522
+ "E",
523
+ "F",
524
+ "F#",
525
+ "G",
526
+ "G#",
527
+ "A",
528
+ "A#",
529
+ "B"
530
+ ];
531
+ function resolveRootSemitone(raw) {
532
+ if (raw in NOTE_ALIASES) return NOTE_ALIASES[raw];
533
+ const titleCase = raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase();
534
+ if (titleCase in NOTE_ALIASES) return NOTE_ALIASES[titleCase];
535
+ const upper = raw.toUpperCase();
536
+ if (upper in NOTE_ALIASES) return NOTE_ALIASES[upper];
537
+ return null;
538
+ }
539
+ function pitchClassName(semitone) {
540
+ return SEMITONE_NAMES[(semitone % 12 + 12) % 12];
541
+ }
542
+ var CHORD_QUALITIES = [
543
+ {
544
+ id: "major",
545
+ label: "Major",
546
+ intervals: [0, 4, 7],
547
+ aliases: ["", "M", "maj", "Maj", "MAJ"]
548
+ },
549
+ {
550
+ id: "minor",
551
+ label: "Minor",
552
+ intervals: [0, 3, 7],
553
+ aliases: ["m", "min", "Min", "MIN", "-"]
554
+ },
555
+ {
556
+ id: "dim",
557
+ label: "Diminished",
558
+ intervals: [0, 3, 6],
559
+ aliases: ["dim", "o", "\xB0"]
560
+ },
561
+ {
562
+ id: "aug",
563
+ label: "Augmented",
564
+ intervals: [0, 4, 8],
565
+ aliases: ["aug", "+"]
566
+ },
567
+ {
568
+ id: "sus2",
569
+ label: "Suspended 2nd",
570
+ intervals: [0, 2, 7],
571
+ aliases: ["sus2"]
572
+ },
573
+ {
574
+ id: "sus4",
575
+ label: "Suspended 4th",
576
+ intervals: [0, 5, 7],
577
+ aliases: ["sus4", "sus"]
578
+ },
579
+ { id: "five", label: "Power chord", intervals: [0, 7], aliases: ["5"] },
580
+ { id: "six", label: "6th", intervals: [0, 4, 7, 9], aliases: ["6"] },
581
+ {
582
+ id: "m6",
583
+ label: "Minor 6th",
584
+ intervals: [0, 3, 7, 9],
585
+ aliases: ["m6", "min6"]
586
+ },
587
+ {
588
+ id: "six9",
589
+ label: "6/9",
590
+ intervals: [0, 4, 7, 9, 14],
591
+ aliases: ["6/9", "69"]
592
+ },
593
+ {
594
+ id: "dom7",
595
+ label: "Dominant 7th",
596
+ intervals: [0, 4, 7, 10],
597
+ aliases: ["7"]
598
+ },
599
+ {
600
+ id: "maj7",
601
+ label: "Major 7th",
602
+ intervals: [0, 4, 7, 11],
603
+ aliases: ["maj7", "Maj7", "MAJ7", "M7", "\u0394", "\u03947"]
604
+ },
605
+ {
606
+ id: "m7",
607
+ label: "Minor 7th",
608
+ intervals: [0, 3, 7, 10],
609
+ aliases: ["m7", "min7", "Min7"]
610
+ },
611
+ {
612
+ id: "mMaj7",
613
+ label: "Minor Major 7th",
614
+ intervals: [0, 3, 7, 11],
615
+ aliases: ["mMaj7", "m(maj7)", "mM7", "minMaj7"]
616
+ },
617
+ {
618
+ id: "m7b5",
619
+ label: "Half-diminished 7th",
620
+ intervals: [0, 3, 6, 10],
621
+ aliases: ["m7b5", "m7-5", "\xF8", "\xF87"]
622
+ },
623
+ {
624
+ id: "dim7",
625
+ label: "Diminished 7th",
626
+ intervals: [0, 3, 6, 9],
627
+ aliases: ["dim7", "o7", "\xB07"]
628
+ },
629
+ {
630
+ id: "aug7",
631
+ label: "7#5",
632
+ intervals: [0, 4, 8, 10],
633
+ aliases: ["7#5", "aug7"]
634
+ },
635
+ { id: "dom7b5", label: "7b5", intervals: [0, 4, 6, 10], aliases: ["7b5"] },
636
+ {
637
+ id: "dom7sus4",
638
+ label: "7sus4",
639
+ intervals: [0, 5, 7, 10],
640
+ aliases: ["7sus4"]
641
+ },
642
+ {
643
+ id: "dom7sus2",
644
+ label: "7sus2",
645
+ intervals: [0, 2, 7, 10],
646
+ aliases: ["7sus2"]
647
+ },
648
+ { id: "nine", label: "9th", intervals: [0, 4, 7, 10, 14], aliases: ["9"] },
649
+ {
650
+ id: "maj9",
651
+ label: "Major 9th",
652
+ intervals: [0, 4, 7, 11, 14],
653
+ aliases: ["maj9", "Maj9", "M9"]
654
+ },
655
+ {
656
+ id: "m9",
657
+ label: "Minor 9th",
658
+ intervals: [0, 3, 7, 10, 14],
659
+ aliases: ["m9", "min9"]
660
+ },
661
+ { id: "add9", label: "Add 9", intervals: [0, 4, 7, 14], aliases: ["add9"] },
662
+ {
663
+ id: "madd9",
664
+ label: "Minor Add 9",
665
+ intervals: [0, 3, 7, 14],
666
+ aliases: ["madd9", "minAdd9"]
667
+ },
668
+ {
669
+ id: "eleven",
670
+ label: "11th",
671
+ intervals: [0, 4, 7, 10, 14, 17],
672
+ aliases: ["11"]
673
+ },
674
+ {
675
+ id: "m11",
676
+ label: "Minor 11th",
677
+ intervals: [0, 3, 7, 10, 14, 17],
678
+ aliases: ["m11"]
679
+ },
680
+ {
681
+ id: "thirteen",
682
+ label: "13th",
683
+ intervals: [0, 4, 7, 10, 14, 17, 21],
684
+ aliases: ["13"]
685
+ },
686
+ {
687
+ id: "m13",
688
+ label: "Minor 13th",
689
+ intervals: [0, 3, 7, 10, 14, 17, 21],
690
+ aliases: ["m13"]
691
+ },
692
+ {
693
+ id: "dom7sharp9",
694
+ label: "7#9",
695
+ intervals: [0, 4, 7, 10, 15],
696
+ aliases: ["7#9"]
697
+ },
698
+ {
699
+ id: "dom7flat9",
700
+ label: "7b9",
701
+ intervals: [0, 4, 7, 10, 13],
702
+ aliases: ["7b9"]
703
+ },
704
+ {
705
+ id: "maj7sharp5",
706
+ label: "maj7#5",
707
+ intervals: [0, 4, 8, 11],
708
+ aliases: ["maj7#5"]
709
+ },
710
+ {
711
+ id: "maj7flat5",
712
+ label: "maj7b5",
713
+ intervals: [0, 4, 6, 11],
714
+ aliases: ["maj7b5"]
715
+ }
716
+ ];
717
+ var QUALITY_BY_ID = Object.fromEntries(
718
+ CHORD_QUALITIES.map((q) => [q.id, q])
719
+ );
720
+ var QUALITY_ALIAS_TABLE = CHORD_QUALITIES.flatMap(
721
+ (quality) => quality.aliases.map((alias) => ({ alias, quality }))
722
+ ).sort((a, b) => b.alias.length - a.alias.length);
723
+ function resolveQuality(qualitySymbol) {
724
+ if (qualitySymbol === "") return QUALITY_BY_ID.major;
725
+ const exact = QUALITY_ALIAS_TABLE.find(
726
+ (e) => e.alias !== "" && e.alias === qualitySymbol
727
+ );
728
+ if (exact) return exact.quality;
729
+ const lower = qualitySymbol.toLowerCase();
730
+ const loose = QUALITY_ALIAS_TABLE.find(
731
+ (e) => e.alias !== "" && e.alias.toLowerCase() === lower
732
+ );
733
+ if (loose) return loose.quality;
734
+ return QUALITY_BY_ID.major;
735
+ }
736
+ var QUALITY_SIMPLIFICATION = {
737
+ dim: "minor",
738
+ dim7: "minor",
739
+ aug: "major",
740
+ six: "major",
741
+ m6: "minor",
742
+ six9: "major",
743
+ mMaj7: "m7",
744
+ m7b5: "m7",
745
+ aug7: "dom7",
746
+ dom7b5: "dom7",
747
+ dom7sus4: "sus4",
748
+ dom7sus2: "sus2",
749
+ nine: "dom7",
750
+ maj9: "maj7",
751
+ m9: "m7",
752
+ add9: "major",
753
+ madd9: "minor",
754
+ eleven: "dom7",
755
+ m11: "m7",
756
+ thirteen: "dom7",
757
+ m13: "m7",
758
+ dom7sharp9: "dom7",
759
+ dom7flat9: "dom7",
760
+ maj7sharp5: "maj7",
761
+ maj7flat5: "maj7"
762
+ };
763
+ var SLASH_CONTAINING_ALIASES = QUALITY_ALIAS_TABLE.filter(
764
+ (e) => e.alias.includes("/")
765
+ ).sort((a, b) => b.alias.length - a.alias.length);
766
+ function parseChordSymbol(chord) {
767
+ const cleaned = chord.replace(/[()]/g, "").trim();
768
+ if (!cleaned) return null;
769
+ const rootMatch = cleaned.match(ROOT_PATTERN);
770
+ if (!rootMatch) return null;
771
+ const rootText = rootMatch[1];
772
+ const rootSemitone = resolveRootSemitone(rootText);
773
+ if (rootSemitone === null) return null;
774
+ const remainder = cleaned.slice(rootText.length);
775
+ const slashAlias = SLASH_CONTAINING_ALIASES.find(
776
+ (e) => remainder.startsWith(e.alias)
777
+ );
778
+ let qualitySymbol;
779
+ let bassPart;
780
+ if (slashAlias) {
781
+ qualitySymbol = slashAlias.alias;
782
+ const rest = remainder.slice(slashAlias.alias.length);
783
+ bassPart = rest.startsWith("/") ? rest.slice(1).trim() : void 0;
784
+ } else {
785
+ const slashIndex = remainder.indexOf("/");
786
+ if (slashIndex === -1) {
787
+ qualitySymbol = remainder;
788
+ } else {
789
+ qualitySymbol = remainder.slice(0, slashIndex);
790
+ bassPart = remainder.slice(slashIndex + 1).trim();
791
+ }
792
+ }
793
+ const quality = resolveQuality(qualitySymbol);
794
+ let bassSemitone;
795
+ if (bassPart) {
796
+ const bassMatch = bassPart.match(ROOT_PATTERN);
797
+ if (bassMatch) {
798
+ const resolved = resolveRootSemitone(bassMatch[1]);
799
+ if (resolved !== null) bassSemitone = resolved;
800
+ }
801
+ }
802
+ return {
803
+ raw: cleaned,
804
+ rootSemitone,
805
+ rootDisplay: pitchClassName(rootSemitone),
806
+ quality,
807
+ bassSemitone
808
+ };
809
+ }
810
+ function computePianoVoicing(rootSemitone, intervals, bassSemitone) {
811
+ const rootPc = (rootSemitone % 12 + 12) % 12;
812
+ if (bassSemitone !== void 0) {
813
+ const bassPc = (bassSemitone % 12 + 12) % 12;
814
+ const notes2 = [pitchClassName(bassPc)];
815
+ const highlightKeys2 = [bassPc];
816
+ for (const iv of intervals) {
817
+ const key = (rootPc + iv) % 12 + 12;
818
+ const name = pitchClassName(rootPc + iv);
819
+ highlightKeys2.push(key);
820
+ if (!notes2.includes(name)) notes2.push(name);
821
+ }
822
+ return { notes: notes2, highlightKeys: highlightKeys2 };
823
+ }
824
+ const notes = intervals.map((iv) => pitchClassName(rootPc + iv));
825
+ const highlightKeys = intervals.map((iv) => (rootPc + iv) % 24);
826
+ return { notes, highlightKeys };
827
+ }
828
+ var OPEN_CHORD_SHAPES = {
829
+ C: { frets: [-1, 3, 2, 0, 1, 0], fingers: [0, 3, 2, 0, 1, 0] },
830
+ Cm: { frets: [-1, 3, 5, 5, 4, 3], fingers: [0, 1, 3, 4, 2, 1], barre: 3 },
831
+ C7: { frets: [-1, 3, 2, 3, 1, 0], fingers: [0, 3, 2, 4, 1, 0] },
832
+ Cmaj7: { frets: [-1, 3, 2, 0, 0, 0], fingers: [0, 3, 2, 0, 0, 0] },
833
+ Cm7: { frets: [-1, 3, 5, 3, 4, 3], fingers: [0, 1, 3, 1, 2, 1], barre: 3 },
834
+ Csus4: { frets: [-1, 3, 3, 0, 1, 1], fingers: [0, 3, 4, 0, 1, 1] },
835
+ Csus2: { frets: [-1, 3, 0, 0, 1, 3], fingers: [0, 2, 0, 0, 1, 4] },
836
+ Cadd9: { frets: [-1, 3, 2, 0, 3, 0], fingers: [0, 2, 1, 0, 3, 0] },
837
+ C9: { frets: [-1, 3, 2, 3, 3, 3], fingers: [0, 2, 1, 3, 3, 3], barre: 3 },
838
+ C6: { frets: [-1, 3, 2, 2, 1, 0], fingers: [0, 4, 2, 3, 1, 0] },
839
+ "C#": { frets: [-1, 4, 6, 6, 6, 4], fingers: [0, 1, 2, 3, 4, 1], barre: 4 },
840
+ "C#m": {
841
+ frets: [-1, 4, 6, 6, 5, 4],
842
+ fingers: [0, 1, 3, 4, 2, 1],
843
+ barre: 4
844
+ },
845
+ "C#7": { frets: [-1, 4, 3, 4, 2, -1], fingers: [0, 3, 2, 4, 1, 0] },
846
+ "C#maj7": {
847
+ frets: [-1, 4, 6, 5, 6, 4],
848
+ fingers: [0, 1, 3, 2, 4, 1],
849
+ barre: 4
850
+ },
851
+ "C#m7": {
852
+ frets: [-1, 4, 6, 4, 5, 4],
853
+ fingers: [0, 1, 3, 1, 2, 1],
854
+ barre: 4
855
+ },
856
+ D: { frets: [-1, -1, 0, 2, 3, 2], fingers: [0, 0, 0, 1, 3, 2] },
857
+ Dm: { frets: [-1, -1, 0, 2, 3, 1], fingers: [0, 0, 0, 2, 3, 1] },
858
+ D7: { frets: [-1, -1, 0, 2, 1, 2], fingers: [0, 0, 0, 2, 1, 3] },
859
+ Dmaj7: {
860
+ frets: [-1, -1, 0, 2, 2, 2],
861
+ fingers: [0, 0, 0, 1, 1, 1],
862
+ barre: 2
863
+ },
864
+ Dm7: { frets: [-1, -1, 0, 2, 1, 1], fingers: [0, 0, 0, 2, 1, 1], barre: 1 },
865
+ Dsus4: { frets: [-1, -1, 0, 2, 3, 3], fingers: [0, 0, 0, 1, 2, 3] },
866
+ Dsus2: { frets: [-1, -1, 0, 2, 3, 0], fingers: [0, 0, 0, 1, 2, 0] },
867
+ Dadd9: { frets: [-1, -1, 0, 2, 5, 2], fingers: [0, 0, 0, 1, 4, 2] },
868
+ D6: { frets: [-1, -1, 0, 2, 0, 2], fingers: [0, 0, 0, 2, 0, 3] },
869
+ D9: { frets: [-1, -1, 0, 2, 1, 0], fingers: [0, 0, 0, 2, 1, 0] },
870
+ Eb: { frets: [-1, 6, 8, 8, 8, 6], fingers: [0, 1, 2, 3, 4, 1], barre: 6 },
871
+ Ebm: { frets: [-1, 6, 8, 8, 7, 6], fingers: [0, 1, 3, 4, 2, 1], barre: 6 },
872
+ Eb7: { frets: [-1, 6, 5, 6, 4, -1], fingers: [0, 3, 2, 4, 1, 0] },
873
+ E: { frets: [0, 2, 2, 1, 0, 0], fingers: [0, 2, 3, 1, 0, 0] },
874
+ Em: { frets: [0, 2, 2, 0, 0, 0], fingers: [0, 2, 3, 0, 0, 0] },
875
+ E7: { frets: [0, 2, 0, 1, 0, 0], fingers: [0, 2, 0, 1, 0, 0] },
876
+ Emaj7: { frets: [0, 2, 1, 1, 0, 0], fingers: [0, 3, 1, 2, 0, 0] },
877
+ Em7: { frets: [0, 2, 0, 0, 0, 0], fingers: [0, 2, 0, 0, 0, 0] },
878
+ Esus4: { frets: [0, 2, 2, 2, 0, 0], fingers: [0, 2, 3, 4, 0, 0] },
879
+ Eadd9: { frets: [0, 2, 4, 1, 0, 0], fingers: [0, 2, 4, 1, 0, 0] },
880
+ E6: { frets: [0, 2, 2, 1, 2, 0], fingers: [0, 2, 3, 1, 4, 0] },
881
+ E9: { frets: [0, 2, 0, 1, 3, 0], fingers: [0, 2, 0, 1, 4, 0] },
882
+ F: { frets: [1, 3, 3, 2, 1, 1], fingers: [1, 3, 4, 2, 1, 1], barre: 1 },
883
+ Fm: { frets: [1, 3, 3, 1, 1, 1], fingers: [1, 3, 4, 1, 1, 1], barre: 1 },
884
+ F7: { frets: [1, 3, 1, 2, 1, 1], fingers: [1, 3, 1, 2, 1, 1], barre: 1 },
885
+ Fmaj7: { frets: [-1, 3, 3, 2, 1, 0], fingers: [0, 3, 4, 2, 1, 0] },
886
+ Fm7: { frets: [1, 3, 1, 1, 1, 1], fingers: [1, 3, 1, 1, 1, 1], barre: 1 },
887
+ "F#": { frets: [2, 4, 4, 3, 2, 2], fingers: [1, 3, 4, 2, 1, 1], barre: 2 },
888
+ "F#m": { frets: [2, 4, 4, 2, 2, 2], fingers: [1, 3, 4, 1, 1, 1], barre: 2 },
889
+ "F#7": { frets: [2, 4, 2, 3, 2, 2], fingers: [1, 3, 1, 2, 1, 1], barre: 2 },
890
+ "F#m7": {
891
+ frets: [2, 4, 2, 2, 2, 2],
892
+ fingers: [1, 3, 1, 1, 1, 1],
893
+ barre: 2
894
+ },
895
+ G: { frets: [3, 2, 0, 0, 3, 3], fingers: [2, 1, 0, 0, 3, 4] },
896
+ Gm: { frets: [3, 5, 5, 3, 3, 3], fingers: [1, 3, 4, 1, 1, 1], barre: 3 },
897
+ G7: { frets: [3, 2, 0, 0, 0, 1], fingers: [3, 2, 0, 0, 0, 1] },
898
+ Gmaj7: { frets: [3, 2, 0, 0, 0, 2], fingers: [2, 1, 0, 0, 0, 3] },
899
+ Gm7: { frets: [3, 5, 3, 3, 3, 3], fingers: [1, 3, 1, 1, 1, 1], barre: 3 },
900
+ Gsus4: { frets: [3, 3, 0, 0, 3, 3], fingers: [2, 3, 0, 0, 1, 4] },
901
+ Gadd9: { frets: [3, 2, 0, 2, 0, 3], fingers: [2, 1, 0, 3, 0, 4] },
902
+ G6: { frets: [3, 2, 0, 0, 0, 0], fingers: [3, 2, 0, 0, 0, 0] },
903
+ Ab: { frets: [4, 6, 6, 5, 4, 4], fingers: [1, 3, 4, 2, 1, 1], barre: 4 },
904
+ Abm: { frets: [4, 6, 6, 4, 4, 4], fingers: [1, 3, 4, 1, 1, 1], barre: 4 },
905
+ A: { frets: [-1, 0, 2, 2, 2, 0], fingers: [0, 0, 1, 2, 3, 0] },
906
+ Am: { frets: [-1, 0, 2, 2, 1, 0], fingers: [0, 0, 2, 3, 1, 0] },
907
+ A7: { frets: [-1, 0, 2, 0, 2, 0], fingers: [0, 0, 1, 0, 2, 0] },
908
+ Amaj7: { frets: [-1, 0, 2, 1, 2, 0], fingers: [0, 0, 2, 1, 3, 0] },
909
+ Am7: { frets: [-1, 0, 2, 0, 1, 0], fingers: [0, 0, 2, 0, 1, 0] },
910
+ Asus4: { frets: [-1, 0, 2, 2, 3, 0], fingers: [0, 0, 1, 2, 4, 0] },
911
+ Asus2: { frets: [-1, 0, 2, 2, 0, 0], fingers: [0, 0, 1, 2, 0, 0] },
912
+ Aadd9: { frets: [-1, 0, 2, 4, 2, 0], fingers: [0, 0, 1, 3, 2, 0] },
913
+ A6: { frets: [-1, 0, 2, 2, 2, 2], fingers: [0, 0, 1, 1, 1, 1], barre: 2 },
914
+ A9: { frets: [-1, 0, 2, 4, 2, 3], fingers: [0, 0, 1, 3, 2, 4] },
915
+ Bb: { frets: [-1, 1, 3, 3, 3, 1], fingers: [0, 1, 2, 3, 4, 1], barre: 1 },
916
+ Bbm: { frets: [-1, 1, 3, 3, 2, 1], fingers: [0, 1, 3, 4, 2, 1], barre: 1 },
917
+ Bb7: { frets: [-1, 1, 3, 1, 3, 1], fingers: [0, 1, 3, 1, 4, 1], barre: 1 },
918
+ B: { frets: [-1, 2, 4, 4, 4, 2], fingers: [0, 1, 2, 3, 4, 1], barre: 2 },
919
+ Bm: { frets: [-1, 2, 4, 4, 3, 2], fingers: [0, 1, 3, 4, 2, 1], barre: 2 },
920
+ B7: { frets: [-1, 2, 1, 2, 0, 2], fingers: [0, 2, 1, 3, 0, 4] },
921
+ Bmaj7: {
922
+ frets: [-1, 2, 4, 3, 4, 2],
923
+ fingers: [0, 1, 3, 2, 4, 1],
924
+ barre: 2
925
+ },
926
+ Bm7: { frets: [-1, 2, 4, 2, 3, 2], fingers: [0, 1, 3, 1, 2, 1], barre: 2 }
927
+ };
928
+ var SLASH_CHORD_SHAPES = {
929
+ "C/E": { frets: [0, 3, 2, 0, 1, 0], fingers: [0, 3, 2, 0, 1, 0] },
930
+ "C/G": { frets: [3, 3, 2, 0, 1, 0], fingers: [3, 4, 2, 0, 1, 0] },
931
+ "C/Bb": { frets: [-1, 3, 2, 3, 1, 0], fingers: [0, 3, 2, 4, 1, 0] },
932
+ "D/F#": { frets: [2, 0, 0, 2, 3, 2], fingers: [1, 0, 0, 2, 4, 3] },
933
+ "D/A": { frets: [-1, 0, 0, 2, 3, 2], fingers: [0, 0, 0, 1, 3, 2] },
934
+ "E/G#": { frets: [4, 2, 2, 1, 0, 0], fingers: [4, 2, 3, 1, 0, 0] },
935
+ "E/B": { frets: [0, 2, 2, 1, 0, 0], fingers: [0, 2, 3, 1, 0, 0] },
936
+ "F/A": { frets: [-1, 0, 3, 2, 1, 1], fingers: [0, 0, 3, 2, 1, 1] },
937
+ "F/C": {
938
+ frets: [8, 8, 10, 10, 10, 8],
939
+ fingers: [1, 1, 2, 3, 4, 1],
940
+ barre: 8
941
+ },
942
+ "G/B": { frets: [-1, 2, 0, 0, 3, 3], fingers: [0, 1, 0, 0, 3, 4] },
943
+ "G/D": { frets: [-1, -1, 0, 0, 3, 3], fingers: [0, 0, 0, 0, 3, 4] },
944
+ "G/F": { frets: [3, 2, 0, 0, 0, 1], fingers: [3, 2, 0, 0, 0, 1] },
945
+ "A/C#": { frets: [-1, 4, 2, 2, 2, -1], fingers: [0, 4, 1, 1, 1, 0] },
946
+ "A/E": { frets: [0, 0, 2, 2, 2, 0], fingers: [0, 0, 1, 2, 3, 0] },
947
+ "A/G": { frets: [3, 0, 2, 2, 2, 0], fingers: [4, 0, 1, 2, 3, 0] },
948
+ "B/D#": { frets: [-1, 6, 4, 4, 4, -1], fingers: [0, 3, 1, 1, 1, 0] },
949
+ "B/F#": {
950
+ frets: [2, 2, 4, 4, 4, 2],
951
+ fingers: [1, 1, 2, 3, 4, 1],
952
+ barre: 2
953
+ },
954
+ "Am/G": { frets: [3, 0, 2, 2, 1, 0], fingers: [4, 0, 2, 3, 1, 0] },
955
+ "Am/F#": { frets: [2, 0, 2, 2, 1, 0], fingers: [2, 0, 3, 4, 1, 0] },
956
+ "Am/E": { frets: [0, 0, 2, 2, 1, 0], fingers: [0, 0, 2, 3, 1, 0] },
957
+ "Dm/C": { frets: [-1, 3, 0, 2, 3, 1], fingers: [0, 3, 0, 2, 4, 1] },
958
+ "Dm/B": { frets: [-1, 2, 0, 2, 3, 1], fingers: [0, 2, 0, 3, 4, 1] },
959
+ "Dm/A": { frets: [-1, 0, 0, 2, 3, 1], fingers: [0, 0, 0, 2, 3, 1] },
960
+ "Dm/F": { frets: [1, -1, 0, 2, 3, 1], fingers: [1, 0, 0, 2, 4, 3] },
961
+ "Em/D": { frets: [0, 2, 0, 0, 0, 0], fingers: [0, 2, 0, 0, 0, 0] },
962
+ "Em/C#": { frets: [0, 4, 2, 0, 0, 0], fingers: [0, 3, 1, 0, 0, 0] },
963
+ "Em/B": {
964
+ frets: [7, 7, 9, 9, 8, 7],
965
+ fingers: [1, 1, 3, 4, 2, 1],
966
+ barre: 7
967
+ },
968
+ "Em/G": { frets: [3, 2, 2, 0, 0, 0], fingers: [3, 1, 2, 0, 0, 0] },
969
+ "Fm/Eb": { frets: [-1, 6, 6, 5, 6, -1], fingers: [0, 2, 3, 1, 4, 0] },
970
+ "Gm/F": {
971
+ frets: [3, 5, 3, 3, 3, 3],
972
+ fingers: [1, 3, 1, 1, 1, 1],
973
+ barre: 3
974
+ },
975
+ "Bm/A": { frets: [-1, 0, 4, 4, 3, 2], fingers: [0, 0, 3, 4, 2, 1] }
976
+ };
977
+ var CANONICAL_SUFFIX = {
978
+ major: "",
979
+ minor: "m",
980
+ dom7: "7",
981
+ maj7: "maj7",
982
+ m7: "m7",
983
+ sus4: "sus4",
984
+ sus2: "sus2",
985
+ six: "6",
986
+ add9: "add9",
987
+ nine: "9"
988
+ };
989
+ var E_FORM = {
990
+ major: {
991
+ formRootSemitone: 4,
992
+ frets: [0, 2, 2, 1, 0, 0],
993
+ openFingers: [0, 2, 3, 1, 0, 0],
994
+ barreFingers: [1, 3, 4, 2, 1, 1]
995
+ },
996
+ minor: {
997
+ formRootSemitone: 4,
998
+ frets: [0, 2, 2, 0, 0, 0],
999
+ openFingers: [0, 2, 3, 0, 0, 0],
1000
+ barreFingers: [1, 3, 4, 1, 1, 1]
1001
+ },
1002
+ dom7: {
1003
+ formRootSemitone: 4,
1004
+ frets: [0, 2, 0, 1, 0, 0],
1005
+ openFingers: [0, 2, 0, 1, 0, 0],
1006
+ barreFingers: [1, 3, 1, 2, 1, 1]
1007
+ },
1008
+ m7: {
1009
+ formRootSemitone: 4,
1010
+ frets: [0, 2, 0, 0, 0, 0],
1011
+ openFingers: [0, 2, 0, 0, 0, 0],
1012
+ barreFingers: [1, 3, 1, 1, 1, 1]
1013
+ },
1014
+ maj7: {
1015
+ formRootSemitone: 4,
1016
+ frets: [0, 2, 1, 1, 0, 0],
1017
+ openFingers: [0, 3, 1, 2, 0, 0],
1018
+ barreFingers: [1, 3, 2, 2, 1, 1]
1019
+ },
1020
+ sus4: {
1021
+ formRootSemitone: 4,
1022
+ frets: [0, 2, 2, 2, 0, 0],
1023
+ openFingers: [0, 2, 3, 4, 0, 0],
1024
+ barreFingers: [1, 3, 4, 4, 1, 1]
1025
+ }
1026
+ };
1027
+ var A_FORM = {
1028
+ major: {
1029
+ formRootSemitone: 9,
1030
+ frets: [-1, 0, 2, 2, 2, 0],
1031
+ openFingers: [0, 0, 1, 2, 3, 0],
1032
+ barreFingers: [0, 1, 3, 4, 4, 1]
1033
+ },
1034
+ minor: {
1035
+ formRootSemitone: 9,
1036
+ frets: [-1, 0, 2, 2, 1, 0],
1037
+ openFingers: [0, 0, 2, 3, 1, 0],
1038
+ barreFingers: [0, 1, 3, 4, 2, 1]
1039
+ },
1040
+ dom7: {
1041
+ formRootSemitone: 9,
1042
+ frets: [-1, 0, 2, 0, 2, 0],
1043
+ openFingers: [0, 0, 1, 0, 2, 0],
1044
+ barreFingers: [0, 1, 3, 1, 4, 1]
1045
+ },
1046
+ m7: {
1047
+ formRootSemitone: 9,
1048
+ frets: [-1, 0, 2, 0, 1, 0],
1049
+ openFingers: [0, 0, 2, 0, 1, 0],
1050
+ barreFingers: [0, 1, 3, 1, 2, 1]
1051
+ },
1052
+ maj7: {
1053
+ formRootSemitone: 9,
1054
+ frets: [-1, 0, 2, 1, 2, 0],
1055
+ openFingers: [0, 0, 2, 1, 3, 0],
1056
+ barreFingers: [0, 1, 3, 2, 4, 1]
1057
+ },
1058
+ sus4: {
1059
+ formRootSemitone: 9,
1060
+ frets: [-1, 0, 2, 2, 3, 0],
1061
+ openFingers: [0, 0, 1, 2, 4, 0],
1062
+ barreFingers: [0, 1, 3, 3, 4, 1]
1063
+ },
1064
+ sus2: {
1065
+ formRootSemitone: 9,
1066
+ frets: [-1, 0, 2, 2, 0, 0],
1067
+ openFingers: [0, 0, 1, 2, 0, 0],
1068
+ barreFingers: [0, 1, 3, 4, 1, 1]
1069
+ }
1070
+ };
1071
+ function realizeTemplate(t, targetSemitone) {
1072
+ const shift = ((targetSemitone - t.formRootSemitone) % 12 + 12) % 12;
1073
+ const frets = t.frets.map((f) => f < 0 ? f : f + shift);
1074
+ if (shift === 0) return { frets, fingers: t.openFingers ?? t.barreFingers };
1075
+ return { frets, fingers: t.barreFingers, barre: shift };
1076
+ }
1077
+ function maxFret(shape) {
1078
+ return Math.max(0, ...shape.frets.filter((f) => f >= 0));
1079
+ }
1080
+ function templateFingering(qualityId, targetSemitone) {
1081
+ const e = E_FORM[qualityId];
1082
+ const a = A_FORM[qualityId];
1083
+ if (!e && !a) return null;
1084
+ if (e && !a) return realizeTemplate(e, targetSemitone);
1085
+ if (a && !e) return realizeTemplate(a, targetSemitone);
1086
+ const eShape = realizeTemplate(e, targetSemitone);
1087
+ const aShape = realizeTemplate(a, targetSemitone);
1088
+ return maxFret(aShape) <= maxFret(eShape) ? aShape : eShape;
1089
+ }
1090
+ function powerChordShape(targetSemitone) {
1091
+ const shift = ((targetSemitone - 4) % 12 + 12) % 12;
1092
+ return {
1093
+ frets: [shift, shift + 2, shift + 2, -1, -1, -1],
1094
+ fingers: [1, 3, 4, 0, 0, 0]
1095
+ };
1096
+ }
1097
+ var TEMPLATE_QUALITIES = /* @__PURE__ */ new Set([
1098
+ "major",
1099
+ "minor",
1100
+ "dom7",
1101
+ "m7",
1102
+ "maj7",
1103
+ "sus4",
1104
+ "sus2"
1105
+ ]);
1106
+ function getGuitarFingering(parsed) {
1107
+ const { rootSemitone, quality, raw, rootDisplay } = parsed;
1108
+ if (SLASH_CHORD_SHAPES[raw])
1109
+ return { shape: SLASH_CHORD_SHAPES[raw], approximate: false };
1110
+ if (OPEN_CHORD_SHAPES[raw])
1111
+ return { shape: OPEN_CHORD_SHAPES[raw], approximate: false };
1112
+ const suffix = CANONICAL_SUFFIX[quality.id];
1113
+ if (suffix !== void 0) {
1114
+ const canonical = rootDisplay + suffix;
1115
+ if (OPEN_CHORD_SHAPES[canonical])
1116
+ return { shape: OPEN_CHORD_SHAPES[canonical], approximate: false };
1117
+ }
1118
+ if (quality.id === "five")
1119
+ return { shape: powerChordShape(rootSemitone), approximate: false };
1120
+ if (TEMPLATE_QUALITIES.has(quality.id)) {
1121
+ const shape = templateFingering(quality.id, rootSemitone);
1122
+ if (shape) return { shape, approximate: false };
1123
+ }
1124
+ let fallbackId = QUALITY_SIMPLIFICATION[quality.id];
1125
+ let hops = 0;
1126
+ while (fallbackId && hops < 4) {
1127
+ if (TEMPLATE_QUALITIES.has(fallbackId)) {
1128
+ const shape = templateFingering(fallbackId, rootSemitone);
1129
+ if (shape) return { shape, approximate: true };
1130
+ }
1131
+ const suf = CANONICAL_SUFFIX[fallbackId];
1132
+ if (suf !== void 0) {
1133
+ const canonical = rootDisplay + suf;
1134
+ if (OPEN_CHORD_SHAPES[canonical])
1135
+ return {
1136
+ shape: OPEN_CHORD_SHAPES[canonical],
1137
+ approximate: true
1138
+ };
1139
+ }
1140
+ fallbackId = QUALITY_SIMPLIFICATION[fallbackId];
1141
+ hops += 1;
1142
+ }
1143
+ return null;
1144
+ }
1145
+ var DefaultChordDictionary = class {
1146
+ getFingering(chord) {
1147
+ const parsed = parseChordSymbol(chord);
1148
+ if (!parsed) return null;
1149
+ const piano = computePianoVoicing(
1150
+ parsed.rootSemitone,
1151
+ parsed.quality.intervals,
1152
+ parsed.bassSemitone
1153
+ );
1154
+ const guitar = getGuitarFingering(parsed);
1155
+ return {
1156
+ chord: parsed.raw,
1157
+ qualityId: parsed.quality.id,
1158
+ qualityLabel: parsed.quality.label,
1159
+ piano,
1160
+ guitar: guitar ? {
1161
+ frets: guitar.shape.frets,
1162
+ fingers: guitar.shape.fingers,
1163
+ barre: guitar.shape.barre,
1164
+ approximate: guitar.approximate || void 0
1165
+ } : void 0
1166
+ };
1167
+ }
1168
+ };
1169
+ var chordDictionary = new DefaultChordDictionary();
1170
+
1171
+ // src/parser/txt-to-chordpro.ts
1172
+ var DEFAULT_OPTIONS = {
1173
+ source: "auto",
1174
+ detectSections: true,
1175
+ strictChordDetection: true,
1176
+ dehyphenateSyllables: "auto",
1177
+ keepRepeatMarkers: true,
1178
+ partTagNames: { start: "start_of_part", end: "end_of_part" }
1179
+ };
1180
+ var CHORD_ROOT = "(?:[A-G]|D[o\xF3]|R[e\xE9]|Mi|F[a\xE1]|Sol|L[a\xE1]|Si)";
1181
+ var CHORD_BODY = CHORD_ROOT + "(?:#|b)?(?:maj|min|m\\(maj7\\)|mM|m7b5|dim|aug|alt|\u0394|\xB0|\xF8|M|m|\\+|-)?(?:2|4|5|6\\/9|6|7|9|11|13)?(?:[Mm+])?(?:sus(?:2|4)?)?(?:add(?:2|4|6|9|11|13))?(?:(?:omit|no)(?:3|5))?(?:\\((?:[#b]?(?:5|9|11|13)|sus(?:2|4)?|add(?:2|4|9|11|13)|alt|omit(?:3|5)|no(?:3|5))(?:[,/]\\s*[#b]?(?:5|9|11|13))*\\))*(?:[#b](?:5|9|11|13))*(?:\\/" + CHORD_ROOT + "(?:#|b)?)?";
1182
+ var CHORD_TOKEN_REGEX = new RegExp(`^${CHORD_BODY}$`, "i");
1183
+ var NO_CHORD_TOKEN_REGEX = /^N\.?C\.?$/i;
1184
+ var CHORD_SCAN_REGEX = new RegExp(
1185
+ `(?<=^|\\s)(${CHORD_BODY}|N\\.?C\\.?)(?=\\s|$)`,
1186
+ "gi"
1187
+ );
1188
+ var PUNCTUATION_TOKENS = /* @__PURE__ */ new Set([
1189
+ "|",
1190
+ "%",
1191
+ "-",
1192
+ ".",
1193
+ "...",
1194
+ "/",
1195
+ "x2",
1196
+ "x3",
1197
+ "x4",
1198
+ "x5",
1199
+ "x6",
1200
+ "x7",
1201
+ "x8",
1202
+ "2x",
1203
+ "3x",
1204
+ "4x",
1205
+ "5x",
1206
+ "6x",
1207
+ "7x",
1208
+ "8x"
1209
+ ]);
1210
+ function isValidChordToken(token) {
1211
+ const clean = token.replace(/^[(]/, "").replace(/[)]$/, "");
1212
+ return CHORD_TOKEN_REGEX.test(clean) || NO_CHORD_TOKEN_REGEX.test(clean);
1213
+ }
1214
+ function isChordLine(line, strict) {
1215
+ const trimmed = line.trim();
1216
+ if (!trimmed) return false;
1217
+ const tokens = trimmed.split(/\s+/).filter((t) => !PUNCTUATION_TOKENS.has(t.toLowerCase()));
1218
+ if (tokens.length === 0) return false;
1219
+ const validCount = tokens.filter(isValidChordToken).length;
1220
+ return strict ? validCount === tokens.length : validCount / tokens.length >= 0.8;
1221
+ }
1222
+ function isAlreadyInlineChordPro(line) {
1223
+ const matches = [...line.matchAll(/\[([^\]]+)\]/g)];
1224
+ if (matches.length === 0) return false;
1225
+ return matches.every((m) => isValidChordToken(m[1]));
1226
+ }
1227
+ var METADATA_MAP = {
1228
+ title: "title",
1229
+ t: "title",
1230
+ titulo: "title",
1231
+ t\u00EDtulo: "title",
1232
+ subtitle: "subtitle",
1233
+ st: "subtitle",
1234
+ artist: "artist",
1235
+ a: "artist",
1236
+ artista: "artist",
1237
+ interprete: "artist",
1238
+ int\u00E9rprete: "artist",
1239
+ composer: "composer",
1240
+ music: "composer",
1241
+ compositor: "composer",
1242
+ lyricist: "lyricist",
1243
+ words: "lyricist",
1244
+ letra: "lyricist",
1245
+ letrista: "lyricist",
1246
+ album: "album",
1247
+ \u00E1lbum: "album",
1248
+ key: "key",
1249
+ tom: "key",
1250
+ capo: "capo",
1251
+ capotraste: "capo",
1252
+ capodastro: "capo",
1253
+ tuning: "tuning",
1254
+ afinacao: "tuning",
1255
+ afina\u00E7\u00E3o: "tuning",
1256
+ tempo: "tempo",
1257
+ andamento: "tempo",
1258
+ bpm: "tempo",
1259
+ time: "time",
1260
+ compasso: "time",
1261
+ year: "year",
1262
+ ano: "year",
1263
+ copyright: "copyright",
1264
+ duration: "duration",
1265
+ duracao: "duration",
1266
+ dura\u00E7\u00E3o: "duration"
1267
+ };
1268
+ function stripAccents(s) {
1269
+ return s.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
1270
+ }
1271
+ var SECTION_KEYWORDS = {
1272
+ verse: ["verso", "verse"],
1273
+ chorus: ["refrao", "chorus", "coro"],
1274
+ bridge: [
1275
+ "ponte",
1276
+ "bridge",
1277
+ "pre-refrao",
1278
+ "prerefrao",
1279
+ "prechorus",
1280
+ "pre chorus",
1281
+ "pre-chorus"
1282
+ ],
1283
+ part: [
1284
+ "intro",
1285
+ "introducao",
1286
+ "introducao1",
1287
+ "outro",
1288
+ "final",
1289
+ "coda",
1290
+ "instrumental",
1291
+ "interlude",
1292
+ "interludio",
1293
+ "solo",
1294
+ "solo de guitarra",
1295
+ "solo de violao",
1296
+ "riff"
1297
+ ]
1298
+ };
1299
+ var SECTION_TAGS = {
1300
+ verse: { start: "start_of_verse", end: "end_of_verse" },
1301
+ chorus: { start: "start_of_chorus", end: "end_of_chorus" },
1302
+ bridge: { start: "start_of_bridge", end: "end_of_bridge" },
1303
+ // 'part' tag names are configurable via options.partTagNames
1304
+ part: { start: "start_of_part", end: "end_of_part" }
1305
+ };
1306
+ var BRACKET_HEADER_REGEX = /^\[([^\]]+)]$/;
1307
+ function classifySectionLabel(rawLabel) {
1308
+ let s = rawLabel.trim().replace(/:$/, "").trim();
1309
+ const base = s.replace(/\s*\d+\s*$/, "").trim();
1310
+ const normalized = stripAccents(base).toLowerCase();
1311
+ for (const kind of Object.keys(SECTION_KEYWORDS)) {
1312
+ if (SECTION_KEYWORDS[kind].some(
1313
+ (kw) => normalized === kw || normalized.startsWith(kw)
1314
+ )) {
1315
+ return { kind, label: s };
1316
+ }
1317
+ }
1318
+ return null;
1319
+ }
1320
+ var REPEAT_NOTATION_REGEX = /\s*\(?\b(\d+)\s*[xX]\b\)?\s*$|\s+[xX](\d+)\s*$/;
1321
+ function extractRepeatNotation(line) {
1322
+ const match = line.match(REPEAT_NOTATION_REGEX);
1323
+ if (match) {
1324
+ return {
1325
+ cleanLine: line.slice(0, match.index).replace(/\s+$/, ""),
1326
+ marker: match[0].trim()
1327
+ };
1328
+ }
1329
+ return { cleanLine: line, marker: null };
1330
+ }
1331
+ function countSyllableHyphens(line) {
1332
+ const matches = line.match(/[^\s-]-[^\s-]/g);
1333
+ return matches ? matches.length : 0;
1334
+ }
1335
+ function dehyphenate(line) {
1336
+ return line.replace(/([^\s-])-([^\s-])/g, "$1 $2");
1337
+ }
1338
+ function detectSourceFormat(input) {
1339
+ const lines = input.split("\n");
1340
+ let ugScore = 0;
1341
+ let ccScore = 0;
1342
+ let sawChordLine = false;
1343
+ for (const raw of lines) {
1344
+ const line = raw.trim();
1345
+ if (!line) continue;
1346
+ if (BRACKET_HEADER_REGEX.test(line)) ugScore += 2;
1347
+ if (/^(capo|tuning|key)\s*:/i.test(line)) ugScore += 2;
1348
+ if (/\s[xX]\d+\s*$/.test(raw)) ugScore += 1;
1349
+ if (/^(tom|capotraste|capodastro|int[ée]rprete|compositor|letrista)\s*:/i.test(
1350
+ line
1351
+ ))
1352
+ ccScore += 2;
1353
+ if (/^\(?\d+x\)?\s*$/i.test(line)) ccScore += 1;
1354
+ if (isChordLine(line, true)) {
1355
+ sawChordLine = true;
1356
+ if (countSyllableHyphens(line) === 0) {
1357
+ }
1358
+ } else if (countSyllableHyphens(line) >= 2) {
1359
+ ccScore += 2;
1360
+ }
1361
+ }
1362
+ if (!sawChordLine) return "plain";
1363
+ if (ccScore > ugScore) return "cifraclub";
1364
+ if (ugScore > 0) return "ultimate-guitar";
1365
+ return "plain";
1366
+ }
1367
+ function mergeChordsIntoLyric(chordLine, lyricLine, trailingMarker) {
1368
+ const matches = [...chordLine.matchAll(CHORD_SCAN_REGEX)];
1369
+ let combined = "";
1370
+ let lastIndex = 0;
1371
+ for (const match of matches) {
1372
+ const token = match[0];
1373
+ const index = match.index ?? 0;
1374
+ const rendered = NO_CHORD_TOKEN_REGEX.test(token) ? token.toUpperCase() : token;
1375
+ combined += lyricLine.substring(lastIndex, index);
1376
+ combined += `[${rendered}]`;
1377
+ lastIndex = index;
1378
+ }
1379
+ combined += lyricLine.substring(lastIndex);
1380
+ if (trailingMarker) {
1381
+ combined += ` ${trailingMarker}`;
1382
+ }
1383
+ return combined;
1384
+ }
1385
+ function classify(lines, opts, effectiveSource) {
1386
+ const result = [];
1387
+ const shouldDehyphenateGlobally = opts.dehyphenateSyllables === true || opts.dehyphenateSyllables === "auto" && effectiveSource === "cifraclub";
1388
+ const hyphenThreshold = effectiveSource === "cifraclub" ? 1 : 2;
1389
+ for (let i = 0; i < lines.length; i++) {
1390
+ const raw = lines[i].replace(/\r$/, "");
1391
+ const trimmed = raw.trim();
1392
+ if (trimmed === "") {
1393
+ result.push({ kind: "blank" });
1394
+ continue;
1395
+ }
1396
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1397
+ result.push({ kind: "directive", rendered: trimmed });
1398
+ continue;
1399
+ }
1400
+ if (isAlreadyInlineChordPro(trimmed)) {
1401
+ result.push({ kind: "inline-chordpro", rendered: raw });
1402
+ continue;
1403
+ }
1404
+ const bracketMatch = trimmed.match(BRACKET_HEADER_REGEX);
1405
+ if (bracketMatch) {
1406
+ const classified = classifySectionLabel(bracketMatch[1]);
1407
+ if (classified) {
1408
+ result.push({ kind: "section-header", section: classified });
1409
+ continue;
1410
+ }
1411
+ }
1412
+ const metaMatch = trimmed.match(/^([A-Za-zÀ-ÿ ]+):\s*(.*)/);
1413
+ if (metaMatch) {
1414
+ const key = stripAccents(metaMatch[1].toLowerCase().trim());
1415
+ const value = metaMatch[2];
1416
+ if (METADATA_MAP[key]) {
1417
+ result.push({
1418
+ kind: "metadata",
1419
+ rendered: `{${METADATA_MAP[key]}: ${value}}`
1420
+ });
1421
+ continue;
1422
+ }
1423
+ }
1424
+ if (trimmed.startsWith("#")) {
1425
+ result.push({
1426
+ kind: "comment",
1427
+ rendered: `{comment: ${trimmed.slice(1).trim()}}`
1428
+ });
1429
+ continue;
1430
+ }
1431
+ if (trimmed.length <= 28 && !isChordLine(trimmed, opts.strictChordDetection)) {
1432
+ const classified = classifySectionLabel(trimmed);
1433
+ if (classified) {
1434
+ result.push({ kind: "section-header", section: classified });
1435
+ continue;
1436
+ }
1437
+ }
1438
+ if (isChordLine(trimmed, opts.strictChordDetection)) {
1439
+ const nextRaw = i + 1 < lines.length ? lines[i + 1].replace(/\r$/, "") : null;
1440
+ const nextIsChord = nextRaw !== null && isChordLine(nextRaw.trim(), opts.strictChordDetection);
1441
+ const nextIsBlank = nextRaw !== null && nextRaw.trim() === "";
1442
+ if (nextRaw === null || nextIsChord || nextIsBlank) {
1443
+ const tokens = trimmed.split(/\s+/);
1444
+ result.push({
1445
+ kind: "chord-only",
1446
+ rendered: tokens.map(
1447
+ (t) => NO_CHORD_TOKEN_REGEX.test(t) ? `[${t.toUpperCase()}]` : `[${t}]`
1448
+ ).join(" ")
1449
+ });
1450
+ continue;
1451
+ }
1452
+ let lyricLine = nextRaw;
1453
+ let marker = null;
1454
+ if (opts.keepRepeatMarkers) {
1455
+ const extracted = extractRepeatNotation(lyricLine);
1456
+ lyricLine = extracted.cleanLine;
1457
+ marker = extracted.marker;
1458
+ }
1459
+ const hyphenCount = countSyllableHyphens(lyricLine);
1460
+ if (shouldDehyphenateGlobally && hyphenCount >= hyphenThreshold) {
1461
+ lyricLine = dehyphenate(lyricLine);
1462
+ } else if (hyphenCount >= 2 && effectiveSource !== "ultimate-guitar") {
1463
+ lyricLine = dehyphenate(lyricLine);
1464
+ }
1465
+ const merged = mergeChordsIntoLyric(raw, lyricLine, marker);
1466
+ result.push({ kind: "chord-lyric", rendered: merged });
1467
+ i++;
1468
+ continue;
1469
+ }
1470
+ result.push({ kind: "lyric", rendered: raw });
1471
+ }
1472
+ return result;
1473
+ }
1474
+ function render(lines, opts) {
1475
+ const out = [];
1476
+ let openSection = null;
1477
+ const tagsFor = (kind) => kind === "part" ? opts.partTagNames : SECTION_TAGS[kind];
1478
+ const closeSection = () => {
1479
+ if (openSection) {
1480
+ out.push(`{${tagsFor(openSection.kind).end}}`);
1481
+ openSection = null;
1482
+ }
1483
+ };
1484
+ for (const line of lines) {
1485
+ if (line.kind === "section-header" && line.section) {
1486
+ if (opts.detectSections) {
1487
+ closeSection();
1488
+ const tags = tagsFor(line.section.kind);
1489
+ out.push(`{${tags.start}: ${line.section.label}}`);
1490
+ openSection = line.section;
1491
+ } else {
1492
+ out.push(`{comment: ${line.section.label}}`);
1493
+ }
1494
+ continue;
1495
+ }
1496
+ if (line.kind === "blank") {
1497
+ if (opts.detectSections) closeSection();
1498
+ out.push("");
1499
+ continue;
1500
+ }
1501
+ out.push(line.rendered ?? "");
1502
+ }
1503
+ if (opts.detectSections) closeSection();
1504
+ return out.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
1505
+ }
1506
+ function extractTitle(chordpro) {
1507
+ const match = chordpro.match(/\{title:\s*(.*)\}/i);
1508
+ return match ? match[1].trim() : null;
1509
+ }
1510
+ function slugifyTitle(title) {
1511
+ if (!title) return "cifra_convertida";
1512
+ return stripAccents(title).trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_]/g, "") || "cifra";
1513
+ }
1514
+ function convertToChordProDetailed(input, options = {}) {
1515
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1516
+ const warnings = [];
1517
+ if (!input || !input.trim()) {
1518
+ return {
1519
+ chordpro: "",
1520
+ title: null,
1521
+ detectedSource: "plain",
1522
+ warnings: ["Empty input."]
1523
+ };
1524
+ }
1525
+ const detectedSource = opts.source === "auto" ? detectSourceFormat(input) : opts.source;
1526
+ const lines = input.split("\n");
1527
+ const parsed = classify(lines, opts, detectedSource);
1528
+ const chordpro = render(parsed, opts);
1529
+ const title = extractTitle(chordpro);
1530
+ if (!title)
1531
+ warnings.push("No {title: ...} directive was detected in the input.");
1532
+ const shorthandTokens = /* @__PURE__ */ new Set();
1533
+ for (const raw of lines) {
1534
+ if (isChordLine(raw.trim(), opts.strictChordDetection)) {
1535
+ for (const m of raw.matchAll(CHORD_SCAN_REGEX)) {
1536
+ if (/^[A-G][#b]?4$/i.test(m[0]) || /^[A-G][#b]?9$/i.test(m[0])) {
1537
+ shorthandTokens.add(m[0]);
1538
+ }
1539
+ }
1540
+ }
1541
+ }
1542
+ if (shorthandTokens.size > 0) {
1543
+ warnings.push(
1544
+ `Ambiguous bare-number chords passed through as-is (not reinterpreted as sus/add): ${[...shorthandTokens].join(", ")}. On Brazilian/Portuguese sheets "G4" usually means Gsus4 - convert manually if your chord engine expects that spelling.`
1545
+ );
1546
+ }
1547
+ return { chordpro, title, detectedSource, warnings };
1548
+ }
1549
+ function toChordPro(input, options) {
1550
+ return convertToChordProDetailed(input, options).chordpro;
1551
+ }
1552
+ // Annotate the CommonJS export names for ESM import in node:
1553
+ 0 && (module.exports = {
1554
+ DefaultChordDictionary,
1555
+ buildChordProText,
1556
+ chordDictionary,
1557
+ convertToChordProDetailed,
1558
+ detectSourceFormat,
1559
+ getNoteValue,
1560
+ getSuggestedCapo,
1561
+ parseChordPro,
1562
+ parseLineSegments,
1563
+ slugifyTitle,
1564
+ toChordPro,
1565
+ transposeChord,
1566
+ transposeNote
1567
+ });