@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.
package/dist/index.js ADDED
@@ -0,0 +1,3459 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ChordFinder: () => ChordFinder,
34
+ ChordProRenderer: () => ChordProRenderer,
35
+ ChordRoll: () => ChordRoll,
36
+ DefaultChordDictionary: () => DefaultChordDictionary,
37
+ Editor: () => Editor,
38
+ GuitarDiagram: () => GuitarDiagram,
39
+ PianoDiagram: () => PianoDiagram,
40
+ buildChordProText: () => buildChordProText,
41
+ chordDictionary: () => chordDictionary,
42
+ convertToChordProDetailed: () => convertToChordProDetailed,
43
+ detectSourceFormat: () => detectSourceFormat,
44
+ getNoteValue: () => getNoteValue,
45
+ getSuggestedCapo: () => getSuggestedCapo,
46
+ parseChordPro: () => parseChordPro,
47
+ parseLineSegments: () => parseLineSegments,
48
+ preloadEditor: () => preloadEditor,
49
+ registerChordproMode: () => registerChordproMode,
50
+ registerChordproSnippets: () => registerChordproSnippets,
51
+ slugifyTitle: () => slugifyTitle,
52
+ toChordPro: () => toChordPro,
53
+ transposeChord: () => transposeChord,
54
+ transposeNote: () => transposeNote
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/parser/parser.ts
59
+ var TIMING_REGEX = /^(.+?)@([0-9]*\.?[0-9]+)x$/;
60
+ function parseChordTiming(rawChord) {
61
+ const match = rawChord.match(TIMING_REGEX);
62
+ if (match) return { chord: match[1], timing: parseFloat(match[2]) };
63
+ return { chord: rawChord };
64
+ }
65
+ function parseLineSegments(lineText) {
66
+ const segments = [];
67
+ const regex = /\[([^\]]+)\]/g;
68
+ let match;
69
+ let lastIndex = 0;
70
+ let currentChord = "";
71
+ let currentTiming;
72
+ while ((match = regex.exec(lineText)) !== null) {
73
+ const rawChord = match[1];
74
+ const { chord, timing } = parseChordTiming(rawChord);
75
+ const textBefore = lineText.slice(lastIndex, match.index);
76
+ if (lastIndex === 0 && textBefore === "") {
77
+ currentChord = chord;
78
+ currentTiming = timing;
79
+ } else {
80
+ segments.push({
81
+ chord: currentChord,
82
+ text: textBefore,
83
+ timing: currentTiming
84
+ });
85
+ currentChord = chord;
86
+ currentTiming = timing;
87
+ }
88
+ lastIndex = regex.lastIndex;
89
+ }
90
+ const remainingText = lineText.slice(lastIndex);
91
+ segments.push({
92
+ chord: currentChord,
93
+ text: remainingText,
94
+ timing: currentTiming
95
+ });
96
+ return segments;
97
+ }
98
+ function parseChordPro(content) {
99
+ const lines = content.split(/\r?\n/);
100
+ const metadata = {};
101
+ const sections = [];
102
+ let currentSection = null;
103
+ let isTab = false;
104
+ let isGrid = false;
105
+ let lastChorusLines = [];
106
+ const commitSection = () => {
107
+ if (currentSection) {
108
+ sections.push(currentSection);
109
+ if (currentSection.type === "chorus") {
110
+ lastChorusLines = [...currentSection.lines];
111
+ }
112
+ currentSection = null;
113
+ }
114
+ };
115
+ const aliasMap = {
116
+ t: "title",
117
+ st: "subtitle",
118
+ a: "artist",
119
+ k: "key",
120
+ c: "comment",
121
+ ci: "comment_italic",
122
+ cb: "comment_box",
123
+ soc: "start_of_chorus",
124
+ eoc: "end_of_chorus",
125
+ sov: "start_of_verse",
126
+ eov: "end_of_verse",
127
+ sob: "start_of_bridge",
128
+ eob: "end_of_bridge",
129
+ sot: "start_of_tab",
130
+ eot: "end_of_tab",
131
+ sog: "start_of_grid",
132
+ eog: "end_of_grid",
133
+ ch: "chorus",
134
+ v: "verse",
135
+ b: "bridge",
136
+ re: "repeat",
137
+ ns: "new_song",
138
+ time_signature: "time",
139
+ timesignature: "time",
140
+ "time signature": "time",
141
+ original_key: "original_key",
142
+ "original key": "original_key"
143
+ };
144
+ for (let line of lines) {
145
+ const trimmed = line.trim();
146
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
147
+ const directive = trimmed.slice(1, -1).trim();
148
+ const colonIndex = directive.indexOf(":");
149
+ let rawName = directive;
150
+ let value = "";
151
+ if (colonIndex !== -1) {
152
+ rawName = directive.substring(0, colonIndex).trim();
153
+ value = directive.substring(colonIndex + 1).trim();
154
+ }
155
+ const lowerName = rawName.toLowerCase();
156
+ const name = aliasMap[lowerName] || lowerName;
157
+ switch (name) {
158
+ case "start_of_chorus":
159
+ commitSection();
160
+ currentSection = {
161
+ type: "chorus",
162
+ label: value || "Refr\xE3o",
163
+ lines: []
164
+ };
165
+ break;
166
+ case "start_of_verse":
167
+ commitSection();
168
+ currentSection = {
169
+ type: "verse",
170
+ label: value || "Verso",
171
+ lines: []
172
+ };
173
+ break;
174
+ case "start_of_bridge":
175
+ commitSection();
176
+ currentSection = {
177
+ type: "bridge",
178
+ label: value || "Ponte",
179
+ lines: []
180
+ };
181
+ break;
182
+ case "start_of_tab":
183
+ commitSection();
184
+ isTab = true;
185
+ currentSection = {
186
+ type: "tab",
187
+ label: value || "Tablatura",
188
+ lines: []
189
+ };
190
+ break;
191
+ case "start_of_grid":
192
+ commitSection();
193
+ isGrid = true;
194
+ currentSection = { type: "grid", label: value || "Grid", lines: [] };
195
+ break;
196
+ case "end_of_chorus":
197
+ if (currentSection?.type === "chorus") commitSection();
198
+ break;
199
+ case "end_of_verse":
200
+ if (currentSection?.type === "verse") commitSection();
201
+ break;
202
+ case "end_of_bridge":
203
+ if (currentSection?.type === "bridge") commitSection();
204
+ break;
205
+ case "end_of_tab":
206
+ isTab = false;
207
+ if (currentSection?.type === "tab") commitSection();
208
+ break;
209
+ case "end_of_grid":
210
+ isGrid = false;
211
+ if (currentSection?.type === "grid") commitSection();
212
+ break;
213
+ case "chorus":
214
+ commitSection();
215
+ sections.push({
216
+ type: "chorus",
217
+ label: value || "Refr\xE3o",
218
+ lines: [...lastChorusLines]
219
+ });
220
+ break;
221
+ case "verse":
222
+ commitSection();
223
+ currentSection = {
224
+ type: "verse",
225
+ label: value || "Verso",
226
+ lines: []
227
+ };
228
+ break;
229
+ case "bridge":
230
+ commitSection();
231
+ currentSection = {
232
+ type: "bridge",
233
+ label: value || "Ponte",
234
+ lines: []
235
+ };
236
+ break;
237
+ case "comment":
238
+ case "comment_italic":
239
+ const commentLine = { type: "comment", text: value };
240
+ if (currentSection) currentSection.lines.push(commentLine);
241
+ else sections.push({ type: "comment", lines: [commentLine] });
242
+ break;
243
+ case "comment_box":
244
+ const cbLine = { type: "comment_box", text: value };
245
+ if (currentSection) currentSection.lines.push(cbLine);
246
+ else sections.push({ type: "comment", lines: [cbLine] });
247
+ break;
248
+ case "repeat":
249
+ if (currentSection) {
250
+ currentSection.repeat = value || "2";
251
+ } else {
252
+ sections.push({
253
+ type: "comment",
254
+ lines: [
255
+ { type: "comment_box", text: `Repetir: ${value || "2"}` }
256
+ ]
257
+ });
258
+ }
259
+ break;
260
+ case "new_song":
261
+ commitSection();
262
+ sections.push({ type: "new_song", lines: [] });
263
+ break;
264
+ case "duration":
265
+ if (/^\d{1,2}:\d{2}$/.test(value)) {
266
+ const [minutes, seconds] = value.split(":").map(Number);
267
+ metadata["duration"] = (minutes * 60 + seconds).toString();
268
+ } else {
269
+ metadata["duration"] = value;
270
+ }
271
+ break;
272
+ default:
273
+ if (value) {
274
+ const metaKey = name.replace(
275
+ /[-_\s]+([a-zA-Z])/g,
276
+ (_, letter) => letter.toUpperCase()
277
+ ).replace(/\s+/g, "");
278
+ metadata[metaKey] = value;
279
+ }
280
+ break;
281
+ }
282
+ continue;
283
+ }
284
+ if (trimmed === "") {
285
+ if (currentSection) currentSection.lines.push({ type: "empty" });
286
+ continue;
287
+ }
288
+ if (trimmed.startsWith("#") && !isTab) continue;
289
+ let lineType = "lyrics";
290
+ let parsedSegments = [];
291
+ if (isTab) {
292
+ lineType = "tab";
293
+ } else {
294
+ parsedSegments = parseLineSegments(line);
295
+ const textContent = parsedSegments.map((s) => s.text).join("");
296
+ const onlyBarsAndSpaces = /^[\s|:\-.%]*$/.test(textContent);
297
+ const hasBars = textContent.includes("|");
298
+ if (isGrid || onlyBarsAndSpaces && hasBars) {
299
+ lineType = "chord-section";
300
+ }
301
+ }
302
+ const parsedLine = { type: lineType };
303
+ if (lineType === "tab") {
304
+ parsedLine.text = line;
305
+ } else if (lineType === "lyrics") {
306
+ parsedLine.segments = parsedSegments;
307
+ } else if (lineType === "chord-section") {
308
+ parsedLine.segments = parsedSegments;
309
+ const measures = [];
310
+ let currentChords = [];
311
+ let startBarline = "";
312
+ let hasSeenChord = false;
313
+ let startBarlineFound = false;
314
+ for (let i = 0; i < parsedSegments.length; i++) {
315
+ const seg = parsedSegments[i];
316
+ if (seg.chord) {
317
+ currentChords.push({
318
+ chord: seg.chord,
319
+ text: "",
320
+ timing: seg.timing
321
+ });
322
+ hasSeenChord = true;
323
+ }
324
+ const barlineMatches = seg.text.match(/\|\||:\||\|:|\|/g);
325
+ if (barlineMatches) {
326
+ for (let j = 0; j < barlineMatches.length; j++) {
327
+ const b = barlineMatches[j];
328
+ if (!hasSeenChord && !startBarlineFound) {
329
+ startBarline = b;
330
+ startBarlineFound = true;
331
+ } else {
332
+ measures.push({ chords: currentChords, endBarline: b });
333
+ currentChords = [];
334
+ }
335
+ }
336
+ }
337
+ }
338
+ if (currentChords.length > 0) {
339
+ measures.push({ chords: currentChords, endBarline: "" });
340
+ }
341
+ parsedLine.measures = measures;
342
+ parsedLine.startBarline = startBarline;
343
+ }
344
+ if (!currentSection) currentSection = { type: "verse", lines: [] };
345
+ currentSection.lines.push(parsedLine);
346
+ }
347
+ commitSection();
348
+ if (!metadata.title) metadata.title = "Sem T\xEDtulo";
349
+ return { metadata, sections };
350
+ }
351
+ function buildChordProText(metadata, bodyContent) {
352
+ const lines = [];
353
+ const primaryKeys = [
354
+ "title",
355
+ "subtitle",
356
+ "artist",
357
+ "composer",
358
+ "album",
359
+ "copyright",
360
+ "key",
361
+ "originalKey",
362
+ "capo",
363
+ "tempo",
364
+ "time",
365
+ "duration",
366
+ "songNumber",
367
+ "ccli",
368
+ "youtube"
369
+ ];
370
+ for (const k of primaryKeys) {
371
+ if (metadata[k]) {
372
+ const directiveName = k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
373
+ lines.push(`{${directiveName}: ${metadata[k]}}`);
374
+ }
375
+ }
376
+ for (const k in metadata) {
377
+ if (!primaryKeys.includes(k) && metadata[k] && k !== "title") {
378
+ const directiveName = k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
379
+ lines.push(`{${directiveName}: ${metadata[k]}}`);
380
+ }
381
+ }
382
+ lines.push("");
383
+ lines.push(bodyContent.trim());
384
+ return lines.join("\n");
385
+ }
386
+
387
+ // src/parser/transpose.ts
388
+ var NOTE_TO_VAL = {
389
+ C: 0,
390
+ "C#": 1,
391
+ DB: 1,
392
+ D: 2,
393
+ "D#": 3,
394
+ EB: 3,
395
+ E: 4,
396
+ F: 5,
397
+ "F#": 6,
398
+ GB: 6,
399
+ G: 7,
400
+ "G#": 8,
401
+ AB: 8,
402
+ A: 9,
403
+ "A#": 10,
404
+ BB: 10,
405
+ B: 11,
406
+ DO: 0,
407
+ RE: 2,
408
+ R\u00C9: 2,
409
+ MI: 4,
410
+ FA: 5,
411
+ F\u00C1: 5,
412
+ SOL: 7,
413
+ LA: 9,
414
+ L\u00C1: 9,
415
+ SI: 11
416
+ };
417
+ var SHARPS = [
418
+ "C",
419
+ "C#",
420
+ "D",
421
+ "D#",
422
+ "E",
423
+ "F",
424
+ "F#",
425
+ "G",
426
+ "G#",
427
+ "A",
428
+ "A#",
429
+ "B"
430
+ ];
431
+ var FLATS = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
432
+ function getNoteValue(note) {
433
+ if (!note) return void 0;
434
+ const match = note.match(
435
+ /^([A-G][#b]?|Do|Ré|Mi|Fá|Sol|Lá|Si|DO|RE|RÉ|MI|FA|FÁ|SOL|LA|LÁ|SI)/i
436
+ );
437
+ if (!match) return void 0;
438
+ return NOTE_TO_VAL[match[1].toUpperCase()];
439
+ }
440
+ function getSuggestedCapo(originalKey, transposeVal) {
441
+ const baseKeyVal = originalKey ? getNoteValue(originalKey) : 0;
442
+ const keyVal = baseKeyVal !== void 0 ? baseKeyVal : 0;
443
+ const soundingVal = (keyVal + transposeVal + 240) % 12;
444
+ const EASY_SHAPES = {
445
+ 0: "C",
446
+ 7: "G",
447
+ 2: "D",
448
+ 9: "A",
449
+ 4: "E"
450
+ };
451
+ const PREFERRED_OPEN_VALS = [0, 7, 2, 9, 4];
452
+ for (const targetShapeVal of PREFERRED_OPEN_VALS) {
453
+ const neededCapo = (soundingVal - targetShapeVal + 12) % 12;
454
+ if (neededCapo >= 1 && neededCapo <= 7) {
455
+ return {
456
+ capo: neededCapo,
457
+ chordShape: EASY_SHAPES[targetShapeVal]
458
+ };
459
+ }
460
+ }
461
+ return null;
462
+ }
463
+ function transposeNote(note, semitones, preferFlats = false) {
464
+ const upper = note.toUpperCase();
465
+ if (NOTE_TO_VAL[upper] === void 0) return note;
466
+ const val = NOTE_TO_VAL[upper];
467
+ const newVal = (val + semitones + 24) % 12;
468
+ const targetScale = preferFlats ? FLATS : SHARPS;
469
+ let transposed = targetScale[newVal];
470
+ if (note[0] === note[0].toLowerCase()) {
471
+ transposed = transposed.toLowerCase();
472
+ }
473
+ return transposed;
474
+ }
475
+ function transposeChord(chord, semitones) {
476
+ if (!chord || semitones === 0) return chord;
477
+ if (chord.includes("/")) {
478
+ return chord.split("/").map((part) => transposeChord(part.trim(), semitones)).join("/");
479
+ }
480
+ const noteRegex = /^([A-G][#b]?|Do|Ré|Mi|Fá|Sol|Lá|Si|DO|RE|RÉ|MI|FA|FÁ|SOL|LA|LÁ|SI)/;
481
+ const match = chord.match(noteRegex);
482
+ if (!match) return chord;
483
+ const note = match[1];
484
+ const suffix = chord.slice(note.length);
485
+ const preferFlats = chord.includes("b") || chord.includes("B");
486
+ const transposedNote = transposeNote(note, semitones, preferFlats);
487
+ return transposedNote + suffix;
488
+ }
489
+
490
+ // src/parser/chordDictionary.ts
491
+ var NOTE_ALIASES = {
492
+ C: 0,
493
+ "B#": 0,
494
+ Do: 0,
495
+ DO: 0,
496
+ "C#": 1,
497
+ Db: 1,
498
+ D: 2,
499
+ Re: 2,
500
+ RE: 2,
501
+ R\u00E9: 2,
502
+ R\u00C9: 2,
503
+ "D#": 3,
504
+ Eb: 3,
505
+ E: 4,
506
+ Fb: 4,
507
+ Mi: 4,
508
+ MI: 4,
509
+ F: 5,
510
+ "E#": 5,
511
+ Fa: 5,
512
+ FA: 5,
513
+ F\u00E1: 5,
514
+ F\u00C1: 5,
515
+ "F#": 6,
516
+ Gb: 6,
517
+ G: 7,
518
+ Sol: 7,
519
+ SOL: 7,
520
+ "G#": 8,
521
+ Ab: 8,
522
+ A: 9,
523
+ La: 9,
524
+ LA: 9,
525
+ L\u00E1: 9,
526
+ L\u00C1: 9,
527
+ "A#": 10,
528
+ Bb: 10,
529
+ B: 11,
530
+ Cb: 11,
531
+ Si: 11,
532
+ SI: 11
533
+ };
534
+ var ROOT_KEYS = Object.keys(NOTE_ALIASES).sort((a, b) => b.length - a.length);
535
+ var ROOT_PATTERN = new RegExp(`^(${ROOT_KEYS.join("|")})`, "i");
536
+ var SEMITONE_NAMES = [
537
+ "C",
538
+ "C#",
539
+ "D",
540
+ "D#",
541
+ "E",
542
+ "F",
543
+ "F#",
544
+ "G",
545
+ "G#",
546
+ "A",
547
+ "A#",
548
+ "B"
549
+ ];
550
+ function resolveRootSemitone(raw) {
551
+ if (raw in NOTE_ALIASES) return NOTE_ALIASES[raw];
552
+ const titleCase = raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase();
553
+ if (titleCase in NOTE_ALIASES) return NOTE_ALIASES[titleCase];
554
+ const upper = raw.toUpperCase();
555
+ if (upper in NOTE_ALIASES) return NOTE_ALIASES[upper];
556
+ return null;
557
+ }
558
+ function pitchClassName(semitone) {
559
+ return SEMITONE_NAMES[(semitone % 12 + 12) % 12];
560
+ }
561
+ var CHORD_QUALITIES = [
562
+ {
563
+ id: "major",
564
+ label: "Major",
565
+ intervals: [0, 4, 7],
566
+ aliases: ["", "M", "maj", "Maj", "MAJ"]
567
+ },
568
+ {
569
+ id: "minor",
570
+ label: "Minor",
571
+ intervals: [0, 3, 7],
572
+ aliases: ["m", "min", "Min", "MIN", "-"]
573
+ },
574
+ {
575
+ id: "dim",
576
+ label: "Diminished",
577
+ intervals: [0, 3, 6],
578
+ aliases: ["dim", "o", "\xB0"]
579
+ },
580
+ {
581
+ id: "aug",
582
+ label: "Augmented",
583
+ intervals: [0, 4, 8],
584
+ aliases: ["aug", "+"]
585
+ },
586
+ {
587
+ id: "sus2",
588
+ label: "Suspended 2nd",
589
+ intervals: [0, 2, 7],
590
+ aliases: ["sus2"]
591
+ },
592
+ {
593
+ id: "sus4",
594
+ label: "Suspended 4th",
595
+ intervals: [0, 5, 7],
596
+ aliases: ["sus4", "sus"]
597
+ },
598
+ { id: "five", label: "Power chord", intervals: [0, 7], aliases: ["5"] },
599
+ { id: "six", label: "6th", intervals: [0, 4, 7, 9], aliases: ["6"] },
600
+ {
601
+ id: "m6",
602
+ label: "Minor 6th",
603
+ intervals: [0, 3, 7, 9],
604
+ aliases: ["m6", "min6"]
605
+ },
606
+ {
607
+ id: "six9",
608
+ label: "6/9",
609
+ intervals: [0, 4, 7, 9, 14],
610
+ aliases: ["6/9", "69"]
611
+ },
612
+ {
613
+ id: "dom7",
614
+ label: "Dominant 7th",
615
+ intervals: [0, 4, 7, 10],
616
+ aliases: ["7"]
617
+ },
618
+ {
619
+ id: "maj7",
620
+ label: "Major 7th",
621
+ intervals: [0, 4, 7, 11],
622
+ aliases: ["maj7", "Maj7", "MAJ7", "M7", "\u0394", "\u03947"]
623
+ },
624
+ {
625
+ id: "m7",
626
+ label: "Minor 7th",
627
+ intervals: [0, 3, 7, 10],
628
+ aliases: ["m7", "min7", "Min7"]
629
+ },
630
+ {
631
+ id: "mMaj7",
632
+ label: "Minor Major 7th",
633
+ intervals: [0, 3, 7, 11],
634
+ aliases: ["mMaj7", "m(maj7)", "mM7", "minMaj7"]
635
+ },
636
+ {
637
+ id: "m7b5",
638
+ label: "Half-diminished 7th",
639
+ intervals: [0, 3, 6, 10],
640
+ aliases: ["m7b5", "m7-5", "\xF8", "\xF87"]
641
+ },
642
+ {
643
+ id: "dim7",
644
+ label: "Diminished 7th",
645
+ intervals: [0, 3, 6, 9],
646
+ aliases: ["dim7", "o7", "\xB07"]
647
+ },
648
+ {
649
+ id: "aug7",
650
+ label: "7#5",
651
+ intervals: [0, 4, 8, 10],
652
+ aliases: ["7#5", "aug7"]
653
+ },
654
+ { id: "dom7b5", label: "7b5", intervals: [0, 4, 6, 10], aliases: ["7b5"] },
655
+ {
656
+ id: "dom7sus4",
657
+ label: "7sus4",
658
+ intervals: [0, 5, 7, 10],
659
+ aliases: ["7sus4"]
660
+ },
661
+ {
662
+ id: "dom7sus2",
663
+ label: "7sus2",
664
+ intervals: [0, 2, 7, 10],
665
+ aliases: ["7sus2"]
666
+ },
667
+ { id: "nine", label: "9th", intervals: [0, 4, 7, 10, 14], aliases: ["9"] },
668
+ {
669
+ id: "maj9",
670
+ label: "Major 9th",
671
+ intervals: [0, 4, 7, 11, 14],
672
+ aliases: ["maj9", "Maj9", "M9"]
673
+ },
674
+ {
675
+ id: "m9",
676
+ label: "Minor 9th",
677
+ intervals: [0, 3, 7, 10, 14],
678
+ aliases: ["m9", "min9"]
679
+ },
680
+ { id: "add9", label: "Add 9", intervals: [0, 4, 7, 14], aliases: ["add9"] },
681
+ {
682
+ id: "madd9",
683
+ label: "Minor Add 9",
684
+ intervals: [0, 3, 7, 14],
685
+ aliases: ["madd9", "minAdd9"]
686
+ },
687
+ {
688
+ id: "eleven",
689
+ label: "11th",
690
+ intervals: [0, 4, 7, 10, 14, 17],
691
+ aliases: ["11"]
692
+ },
693
+ {
694
+ id: "m11",
695
+ label: "Minor 11th",
696
+ intervals: [0, 3, 7, 10, 14, 17],
697
+ aliases: ["m11"]
698
+ },
699
+ {
700
+ id: "thirteen",
701
+ label: "13th",
702
+ intervals: [0, 4, 7, 10, 14, 17, 21],
703
+ aliases: ["13"]
704
+ },
705
+ {
706
+ id: "m13",
707
+ label: "Minor 13th",
708
+ intervals: [0, 3, 7, 10, 14, 17, 21],
709
+ aliases: ["m13"]
710
+ },
711
+ {
712
+ id: "dom7sharp9",
713
+ label: "7#9",
714
+ intervals: [0, 4, 7, 10, 15],
715
+ aliases: ["7#9"]
716
+ },
717
+ {
718
+ id: "dom7flat9",
719
+ label: "7b9",
720
+ intervals: [0, 4, 7, 10, 13],
721
+ aliases: ["7b9"]
722
+ },
723
+ {
724
+ id: "maj7sharp5",
725
+ label: "maj7#5",
726
+ intervals: [0, 4, 8, 11],
727
+ aliases: ["maj7#5"]
728
+ },
729
+ {
730
+ id: "maj7flat5",
731
+ label: "maj7b5",
732
+ intervals: [0, 4, 6, 11],
733
+ aliases: ["maj7b5"]
734
+ }
735
+ ];
736
+ var QUALITY_BY_ID = Object.fromEntries(
737
+ CHORD_QUALITIES.map((q) => [q.id, q])
738
+ );
739
+ var QUALITY_ALIAS_TABLE = CHORD_QUALITIES.flatMap(
740
+ (quality) => quality.aliases.map((alias) => ({ alias, quality }))
741
+ ).sort((a, b) => b.alias.length - a.alias.length);
742
+ function resolveQuality(qualitySymbol) {
743
+ if (qualitySymbol === "") return QUALITY_BY_ID.major;
744
+ const exact = QUALITY_ALIAS_TABLE.find(
745
+ (e) => e.alias !== "" && e.alias === qualitySymbol
746
+ );
747
+ if (exact) return exact.quality;
748
+ const lower = qualitySymbol.toLowerCase();
749
+ const loose = QUALITY_ALIAS_TABLE.find(
750
+ (e) => e.alias !== "" && e.alias.toLowerCase() === lower
751
+ );
752
+ if (loose) return loose.quality;
753
+ return QUALITY_BY_ID.major;
754
+ }
755
+ var QUALITY_SIMPLIFICATION = {
756
+ dim: "minor",
757
+ dim7: "minor",
758
+ aug: "major",
759
+ six: "major",
760
+ m6: "minor",
761
+ six9: "major",
762
+ mMaj7: "m7",
763
+ m7b5: "m7",
764
+ aug7: "dom7",
765
+ dom7b5: "dom7",
766
+ dom7sus4: "sus4",
767
+ dom7sus2: "sus2",
768
+ nine: "dom7",
769
+ maj9: "maj7",
770
+ m9: "m7",
771
+ add9: "major",
772
+ madd9: "minor",
773
+ eleven: "dom7",
774
+ m11: "m7",
775
+ thirteen: "dom7",
776
+ m13: "m7",
777
+ dom7sharp9: "dom7",
778
+ dom7flat9: "dom7",
779
+ maj7sharp5: "maj7",
780
+ maj7flat5: "maj7"
781
+ };
782
+ var SLASH_CONTAINING_ALIASES = QUALITY_ALIAS_TABLE.filter(
783
+ (e) => e.alias.includes("/")
784
+ ).sort((a, b) => b.alias.length - a.alias.length);
785
+ function parseChordSymbol(chord) {
786
+ const cleaned = chord.replace(/[()]/g, "").trim();
787
+ if (!cleaned) return null;
788
+ const rootMatch = cleaned.match(ROOT_PATTERN);
789
+ if (!rootMatch) return null;
790
+ const rootText = rootMatch[1];
791
+ const rootSemitone = resolveRootSemitone(rootText);
792
+ if (rootSemitone === null) return null;
793
+ const remainder = cleaned.slice(rootText.length);
794
+ const slashAlias = SLASH_CONTAINING_ALIASES.find(
795
+ (e) => remainder.startsWith(e.alias)
796
+ );
797
+ let qualitySymbol;
798
+ let bassPart;
799
+ if (slashAlias) {
800
+ qualitySymbol = slashAlias.alias;
801
+ const rest = remainder.slice(slashAlias.alias.length);
802
+ bassPart = rest.startsWith("/") ? rest.slice(1).trim() : void 0;
803
+ } else {
804
+ const slashIndex = remainder.indexOf("/");
805
+ if (slashIndex === -1) {
806
+ qualitySymbol = remainder;
807
+ } else {
808
+ qualitySymbol = remainder.slice(0, slashIndex);
809
+ bassPart = remainder.slice(slashIndex + 1).trim();
810
+ }
811
+ }
812
+ const quality = resolveQuality(qualitySymbol);
813
+ let bassSemitone;
814
+ if (bassPart) {
815
+ const bassMatch = bassPart.match(ROOT_PATTERN);
816
+ if (bassMatch) {
817
+ const resolved = resolveRootSemitone(bassMatch[1]);
818
+ if (resolved !== null) bassSemitone = resolved;
819
+ }
820
+ }
821
+ return {
822
+ raw: cleaned,
823
+ rootSemitone,
824
+ rootDisplay: pitchClassName(rootSemitone),
825
+ quality,
826
+ bassSemitone
827
+ };
828
+ }
829
+ function computePianoVoicing(rootSemitone, intervals, bassSemitone) {
830
+ const rootPc = (rootSemitone % 12 + 12) % 12;
831
+ if (bassSemitone !== void 0) {
832
+ const bassPc = (bassSemitone % 12 + 12) % 12;
833
+ const notes2 = [pitchClassName(bassPc)];
834
+ const highlightKeys2 = [bassPc];
835
+ for (const iv of intervals) {
836
+ const key = (rootPc + iv) % 12 + 12;
837
+ const name = pitchClassName(rootPc + iv);
838
+ highlightKeys2.push(key);
839
+ if (!notes2.includes(name)) notes2.push(name);
840
+ }
841
+ return { notes: notes2, highlightKeys: highlightKeys2 };
842
+ }
843
+ const notes = intervals.map((iv) => pitchClassName(rootPc + iv));
844
+ const highlightKeys = intervals.map((iv) => (rootPc + iv) % 24);
845
+ return { notes, highlightKeys };
846
+ }
847
+ var OPEN_CHORD_SHAPES = {
848
+ C: { frets: [-1, 3, 2, 0, 1, 0], fingers: [0, 3, 2, 0, 1, 0] },
849
+ Cm: { frets: [-1, 3, 5, 5, 4, 3], fingers: [0, 1, 3, 4, 2, 1], barre: 3 },
850
+ C7: { frets: [-1, 3, 2, 3, 1, 0], fingers: [0, 3, 2, 4, 1, 0] },
851
+ Cmaj7: { frets: [-1, 3, 2, 0, 0, 0], fingers: [0, 3, 2, 0, 0, 0] },
852
+ Cm7: { frets: [-1, 3, 5, 3, 4, 3], fingers: [0, 1, 3, 1, 2, 1], barre: 3 },
853
+ Csus4: { frets: [-1, 3, 3, 0, 1, 1], fingers: [0, 3, 4, 0, 1, 1] },
854
+ Csus2: { frets: [-1, 3, 0, 0, 1, 3], fingers: [0, 2, 0, 0, 1, 4] },
855
+ Cadd9: { frets: [-1, 3, 2, 0, 3, 0], fingers: [0, 2, 1, 0, 3, 0] },
856
+ C9: { frets: [-1, 3, 2, 3, 3, 3], fingers: [0, 2, 1, 3, 3, 3], barre: 3 },
857
+ C6: { frets: [-1, 3, 2, 2, 1, 0], fingers: [0, 4, 2, 3, 1, 0] },
858
+ "C#": { frets: [-1, 4, 6, 6, 6, 4], fingers: [0, 1, 2, 3, 4, 1], barre: 4 },
859
+ "C#m": {
860
+ frets: [-1, 4, 6, 6, 5, 4],
861
+ fingers: [0, 1, 3, 4, 2, 1],
862
+ barre: 4
863
+ },
864
+ "C#7": { frets: [-1, 4, 3, 4, 2, -1], fingers: [0, 3, 2, 4, 1, 0] },
865
+ "C#maj7": {
866
+ frets: [-1, 4, 6, 5, 6, 4],
867
+ fingers: [0, 1, 3, 2, 4, 1],
868
+ barre: 4
869
+ },
870
+ "C#m7": {
871
+ frets: [-1, 4, 6, 4, 5, 4],
872
+ fingers: [0, 1, 3, 1, 2, 1],
873
+ barre: 4
874
+ },
875
+ D: { frets: [-1, -1, 0, 2, 3, 2], fingers: [0, 0, 0, 1, 3, 2] },
876
+ Dm: { frets: [-1, -1, 0, 2, 3, 1], fingers: [0, 0, 0, 2, 3, 1] },
877
+ D7: { frets: [-1, -1, 0, 2, 1, 2], fingers: [0, 0, 0, 2, 1, 3] },
878
+ Dmaj7: {
879
+ frets: [-1, -1, 0, 2, 2, 2],
880
+ fingers: [0, 0, 0, 1, 1, 1],
881
+ barre: 2
882
+ },
883
+ Dm7: { frets: [-1, -1, 0, 2, 1, 1], fingers: [0, 0, 0, 2, 1, 1], barre: 1 },
884
+ Dsus4: { frets: [-1, -1, 0, 2, 3, 3], fingers: [0, 0, 0, 1, 2, 3] },
885
+ Dsus2: { frets: [-1, -1, 0, 2, 3, 0], fingers: [0, 0, 0, 1, 2, 0] },
886
+ Dadd9: { frets: [-1, -1, 0, 2, 5, 2], fingers: [0, 0, 0, 1, 4, 2] },
887
+ D6: { frets: [-1, -1, 0, 2, 0, 2], fingers: [0, 0, 0, 2, 0, 3] },
888
+ D9: { frets: [-1, -1, 0, 2, 1, 0], fingers: [0, 0, 0, 2, 1, 0] },
889
+ Eb: { frets: [-1, 6, 8, 8, 8, 6], fingers: [0, 1, 2, 3, 4, 1], barre: 6 },
890
+ Ebm: { frets: [-1, 6, 8, 8, 7, 6], fingers: [0, 1, 3, 4, 2, 1], barre: 6 },
891
+ Eb7: { frets: [-1, 6, 5, 6, 4, -1], fingers: [0, 3, 2, 4, 1, 0] },
892
+ E: { frets: [0, 2, 2, 1, 0, 0], fingers: [0, 2, 3, 1, 0, 0] },
893
+ Em: { frets: [0, 2, 2, 0, 0, 0], fingers: [0, 2, 3, 0, 0, 0] },
894
+ E7: { frets: [0, 2, 0, 1, 0, 0], fingers: [0, 2, 0, 1, 0, 0] },
895
+ Emaj7: { frets: [0, 2, 1, 1, 0, 0], fingers: [0, 3, 1, 2, 0, 0] },
896
+ Em7: { frets: [0, 2, 0, 0, 0, 0], fingers: [0, 2, 0, 0, 0, 0] },
897
+ Esus4: { frets: [0, 2, 2, 2, 0, 0], fingers: [0, 2, 3, 4, 0, 0] },
898
+ Eadd9: { frets: [0, 2, 4, 1, 0, 0], fingers: [0, 2, 4, 1, 0, 0] },
899
+ E6: { frets: [0, 2, 2, 1, 2, 0], fingers: [0, 2, 3, 1, 4, 0] },
900
+ E9: { frets: [0, 2, 0, 1, 3, 0], fingers: [0, 2, 0, 1, 4, 0] },
901
+ F: { frets: [1, 3, 3, 2, 1, 1], fingers: [1, 3, 4, 2, 1, 1], barre: 1 },
902
+ Fm: { frets: [1, 3, 3, 1, 1, 1], fingers: [1, 3, 4, 1, 1, 1], barre: 1 },
903
+ F7: { frets: [1, 3, 1, 2, 1, 1], fingers: [1, 3, 1, 2, 1, 1], barre: 1 },
904
+ Fmaj7: { frets: [-1, 3, 3, 2, 1, 0], fingers: [0, 3, 4, 2, 1, 0] },
905
+ Fm7: { frets: [1, 3, 1, 1, 1, 1], fingers: [1, 3, 1, 1, 1, 1], barre: 1 },
906
+ "F#": { frets: [2, 4, 4, 3, 2, 2], fingers: [1, 3, 4, 2, 1, 1], barre: 2 },
907
+ "F#m": { frets: [2, 4, 4, 2, 2, 2], fingers: [1, 3, 4, 1, 1, 1], barre: 2 },
908
+ "F#7": { frets: [2, 4, 2, 3, 2, 2], fingers: [1, 3, 1, 2, 1, 1], barre: 2 },
909
+ "F#m7": {
910
+ frets: [2, 4, 2, 2, 2, 2],
911
+ fingers: [1, 3, 1, 1, 1, 1],
912
+ barre: 2
913
+ },
914
+ G: { frets: [3, 2, 0, 0, 3, 3], fingers: [2, 1, 0, 0, 3, 4] },
915
+ Gm: { frets: [3, 5, 5, 3, 3, 3], fingers: [1, 3, 4, 1, 1, 1], barre: 3 },
916
+ G7: { frets: [3, 2, 0, 0, 0, 1], fingers: [3, 2, 0, 0, 0, 1] },
917
+ Gmaj7: { frets: [3, 2, 0, 0, 0, 2], fingers: [2, 1, 0, 0, 0, 3] },
918
+ Gm7: { frets: [3, 5, 3, 3, 3, 3], fingers: [1, 3, 1, 1, 1, 1], barre: 3 },
919
+ Gsus4: { frets: [3, 3, 0, 0, 3, 3], fingers: [2, 3, 0, 0, 1, 4] },
920
+ Gadd9: { frets: [3, 2, 0, 2, 0, 3], fingers: [2, 1, 0, 3, 0, 4] },
921
+ G6: { frets: [3, 2, 0, 0, 0, 0], fingers: [3, 2, 0, 0, 0, 0] },
922
+ Ab: { frets: [4, 6, 6, 5, 4, 4], fingers: [1, 3, 4, 2, 1, 1], barre: 4 },
923
+ Abm: { frets: [4, 6, 6, 4, 4, 4], fingers: [1, 3, 4, 1, 1, 1], barre: 4 },
924
+ A: { frets: [-1, 0, 2, 2, 2, 0], fingers: [0, 0, 1, 2, 3, 0] },
925
+ Am: { frets: [-1, 0, 2, 2, 1, 0], fingers: [0, 0, 2, 3, 1, 0] },
926
+ A7: { frets: [-1, 0, 2, 0, 2, 0], fingers: [0, 0, 1, 0, 2, 0] },
927
+ Amaj7: { frets: [-1, 0, 2, 1, 2, 0], fingers: [0, 0, 2, 1, 3, 0] },
928
+ Am7: { frets: [-1, 0, 2, 0, 1, 0], fingers: [0, 0, 2, 0, 1, 0] },
929
+ Asus4: { frets: [-1, 0, 2, 2, 3, 0], fingers: [0, 0, 1, 2, 4, 0] },
930
+ Asus2: { frets: [-1, 0, 2, 2, 0, 0], fingers: [0, 0, 1, 2, 0, 0] },
931
+ Aadd9: { frets: [-1, 0, 2, 4, 2, 0], fingers: [0, 0, 1, 3, 2, 0] },
932
+ A6: { frets: [-1, 0, 2, 2, 2, 2], fingers: [0, 0, 1, 1, 1, 1], barre: 2 },
933
+ A9: { frets: [-1, 0, 2, 4, 2, 3], fingers: [0, 0, 1, 3, 2, 4] },
934
+ Bb: { frets: [-1, 1, 3, 3, 3, 1], fingers: [0, 1, 2, 3, 4, 1], barre: 1 },
935
+ Bbm: { frets: [-1, 1, 3, 3, 2, 1], fingers: [0, 1, 3, 4, 2, 1], barre: 1 },
936
+ Bb7: { frets: [-1, 1, 3, 1, 3, 1], fingers: [0, 1, 3, 1, 4, 1], barre: 1 },
937
+ B: { frets: [-1, 2, 4, 4, 4, 2], fingers: [0, 1, 2, 3, 4, 1], barre: 2 },
938
+ Bm: { frets: [-1, 2, 4, 4, 3, 2], fingers: [0, 1, 3, 4, 2, 1], barre: 2 },
939
+ B7: { frets: [-1, 2, 1, 2, 0, 2], fingers: [0, 2, 1, 3, 0, 4] },
940
+ Bmaj7: {
941
+ frets: [-1, 2, 4, 3, 4, 2],
942
+ fingers: [0, 1, 3, 2, 4, 1],
943
+ barre: 2
944
+ },
945
+ Bm7: { frets: [-1, 2, 4, 2, 3, 2], fingers: [0, 1, 3, 1, 2, 1], barre: 2 }
946
+ };
947
+ var SLASH_CHORD_SHAPES = {
948
+ "C/E": { frets: [0, 3, 2, 0, 1, 0], fingers: [0, 3, 2, 0, 1, 0] },
949
+ "C/G": { frets: [3, 3, 2, 0, 1, 0], fingers: [3, 4, 2, 0, 1, 0] },
950
+ "C/Bb": { frets: [-1, 3, 2, 3, 1, 0], fingers: [0, 3, 2, 4, 1, 0] },
951
+ "D/F#": { frets: [2, 0, 0, 2, 3, 2], fingers: [1, 0, 0, 2, 4, 3] },
952
+ "D/A": { frets: [-1, 0, 0, 2, 3, 2], fingers: [0, 0, 0, 1, 3, 2] },
953
+ "E/G#": { frets: [4, 2, 2, 1, 0, 0], fingers: [4, 2, 3, 1, 0, 0] },
954
+ "E/B": { frets: [0, 2, 2, 1, 0, 0], fingers: [0, 2, 3, 1, 0, 0] },
955
+ "F/A": { frets: [-1, 0, 3, 2, 1, 1], fingers: [0, 0, 3, 2, 1, 1] },
956
+ "F/C": {
957
+ frets: [8, 8, 10, 10, 10, 8],
958
+ fingers: [1, 1, 2, 3, 4, 1],
959
+ barre: 8
960
+ },
961
+ "G/B": { frets: [-1, 2, 0, 0, 3, 3], fingers: [0, 1, 0, 0, 3, 4] },
962
+ "G/D": { frets: [-1, -1, 0, 0, 3, 3], fingers: [0, 0, 0, 0, 3, 4] },
963
+ "G/F": { frets: [3, 2, 0, 0, 0, 1], fingers: [3, 2, 0, 0, 0, 1] },
964
+ "A/C#": { frets: [-1, 4, 2, 2, 2, -1], fingers: [0, 4, 1, 1, 1, 0] },
965
+ "A/E": { frets: [0, 0, 2, 2, 2, 0], fingers: [0, 0, 1, 2, 3, 0] },
966
+ "A/G": { frets: [3, 0, 2, 2, 2, 0], fingers: [4, 0, 1, 2, 3, 0] },
967
+ "B/D#": { frets: [-1, 6, 4, 4, 4, -1], fingers: [0, 3, 1, 1, 1, 0] },
968
+ "B/F#": {
969
+ frets: [2, 2, 4, 4, 4, 2],
970
+ fingers: [1, 1, 2, 3, 4, 1],
971
+ barre: 2
972
+ },
973
+ "Am/G": { frets: [3, 0, 2, 2, 1, 0], fingers: [4, 0, 2, 3, 1, 0] },
974
+ "Am/F#": { frets: [2, 0, 2, 2, 1, 0], fingers: [2, 0, 3, 4, 1, 0] },
975
+ "Am/E": { frets: [0, 0, 2, 2, 1, 0], fingers: [0, 0, 2, 3, 1, 0] },
976
+ "Dm/C": { frets: [-1, 3, 0, 2, 3, 1], fingers: [0, 3, 0, 2, 4, 1] },
977
+ "Dm/B": { frets: [-1, 2, 0, 2, 3, 1], fingers: [0, 2, 0, 3, 4, 1] },
978
+ "Dm/A": { frets: [-1, 0, 0, 2, 3, 1], fingers: [0, 0, 0, 2, 3, 1] },
979
+ "Dm/F": { frets: [1, -1, 0, 2, 3, 1], fingers: [1, 0, 0, 2, 4, 3] },
980
+ "Em/D": { frets: [0, 2, 0, 0, 0, 0], fingers: [0, 2, 0, 0, 0, 0] },
981
+ "Em/C#": { frets: [0, 4, 2, 0, 0, 0], fingers: [0, 3, 1, 0, 0, 0] },
982
+ "Em/B": {
983
+ frets: [7, 7, 9, 9, 8, 7],
984
+ fingers: [1, 1, 3, 4, 2, 1],
985
+ barre: 7
986
+ },
987
+ "Em/G": { frets: [3, 2, 2, 0, 0, 0], fingers: [3, 1, 2, 0, 0, 0] },
988
+ "Fm/Eb": { frets: [-1, 6, 6, 5, 6, -1], fingers: [0, 2, 3, 1, 4, 0] },
989
+ "Gm/F": {
990
+ frets: [3, 5, 3, 3, 3, 3],
991
+ fingers: [1, 3, 1, 1, 1, 1],
992
+ barre: 3
993
+ },
994
+ "Bm/A": { frets: [-1, 0, 4, 4, 3, 2], fingers: [0, 0, 3, 4, 2, 1] }
995
+ };
996
+ var CANONICAL_SUFFIX = {
997
+ major: "",
998
+ minor: "m",
999
+ dom7: "7",
1000
+ maj7: "maj7",
1001
+ m7: "m7",
1002
+ sus4: "sus4",
1003
+ sus2: "sus2",
1004
+ six: "6",
1005
+ add9: "add9",
1006
+ nine: "9"
1007
+ };
1008
+ var E_FORM = {
1009
+ major: {
1010
+ formRootSemitone: 4,
1011
+ frets: [0, 2, 2, 1, 0, 0],
1012
+ openFingers: [0, 2, 3, 1, 0, 0],
1013
+ barreFingers: [1, 3, 4, 2, 1, 1]
1014
+ },
1015
+ minor: {
1016
+ formRootSemitone: 4,
1017
+ frets: [0, 2, 2, 0, 0, 0],
1018
+ openFingers: [0, 2, 3, 0, 0, 0],
1019
+ barreFingers: [1, 3, 4, 1, 1, 1]
1020
+ },
1021
+ dom7: {
1022
+ formRootSemitone: 4,
1023
+ frets: [0, 2, 0, 1, 0, 0],
1024
+ openFingers: [0, 2, 0, 1, 0, 0],
1025
+ barreFingers: [1, 3, 1, 2, 1, 1]
1026
+ },
1027
+ m7: {
1028
+ formRootSemitone: 4,
1029
+ frets: [0, 2, 0, 0, 0, 0],
1030
+ openFingers: [0, 2, 0, 0, 0, 0],
1031
+ barreFingers: [1, 3, 1, 1, 1, 1]
1032
+ },
1033
+ maj7: {
1034
+ formRootSemitone: 4,
1035
+ frets: [0, 2, 1, 1, 0, 0],
1036
+ openFingers: [0, 3, 1, 2, 0, 0],
1037
+ barreFingers: [1, 3, 2, 2, 1, 1]
1038
+ },
1039
+ sus4: {
1040
+ formRootSemitone: 4,
1041
+ frets: [0, 2, 2, 2, 0, 0],
1042
+ openFingers: [0, 2, 3, 4, 0, 0],
1043
+ barreFingers: [1, 3, 4, 4, 1, 1]
1044
+ }
1045
+ };
1046
+ var A_FORM = {
1047
+ major: {
1048
+ formRootSemitone: 9,
1049
+ frets: [-1, 0, 2, 2, 2, 0],
1050
+ openFingers: [0, 0, 1, 2, 3, 0],
1051
+ barreFingers: [0, 1, 3, 4, 4, 1]
1052
+ },
1053
+ minor: {
1054
+ formRootSemitone: 9,
1055
+ frets: [-1, 0, 2, 2, 1, 0],
1056
+ openFingers: [0, 0, 2, 3, 1, 0],
1057
+ barreFingers: [0, 1, 3, 4, 2, 1]
1058
+ },
1059
+ dom7: {
1060
+ formRootSemitone: 9,
1061
+ frets: [-1, 0, 2, 0, 2, 0],
1062
+ openFingers: [0, 0, 1, 0, 2, 0],
1063
+ barreFingers: [0, 1, 3, 1, 4, 1]
1064
+ },
1065
+ m7: {
1066
+ formRootSemitone: 9,
1067
+ frets: [-1, 0, 2, 0, 1, 0],
1068
+ openFingers: [0, 0, 2, 0, 1, 0],
1069
+ barreFingers: [0, 1, 3, 1, 2, 1]
1070
+ },
1071
+ maj7: {
1072
+ formRootSemitone: 9,
1073
+ frets: [-1, 0, 2, 1, 2, 0],
1074
+ openFingers: [0, 0, 2, 1, 3, 0],
1075
+ barreFingers: [0, 1, 3, 2, 4, 1]
1076
+ },
1077
+ sus4: {
1078
+ formRootSemitone: 9,
1079
+ frets: [-1, 0, 2, 2, 3, 0],
1080
+ openFingers: [0, 0, 1, 2, 4, 0],
1081
+ barreFingers: [0, 1, 3, 3, 4, 1]
1082
+ },
1083
+ sus2: {
1084
+ formRootSemitone: 9,
1085
+ frets: [-1, 0, 2, 2, 0, 0],
1086
+ openFingers: [0, 0, 1, 2, 0, 0],
1087
+ barreFingers: [0, 1, 3, 4, 1, 1]
1088
+ }
1089
+ };
1090
+ function realizeTemplate(t, targetSemitone) {
1091
+ const shift = ((targetSemitone - t.formRootSemitone) % 12 + 12) % 12;
1092
+ const frets = t.frets.map((f) => f < 0 ? f : f + shift);
1093
+ if (shift === 0) return { frets, fingers: t.openFingers ?? t.barreFingers };
1094
+ return { frets, fingers: t.barreFingers, barre: shift };
1095
+ }
1096
+ function maxFret(shape) {
1097
+ return Math.max(0, ...shape.frets.filter((f) => f >= 0));
1098
+ }
1099
+ function templateFingering(qualityId, targetSemitone) {
1100
+ const e = E_FORM[qualityId];
1101
+ const a = A_FORM[qualityId];
1102
+ if (!e && !a) return null;
1103
+ if (e && !a) return realizeTemplate(e, targetSemitone);
1104
+ if (a && !e) return realizeTemplate(a, targetSemitone);
1105
+ const eShape = realizeTemplate(e, targetSemitone);
1106
+ const aShape = realizeTemplate(a, targetSemitone);
1107
+ return maxFret(aShape) <= maxFret(eShape) ? aShape : eShape;
1108
+ }
1109
+ function powerChordShape(targetSemitone) {
1110
+ const shift = ((targetSemitone - 4) % 12 + 12) % 12;
1111
+ return {
1112
+ frets: [shift, shift + 2, shift + 2, -1, -1, -1],
1113
+ fingers: [1, 3, 4, 0, 0, 0]
1114
+ };
1115
+ }
1116
+ var TEMPLATE_QUALITIES = /* @__PURE__ */ new Set([
1117
+ "major",
1118
+ "minor",
1119
+ "dom7",
1120
+ "m7",
1121
+ "maj7",
1122
+ "sus4",
1123
+ "sus2"
1124
+ ]);
1125
+ function getGuitarFingering(parsed) {
1126
+ const { rootSemitone, quality, raw, rootDisplay } = parsed;
1127
+ if (SLASH_CHORD_SHAPES[raw])
1128
+ return { shape: SLASH_CHORD_SHAPES[raw], approximate: false };
1129
+ if (OPEN_CHORD_SHAPES[raw])
1130
+ return { shape: OPEN_CHORD_SHAPES[raw], approximate: false };
1131
+ const suffix = CANONICAL_SUFFIX[quality.id];
1132
+ if (suffix !== void 0) {
1133
+ const canonical = rootDisplay + suffix;
1134
+ if (OPEN_CHORD_SHAPES[canonical])
1135
+ return { shape: OPEN_CHORD_SHAPES[canonical], approximate: false };
1136
+ }
1137
+ if (quality.id === "five")
1138
+ return { shape: powerChordShape(rootSemitone), approximate: false };
1139
+ if (TEMPLATE_QUALITIES.has(quality.id)) {
1140
+ const shape = templateFingering(quality.id, rootSemitone);
1141
+ if (shape) return { shape, approximate: false };
1142
+ }
1143
+ let fallbackId = QUALITY_SIMPLIFICATION[quality.id];
1144
+ let hops = 0;
1145
+ while (fallbackId && hops < 4) {
1146
+ if (TEMPLATE_QUALITIES.has(fallbackId)) {
1147
+ const shape = templateFingering(fallbackId, rootSemitone);
1148
+ if (shape) return { shape, approximate: true };
1149
+ }
1150
+ const suf = CANONICAL_SUFFIX[fallbackId];
1151
+ if (suf !== void 0) {
1152
+ const canonical = rootDisplay + suf;
1153
+ if (OPEN_CHORD_SHAPES[canonical])
1154
+ return {
1155
+ shape: OPEN_CHORD_SHAPES[canonical],
1156
+ approximate: true
1157
+ };
1158
+ }
1159
+ fallbackId = QUALITY_SIMPLIFICATION[fallbackId];
1160
+ hops += 1;
1161
+ }
1162
+ return null;
1163
+ }
1164
+ var DefaultChordDictionary = class {
1165
+ getFingering(chord) {
1166
+ const parsed = parseChordSymbol(chord);
1167
+ if (!parsed) return null;
1168
+ const piano = computePianoVoicing(
1169
+ parsed.rootSemitone,
1170
+ parsed.quality.intervals,
1171
+ parsed.bassSemitone
1172
+ );
1173
+ const guitar = getGuitarFingering(parsed);
1174
+ return {
1175
+ chord: parsed.raw,
1176
+ qualityId: parsed.quality.id,
1177
+ qualityLabel: parsed.quality.label,
1178
+ piano,
1179
+ guitar: guitar ? {
1180
+ frets: guitar.shape.frets,
1181
+ fingers: guitar.shape.fingers,
1182
+ barre: guitar.shape.barre,
1183
+ approximate: guitar.approximate || void 0
1184
+ } : void 0
1185
+ };
1186
+ }
1187
+ };
1188
+ var chordDictionary = new DefaultChordDictionary();
1189
+
1190
+ // src/parser/txt-to-chordpro.ts
1191
+ var DEFAULT_OPTIONS = {
1192
+ source: "auto",
1193
+ detectSections: true,
1194
+ strictChordDetection: true,
1195
+ dehyphenateSyllables: "auto",
1196
+ keepRepeatMarkers: true,
1197
+ partTagNames: { start: "start_of_part", end: "end_of_part" }
1198
+ };
1199
+ var CHORD_ROOT = "(?:[A-G]|D[o\xF3]|R[e\xE9]|Mi|F[a\xE1]|Sol|L[a\xE1]|Si)";
1200
+ 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)?)?";
1201
+ var CHORD_TOKEN_REGEX = new RegExp(`^${CHORD_BODY}$`, "i");
1202
+ var NO_CHORD_TOKEN_REGEX = /^N\.?C\.?$/i;
1203
+ var CHORD_SCAN_REGEX = new RegExp(
1204
+ `(?<=^|\\s)(${CHORD_BODY}|N\\.?C\\.?)(?=\\s|$)`,
1205
+ "gi"
1206
+ );
1207
+ var PUNCTUATION_TOKENS = /* @__PURE__ */ new Set([
1208
+ "|",
1209
+ "%",
1210
+ "-",
1211
+ ".",
1212
+ "...",
1213
+ "/",
1214
+ "x2",
1215
+ "x3",
1216
+ "x4",
1217
+ "x5",
1218
+ "x6",
1219
+ "x7",
1220
+ "x8",
1221
+ "2x",
1222
+ "3x",
1223
+ "4x",
1224
+ "5x",
1225
+ "6x",
1226
+ "7x",
1227
+ "8x"
1228
+ ]);
1229
+ function isValidChordToken(token) {
1230
+ const clean = token.replace(/^[(]/, "").replace(/[)]$/, "");
1231
+ return CHORD_TOKEN_REGEX.test(clean) || NO_CHORD_TOKEN_REGEX.test(clean);
1232
+ }
1233
+ function isChordLine(line, strict) {
1234
+ const trimmed = line.trim();
1235
+ if (!trimmed) return false;
1236
+ const tokens = trimmed.split(/\s+/).filter((t) => !PUNCTUATION_TOKENS.has(t.toLowerCase()));
1237
+ if (tokens.length === 0) return false;
1238
+ const validCount = tokens.filter(isValidChordToken).length;
1239
+ return strict ? validCount === tokens.length : validCount / tokens.length >= 0.8;
1240
+ }
1241
+ function isAlreadyInlineChordPro(line) {
1242
+ const matches = [...line.matchAll(/\[([^\]]+)\]/g)];
1243
+ if (matches.length === 0) return false;
1244
+ return matches.every((m) => isValidChordToken(m[1]));
1245
+ }
1246
+ var METADATA_MAP = {
1247
+ title: "title",
1248
+ t: "title",
1249
+ titulo: "title",
1250
+ t\u00EDtulo: "title",
1251
+ subtitle: "subtitle",
1252
+ st: "subtitle",
1253
+ artist: "artist",
1254
+ a: "artist",
1255
+ artista: "artist",
1256
+ interprete: "artist",
1257
+ int\u00E9rprete: "artist",
1258
+ composer: "composer",
1259
+ music: "composer",
1260
+ compositor: "composer",
1261
+ lyricist: "lyricist",
1262
+ words: "lyricist",
1263
+ letra: "lyricist",
1264
+ letrista: "lyricist",
1265
+ album: "album",
1266
+ \u00E1lbum: "album",
1267
+ key: "key",
1268
+ tom: "key",
1269
+ capo: "capo",
1270
+ capotraste: "capo",
1271
+ capodastro: "capo",
1272
+ tuning: "tuning",
1273
+ afinacao: "tuning",
1274
+ afina\u00E7\u00E3o: "tuning",
1275
+ tempo: "tempo",
1276
+ andamento: "tempo",
1277
+ bpm: "tempo",
1278
+ time: "time",
1279
+ compasso: "time",
1280
+ year: "year",
1281
+ ano: "year",
1282
+ copyright: "copyright",
1283
+ duration: "duration",
1284
+ duracao: "duration",
1285
+ dura\u00E7\u00E3o: "duration"
1286
+ };
1287
+ function stripAccents(s) {
1288
+ return s.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
1289
+ }
1290
+ var SECTION_KEYWORDS = {
1291
+ verse: ["verso", "verse"],
1292
+ chorus: ["refrao", "chorus", "coro"],
1293
+ bridge: [
1294
+ "ponte",
1295
+ "bridge",
1296
+ "pre-refrao",
1297
+ "prerefrao",
1298
+ "prechorus",
1299
+ "pre chorus",
1300
+ "pre-chorus"
1301
+ ],
1302
+ part: [
1303
+ "intro",
1304
+ "introducao",
1305
+ "introducao1",
1306
+ "outro",
1307
+ "final",
1308
+ "coda",
1309
+ "instrumental",
1310
+ "interlude",
1311
+ "interludio",
1312
+ "solo",
1313
+ "solo de guitarra",
1314
+ "solo de violao",
1315
+ "riff"
1316
+ ]
1317
+ };
1318
+ var SECTION_TAGS = {
1319
+ verse: { start: "start_of_verse", end: "end_of_verse" },
1320
+ chorus: { start: "start_of_chorus", end: "end_of_chorus" },
1321
+ bridge: { start: "start_of_bridge", end: "end_of_bridge" },
1322
+ // 'part' tag names are configurable via options.partTagNames
1323
+ part: { start: "start_of_part", end: "end_of_part" }
1324
+ };
1325
+ var BRACKET_HEADER_REGEX = /^\[([^\]]+)]$/;
1326
+ function classifySectionLabel(rawLabel) {
1327
+ let s = rawLabel.trim().replace(/:$/, "").trim();
1328
+ const base = s.replace(/\s*\d+\s*$/, "").trim();
1329
+ const normalized = stripAccents(base).toLowerCase();
1330
+ for (const kind of Object.keys(SECTION_KEYWORDS)) {
1331
+ if (SECTION_KEYWORDS[kind].some(
1332
+ (kw) => normalized === kw || normalized.startsWith(kw)
1333
+ )) {
1334
+ return { kind, label: s };
1335
+ }
1336
+ }
1337
+ return null;
1338
+ }
1339
+ var REPEAT_NOTATION_REGEX = /\s*\(?\b(\d+)\s*[xX]\b\)?\s*$|\s+[xX](\d+)\s*$/;
1340
+ function extractRepeatNotation(line) {
1341
+ const match = line.match(REPEAT_NOTATION_REGEX);
1342
+ if (match) {
1343
+ return {
1344
+ cleanLine: line.slice(0, match.index).replace(/\s+$/, ""),
1345
+ marker: match[0].trim()
1346
+ };
1347
+ }
1348
+ return { cleanLine: line, marker: null };
1349
+ }
1350
+ function countSyllableHyphens(line) {
1351
+ const matches = line.match(/[^\s-]-[^\s-]/g);
1352
+ return matches ? matches.length : 0;
1353
+ }
1354
+ function dehyphenate(line) {
1355
+ return line.replace(/([^\s-])-([^\s-])/g, "$1 $2");
1356
+ }
1357
+ function detectSourceFormat(input) {
1358
+ const lines = input.split("\n");
1359
+ let ugScore = 0;
1360
+ let ccScore = 0;
1361
+ let sawChordLine = false;
1362
+ for (const raw of lines) {
1363
+ const line = raw.trim();
1364
+ if (!line) continue;
1365
+ if (BRACKET_HEADER_REGEX.test(line)) ugScore += 2;
1366
+ if (/^(capo|tuning|key)\s*:/i.test(line)) ugScore += 2;
1367
+ if (/\s[xX]\d+\s*$/.test(raw)) ugScore += 1;
1368
+ if (/^(tom|capotraste|capodastro|int[ée]rprete|compositor|letrista)\s*:/i.test(
1369
+ line
1370
+ ))
1371
+ ccScore += 2;
1372
+ if (/^\(?\d+x\)?\s*$/i.test(line)) ccScore += 1;
1373
+ if (isChordLine(line, true)) {
1374
+ sawChordLine = true;
1375
+ if (countSyllableHyphens(line) === 0) {
1376
+ }
1377
+ } else if (countSyllableHyphens(line) >= 2) {
1378
+ ccScore += 2;
1379
+ }
1380
+ }
1381
+ if (!sawChordLine) return "plain";
1382
+ if (ccScore > ugScore) return "cifraclub";
1383
+ if (ugScore > 0) return "ultimate-guitar";
1384
+ return "plain";
1385
+ }
1386
+ function mergeChordsIntoLyric(chordLine, lyricLine, trailingMarker) {
1387
+ const matches = [...chordLine.matchAll(CHORD_SCAN_REGEX)];
1388
+ let combined = "";
1389
+ let lastIndex = 0;
1390
+ for (const match of matches) {
1391
+ const token = match[0];
1392
+ const index = match.index ?? 0;
1393
+ const rendered = NO_CHORD_TOKEN_REGEX.test(token) ? token.toUpperCase() : token;
1394
+ combined += lyricLine.substring(lastIndex, index);
1395
+ combined += `[${rendered}]`;
1396
+ lastIndex = index;
1397
+ }
1398
+ combined += lyricLine.substring(lastIndex);
1399
+ if (trailingMarker) {
1400
+ combined += ` ${trailingMarker}`;
1401
+ }
1402
+ return combined;
1403
+ }
1404
+ function classify(lines, opts, effectiveSource) {
1405
+ const result = [];
1406
+ const shouldDehyphenateGlobally = opts.dehyphenateSyllables === true || opts.dehyphenateSyllables === "auto" && effectiveSource === "cifraclub";
1407
+ const hyphenThreshold = effectiveSource === "cifraclub" ? 1 : 2;
1408
+ for (let i = 0; i < lines.length; i++) {
1409
+ const raw = lines[i].replace(/\r$/, "");
1410
+ const trimmed = raw.trim();
1411
+ if (trimmed === "") {
1412
+ result.push({ kind: "blank" });
1413
+ continue;
1414
+ }
1415
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1416
+ result.push({ kind: "directive", rendered: trimmed });
1417
+ continue;
1418
+ }
1419
+ if (isAlreadyInlineChordPro(trimmed)) {
1420
+ result.push({ kind: "inline-chordpro", rendered: raw });
1421
+ continue;
1422
+ }
1423
+ const bracketMatch = trimmed.match(BRACKET_HEADER_REGEX);
1424
+ if (bracketMatch) {
1425
+ const classified = classifySectionLabel(bracketMatch[1]);
1426
+ if (classified) {
1427
+ result.push({ kind: "section-header", section: classified });
1428
+ continue;
1429
+ }
1430
+ }
1431
+ const metaMatch = trimmed.match(/^([A-Za-zÀ-ÿ ]+):\s*(.*)/);
1432
+ if (metaMatch) {
1433
+ const key = stripAccents(metaMatch[1].toLowerCase().trim());
1434
+ const value = metaMatch[2];
1435
+ if (METADATA_MAP[key]) {
1436
+ result.push({
1437
+ kind: "metadata",
1438
+ rendered: `{${METADATA_MAP[key]}: ${value}}`
1439
+ });
1440
+ continue;
1441
+ }
1442
+ }
1443
+ if (trimmed.startsWith("#")) {
1444
+ result.push({
1445
+ kind: "comment",
1446
+ rendered: `{comment: ${trimmed.slice(1).trim()}}`
1447
+ });
1448
+ continue;
1449
+ }
1450
+ if (trimmed.length <= 28 && !isChordLine(trimmed, opts.strictChordDetection)) {
1451
+ const classified = classifySectionLabel(trimmed);
1452
+ if (classified) {
1453
+ result.push({ kind: "section-header", section: classified });
1454
+ continue;
1455
+ }
1456
+ }
1457
+ if (isChordLine(trimmed, opts.strictChordDetection)) {
1458
+ const nextRaw = i + 1 < lines.length ? lines[i + 1].replace(/\r$/, "") : null;
1459
+ const nextIsChord = nextRaw !== null && isChordLine(nextRaw.trim(), opts.strictChordDetection);
1460
+ const nextIsBlank = nextRaw !== null && nextRaw.trim() === "";
1461
+ if (nextRaw === null || nextIsChord || nextIsBlank) {
1462
+ const tokens = trimmed.split(/\s+/);
1463
+ result.push({
1464
+ kind: "chord-only",
1465
+ rendered: tokens.map(
1466
+ (t) => NO_CHORD_TOKEN_REGEX.test(t) ? `[${t.toUpperCase()}]` : `[${t}]`
1467
+ ).join(" ")
1468
+ });
1469
+ continue;
1470
+ }
1471
+ let lyricLine = nextRaw;
1472
+ let marker = null;
1473
+ if (opts.keepRepeatMarkers) {
1474
+ const extracted = extractRepeatNotation(lyricLine);
1475
+ lyricLine = extracted.cleanLine;
1476
+ marker = extracted.marker;
1477
+ }
1478
+ const hyphenCount = countSyllableHyphens(lyricLine);
1479
+ if (shouldDehyphenateGlobally && hyphenCount >= hyphenThreshold) {
1480
+ lyricLine = dehyphenate(lyricLine);
1481
+ } else if (hyphenCount >= 2 && effectiveSource !== "ultimate-guitar") {
1482
+ lyricLine = dehyphenate(lyricLine);
1483
+ }
1484
+ const merged = mergeChordsIntoLyric(raw, lyricLine, marker);
1485
+ result.push({ kind: "chord-lyric", rendered: merged });
1486
+ i++;
1487
+ continue;
1488
+ }
1489
+ result.push({ kind: "lyric", rendered: raw });
1490
+ }
1491
+ return result;
1492
+ }
1493
+ function render(lines, opts) {
1494
+ const out = [];
1495
+ let openSection = null;
1496
+ const tagsFor = (kind) => kind === "part" ? opts.partTagNames : SECTION_TAGS[kind];
1497
+ const closeSection = () => {
1498
+ if (openSection) {
1499
+ out.push(`{${tagsFor(openSection.kind).end}}`);
1500
+ openSection = null;
1501
+ }
1502
+ };
1503
+ for (const line of lines) {
1504
+ if (line.kind === "section-header" && line.section) {
1505
+ if (opts.detectSections) {
1506
+ closeSection();
1507
+ const tags = tagsFor(line.section.kind);
1508
+ out.push(`{${tags.start}: ${line.section.label}}`);
1509
+ openSection = line.section;
1510
+ } else {
1511
+ out.push(`{comment: ${line.section.label}}`);
1512
+ }
1513
+ continue;
1514
+ }
1515
+ if (line.kind === "blank") {
1516
+ if (opts.detectSections) closeSection();
1517
+ out.push("");
1518
+ continue;
1519
+ }
1520
+ out.push(line.rendered ?? "");
1521
+ }
1522
+ if (opts.detectSections) closeSection();
1523
+ return out.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
1524
+ }
1525
+ function extractTitle(chordpro) {
1526
+ const match = chordpro.match(/\{title:\s*(.*)\}/i);
1527
+ return match ? match[1].trim() : null;
1528
+ }
1529
+ function slugifyTitle(title) {
1530
+ if (!title) return "cifra_convertida";
1531
+ return stripAccents(title).trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_]/g, "") || "cifra";
1532
+ }
1533
+ function convertToChordProDetailed(input, options = {}) {
1534
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1535
+ const warnings = [];
1536
+ if (!input || !input.trim()) {
1537
+ return {
1538
+ chordpro: "",
1539
+ title: null,
1540
+ detectedSource: "plain",
1541
+ warnings: ["Empty input."]
1542
+ };
1543
+ }
1544
+ const detectedSource = opts.source === "auto" ? detectSourceFormat(input) : opts.source;
1545
+ const lines = input.split("\n");
1546
+ const parsed = classify(lines, opts, detectedSource);
1547
+ const chordpro = render(parsed, opts);
1548
+ const title = extractTitle(chordpro);
1549
+ if (!title)
1550
+ warnings.push("No {title: ...} directive was detected in the input.");
1551
+ const shorthandTokens = /* @__PURE__ */ new Set();
1552
+ for (const raw of lines) {
1553
+ if (isChordLine(raw.trim(), opts.strictChordDetection)) {
1554
+ for (const m of raw.matchAll(CHORD_SCAN_REGEX)) {
1555
+ if (/^[A-G][#b]?4$/i.test(m[0]) || /^[A-G][#b]?9$/i.test(m[0])) {
1556
+ shorthandTokens.add(m[0]);
1557
+ }
1558
+ }
1559
+ }
1560
+ }
1561
+ if (shorthandTokens.size > 0) {
1562
+ warnings.push(
1563
+ `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.`
1564
+ );
1565
+ }
1566
+ return { chordpro, title, detectedSource, warnings };
1567
+ }
1568
+ function toChordPro(input, options) {
1569
+ return convertToChordProDetailed(input, options).chordpro;
1570
+ }
1571
+
1572
+ // src/renderer/ChordProRenderer.tsx
1573
+ var import_lucide_react = require("lucide-react");
1574
+ var import_react2 = __toESM(require("react"));
1575
+ var import_react_youtube = __toESM(require("react-youtube"));
1576
+
1577
+ // src/renderer/ChordRoll.tsx
1578
+ var import_react = require("react");
1579
+ var import_jsx_runtime = require("react/jsx-runtime");
1580
+ function GuitarDiagram({
1581
+ frets,
1582
+ fingers,
1583
+ barre
1584
+ }) {
1585
+ const maxFret2 = Math.max(...frets);
1586
+ let startFret = 1;
1587
+ if (maxFret2 > 4) {
1588
+ const minFret = Math.min(...frets.filter((f) => f > 0));
1589
+ startFret = minFret;
1590
+ }
1591
+ const numFrets = 4;
1592
+ const width = 100;
1593
+ const height = 110;
1594
+ const getStringX = (index) => 14 + index * 14;
1595
+ const getFretY = (index) => 22 + index * 20;
1596
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1597
+ "svg",
1598
+ {
1599
+ width,
1600
+ height,
1601
+ className: "text-m3-text dark:text-m3-dark-text select-none",
1602
+ children: [
1603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1604
+ "line",
1605
+ {
1606
+ x1: getStringX(0),
1607
+ y1: getFretY(0) - (startFret === 1 ? 3 : 0),
1608
+ x2: getStringX(5),
1609
+ y2: getFretY(0) - (startFret === 1 ? 3 : 0),
1610
+ className: "stroke-zinc-800 dark:stroke-zinc-200",
1611
+ strokeWidth: startFret === 1 ? 3.5 : 1.5
1612
+ }
1613
+ ),
1614
+ startFret > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1615
+ "text",
1616
+ {
1617
+ x: getStringX(0) - 4,
1618
+ y: getFretY(0) + 12,
1619
+ fontSize: "8",
1620
+ className: "font-mono font-black fill-m3-primary dark:fill-m3-dark-primary",
1621
+ textAnchor: "end",
1622
+ children: [
1623
+ startFret,
1624
+ "\xAA"
1625
+ ]
1626
+ }
1627
+ ),
1628
+ [0, 1, 2, 3, 4, 5].map((idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1629
+ "line",
1630
+ {
1631
+ x1: getStringX(idx),
1632
+ y1: getFretY(0),
1633
+ x2: getStringX(idx),
1634
+ y2: getFretY(numFrets),
1635
+ className: "stroke-zinc-400 dark:stroke-zinc-600",
1636
+ strokeOpacity: "0.5",
1637
+ strokeWidth: "1.2"
1638
+ },
1639
+ idx
1640
+ )),
1641
+ [0, 1, 2, 3, 4].map((idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1642
+ "line",
1643
+ {
1644
+ x1: getStringX(0),
1645
+ y1: getFretY(idx),
1646
+ x2: getStringX(5),
1647
+ y2: getFretY(idx),
1648
+ className: "stroke-zinc-400 dark:stroke-zinc-600",
1649
+ strokeOpacity: "0.4",
1650
+ strokeWidth: "1"
1651
+ },
1652
+ idx
1653
+ )),
1654
+ barre !== void 0 && (() => {
1655
+ const barreFretInWindow = barre - startFret;
1656
+ if (barreFretInWindow >= 0 && barreFretInWindow < numFrets) {
1657
+ const y = getFretY(barreFretInWindow) + 10;
1658
+ const startStr = frets.findIndex((f) => f === barre);
1659
+ const endStr = 5;
1660
+ if (startStr !== -1) {
1661
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1662
+ "rect",
1663
+ {
1664
+ x: getStringX(startStr) - 4,
1665
+ y: y - 4,
1666
+ width: getStringX(endStr) - getStringX(startStr) + 8,
1667
+ height: 8,
1668
+ rx: 4,
1669
+ className: "fill-m3-primary dark:fill-m3-dark-primary opacity-80"
1670
+ }
1671
+ );
1672
+ }
1673
+ }
1674
+ return null;
1675
+ })(),
1676
+ frets.map((fret, stringIdx) => {
1677
+ if (fret === -1) {
1678
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1679
+ "text",
1680
+ {
1681
+ x: getStringX(stringIdx),
1682
+ y: 12,
1683
+ fontSize: "10",
1684
+ fontWeight: "bold",
1685
+ className: "fill-red-500 font-black",
1686
+ textAnchor: "middle",
1687
+ children: "\xD7"
1688
+ },
1689
+ stringIdx
1690
+ );
1691
+ }
1692
+ if (fret === 0) {
1693
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1694
+ "circle",
1695
+ {
1696
+ cx: getStringX(stringIdx),
1697
+ cy: 10,
1698
+ r: 2,
1699
+ fill: "none",
1700
+ className: "stroke-emerald-500",
1701
+ strokeWidth: "1.2"
1702
+ },
1703
+ stringIdx
1704
+ );
1705
+ }
1706
+ const fretInWindow = fret - startFret;
1707
+ if (fretInWindow >= 0 && fretInWindow < numFrets) {
1708
+ const cx = getStringX(stringIdx);
1709
+ const cy = getFretY(fretInWindow) + 10;
1710
+ const finger = fingers ? fingers[stringIdx] : 0;
1711
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { children: [
1712
+ !(barre !== void 0 && fret === barre && stringIdx >= frets.findIndex((f) => f === barre)) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1713
+ "circle",
1714
+ {
1715
+ cx,
1716
+ cy,
1717
+ r: 5,
1718
+ className: "fill-m3-primary dark:fill-m3-dark-primary"
1719
+ }
1720
+ ),
1721
+ finger > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1722
+ "text",
1723
+ {
1724
+ x: cx,
1725
+ y: cy + 2.5,
1726
+ fontSize: "6.5",
1727
+ fontWeight: "bold",
1728
+ fill: "white",
1729
+ textAnchor: "middle",
1730
+ className: "font-bold fill-white",
1731
+ children: finger
1732
+ }
1733
+ )
1734
+ ] }, stringIdx);
1735
+ }
1736
+ return null;
1737
+ })
1738
+ ]
1739
+ }
1740
+ );
1741
+ }
1742
+ function PianoDiagram({ highlightKeys }) {
1743
+ const whiteKeySemitones = [
1744
+ 0,
1745
+ 2,
1746
+ 4,
1747
+ 5,
1748
+ 7,
1749
+ 9,
1750
+ 11,
1751
+ // Octave 1
1752
+ 12,
1753
+ 14,
1754
+ 16,
1755
+ 17,
1756
+ 19,
1757
+ 21,
1758
+ 23
1759
+ // Octave 2
1760
+ ];
1761
+ const keyWidth = 14;
1762
+ const keyHeight = 56;
1763
+ const blackWidth = 9;
1764
+ const blackHeight = 34;
1765
+ const blackKeySemitones = [1, 3, 6, 8, 10, 13, 15, 18, 20, 22];
1766
+ const getBlackKeyX = (semitone) => {
1767
+ const octave = Math.floor(semitone / 12);
1768
+ const semitoneInOctave = semitone % 12;
1769
+ let whiteIndexBefore = 0;
1770
+ if (semitoneInOctave === 1) whiteIndexBefore = 1;
1771
+ else if (semitoneInOctave === 3) whiteIndexBefore = 2;
1772
+ else if (semitoneInOctave === 6) whiteIndexBefore = 4;
1773
+ else if (semitoneInOctave === 8) whiteIndexBefore = 5;
1774
+ else if (semitoneInOctave === 10) whiteIndexBefore = 6;
1775
+ const absoluteWhiteIndex = octave * 7 + whiteIndexBefore;
1776
+ return absoluteWhiteIndex * keyWidth - blackWidth / 2;
1777
+ };
1778
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1779
+ "svg",
1780
+ {
1781
+ width: 14 * keyWidth + 2,
1782
+ height: keyHeight + 4,
1783
+ className: "text-m3-text dark:text-m3-dark-text p-1 rounded-lg bg-zinc-100 dark:bg-zinc-900 border border-m3-border dark:border-m3-dark-border select-none",
1784
+ children: [
1785
+ whiteKeySemitones.map((st, idx) => {
1786
+ const isHighlighted = highlightKeys.includes(st);
1787
+ const x = idx * keyWidth + 1;
1788
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { children: [
1789
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1790
+ "rect",
1791
+ {
1792
+ x,
1793
+ y: 2,
1794
+ width: keyWidth - 1,
1795
+ height: keyHeight,
1796
+ rx: 1.5,
1797
+ className: `${isHighlighted ? "fill-m3-primary dark:fill-m3-dark-primary" : "fill-white dark:fill-zinc-800"} stroke-zinc-300 dark:stroke-zinc-700`,
1798
+ strokeWidth: "0.8"
1799
+ }
1800
+ ),
1801
+ isHighlighted && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1802
+ "circle",
1803
+ {
1804
+ cx: x + (keyWidth - 1) / 2,
1805
+ cy: keyHeight - 8,
1806
+ r: 2,
1807
+ className: "fill-white dark:fill-zinc-900"
1808
+ }
1809
+ )
1810
+ ] }, st);
1811
+ }),
1812
+ blackKeySemitones.map((st) => {
1813
+ const isHighlighted = highlightKeys.includes(st);
1814
+ const x = getBlackKeyX(st) + 1;
1815
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1816
+ "rect",
1817
+ {
1818
+ x,
1819
+ y: 2,
1820
+ width: blackWidth,
1821
+ height: blackHeight,
1822
+ rx: 1,
1823
+ className: `${isHighlighted ? "fill-m3-primary dark:fill-m3-dark-primary" : "fill-zinc-800 dark:fill-zinc-950"} stroke-zinc-900 dark:stroke-black`,
1824
+ strokeWidth: "0.8"
1825
+ }
1826
+ ) }, st);
1827
+ })
1828
+ ]
1829
+ }
1830
+ );
1831
+ }
1832
+ function ChordRoll({
1833
+ uniqueChords,
1834
+ transposeVal,
1835
+ capoVal = 0,
1836
+ onChordClick,
1837
+ instrument,
1838
+ showDiagrams,
1839
+ showChords
1840
+ }) {
1841
+ const isGuitar = instrument === "guitar";
1842
+ const effectiveTranspose = transposeVal - (isGuitar ? capoVal : 0);
1843
+ const chordItems = (0, import_react.useMemo)(() => {
1844
+ return uniqueChords.map((chord) => {
1845
+ const transposed = transposeChord(chord, effectiveTranspose);
1846
+ const fingering = chordDictionary.getFingering(transposed);
1847
+ return {
1848
+ original: chord,
1849
+ transposed,
1850
+ fingering
1851
+ };
1852
+ });
1853
+ }, [uniqueChords, effectiveTranspose]);
1854
+ if (uniqueChords.length === 0 || !showDiagrams || !showChords) return null;
1855
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1856
+ "div",
1857
+ {
1858
+ className: "w-full select-none mb-6",
1859
+ onTouchStart: (e) => e.stopPropagation(),
1860
+ onTouchMove: (e) => e.stopPropagation(),
1861
+ onTouchEnd: (e) => e.stopPropagation(),
1862
+ children: [
1863
+ isGuitar && capoVal > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-4 mb-2 flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-[11px] font-bold text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/50 px-2.5 py-0.5 rounded-md border border-amber-200 dark:border-amber-900/40", children: [
1864
+ "Capo na ",
1865
+ capoVal,
1866
+ "\xAA casa \u2014 acordes em formato relativo"
1867
+ ] }) }),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-row overflow-x-auto gap-6 py-2 px-4 no-scrollbar scroll-smooth", children: chordItems.map((item, idx) => {
1869
+ const isPiano = instrument === "piano";
1870
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1871
+ "div",
1872
+ {
1873
+ onClick: () => onChordClick?.(item.transposed),
1874
+ className: `flex flex-col items-center p-2 transition-all cursor-pointer hover:opacity-85 shrink-0 ${isPiano ? "w-48" : "w-24"}`,
1875
+ title: "Clique para ver detalhes do acorde",
1876
+ children: [
1877
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs font-black text-m3-primary dark:text-m3-dark-primary font-mono mb-1.5", children: item.transposed }),
1878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center flex-1 min-h-17.5", children: item.fingering ? isPiano && item.fingering.piano ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1879
+ PianoDiagram,
1880
+ {
1881
+ highlightKeys: item.fingering.piano.highlightKeys
1882
+ }
1883
+ ) : !isPiano && item.fingering.guitar ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1884
+ GuitarDiagram,
1885
+ {
1886
+ frets: item.fingering.guitar.frets,
1887
+ fingers: item.fingering.guitar.fingers,
1888
+ barre: item.fingering.guitar.barre
1889
+ }
1890
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-[10px] text-zinc-400 font-mono italic", children: "Sem visual" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-[10px] text-zinc-400 font-mono italic", children: "N\xE3o registado" }) }),
1891
+ item.fingering?.piano && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[9px] font-bold text-m3-secondary dark:text-m3-dark-secondary mt-1.5 font-mono whitespace-nowrap overflow-hidden text-ellipsis max-w-full", children: item.fingering.piano.notes.join(" - ") })
1892
+ ]
1893
+ },
1894
+ idx
1895
+ );
1896
+ }) })
1897
+ ]
1898
+ }
1899
+ );
1900
+ }
1901
+
1902
+ // src/renderer/ChordProRenderer.tsx
1903
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1904
+ var YT_ID_REGEX = /(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([^&?]+)/;
1905
+ function extractYoutubeId(urlOrId) {
1906
+ const match = urlOrId.match(YT_ID_REGEX);
1907
+ return match?.[1] || urlOrId;
1908
+ }
1909
+ function getDuration(duration) {
1910
+ const seconds = Number(duration);
1911
+ if (isNaN(seconds)) {
1912
+ return "00:00";
1913
+ } else {
1914
+ return `${Math.trunc(seconds / 60)}:${seconds % 60}`;
1915
+ }
1916
+ }
1917
+ var ChordProRenderer = import_react2.default.memo(
1918
+ ({
1919
+ content,
1920
+ showChords,
1921
+ transposeVal = 0,
1922
+ capoVal = 0,
1923
+ onTransposeChange,
1924
+ onCapoChange,
1925
+ twoColumnLayout = false,
1926
+ fontSize,
1927
+ instrument = "guitar",
1928
+ showDiagrams = false,
1929
+ fileName,
1930
+ showYoutubePlayer: showYoutubePlayerProp,
1931
+ onShowYoutubePlayerChange,
1932
+ scrollContainerRef
1933
+ }) => {
1934
+ const [ytPlayerRef, setYtPlayerRef] = (0, import_react2.useState)(null);
1935
+ const [isPlayingYoutube, setIsPlayingYoutube] = (0, import_react2.useState)(false);
1936
+ const [isYoutubeRepeat, setIsYoutubeRepeat] = (0, import_react2.useState)(false);
1937
+ const [showYoutubeInternal, setShowYoutubeInternal] = (0, import_react2.useState)(false);
1938
+ const showYoutubePlayer = showYoutubePlayerProp ?? showYoutubeInternal;
1939
+ const setShowYoutubePlayer = (0, import_react2.useCallback)(
1940
+ (val) => {
1941
+ setShowYoutubeInternal(val);
1942
+ onShowYoutubePlayerChange?.(val);
1943
+ },
1944
+ [onShowYoutubePlayerChange]
1945
+ );
1946
+ const isYoutubeRepeatRef = (0, import_react2.useRef)(isYoutubeRepeat);
1947
+ isYoutubeRepeatRef.current = isYoutubeRepeat;
1948
+ const parsedSong = (0, import_react2.useMemo)(() => {
1949
+ return parseChordPro(content);
1950
+ }, [content]);
1951
+ const { metadata } = parsedSong;
1952
+ const isGuitar = instrument === "guitar";
1953
+ const effectiveCapo = isGuitar ? capoVal : 0;
1954
+ const effectiveTranspose = transposeVal - effectiveCapo;
1955
+ const soundingKey = (0, import_react2.useMemo)(() => {
1956
+ return transposeChord(metadata.key || "C", transposeVal);
1957
+ }, [metadata.key, transposeVal]);
1958
+ const renderedKey = (0, import_react2.useMemo)(() => {
1959
+ return transposeChord(metadata.key || "C", effectiveTranspose);
1960
+ }, [metadata.key, effectiveTranspose]);
1961
+ const resolvedUniqueChords = (0, import_react2.useMemo)(() => {
1962
+ const chords = /* @__PURE__ */ new Set();
1963
+ for (const section of parsedSong.sections) {
1964
+ for (const line of section.lines) {
1965
+ if (line.segments) {
1966
+ for (const seg of line.segments) {
1967
+ if (seg.chord) chords.add(seg.chord);
1968
+ }
1969
+ }
1970
+ if (line.measures) {
1971
+ for (const m of line.measures) {
1972
+ for (const c of m.chords) {
1973
+ if (c.chord) chords.add(c.chord);
1974
+ }
1975
+ }
1976
+ }
1977
+ }
1978
+ }
1979
+ return Array.from(chords);
1980
+ }, [parsedSong]);
1981
+ const [selectedChord, setSelectedChord] = (0, import_react2.useState)(null);
1982
+ const [modalInstrument, setModalInstrument] = (0, import_react2.useState)(
1983
+ instrument
1984
+ );
1985
+ const chordFingering = (0, import_react2.useMemo)(() => {
1986
+ if (!selectedChord) return null;
1987
+ return chordDictionary.getFingering(selectedChord);
1988
+ }, [selectedChord]);
1989
+ const handleChordClick = (chord) => {
1990
+ setModalInstrument(instrument);
1991
+ setSelectedChord(chord);
1992
+ };
1993
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative flex flex-col flex-1 overflow-hidden h-full", children: [
1994
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1995
+ "div",
1996
+ {
1997
+ ref: scrollContainerRef,
1998
+ className: "flex-1 overflow-y-auto px-4 py-4 sm:px-6 bg-slate-50 dark:bg-slate-950 print-page select-text leading-relaxed no-scrollbar relative",
1999
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-3xl mx-auto print-song-card", children: [
2000
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-6 border-b border-neutral-100 dark:border-slate-800 pb-5 select-none", children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [
2002
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
2003
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "text-2xl font-bold tracking-tight text-neutral-900 dark:text-white", children: metadata.title }),
2004
+ metadata.subtitle && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-[15px] font-medium text-neutral-600 dark:text-neutral-400 mt-1", children: metadata.subtitle }),
2005
+ (metadata.artist || metadata.composer) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-neutral-500 mt-2 font-medium flex-wrap", children: [
2006
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.User, { className: "w-3.5 h-3.5 text-[#0284c7]" }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2008
+ "Por:",
2009
+ " ",
2010
+ [metadata.artist, metadata.composer].filter(Boolean).join(" / ")
2011
+ ] })
2012
+ ] }),
2013
+ metadata.translator && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-neutral-500 mt-2 font-medium flex-wrap", children: [
2014
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.User, { className: "w-3.5 h-3.5 text-[#0284c7]" }),
2015
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2016
+ "Traduzido por: ",
2017
+ metadata.translator
2018
+ ] })
2019
+ ] }),
2020
+ metadata.album && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-neutral-500 mt-1 font-medium", children: [
2021
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Disc, { className: "w-3.5 h-3.5 text-[#0284c7]" }),
2022
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2023
+ "\xC1lbum: ",
2024
+ metadata.album
2025
+ ] })
2026
+ ] })
2027
+ ] }),
2028
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5 justify-end", children: [
2029
+ metadata.songNumber && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-neutral-100 dark:bg-slate-800 text-neutral-600 dark:text-neutral-400 px-2 py-1 rounded-lg border border-neutral-200 dark:border-slate-700", children: [
2030
+ "N\xBA ",
2031
+ metadata.songNumber
2032
+ ] }),
2033
+ (metadata.key || transposeVal !== 0) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300 px-2.5 py-1 rounded-lg border border-indigo-100 dark:border-indigo-950/50 flex items-center gap-1", children: [
2034
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Key, { className: "w-3 h-3" }),
2035
+ "Tom: ",
2036
+ soundingKey
2037
+ ] }),
2038
+ effectiveCapo > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 px-2.5 py-1 rounded-lg border border-amber-100 dark:border-amber-950/50", children: [
2039
+ "Capo: ",
2040
+ effectiveCapo,
2041
+ "\xAA casa"
2042
+ ] }),
2043
+ effectiveCapo > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 px-2.5 py-1 rounded-lg border border-emerald-100 dark:border-emerald-950/50", children: [
2044
+ "Formato: ",
2045
+ renderedKey
2046
+ ] }),
2047
+ metadata.originalKey && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-purple-50 dark:bg-purple-950/40 text-purple-700 dark:text-purple-300 px-2.5 py-1 rounded-lg border border-purple-100 dark:border-purple-950/50", children: [
2048
+ "Tom Orig: ",
2049
+ metadata.originalKey
2050
+ ] }),
2051
+ metadata.capo && metadata.capo !== "0" && effectiveCapo === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 px-2.5 py-1 rounded-lg border border-amber-100 dark:border-amber-950/50", children: [
2052
+ "Capo Orig: ",
2053
+ metadata.capo,
2054
+ "\xAA casa"
2055
+ ] }),
2056
+ metadata.tempo && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 px-2.5 py-1 rounded-lg border border-emerald-100 dark:border-emerald-950/50 flex items-center gap-1", children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Flame, { className: "w-3 h-3" }),
2058
+ metadata.tempo,
2059
+ " BPM"
2060
+ ] }),
2061
+ metadata.time && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] font-bold bg-cyan-50 dark:bg-cyan-950/40 text-cyan-700 dark:text-cyan-300 px-2.5 py-1 rounded-lg border border-cyan-100 dark:border-cyan-950/50", children: metadata.time }),
2062
+ metadata.ccli && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-bold bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 px-2.5 py-1 rounded-lg border border-slate-200 dark:border-slate-700", children: [
2063
+ "CCLI: ",
2064
+ metadata.ccli
2065
+ ] })
2066
+ ] })
2067
+ ] }),
2068
+ (metadata.copyright || metadata.duration) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-4 text-[10px] text-neutral-400 dark:text-neutral-500 mt-3 pt-3 border-t border-neutral-100 dark:border-slate-800/50", children: [
2069
+ metadata.copyright && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2070
+ "\xA9 ",
2071
+ metadata.copyright
2072
+ ] }),
2073
+ metadata.duration && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2074
+ "Dura\xE7\xE3o: ",
2075
+ getDuration(metadata.duration)
2076
+ ] })
2077
+ ] }),
2078
+ (transposeVal !== 0 || effectiveCapo !== 0) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 bg-indigo-50 dark:bg-indigo-950/40 text-xs px-3 py-2 rounded-xl text-indigo-700 dark:text-indigo-300 flex items-center justify-between border border-indigo-100 dark:border-indigo-950/50 flex-wrap gap-2", children: [
2079
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 flex-wrap", children: [
2080
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-semibold", children: [
2081
+ "Tom:",
2082
+ " ",
2083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-indigo-600 dark:text-indigo-400 font-bold bg-white dark:bg-zinc-900 px-2 py-0.5 rounded border ml-0.5 text-xs", children: soundingKey })
2084
+ ] }),
2085
+ effectiveCapo > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-semibold text-amber-700 dark:text-amber-300", children: [
2086
+ "\u2022 Capo:",
2087
+ " ",
2088
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-amber-700 dark:text-amber-300 font-bold bg-amber-100 dark:bg-amber-900/60 px-2 py-0.5 rounded border border-amber-300 dark:border-amber-800 text-xs ml-0.5", children: [
2089
+ effectiveCapo,
2090
+ "\xAA casa (",
2091
+ renderedKey,
2092
+ ")"
2093
+ ] })
2094
+ ] })
2095
+ ] }),
2096
+ (onTransposeChange || onCapoChange) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2097
+ "button",
2098
+ {
2099
+ type: "button",
2100
+ onClick: () => {
2101
+ onTransposeChange?.(0);
2102
+ onCapoChange?.(0);
2103
+ },
2104
+ className: "text-[10px] font-bold hover:underline underline-offset-2 uppercase text-indigo-600 dark:text-indigo-400 cursor-pointer",
2105
+ children: "Repor Tom / Capo"
2106
+ }
2107
+ )
2108
+ ] })
2109
+ ] }),
2110
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2111
+ ChordRoll,
2112
+ {
2113
+ uniqueChords: resolvedUniqueChords,
2114
+ transposeVal,
2115
+ capoVal: effectiveCapo,
2116
+ onChordClick: handleChordClick,
2117
+ instrument,
2118
+ showDiagrams,
2119
+ showChords
2120
+ }
2121
+ ),
2122
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2123
+ "div",
2124
+ {
2125
+ className: `font-sans leading-relaxed text-sm select-text ${twoColumnLayout ? "columns-1 sm:columns-2 gap-8 space-y-6 [column-fill:_balance]" : "space-y-6"}`,
2126
+ style: fontSize ? { fontSize: `${fontSize}px` } : void 0,
2127
+ children: parsedSong.sections.map((section, secIdx) => {
2128
+ const isChorus = section.type === "chorus";
2129
+ const isBridge = section.type === "bridge";
2130
+ if (section.type === "new_song") {
2131
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2132
+ "div",
2133
+ {
2134
+ className: "w-full my-12 flex items-center justify-center select-none break-before-column",
2135
+ children: [
2136
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 border-t-2 border-slate-200 dark:border-slate-800 border-dashed" }),
2137
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Music, { className: "w-5 h-5 mx-4 text-slate-300 dark:text-slate-700" }),
2138
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 border-t-2 border-slate-200 dark:border-slate-800 border-dashed" })
2139
+ ]
2140
+ },
2141
+ secIdx
2142
+ );
2143
+ }
2144
+ if (section.type === "grid") {
2145
+ const maxMeasures = Math.max(
2146
+ 1,
2147
+ ...section.lines.map((l) => l.measures?.length || 1)
2148
+ );
2149
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2150
+ "div",
2151
+ {
2152
+ className: `bg-slate-100/60 dark:bg-slate-900/40 p-4 rounded-xl border border-slate-200/80 dark:border-slate-800/60 my-6 ${twoColumnLayout ? "break-inside-avoid-column inline-block w-full" : ""}`,
2153
+ children: [
2154
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
2155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5 text-[11px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider select-none", children: [
2156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.LayoutGrid, { className: "w-3.5 h-3.5" }),
2157
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: section.label || "Instrumental" })
2158
+ ] }),
2159
+ section.repeat && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-black bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 px-2 py-0.5 rounded uppercase border border-indigo-200 dark:border-indigo-800/50", children: [
2160
+ "Repetir ",
2161
+ section.repeat,
2162
+ "x"
2163
+ ] })
2164
+ ] }),
2165
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-1.5 font-medium", children: section.lines.map((line, lineIdx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2166
+ LineRenderer,
2167
+ {
2168
+ line,
2169
+ showChords,
2170
+ transpose: effectiveTranspose,
2171
+ onChordClick: handleChordClick,
2172
+ maxGridMeasures: maxMeasures
2173
+ },
2174
+ lineIdx
2175
+ )) })
2176
+ ]
2177
+ },
2178
+ secIdx
2179
+ );
2180
+ }
2181
+ if (isChorus || isBridge) {
2182
+ const borderColor = isChorus ? "border-m3-primary/30 dark:border-m3-dark-primary/30" : "border-amber-500/30 dark:border-amber-400/30";
2183
+ const labelColor = isChorus ? "text-m3-text dark:text-m3-dark-text" : "text-amber-700 dark:text-amber-400";
2184
+ const iconColor = isChorus ? "text-m3-secondary" : "text-amber-500";
2185
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2186
+ "div",
2187
+ {
2188
+ className: `pl-4 md:pl-6 border-l-2 my-6 ${borderColor} ${twoColumnLayout ? "break-inside-avoid-column inline-block w-full" : ""}`,
2189
+ children: [
2190
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2191
+ "div",
2192
+ {
2193
+ className: `flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-wider mb-3 select-none ${labelColor}`,
2194
+ children: [
2195
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2196
+ import_lucide_react.Music,
2197
+ {
2198
+ className: `w-3.5 h-3.5 shrink-0 ${iconColor}`
2199
+ }
2200
+ ),
2201
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: section.label || (isChorus ? "Refr\xE3o" : "Ponte") }),
2202
+ section.repeat && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2203
+ "span",
2204
+ {
2205
+ className: `ml-2 px-2 py-0.5 rounded text-[10px] font-black border ${isChorus ? "bg-blue-100/50 border-blue-200 text-blue-700 dark:bg-blue-900/30 dark:border-blue-800 dark:text-blue-300" : "bg-amber-100/50 border-amber-200 text-amber-700 dark:bg-amber-900/30 dark:border-amber-800 dark:text-amber-300"}`,
2206
+ children: [
2207
+ section.repeat,
2208
+ "x"
2209
+ ]
2210
+ }
2211
+ )
2212
+ ]
2213
+ }
2214
+ ),
2215
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-4 font-medium", children: section.lines.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2216
+ "div",
2217
+ {
2218
+ className: `text-xs italic my-1 opacity-70 ${labelColor}`,
2219
+ children: "(Repete o refr\xE3o)"
2220
+ }
2221
+ ) : section.lines.map((line, lineIdx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2222
+ LineRenderer,
2223
+ {
2224
+ line,
2225
+ showChords,
2226
+ transpose: effectiveTranspose,
2227
+ onChordClick: handleChordClick
2228
+ },
2229
+ lineIdx
2230
+ )) })
2231
+ ]
2232
+ },
2233
+ secIdx
2234
+ );
2235
+ }
2236
+ if (section.type === "tab") {
2237
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2238
+ "div",
2239
+ {
2240
+ className: `bg-slate-900 p-4 pt-6 rounded-xl shadow-inner border border-slate-800 my-6 select-text relative group ${twoColumnLayout ? "break-inside-avoid-column inline-block w-full" : ""}`,
2241
+ children: [
2242
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute top-0 left-4 -translate-y-1/2 bg-slate-800 text-[10px] font-bold text-slate-300 uppercase tracking-widest px-2 py-0.5 rounded border border-slate-700 shadow-sm select-none", children: section.label || "Tablatura" }),
2243
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "overflow-x-auto no-scrollbar", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { className: "font-mono text-[13px] md:text-sm text-slate-300 leading-relaxed whitespace-pre drop-shadow-sm", children: section.lines.map((line) => line.text || "").join("\n") }) })
2244
+ ]
2245
+ },
2246
+ secIdx
2247
+ );
2248
+ }
2249
+ if (section.type === "comment") {
2250
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2251
+ "div",
2252
+ {
2253
+ className: `my-2 select-none pl-3 text-[11px] italic text-m3-secondary/70 dark:text-m3-dark-secondary/70 ${twoColumnLayout ? "break-inside-avoid-column inline-block w-full" : ""}`,
2254
+ children: section.lines.map((l) => l.text).join(", ")
2255
+ },
2256
+ secIdx
2257
+ );
2258
+ }
2259
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2260
+ "div",
2261
+ {
2262
+ className: `relative pl-6 sm:pl-8 border-l border-m3-border/30 dark:border-m3-dark-border/30 py-1.5 my-4 ${twoColumnLayout ? "break-inside-avoid-column inline-block w-full" : ""}`,
2263
+ children: [
2264
+ section.label && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute -left-0.5 top-0 bottom-0 w-0.5 bg-m3-secondary/20 dark:bg-m3-dark-secondary/20 rounded-full" }),
2265
+ section.label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5 text-[11px] font-bold text-m3-text/60 dark:text-m3-dark-text/60 uppercase tracking-wider mb-3 select-none", children: [
2266
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Music, { className: "w-3.5 h-3.5 text-m3-secondary/60 shrink-0" }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: section.label }),
2268
+ section.repeat && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ml-2 bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 px-2 py-0.5 rounded text-[10px] font-black border border-slate-200 dark:border-slate-700", children: [
2269
+ section.repeat,
2270
+ "x"
2271
+ ] })
2272
+ ] }),
2273
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-4", children: section.lines.map((line, lineIdx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2274
+ LineRenderer,
2275
+ {
2276
+ line,
2277
+ showChords,
2278
+ transpose: effectiveTranspose,
2279
+ onChordClick: handleChordClick
2280
+ },
2281
+ lineIdx
2282
+ )) })
2283
+ ]
2284
+ },
2285
+ secIdx
2286
+ );
2287
+ })
2288
+ }
2289
+ ),
2290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "border-t border-neutral-100 dark:border-zinc-900 pt-6 mt-12 pb-12 text-center text-[10px] text-neutral-400 dark:text-neutral-500 select-none space-y-1.5", children: [
2291
+ metadata.artist && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2292
+ "Artista Original: ",
2293
+ metadata.artist
2294
+ ] }),
2295
+ metadata.composer && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2296
+ "Compositor: ",
2297
+ metadata.composer
2298
+ ] }),
2299
+ metadata.lyricist && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2300
+ "Letra: ",
2301
+ metadata.lyricist
2302
+ ] }),
2303
+ metadata.arranger && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2304
+ "Arranjo: ",
2305
+ metadata.arranger
2306
+ ] }),
2307
+ metadata.year && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2308
+ "Ano: ",
2309
+ metadata.year
2310
+ ] }),
2311
+ metadata.originalKey && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2312
+ "Tom Original: ",
2313
+ metadata.originalKey
2314
+ ] }),
2315
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "pt-2 mt-2 border-t border-neutral-50/50 dark:border-zinc-900/50 inline-block", children: [
2316
+ metadata.copyright && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2317
+ "\xA9 Copyright: ",
2318
+ metadata.copyright
2319
+ ] }),
2320
+ metadata.ccli && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
2321
+ "CCLI: ",
2322
+ metadata.ccli
2323
+ ] })
2324
+ ] }),
2325
+ fileName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-4 opacity-50", children: [
2326
+ "Ficheiro base: ",
2327
+ fileName
2328
+ ] })
2329
+ ] })
2330
+ ] })
2331
+ }
2332
+ ),
2333
+ metadata.youtube && showYoutubePlayer && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2334
+ import_react_youtube.default,
2335
+ {
2336
+ videoId: extractYoutubeId(metadata.youtube),
2337
+ opts: {
2338
+ height: "0",
2339
+ width: "0",
2340
+ playerVars: { autoplay: 1, controls: 0, disablekb: 1 }
2341
+ },
2342
+ onReady: (e) => {
2343
+ setYtPlayerRef(e.target);
2344
+ e.target.pauseVideo();
2345
+ setIsPlayingYoutube(false);
2346
+ },
2347
+ onPlay: () => setIsPlayingYoutube(true),
2348
+ onPause: () => setIsPlayingYoutube(false),
2349
+ onEnd: (e) => {
2350
+ if (isYoutubeRepeatRef.current) {
2351
+ e.target.seekTo(0, true);
2352
+ e.target.playVideo();
2353
+ } else {
2354
+ setIsPlayingYoutube(false);
2355
+ }
2356
+ }
2357
+ }
2358
+ ) }),
2359
+ showYoutubePlayer && metadata.youtube && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fixed bottom-0 left-0 right-0 h-16 bg-m3-card dark:bg-m3-dark-card border-t border-m3-border dark:border-m3-dark-border shadow-[0_-4px_10px_rgba(0,0,0,0.05)] z-[200] px-4 flex items-center justify-between animate-in slide-in-from-bottom-full", children: [
2360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-3", children: [
2361
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-10 rounded overflow-hidden bg-slate-200 dark:bg-slate-800 shrink-0 border border-m3-border/50", children: metadata.youtube.match(YT_ID_REGEX) || metadata.youtube.match(/^[^&?]+$/) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2362
+ "img",
2363
+ {
2364
+ src: `https://img.youtube.com/vi/${extractYoutubeId(metadata.youtube)}/default.jpg`,
2365
+ alt: "YouTube Thumbnail",
2366
+ className: "w-full h-full object-cover scale-150"
2367
+ }
2368
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Disc, { className: "w-5 h-5 m-auto mt-2.5 text-m3-secondary opacity-50" }) }),
2369
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:block", children: [
2370
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[10px] font-black text-m3-text dark:text-m3-dark-text truncate max-w-[120px]", children: metadata.title }),
2371
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[9px] text-m3-secondary font-medium", children: "\xC1udio do YouTube" })
2372
+ ] })
2373
+ ] }),
2374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-4", children: [
2375
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2376
+ "button",
2377
+ {
2378
+ onClick: async () => {
2379
+ if (!ytPlayerRef) return;
2380
+ const t = await ytPlayerRef.getCurrentTime();
2381
+ ytPlayerRef.seekTo(Math.max(0, t - 10), true);
2382
+ },
2383
+ className: "text-m3-secondary hover:text-m3-primary transition-colors active:scale-95",
2384
+ title: "Retroceder 10s",
2385
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.SkipBack, { className: "w-4 h-4" })
2386
+ }
2387
+ ),
2388
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2389
+ "button",
2390
+ {
2391
+ onClick: () => {
2392
+ if (!ytPlayerRef) return;
2393
+ if (isPlayingYoutube) ytPlayerRef.pauseVideo();
2394
+ else ytPlayerRef.playVideo();
2395
+ },
2396
+ className: "w-10 h-10 rounded-full bg-m3-primary text-white flex items-center justify-center hover:opacity-95 shadow-md active:scale-95 transition-all",
2397
+ children: isPlayingYoutube ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Pause, { className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Play, { className: "w-5 h-5 ml-1" })
2398
+ }
2399
+ ),
2400
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2401
+ "button",
2402
+ {
2403
+ onClick: async () => {
2404
+ if (!ytPlayerRef) return;
2405
+ const t = await ytPlayerRef.getCurrentTime();
2406
+ ytPlayerRef.seekTo(t + 10, true);
2407
+ },
2408
+ className: "text-m3-secondary hover:text-m3-primary transition-colors active:scale-95",
2409
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.SkipForward, { className: "w-4 h-4" })
2410
+ }
2411
+ ),
2412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2413
+ "button",
2414
+ {
2415
+ onClick: () => setIsYoutubeRepeat((r) => !r),
2416
+ className: `ml-2 transition-colors active:scale-95 ${isYoutubeRepeat ? "text-m3-primary" : "text-m3-secondary hover:text-m3-text"}`,
2417
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Repeat, { className: "w-4 h-4" })
2418
+ }
2419
+ )
2420
+ ] }),
2421
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2422
+ "button",
2423
+ {
2424
+ onClick: () => {
2425
+ ytPlayerRef?.pauseVideo();
2426
+ setIsPlayingYoutube(false);
2427
+ setShowYoutubePlayer(false);
2428
+ },
2429
+ className: "p-2 text-m3-secondary hover:text-red-500 transition-colors",
2430
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.X, { className: "w-4 h-4" })
2431
+ }
2432
+ ) })
2433
+ ] }),
2434
+ selectedChord && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 select-none animate-in fade-in duration-200", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-m3-card dark:bg-m3-dark-card border border-m3-border dark:border-m3-dark-border rounded-3xl w-full max-w-sm overflow-hidden shadow-2xl flex flex-col p-6 space-y-4 animate-in zoom-in-95 duration-200", children: [
2435
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
2436
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
2437
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.BookOpen, { className: "w-5 h-5 text-m3-primary dark:text-m3-dark-primary" }),
2438
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { className: "text-sm font-black text-m3-text dark:text-m3-dark-text uppercase tracking-wider", children: [
2439
+ "Dicion\xE1rio: ",
2440
+ selectedChord
2441
+ ] })
2442
+ ] }),
2443
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2444
+ "button",
2445
+ {
2446
+ onClick: () => setSelectedChord(null),
2447
+ className: "p-1 rounded-full hover:bg-m3-hover dark:hover:bg-m3-dark-hover text-m3-secondary dark:text-m3-dark-secondary",
2448
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.X, { className: "w-5 h-5" })
2449
+ }
2450
+ )
2451
+ ] }),
2452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex bg-m3-sidebar dark:bg-m3-dark-sidebar p-1 rounded-2xl border border-m3-border dark:border-m3-dark-border", children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2454
+ "button",
2455
+ {
2456
+ onClick: () => setModalInstrument("guitar"),
2457
+ className: `flex-1 py-2 text-xs font-bold rounded-xl transition-all ${modalInstrument === "guitar" ? "bg-m3-primary text-white shadow-sm" : "text-m3-secondary dark:text-m3-dark-secondary hover:text-m3-text"}`,
2458
+ children: "Diagrama de Guitarra"
2459
+ }
2460
+ ),
2461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2462
+ "button",
2463
+ {
2464
+ onClick: () => setModalInstrument("piano"),
2465
+ className: `flex-1 py-2 text-xs font-bold rounded-xl transition-all ${modalInstrument === "piano" ? "bg-m3-primary text-white shadow-sm" : "text-m3-secondary dark:text-m3-dark-secondary hover:text-m3-text"}`,
2466
+ children: "Teclado de Piano"
2467
+ }
2468
+ )
2469
+ ] }),
2470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "py-4 flex flex-col items-center justify-center min-h-[140px] border border-m3-border/30 dark:border-m3-dark-border/30 rounded-2xl bg-m3-sidebar/30 dark:bg-m3-dark-sidebar/10", children: chordFingering ? modalInstrument === "guitar" && chordFingering.guitar ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2471
+ GuitarDiagram,
2472
+ {
2473
+ frets: chordFingering.guitar.frets,
2474
+ fingers: chordFingering.guitar.fingers,
2475
+ barre: chordFingering.guitar.barre
2476
+ }
2477
+ ) : modalInstrument === "piano" && chordFingering.piano ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2478
+ PianoDiagram,
2479
+ {
2480
+ highlightKeys: chordFingering.piano.highlightKeys
2481
+ }
2482
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center p-4", children: [
2483
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.HelpCircle, { className: "w-8 h-8 mx-auto text-amber-500 opacity-80 mb-2" }),
2484
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-xs text-m3-secondary dark:text-m3-dark-secondary font-medium", children: [
2485
+ "O diagrama para",
2486
+ " ",
2487
+ modalInstrument === "guitar" ? "Guitarra" : "Piano",
2488
+ " ",
2489
+ "n\xE3o p\xF4de ser calculado."
2490
+ ] })
2491
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center p-6 space-y-2", children: [
2492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.HelpCircle, { className: "w-8 h-8 mx-auto text-amber-500 opacity-80" }),
2493
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-xs text-m3-text dark:text-m3-dark-text font-bold", children: [
2494
+ 'Acorde "',
2495
+ selectedChord,
2496
+ '" n\xE3o registado'
2497
+ ] }),
2498
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[10px] text-m3-secondary dark:text-m3-dark-secondary max-w-[200px] leading-normal", children: "Este acorde n\xE3o se encontra no nosso dicion\xE1rio estrito, mas pode toc\xE1-lo com as notas de acompanhamento habituais." })
2499
+ ] }) }),
2500
+ chordFingering?.piano && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center font-mono text-xs text-m3-secondary dark:text-m3-dark-secondary bg-m3-sidebar dark:bg-m3-dark-sidebar py-2 rounded-xl", children: [
2501
+ "Notas do Acorde:",
2502
+ " ",
2503
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-bold text-m3-primary dark:text-m3-dark-primary", children: chordFingering.piano.notes.join(" - ") })
2504
+ ] }),
2505
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2506
+ "button",
2507
+ {
2508
+ onClick: () => setSelectedChord(null),
2509
+ className: "w-full bg-m3-sidebar dark:bg-m3-dark-sidebar hover:bg-m3-hover dark:hover:bg-m3-dark-hover text-m3-text dark:text-m3-dark-text text-xs py-3 rounded-2xl border border-m3-border dark:border-m3-dark-border font-bold active:scale-95 transition-all",
2510
+ children: "Voltar ao C\xE2ntico"
2511
+ }
2512
+ )
2513
+ ] }) })
2514
+ ] });
2515
+ }
2516
+ );
2517
+ var LineRenderer = import_react2.default.memo(
2518
+ ({
2519
+ line,
2520
+ showChords,
2521
+ transpose = 0,
2522
+ onChordClick,
2523
+ maxGridMeasures
2524
+ }) => {
2525
+ if (line.type === "empty") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-2" });
2526
+ if (line.type === "comment") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CommentRenderer, { line });
2527
+ if (line.type === "comment_box") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CommentBoxRenderer, { line });
2528
+ if (line.type === "chord-section")
2529
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2530
+ ChordSectionRenderer,
2531
+ {
2532
+ line,
2533
+ showChords,
2534
+ transpose,
2535
+ onChordClick,
2536
+ maxMeasures: maxGridMeasures
2537
+ }
2538
+ );
2539
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2540
+ LyricsRenderer,
2541
+ {
2542
+ line,
2543
+ showChords,
2544
+ transpose,
2545
+ onChordClick
2546
+ }
2547
+ );
2548
+ }
2549
+ );
2550
+ var CommentRenderer = import_react2.default.memo(({ line }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs text-slate-400 dark:text-slate-500 italic my-1", children: line.text }));
2551
+ var CommentBoxRenderer = import_react2.default.memo(({ line }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-amber-100 dark:bg-amber-900/30 border-l-4 border-amber-500 text-amber-900 dark:text-amber-200 p-2.5 my-2.5 rounded-r-md text-xs font-bold tracking-wide flex items-center gap-2 shadow-sm max-w-fit", children: [
2552
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Info, { className: "w-4 h-4 shrink-0 text-amber-600 dark:text-amber-500" }),
2553
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: line.text })
2554
+ ] }));
2555
+ var LyricsRenderer = import_react2.default.memo(
2556
+ ({
2557
+ line,
2558
+ showChords,
2559
+ transpose = 0,
2560
+ onChordClick
2561
+ }) => {
2562
+ const segments = line.segments || [];
2563
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap items-end leading-relaxed", children: segments.map((seg, segIdx) => {
2564
+ const hasChord = !!seg.chord;
2565
+ const transposed = hasChord ? transposeChord(seg.chord, transpose) : "";
2566
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2567
+ "div",
2568
+ {
2569
+ className: "flex flex-col justify-end relative select-text",
2570
+ style: {
2571
+ minWidth: hasChord && showChords ? `${Math.max(1.1, transposed.length * 0.65)}em` : void 0
2572
+ },
2573
+ children: [
2574
+ showChords && hasChord && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2575
+ "span",
2576
+ {
2577
+ className: "font-black text-[#0284c7] font-mono select-none pr-1 inline-block pb-0.5 transition-all cursor-pointer hover:opacity-80",
2578
+ style: { fontSize: "0.85em", lineHeight: "1" },
2579
+ onClick: () => onChordClick?.(transposed),
2580
+ children: transposed
2581
+ }
2582
+ ),
2583
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-slate-800 dark:text-slate-200 whitespace-pre", children: seg.text || "\xA0" })
2584
+ ]
2585
+ },
2586
+ segIdx
2587
+ );
2588
+ }) });
2589
+ }
2590
+ );
2591
+ var ChordSectionRenderer = import_react2.default.memo(
2592
+ ({
2593
+ line,
2594
+ showChords,
2595
+ transpose = 0,
2596
+ onChordClick,
2597
+ maxMeasures
2598
+ }) => {
2599
+ if (!showChords) return null;
2600
+ const measures = line.measures || [];
2601
+ const hasTiming = measures.some(
2602
+ (m) => m.chords.some((c) => c.timing !== void 0 && c.timing !== 1)
2603
+ );
2604
+ const isGridAligned = maxMeasures !== void 0 && maxMeasures > 0;
2605
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "my-1.5 p-1 w-full flex items-stretch bg-slate-50/80 dark:bg-slate-900/40 rounded-lg border border-slate-200/50 dark:border-slate-800/50 shadow-sm", children: [
2606
+ line.startBarline && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center justify-center shrink-0 w-6", children: renderBarline(line.startBarline) }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2608
+ "div",
2609
+ {
2610
+ className: isGridAligned ? "grid gap-1 flex-1" : "flex flex-wrap flex-1",
2611
+ style: isGridAligned ? {
2612
+ gridTemplateColumns: `repeat(${maxMeasures}, minmax(0, 1fr))`
2613
+ } : void 0,
2614
+ children: [
2615
+ measures.map((measure, mIdx) => {
2616
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2617
+ "div",
2618
+ {
2619
+ className: `flex items-stretch border-r border-slate-200/60 dark:border-slate-700/50 last:border-none ${isGridAligned ? "" : "flex-1 min-w-[120px]"}`,
2620
+ children: [
2621
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 flex items-center justify-evenly gap-1 px-1 py-1", children: measure.chords.map((chordSeg, cIdx) => {
2622
+ const transposed = transposeChord(
2623
+ chordSeg.chord,
2624
+ transpose
2625
+ );
2626
+ const timing = chordSeg.timing ?? 1;
2627
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2628
+ "span",
2629
+ {
2630
+ className: "flex-1 flex items-center justify-center cursor-pointer rounded transition-colors hover:bg-white dark:hover:bg-slate-800 py-1.5 px-1 min-w-max shadow-sm border border-transparent hover:border-slate-200 dark:hover:border-slate-700",
2631
+ style: { flexGrow: timing },
2632
+ onClick: () => onChordClick?.(transposed),
2633
+ title: timing !== 1 ? `Dura\xE7\xE3o: ${timing}x` : void 0,
2634
+ children: [
2635
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-bold text-[#0284c7] dark:text-sky-400 font-mono select-none text-[15px] tracking-wide", children: transposed }),
2636
+ hasTiming && timing !== 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ml-0.5 mt-0.5 text-[10px] font-semibold text-indigo-400 dark:text-indigo-500 opacity-80", children: [
2637
+ timing,
2638
+ "\xD7"
2639
+ ] })
2640
+ ]
2641
+ },
2642
+ cIdx
2643
+ );
2644
+ }) }),
2645
+ measure.endBarline && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center justify-center shrink-0 px-1 w-6 bg-slate-100/50 dark:bg-slate-800/50 rounded-r", children: renderBarline(measure.endBarline) })
2646
+ ]
2647
+ },
2648
+ mIdx
2649
+ );
2650
+ }),
2651
+ isGridAligned && Array.from({
2652
+ length: Math.max(0, maxMeasures - measures.length)
2653
+ }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2654
+ "div",
2655
+ {
2656
+ className: "border-r border-slate-200/60 dark:border-slate-700/50 last:border-none bg-slate-50/30 dark:bg-slate-800/10 rounded-sm"
2657
+ },
2658
+ `empty-${i}`
2659
+ ))
2660
+ ]
2661
+ }
2662
+ )
2663
+ ] });
2664
+ }
2665
+ );
2666
+ function renderBarline(barline) {
2667
+ const wrapperStyle = "flex items-center justify-center flex-shrink-0 select-none";
2668
+ const repeatStyle = "text-indigo-400 dark:text-indigo-500 text-[18px] leading-none";
2669
+ const normalStyle = "text-slate-300 dark:text-slate-600 font-black text-[16px] leading-none";
2670
+ switch (barline) {
2671
+ case "|:":
2672
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: wrapperStyle, title: "Start repeat", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: repeatStyle, children: "\u{1D106}" }) });
2673
+ case ":|":
2674
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: wrapperStyle, title: "End repeat", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: repeatStyle, children: "\u{1D107}" }) });
2675
+ case "||":
2676
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: wrapperStyle, title: "Double barline", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: normalStyle, children: "\u2016" }) });
2677
+ case "|]":
2678
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: wrapperStyle, title: "End of piece", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: normalStyle, children: "\u{1D102}" }) });
2679
+ case "|":
2680
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: normalStyle, children: "|" }) });
2681
+ default:
2682
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2683
+ "span",
2684
+ {
2685
+ className: `${wrapperStyle} text-slate-400 dark:text-slate-500 text-xs font-bold tracking-widest`,
2686
+ children: barline
2687
+ }
2688
+ );
2689
+ }
2690
+ }
2691
+
2692
+ // src/editor/Editor.tsx
2693
+ var import_react3 = __toESM(require("react"));
2694
+
2695
+ // src/editor/ChordFinder.ts
2696
+ var ChordFinder = class {
2697
+ static {
2698
+ this.CHORD_REGEX = /\[(.*?)\]/gi;
2699
+ }
2700
+ /**
2701
+ * Extracts chords from text and returns them formatted for Ace Autocomplete
2702
+ */
2703
+ static getChords(text) {
2704
+ const matches = String(text).match(this.CHORD_REGEX);
2705
+ if (!matches || matches.length === 0) {
2706
+ return [];
2707
+ }
2708
+ const chordCounts = matches.reduce(
2709
+ (acc, chord) => {
2710
+ acc[chord] = (acc[chord] || 0) + 1;
2711
+ return acc;
2712
+ },
2713
+ {}
2714
+ );
2715
+ return Object.entries(chordCounts).map(([chord, count]) => ({
2716
+ value: chord,
2717
+ meta: `${count} occurrence${count > 1 ? "s" : ""}`,
2718
+ score: count
2719
+ // Ace uses score to rank autocomplete suggestions
2720
+ })).sort((a, b) => b.score - a.score);
2721
+ }
2722
+ };
2723
+
2724
+ // src/editor/mode-chordpro.ts
2725
+ async function registerChordproMode(aceInstance) {
2726
+ let ace = aceInstance;
2727
+ if (!ace) {
2728
+ try {
2729
+ const mod = await import("ace-builds");
2730
+ ace = mod.default || mod;
2731
+ } catch {
2732
+ return;
2733
+ }
2734
+ }
2735
+ if (!ace || typeof ace.define !== "function" || ace._chordproModeRegistered) {
2736
+ return;
2737
+ }
2738
+ ace.define(
2739
+ "ace/mode/chordpro_highlight_rules",
2740
+ [
2741
+ "require",
2742
+ "exports",
2743
+ "module",
2744
+ "ace/lib/oop",
2745
+ "ace/mode/text_highlight_rules"
2746
+ ],
2747
+ (require2, exports2) => {
2748
+ const oop = require2("ace/lib/oop");
2749
+ const TextHighlightRules = require2("ace/mode/text_highlight_rules").TextHighlightRules;
2750
+ const ChordproHighlightRules = function() {
2751
+ const rStart = "(\\{\\s*)";
2752
+ const rEnd = "(\\s*\\})";
2753
+ const rSep = "(\\s*:\\s*)";
2754
+ this.$rules = {
2755
+ start: [
2756
+ // Comments
2757
+ { token: "comment.line.number-sign", regex: "^#.*$" },
2758
+ // Tab Block Start -> moves to tabBlock state
2759
+ {
2760
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
2761
+ regex: rStart + "(start_of_tab|sot)" + rEnd,
2762
+ caseInsensitive: true,
2763
+ next: "tabBlock"
2764
+ },
2765
+ // Grid Block Start -> moves to gridBlock state
2766
+ {
2767
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
2768
+ regex: rStart + "(start_of_grid|sog)" + rEnd,
2769
+ caseInsensitive: true,
2770
+ next: "gridBlock"
2771
+ },
2772
+ // Generic Block Starts (Chorus, Verse, Bridge, Custom) with optional labels
2773
+ {
2774
+ token: [
2775
+ "punctuation.tag",
2776
+ "markup.bold",
2777
+ "punctuation.separator",
2778
+ "string",
2779
+ "punctuation.tag"
2780
+ ],
2781
+ regex: rStart + "(start_of_[a-z_]+|so[a-z])" + rSep + "(.*?)" + rEnd,
2782
+ caseInsensitive: true
2783
+ },
2784
+ {
2785
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
2786
+ regex: rStart + "(start_of_[a-z_]+|so[a-z])" + rEnd,
2787
+ caseInsensitive: true
2788
+ },
2789
+ // Generic Block Ends
2790
+ {
2791
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
2792
+ regex: rStart + "(end_of_[a-z_]+|eo[a-z])" + rEnd,
2793
+ caseInsensitive: true
2794
+ },
2795
+ // Formatting & Styling Directives (e.g. textfont, chordsize)
2796
+ {
2797
+ token: [
2798
+ "punctuation.tag",
2799
+ "support.type",
2800
+ "punctuation.separator",
2801
+ "constant.numeric",
2802
+ "punctuation.tag"
2803
+ ],
2804
+ regex: rStart + "(textfont|textsize|chordfont|chordsize|tabfont|tabsize|gridfont|gridsize)" + rSep + "(.*?)" + rEnd,
2805
+ caseInsensitive: true
2806
+ },
2807
+ // Meta Directives (e.g. title, key, tempo)
2808
+ {
2809
+ token: [
2810
+ "punctuation.tag",
2811
+ "keyword.control",
2812
+ "punctuation.separator",
2813
+ "string",
2814
+ "punctuation.tag"
2815
+ ],
2816
+ regex: rStart + "(title|t|subtitle|st|artist|a|composer|lyricist|ccli|translator|youtube|chorus|copyright|album|year|key|k|time|tempo|duration|capo|meta|c|comment|chord|define|song_number|x_[a-zA-Z0-9_]+)" + rSep + "(.*?)" + rEnd,
2817
+ caseInsensitive: true
2818
+ },
2819
+ // Standalone Directives (e.g. column_break, new_page)
2820
+ {
2821
+ token: ["punctuation.tag", "keyword.operator", "punctuation.tag"],
2822
+ regex: rStart + "(column_break|cb|new_page|np|new_song|ns|chorus)" + rEnd,
2823
+ caseInsensitive: true
2824
+ },
2825
+ // Chord Section barlines (||, |:, :|, |)
2826
+ {
2827
+ token: "constant.character.barline",
2828
+ regex: "\\|\\||:\\||\\|:|\\|"
2829
+ },
2830
+ // Inline Annotations e.g. [* Bass fill]
2831
+ {
2832
+ token: ["punctuation.tag", "comment.line", "punctuation.tag"],
2833
+ regex: "(\\[\\s*\\*)(.*?)(\\])"
2834
+ },
2835
+ // Chord with timing annotation e.g. [Em@2x] or [C@0.5x]
2836
+ {
2837
+ token: [
2838
+ "punctuation.tag",
2839
+ "constant.language.bold",
2840
+ "keyword.operator.timing",
2841
+ "punctuation.tag"
2842
+ ],
2843
+ regex: "(\\[)([^\\]@]+)(@[0-9]*\\.?[0-9]+x)(\\])"
2844
+ },
2845
+ // Chords e.g. [C#maj7/F]
2846
+ {
2847
+ token: [
2848
+ "punctuation.tag",
2849
+ "constant.language.bold",
2850
+ "punctuation.tag"
2851
+ ],
2852
+ regex: "(\\[)([^\\]]+)(\\])"
2853
+ },
2854
+ // Invalid/Unknown braces catch-all
2855
+ {
2856
+ token: ["punctuation.tag", "invalid", "punctuation.tag"],
2857
+ regex: rStart + "(.+?)" + rEnd
2858
+ }
2859
+ ],
2860
+ // Advanced State for Tablature blocks
2861
+ tabBlock: [
2862
+ {
2863
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
2864
+ regex: rStart + "(end_of_tab|eot)" + rEnd,
2865
+ caseInsensitive: true,
2866
+ next: "start"
2867
+ },
2868
+ { token: "comment.line", regex: "-+" },
2869
+ // Tab lines
2870
+ { token: "constant.character", regex: "\\|+" },
2871
+ // Measure bars
2872
+ { token: "string.regexp", regex: "^[a-gA-G][b#]?\\s*\\|" },
2873
+ // Tuning strings at start of line
2874
+ { token: "constant.numeric", regex: "\\b[0-9]+\\b" },
2875
+ // Fret numbers
2876
+ { token: "support.function", regex: "[hpsbrv~t]" },
2877
+ // Articulations (hammer-on, pull-off, bend)
2878
+ { defaultToken: "comment" }
2879
+ // Fallback to comment color for tab lines
2880
+ ],
2881
+ // Advanced State for Grid blocks (Jazz grids / Chord progression boxes)
2882
+ gridBlock: [
2883
+ {
2884
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
2885
+ regex: rStart + "(end_of_grid|eog)" + rEnd,
2886
+ caseInsensitive: true,
2887
+ next: "start"
2888
+ },
2889
+ { token: "constant.character", regex: "\\|\\||\\|\\.|\\.\\||\\|" },
2890
+ // Grid barlines
2891
+ { token: "keyword.operator", regex: "%" },
2892
+ // Grid repeat markers
2893
+ {
2894
+ token: "constant.language.bold",
2895
+ regex: "[A-G][b#]?(?:m|maj|dim|aug|sus|[0-9])*(?:\\/[A-G][b#]?)?"
2896
+ },
2897
+ // Grid Chords (raw text)
2898
+ { defaultToken: "text" }
2899
+ ]
2900
+ };
2901
+ };
2902
+ oop.inherits(ChordproHighlightRules, TextHighlightRules);
2903
+ exports2.ChordproHighlightRules = ChordproHighlightRules;
2904
+ }
2905
+ );
2906
+ ace.define(
2907
+ "ace/mode/folding/chordpro",
2908
+ [
2909
+ "require",
2910
+ "exports",
2911
+ "module",
2912
+ "ace/lib/oop",
2913
+ "ace/range",
2914
+ "ace/mode/folding/fold_mode"
2915
+ ],
2916
+ (require2, exports2) => {
2917
+ const oop = require2("ace/lib/oop");
2918
+ const Range = require2("ace/range").Range;
2919
+ const BaseFoldMode = require2("ace/mode/folding/fold_mode").FoldMode;
2920
+ const FoldMode = exports2.FoldMode = function() {
2921
+ };
2922
+ oop.inherits(FoldMode, BaseFoldMode);
2923
+ (function() {
2924
+ this.foldingStartMarker = /\{\s*(so(?<short>[a-z_]+)|start_of_(?<long>[a-z_]+))(:.*?)?\s*\}/i;
2925
+ this.foldingStopMarker = /\{\s*(eo(?<short>[a-z_]+)|end_of_(?<long>[a-z_]+))\s*\}/i;
2926
+ const mapBlockName = (name) => {
2927
+ const map = {
2928
+ c: "chorus",
2929
+ v: "verse",
2930
+ b: "bridge",
2931
+ t: "tab",
2932
+ g: "grid"
2933
+ };
2934
+ return map[name.toLowerCase()] || name.toLowerCase();
2935
+ };
2936
+ this.getFoldWidgetRange = function(session, _, row) {
2937
+ const line = session.getLine(row);
2938
+ const match = line.match(this.foldingStartMarker);
2939
+ if (match && match.groups) {
2940
+ const rawName = match.groups["short"] || match.groups["long"];
2941
+ if (!rawName) return;
2942
+ return this.getRegionBlock(
2943
+ session,
2944
+ line,
2945
+ row,
2946
+ mapBlockName(rawName)
2947
+ );
2948
+ }
2949
+ };
2950
+ this.getRegionBlock = function(session, line, row, normalizedStartName) {
2951
+ const startColumn = line.search(/\s*$/);
2952
+ const maxRow = session.getLength();
2953
+ const startRow = row;
2954
+ let hasMatch = false;
2955
+ while (++row < maxRow) {
2956
+ line = session.getLine(row);
2957
+ const m = this.foldingStopMarker.exec(line);
2958
+ if (m && m.groups) {
2959
+ const rawEndName = m.groups["short"] || m.groups["long"];
2960
+ if (rawEndName && mapBlockName(rawEndName) === normalizedStartName) {
2961
+ hasMatch = true;
2962
+ break;
2963
+ }
2964
+ }
2965
+ }
2966
+ if (hasMatch)
2967
+ return new Range(startRow, startColumn, row, line.length);
2968
+ };
2969
+ }).call(FoldMode.prototype);
2970
+ }
2971
+ );
2972
+ ace.define(
2973
+ "ace/mode/chordpro",
2974
+ [
2975
+ "require",
2976
+ "exports",
2977
+ "module",
2978
+ "ace/lib/oop",
2979
+ "ace/mode/text",
2980
+ "ace/mode/chordpro_highlight_rules",
2981
+ "ace/mode/folding/chordpro"
2982
+ ],
2983
+ (require2, exports2) => {
2984
+ const oop = require2("ace/lib/oop");
2985
+ const TextMode = require2("ace/mode/text").Mode;
2986
+ const ChordproHighlightRules = require2("ace/mode/chordpro_highlight_rules").ChordproHighlightRules;
2987
+ const FoldMode = require2("ace/mode/folding/chordpro").FoldMode;
2988
+ const Mode = function() {
2989
+ this.HighlightRules = ChordproHighlightRules;
2990
+ this.foldingRules = new FoldMode();
2991
+ };
2992
+ oop.inherits(Mode, TextMode);
2993
+ (function() {
2994
+ this.$id = "ace/mode/chordpro";
2995
+ this.snippetFileId = "ace/snippets/chordpro";
2996
+ }).call(Mode.prototype);
2997
+ exports2.Mode = Mode;
2998
+ }
2999
+ );
3000
+ ace._chordproModeRegistered = true;
3001
+ }
3002
+
3003
+ // src/editor/snippets-chordpro.ts
3004
+ async function registerChordproSnippets(aceInstance) {
3005
+ let ace = aceInstance;
3006
+ if (!ace) {
3007
+ try {
3008
+ const mod = await import("ace-builds");
3009
+ ace = mod.default || mod;
3010
+ } catch {
3011
+ return;
3012
+ }
3013
+ }
3014
+ if (!ace || typeof ace.define !== "function" || ace._chordproSnippetsRegistered) {
3015
+ return;
3016
+ }
3017
+ ace.define(
3018
+ "ace/snippets/chordpro",
3019
+ ["require", "exports", "module"],
3020
+ (_, exports2) => {
3021
+ exports2.snippetText = [
3022
+ // album tag
3023
+ "snippet album",
3024
+ " {album: ${1:value}}",
3025
+ "snippet youtube",
3026
+ " {youtube: ${1:url}}",
3027
+ "snippet number",
3028
+ " {song_number: ${1:number}}",
3029
+ "snippet cc",
3030
+ " {chorus}",
3031
+ // arranger tag
3032
+ "snippet arranger",
3033
+ " {arranger: ${1:value}}",
3034
+ // artist tag
3035
+ "snippet a",
3036
+ " {artist: ${1:value}}",
3037
+ "snippet artist",
3038
+ " {artist: ${1:value}}",
3039
+ // capo tag
3040
+ "snippet capo",
3041
+ " {capo: ${1:5}}",
3042
+ // composer tag
3043
+ "snippet composer",
3044
+ " {composer: ${1:value}}",
3045
+ // copyright tag
3046
+ "snippet copyright",
3047
+ " {copyright: ${1:value}}",
3048
+ // duration tag
3049
+ "snippet duration",
3050
+ " {duration: ${1:4}:${2:00}}",
3051
+ // key tag
3052
+ "snippet k",
3053
+ " {key: ${1:Am}}",
3054
+ "snippet key",
3055
+ " {key: ${1:Am}}",
3056
+ // lyricist tag
3057
+ "snippet lyricist",
3058
+ " {lyricist: ${1:value}}",
3059
+ // tempo tag
3060
+ "snippet tempo",
3061
+ " {tempo: ${1:120}}",
3062
+ // time tag
3063
+ "snippet time",
3064
+ " {time: ${1:4}/${2:4}}",
3065
+ // title tag
3066
+ "snippet t",
3067
+ " {title: ${1:value}}",
3068
+ "snippet title",
3069
+ " {title: ${1:value}}",
3070
+ // subtitle tag
3071
+ "snippet st",
3072
+ " {subtitle: ${1:value}}",
3073
+ "snippet subtitle",
3074
+ " {subtitle: ${1:value}}",
3075
+ // year tag
3076
+ "snippet year",
3077
+ " {year: ${1:2020}}",
3078
+ // meta tag
3079
+ "snippet meta",
3080
+ " {meta: ${1:label} ${2:value}}",
3081
+ // comment tag
3082
+ "snippet c",
3083
+ " {comment: ${1:value}}",
3084
+ "snippet comment",
3085
+ " {comment: ${1:value}}",
3086
+ // chorus block
3087
+ "snippet soc",
3088
+ " {start_of_chorus}",
3089
+ "snippet eoc",
3090
+ " {end_of_chorus}",
3091
+ "snippet chorus",
3092
+ " {start_of_chorus: ${1:Refr\xE3o}}",
3093
+ " ${2:lyrics}",
3094
+ " {end_of_chorus}",
3095
+ // verse block
3096
+ "snippet sov",
3097
+ " {start_of_verse}",
3098
+ "snippet eov",
3099
+ " {end_of_verse}",
3100
+ "snippet verse",
3101
+ " {start_of_verse: ${1:Verso} ${2:1}}",
3102
+ " ${3:lyrics}",
3103
+ " {end_of_verse}",
3104
+ // bridge block
3105
+ "snippet sob",
3106
+ " {start_of_bridge}",
3107
+ "snippet eob",
3108
+ " {end_of_bridge}",
3109
+ "snippet bridge",
3110
+ " {start_of_bridge: ${1:Ponte}}",
3111
+ " ${2:lyrics}",
3112
+ " {end_of_bridge}",
3113
+ // tabs block
3114
+ "snippet sot",
3115
+ " {start_of_tab}",
3116
+ "snippet eot",
3117
+ " {end_of_tab}",
3118
+ "snippet tab",
3119
+ " {start_of_tab}",
3120
+ " e|-${1:-}--------------------------------|",
3121
+ " B|----------------------------------|",
3122
+ " G|----------------------------------|",
3123
+ " D|----------------------------------|",
3124
+ " A|----------------------------------|",
3125
+ " E|----------------------------------|",
3126
+ " {end_of_tab}",
3127
+ // define tag
3128
+ "snippet d",
3129
+ " {define: ${1:Am} base-fret ${2:1} frets ${3:0 0 0 0 0 0} fingers ${4:0 0 0 0 0 0}}",
3130
+ "snippet define",
3131
+ " {define: ${1:Am} base-fret ${2:1} frets ${3:0 0 0 0 0 0} fingers ${4:0 0 0 0 0 0}}",
3132
+ // single-liners
3133
+ "snippet cb",
3134
+ " {column_break}",
3135
+ "snippet column",
3136
+ " {column_break}",
3137
+ // that's all folks!
3138
+ // chord usage
3139
+ "snippet [",
3140
+ " [${1:Am}]",
3141
+ // chord section / grid notation
3142
+ "snippet ||",
3143
+ " ||[${1:Am}]|[${2:C}]|[${3:G}]|[${4:F}]||",
3144
+ "snippet grid",
3145
+ " {start_of_grid}",
3146
+ " ||[${1:Em}]|[${2:C}]|[${3:D}]||",
3147
+ " {end_of_grid}",
3148
+ "snippet !",
3149
+ " {title: ${1:value}}",
3150
+ " {artist: ${2:value}}",
3151
+ " {duration: ${3:4:00}}",
3152
+ " {key: ${4:C}}",
3153
+ " ",
3154
+ " ${5:lyrics}",
3155
+ " {start_of_chorus}",
3156
+ " ${6:lyrics}",
3157
+ " {end_of_chorus}"
3158
+ ].join("\n");
3159
+ exports2.scope = "chordpro";
3160
+ }
3161
+ );
3162
+ ace._chordproSnippetsRegistered = true;
3163
+ }
3164
+
3165
+ // src/editor/Editor.tsx
3166
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3167
+ var aceLoaderPromise = null;
3168
+ function preloadEditor() {
3169
+ if (!aceLoaderPromise) {
3170
+ aceLoaderPromise = (async () => {
3171
+ try {
3172
+ const aceModule = await import("ace-builds");
3173
+ const ace = aceModule?.default || aceModule;
3174
+ if (typeof window !== "undefined" && ace) {
3175
+ window.ace = ace;
3176
+ }
3177
+ const [reactAceModule] = await Promise.all([
3178
+ import("react-ace"),
3179
+ import("ace-builds/src-noconflict/ext-language_tools"),
3180
+ import("ace-builds/src-noconflict/theme-dracula"),
3181
+ import("ace-builds/src-noconflict/theme-github"),
3182
+ import("ace-builds/src-noconflict/theme-monokai"),
3183
+ import("ace-builds/src-noconflict/theme-solarized_dark"),
3184
+ import("ace-builds/src-noconflict/theme-solarized_light"),
3185
+ import("ace-builds/src-noconflict/theme-textmate"),
3186
+ import("ace-builds/src-noconflict/theme-tomorrow"),
3187
+ import("ace-builds/src-noconflict/theme-tomorrow_night")
3188
+ ]);
3189
+ const AceEditor = reactAceModule?.default || reactAceModule;
3190
+ if (ace) {
3191
+ await registerChordproMode(ace);
3192
+ await registerChordproSnippets(ace);
3193
+ if (typeof ace.require === "function") {
3194
+ try {
3195
+ const langTools = ace.require("ace/ext/language_tools");
3196
+ if (langTools && typeof langTools.addCompleter === "function" && typeof window !== "undefined" && !window._chordproCompleterRegistered) {
3197
+ const chordCompleter = {
3198
+ getCompletions: (editor, _session, _pos, _prefix, callback) => {
3199
+ if (!editor || typeof editor.getValue !== "function") {
3200
+ callback(null, []);
3201
+ return;
3202
+ }
3203
+ const text = editor.getValue();
3204
+ const chords = ChordFinder.getChords(text);
3205
+ callback(null, chords);
3206
+ }
3207
+ };
3208
+ langTools.addCompleter(chordCompleter);
3209
+ window._chordproCompleterRegistered = true;
3210
+ }
3211
+ } catch {
3212
+ }
3213
+ }
3214
+ }
3215
+ if (!AceEditor) {
3216
+ throw new Error("Failed to resolve AceEditor component");
3217
+ }
3218
+ return AceEditor;
3219
+ } catch (error) {
3220
+ console.error("Failed to load Ace editor:", error);
3221
+ aceLoaderPromise = null;
3222
+ const ErrorFallback = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-full h-full flex items-center justify-center p-4 text-center text-sm text-red-500 bg-red-50/50 dark:bg-red-950/20 rounded-lg border border-red-200 dark:border-red-900/30", children: "Failed to load code editor. Please ensure 'ace-builds' and 'react-ace' are installed." });
3223
+ return ErrorFallback;
3224
+ }
3225
+ })();
3226
+ }
3227
+ return aceLoaderPromise;
3228
+ }
3229
+ var LazyAce = import_react3.default.lazy(async () => {
3230
+ const Component = await preloadEditor();
3231
+ return { default: Component };
3232
+ });
3233
+ var SECTION_LABELS = {
3234
+ verse: {
3235
+ start: "start_of_verse",
3236
+ end: "end_of_verse",
3237
+ defaultLabel: "Verso"
3238
+ },
3239
+ chorus: {
3240
+ start: "start_of_chorus",
3241
+ end: "end_of_chorus",
3242
+ defaultLabel: "Refr\xE3o"
3243
+ },
3244
+ bridge: {
3245
+ start: "start_of_bridge",
3246
+ end: "end_of_bridge",
3247
+ defaultLabel: "Ponte"
3248
+ }
3249
+ };
3250
+ function wrapSelectionInSection(editor, sectionType) {
3251
+ if (!editor || !editor.getSelection || !editor.session) return;
3252
+ if (typeof editor.session.getTextRange !== "function" || typeof editor.session.replace !== "function") {
3253
+ return;
3254
+ }
3255
+ const selection = editor.getSelection();
3256
+ if (!selection || typeof selection.getRange !== "function") return;
3257
+ const range = selection.getRange();
3258
+ if (!range) return;
3259
+ const selectedText = editor.session.getTextRange(range);
3260
+ if (!selectedText || !selectedText.trim()) return;
3261
+ const info = SECTION_LABELS[sectionType];
3262
+ const wrapped = `{${info.start}: ${info.defaultLabel}}
3263
+ ${selectedText}
3264
+ {${info.end}}`;
3265
+ editor.session.replace(range, wrapped);
3266
+ }
3267
+ var MENU_ITEMS = [
3268
+ { type: "verse", label: "Envolver em Verso", shortcut: "Alt+V" },
3269
+ { type: "chorus", label: "Envolver em Refr\xE3o", shortcut: "Alt+R" },
3270
+ { type: "bridge", label: "Envolver em Ponte", shortcut: "Alt+B" }
3271
+ ];
3272
+ function EditorContextMenu({
3273
+ state,
3274
+ onAction,
3275
+ onClose
3276
+ }) {
3277
+ const menuRef = (0, import_react3.useRef)(null);
3278
+ (0, import_react3.useEffect)(() => {
3279
+ const handleClickOutside = (e) => {
3280
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
3281
+ onClose();
3282
+ }
3283
+ };
3284
+ const handleEscape = (e) => {
3285
+ if (e.key === "Escape") onClose();
3286
+ };
3287
+ if (state.visible) {
3288
+ document.addEventListener("mousedown", handleClickOutside);
3289
+ document.addEventListener("keydown", handleEscape);
3290
+ }
3291
+ return () => {
3292
+ document.removeEventListener("mousedown", handleClickOutside);
3293
+ document.removeEventListener("keydown", handleEscape);
3294
+ };
3295
+ }, [state.visible, onClose]);
3296
+ if (!state.visible) return null;
3297
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3298
+ "div",
3299
+ {
3300
+ ref: menuRef,
3301
+ className: "fixed z-[9999] min-w-[240px] bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-xl shadow-2xl py-1.5 animate-in fade-in zoom-in-95 duration-100",
3302
+ style: { left: state.x, top: state.y },
3303
+ children: [
3304
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-3 py-1.5 text-[10px] font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider select-none border-b border-zinc-100 dark:border-zinc-800 mb-1", children: "Envolver sele\xE7\xE3o em" }),
3305
+ MENU_ITEMS.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3306
+ "button",
3307
+ {
3308
+ className: "w-full flex items-center gap-2.5 px-3 py-2 text-left text-sm text-zinc-700 dark:text-zinc-200 hover:bg-indigo-50 dark:hover:bg-indigo-950/40 hover:text-indigo-700 dark:hover:text-indigo-300 transition-colors",
3309
+ onClick: () => {
3310
+ onAction(item.type);
3311
+ onClose();
3312
+ },
3313
+ children: [
3314
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1 font-medium", children: item.label }),
3315
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] font-mono text-zinc-400 dark:text-zinc-500 bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 rounded", children: item.shortcut })
3316
+ ]
3317
+ },
3318
+ item.type
3319
+ ))
3320
+ ]
3321
+ }
3322
+ );
3323
+ }
3324
+ var DEFAULT_EDITOR_SETTINGS = {
3325
+ theme: "textmate",
3326
+ fontSize: 14,
3327
+ wordWrap: true,
3328
+ showLineNumbers: true
3329
+ };
3330
+ function Editor({
3331
+ value,
3332
+ onChange,
3333
+ onSave,
3334
+ settings,
3335
+ mode = "chordpro",
3336
+ readOnly = false,
3337
+ fallback = null
3338
+ }) {
3339
+ const activeSettings = {
3340
+ ...DEFAULT_EDITOR_SETTINGS,
3341
+ ...settings
3342
+ };
3343
+ const editorRef = (0, import_react3.useRef)(null);
3344
+ const [contextMenu, setContextMenu] = (0, import_react3.useState)({
3345
+ x: 0,
3346
+ y: 0,
3347
+ visible: false
3348
+ });
3349
+ const handleContextMenuAction = (0, import_react3.useCallback)((type) => {
3350
+ if (editorRef.current) {
3351
+ wrapSelectionInSection(editorRef.current, type);
3352
+ }
3353
+ }, []);
3354
+ const handleLoad = (0, import_react3.useCallback)((editor) => {
3355
+ if (!editor) return;
3356
+ editorRef.current = editor;
3357
+ if (editor.commands && typeof editor.commands.addCommand === "function") {
3358
+ editor.commands.addCommand({
3359
+ name: "save",
3360
+ bindKey: { win: "Ctrl-S", mac: "Cmd-S" },
3361
+ exec: (ed) => {
3362
+ if (ed && typeof ed.getValue === "function") {
3363
+ onSave?.(ed.getValue());
3364
+ }
3365
+ }
3366
+ });
3367
+ editor.commands.addCommand({
3368
+ name: "wrapInVerse",
3369
+ bindKey: { win: "Alt-V", mac: "Alt-V" },
3370
+ exec: (ed) => wrapSelectionInSection(ed, "verse")
3371
+ });
3372
+ editor.commands.addCommand({
3373
+ name: "wrapInChorus",
3374
+ bindKey: { win: "Alt-R", mac: "Alt-R" },
3375
+ exec: (ed) => wrapSelectionInSection(ed, "chorus")
3376
+ });
3377
+ editor.commands.addCommand({
3378
+ name: "wrapInBridge",
3379
+ bindKey: { win: "Alt-B", mac: "Alt-B" },
3380
+ exec: (ed) => wrapSelectionInSection(ed, "bridge")
3381
+ });
3382
+ }
3383
+ if (editor.container && typeof editor.container.addEventListener === "function") {
3384
+ const handleContextMenu = (e) => {
3385
+ if (!editor || typeof editor.getSelectedText !== "function") return;
3386
+ const selectedText = editor.getSelectedText();
3387
+ if (selectedText && selectedText.trim()) {
3388
+ e.preventDefault();
3389
+ e.stopPropagation();
3390
+ setContextMenu({ x: e.clientX, y: e.clientY, visible: true });
3391
+ }
3392
+ };
3393
+ editor.container.addEventListener("contextmenu", handleContextMenu);
3394
+ }
3395
+ }, [onSave]);
3396
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3397
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react3.Suspense, { fallback, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3398
+ LazyAce,
3399
+ {
3400
+ mode,
3401
+ theme: activeSettings.theme,
3402
+ width: "100%",
3403
+ height: "100%",
3404
+ value,
3405
+ onChange,
3406
+ onLoad: handleLoad,
3407
+ readOnly,
3408
+ fontSize: activeSettings.fontSize,
3409
+ wrapEnabled: activeSettings.wordWrap,
3410
+ showGutter: activeSettings.showLineNumbers,
3411
+ setOptions: {
3412
+ enableLiveAutocompletion: true,
3413
+ enableBasicAutocompletion: true,
3414
+ enableSnippets: true,
3415
+ showLineNumbers: activeSettings.showLineNumbers,
3416
+ tabSize: 2,
3417
+ useWorker: false
3418
+ }
3419
+ }
3420
+ ) }),
3421
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3422
+ EditorContextMenu,
3423
+ {
3424
+ state: contextMenu,
3425
+ onAction: handleContextMenuAction,
3426
+ onClose: () => setContextMenu((s) => ({ ...s, visible: false }))
3427
+ }
3428
+ )
3429
+ ] });
3430
+ }
3431
+ // Annotate the CommonJS export names for ESM import in node:
3432
+ 0 && (module.exports = {
3433
+ ChordFinder,
3434
+ ChordProRenderer,
3435
+ ChordRoll,
3436
+ DefaultChordDictionary,
3437
+ Editor,
3438
+ GuitarDiagram,
3439
+ PianoDiagram,
3440
+ buildChordProText,
3441
+ chordDictionary,
3442
+ convertToChordProDetailed,
3443
+ detectSourceFormat,
3444
+ getNoteValue,
3445
+ getSuggestedCapo,
3446
+ parseChordPro,
3447
+ parseLineSegments,
3448
+ preloadEditor,
3449
+ registerChordproMode,
3450
+ registerChordproSnippets,
3451
+ slugifyTitle,
3452
+ toChordPro,
3453
+ transposeChord,
3454
+ transposeNote
3455
+ });
3456
+ /**
3457
+ * @license
3458
+ * SPDX-License-Identifier: Apache-2.0
3459
+ */