guitarpro-parser 1.0.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,1621 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/pitch.ts
9
+ var SHARP_NAMES = {
10
+ 0: "C",
11
+ 1: "C#",
12
+ 2: "D",
13
+ 3: "D#",
14
+ 4: "E",
15
+ 5: "F",
16
+ 6: "F#",
17
+ 7: "G",
18
+ 8: "G#",
19
+ 9: "A",
20
+ 10: "A#",
21
+ 11: "B"
22
+ };
23
+ var FLAT_NAMES = {
24
+ 0: "C",
25
+ 1: "Db",
26
+ 2: "D",
27
+ 3: "Eb",
28
+ 4: "E",
29
+ 5: "F",
30
+ 6: "Gb",
31
+ 7: "G",
32
+ 8: "Ab",
33
+ 9: "A",
34
+ 10: "Bb",
35
+ 11: "B"
36
+ };
37
+ var NATURAL_PITCH_CLASSES = /* @__PURE__ */ new Set([0, 2, 4, 5, 7, 9, 11]);
38
+ function resolveAccidental(pc, preferFlats) {
39
+ if (NATURAL_PITCH_CLASSES.has(pc)) return "natural";
40
+ return preferFlats ? "flat" : "sharp";
41
+ }
42
+ function noteFromPitchClass(pc, preferFlats = false, octave) {
43
+ const name = preferFlats ? FLAT_NAMES[pc] : SHARP_NAMES[pc];
44
+ return {
45
+ pitchClass: pc,
46
+ name,
47
+ accidental: resolveAccidental(pc, preferFlats),
48
+ octave
49
+ };
50
+ }
51
+ function midiToPitchClass(midi) {
52
+ return (midi % 12 + 12) % 12;
53
+ }
54
+
55
+ // src/dom.ts
56
+ function getDOMParser() {
57
+ if (typeof globalThis.DOMParser !== "undefined") {
58
+ return globalThis.DOMParser;
59
+ }
60
+ try {
61
+ const linkedom = __require("linkedom");
62
+ return linkedom.DOMParser;
63
+ } catch {
64
+ throw new Error(
65
+ "DOMParser is not available. In Node.js, install linkedom: npm install linkedom"
66
+ );
67
+ }
68
+ }
69
+
70
+ // src/gpx-parser.ts
71
+ var DURATION_BEATS = {
72
+ whole: 4,
73
+ half: 2,
74
+ quarter: 1,
75
+ eighth: 0.5,
76
+ "16th": 0.25,
77
+ "32nd": 0.125,
78
+ "64th": 0.0625,
79
+ "128th": 0.03125
80
+ };
81
+ function durationToBeats(duration, dotCount, tuplet) {
82
+ let beats = DURATION_BEATS[duration] ?? 1;
83
+ let dotValue = beats;
84
+ for (let i = 0; i < dotCount; i++) {
85
+ dotValue /= 2;
86
+ beats += dotValue;
87
+ }
88
+ if (tuplet && tuplet.num > 0) {
89
+ beats *= tuplet.den / tuplet.num;
90
+ }
91
+ return beats;
92
+ }
93
+ function beatDurationMs(beat) {
94
+ const beatFraction = durationToBeats(beat.duration, beat.dotted, beat.tuplet);
95
+ const quarterNoteMs = 6e4 / beat.tempo;
96
+ return beatFraction * quarterNoteMs;
97
+ }
98
+ var BinaryReader = class {
99
+ constructor(buffer) {
100
+ this.view = new DataView(buffer);
101
+ this.bytes = new Uint8Array(buffer);
102
+ this.pos = 0;
103
+ this.bitOffset = 0;
104
+ this.byteLength = buffer.byteLength;
105
+ }
106
+ seek(offset) {
107
+ this.pos = offset;
108
+ this.bitOffset = 0;
109
+ }
110
+ getPosition() {
111
+ return this.pos;
112
+ }
113
+ getUint8() {
114
+ this.bitOffset = 0;
115
+ const v = this.view.getUint8(this.pos);
116
+ this.pos += 1;
117
+ return v;
118
+ }
119
+ getUint32LE(offset) {
120
+ this.bitOffset = 0;
121
+ if (offset !== void 0) {
122
+ return this.view.getUint32(offset, true);
123
+ }
124
+ const v = this.view.getUint32(this.pos, true);
125
+ this.pos += 4;
126
+ return v;
127
+ }
128
+ getString(length) {
129
+ this.bitOffset = 0;
130
+ const chars = [];
131
+ for (let i = 0; i < length; i++) {
132
+ chars.push(this.view.getUint8(this.pos + i));
133
+ }
134
+ this.pos += length;
135
+ return String.fromCharCode(...chars);
136
+ }
137
+ getZeroTerminatedString(offset, maxLength) {
138
+ const chars = [];
139
+ for (let i = 0; i < maxLength; i++) {
140
+ const code = this.view.getUint8(offset + i) & 255;
141
+ if (code === 0) break;
142
+ chars.push(code);
143
+ }
144
+ return String.fromCharCode(...chars);
145
+ }
146
+ getBytes(length, offset) {
147
+ this.bitOffset = 0;
148
+ const start = offset !== void 0 ? offset : this.pos;
149
+ if (offset === void 0) this.pos += length;
150
+ return new Uint8Array(this.view.buffer, start, length);
151
+ }
152
+ /**
153
+ * Reads `bitLength` bits as an unsigned integer (MSB-first), matching
154
+ * jDataView.getUnsigned() semantics: a persistent bitOffset carries across
155
+ * calls, and bits are read big-endian from the byte stream.
156
+ */
157
+ getUnsigned(bitLength) {
158
+ const startBit = (this.pos << 3) + this.bitOffset;
159
+ const endBit = startBit + bitLength;
160
+ const startByte = startBit >>> 3;
161
+ const endByte = endBit + 7 >>> 3;
162
+ this.bitOffset = endBit & 7;
163
+ if (this.bitOffset !== 0) {
164
+ this.pos = endBit >>> 3;
165
+ } else {
166
+ this.pos = endBit >>> 3;
167
+ }
168
+ let wideValue = 0;
169
+ for (let i = startByte; i < endByte; i++) {
170
+ wideValue = wideValue << 8 | (this.bytes[i] ?? 0);
171
+ }
172
+ const trailingBits = (endByte << 3) - endBit;
173
+ wideValue = wideValue >>> trailingBits;
174
+ if (bitLength < 32) {
175
+ wideValue = wideValue & (1 << bitLength) - 1;
176
+ }
177
+ return wideValue;
178
+ }
179
+ };
180
+ function readBitsReversed(reader, count) {
181
+ let bits = 0;
182
+ for (let i = 0; i < count; i++) {
183
+ bits |= reader.getUnsigned(1) << i;
184
+ }
185
+ return bits;
186
+ }
187
+ function isFileToStore(name) {
188
+ return name === "score.gpif" || name === "misc.xml";
189
+ }
190
+ function decompressBlock(reader, skipHeader) {
191
+ const expectedLength = reader.getUint32LE();
192
+ const temp = new Uint8Array(expectedLength);
193
+ let pos = 0;
194
+ try {
195
+ while (pos < expectedLength) {
196
+ const flag = reader.getUnsigned(1);
197
+ if (flag === 1) {
198
+ const wordSize = reader.getUnsigned(4);
199
+ const offset = readBitsReversed(reader, wordSize);
200
+ const size = readBitsReversed(reader, wordSize);
201
+ const sourcePosition = pos - offset;
202
+ const readSize = Math.min(offset, size);
203
+ for (let i = 0; i < readSize; i++) {
204
+ temp[pos + i] = temp[sourcePosition + i];
205
+ }
206
+ pos += readSize;
207
+ } else {
208
+ const size = readBitsReversed(reader, 2);
209
+ for (let i = 0; i < size; i++) {
210
+ temp[pos++] = reader.getUnsigned(8);
211
+ }
212
+ }
213
+ }
214
+ } catch {
215
+ }
216
+ if (skipHeader) {
217
+ return temp.buffer.slice(4, temp.byteLength);
218
+ }
219
+ return temp.buffer;
220
+ }
221
+ function parseBlockFilesystem(buffer) {
222
+ const SECTOR_SIZE = 4096;
223
+ const reader = new BinaryReader(buffer);
224
+ let offset = SECTOR_SIZE;
225
+ const files = [];
226
+ while (offset + SECTOR_SIZE + 3 < reader.byteLength) {
227
+ const entryType = reader.getUint32LE(offset);
228
+ if (entryType === 2) {
229
+ const name = reader.getZeroTerminatedString(offset + 4, 127);
230
+ const size = reader.getUint32LE(offset + 140);
231
+ const file = { name, size, data: null };
232
+ files.push(file);
233
+ const store = isFileToStore(name);
234
+ const blocksOffset = offset + 148;
235
+ const dataChunks = [];
236
+ let blockCount = 0;
237
+ let blockId;
238
+ while ((blockId = reader.getUint32LE(blocksOffset + 4 * blockCount)) !== 0) {
239
+ const blockOffset = blockId * SECTOR_SIZE;
240
+ if (store) {
241
+ const max = blockOffset + SECTOR_SIZE;
242
+ const blockSize = max > reader.byteLength ? SECTOR_SIZE - (max - reader.byteLength) : SECTOR_SIZE;
243
+ dataChunks.push(reader.getBytes(blockSize, blockOffset));
244
+ }
245
+ blockCount++;
246
+ }
247
+ if (store && dataChunks.length > 0) {
248
+ const totalSize = dataChunks.reduce((s, c) => s + c.length, 0);
249
+ const combined = new Uint8Array(Math.max(size, totalSize));
250
+ let writePos = 0;
251
+ for (const chunk of dataChunks) {
252
+ combined.set(chunk, writePos);
253
+ writePos += chunk.length;
254
+ }
255
+ file.data = combined.subarray(0, Math.min(size, totalSize));
256
+ }
257
+ }
258
+ offset += SECTOR_SIZE;
259
+ }
260
+ return files;
261
+ }
262
+ function decodeGpxBinary(data) {
263
+ const buf = new ArrayBuffer(data.byteLength);
264
+ new Uint8Array(buf).set(data);
265
+ const reader = new BinaryReader(buf);
266
+ const header = reader.getString(4);
267
+ let filesystemBuffer;
268
+ switch (header) {
269
+ case "BCFZ":
270
+ filesystemBuffer = decompressBlock(reader, true);
271
+ break;
272
+ case "BCFS":
273
+ {
274
+ const raw = reader.getBytes(reader.byteLength - 4);
275
+ const copy = new ArrayBuffer(raw.byteLength);
276
+ new Uint8Array(copy).set(raw);
277
+ filesystemBuffer = copy;
278
+ }
279
+ break;
280
+ default:
281
+ throw new Error(`Bad GPX header: "${header}" (unsupported format)`);
282
+ }
283
+ const files = parseBlockFilesystem(filesystemBuffer);
284
+ const result = /* @__PURE__ */ new Map();
285
+ for (const file of files) {
286
+ if (file.data && isFileToStore(file.name)) {
287
+ const decoder = new TextDecoder("utf-8");
288
+ result.set(file.name, decoder.decode(file.data));
289
+ }
290
+ }
291
+ return result;
292
+ }
293
+ function childText(el, tag) {
294
+ const child = el.querySelector(`:scope > ${tag}`);
295
+ return child?.textContent?.trim() ?? null;
296
+ }
297
+ function parseTimeSignature(time) {
298
+ if (!time) return { numerator: 4, denominator: 4 };
299
+ const parts = time.split("/");
300
+ return {
301
+ numerator: parseInt(parts[0], 10) || 4,
302
+ denominator: parseInt(parts[1], 10) || 4
303
+ };
304
+ }
305
+ function pitchClassFromStringFret(tuningPitches, stringIndex, fret) {
306
+ const openPitch = tuningPitches[stringIndex] ?? 0;
307
+ return ((openPitch + fret) % 12 + 12) % 12;
308
+ }
309
+ function parseTuningPitches(pitchesStr) {
310
+ return pitchesStr.split(/\s+/).filter((s) => s.length > 0).map(Number);
311
+ }
312
+ function findProperty(propertiesEl, name) {
313
+ if (!propertiesEl) return null;
314
+ const props = propertiesEl.querySelectorAll(":scope > Property");
315
+ for (const prop of props) {
316
+ if (prop.getAttribute("name") === name) return prop;
317
+ }
318
+ return null;
319
+ }
320
+ function propValue(propertiesEl, name) {
321
+ const prop = findProperty(propertiesEl, name);
322
+ if (!prop) return null;
323
+ const firstChild = prop.firstElementChild;
324
+ return firstChild?.textContent?.trim() ?? null;
325
+ }
326
+ function propEnabled(propertiesEl, name) {
327
+ const prop = findProperty(propertiesEl, name);
328
+ if (!prop) return null;
329
+ return prop.querySelector("Enable") !== null;
330
+ }
331
+ function propFlags(propertiesEl, name) {
332
+ const prop = findProperty(propertiesEl, name);
333
+ if (!prop) return null;
334
+ const flagsEl = prop.querySelector("Flags");
335
+ if (!flagsEl) return null;
336
+ const val = parseInt(flagsEl.textContent?.trim() ?? "", 10);
337
+ return isNaN(val) ? null : val;
338
+ }
339
+ function propFloat(propertiesEl, name) {
340
+ const prop = findProperty(propertiesEl, name);
341
+ if (!prop) return null;
342
+ const floatEl = prop.querySelector("Float");
343
+ if (!floatEl) return null;
344
+ const val = parseFloat(floatEl.textContent?.trim() ?? "");
345
+ return isNaN(val) ? null : val;
346
+ }
347
+ function propHType(propertiesEl, name) {
348
+ const prop = findProperty(propertiesEl, name);
349
+ if (!prop) return null;
350
+ const htypeEl = prop.querySelector("HType");
351
+ return htypeEl?.textContent?.trim() ?? null;
352
+ }
353
+ function transformNoteElement(noteEl, tuningPitches) {
354
+ const propsEl = noteEl.querySelector(":scope > Properties");
355
+ const stringIndex = parseInt(propValue(propsEl, "String") ?? "0", 10);
356
+ const fret = parseInt(propValue(propsEl, "Fret") ?? "0", 10);
357
+ const pc = pitchClassFromStringFret(tuningPitches, stringIndex, fret);
358
+ const note = noteFromPitchClass(pc, false);
359
+ const isBended = propEnabled(propsEl, "Bended");
360
+ const bend = isBended ? {
361
+ origin: propFloat(propsEl, "BendOriginValue") ?? 0,
362
+ destination: propFloat(propsEl, "BendDestinationValue") ?? 0,
363
+ middle: propFloat(propsEl, "BendMiddleValue") ?? 0
364
+ } : null;
365
+ const tieEl = noteEl.querySelector(":scope > Tie");
366
+ const letRingEl = noteEl.querySelector(":scope > LetRing");
367
+ const vibratoEl = noteEl.querySelector(":scope > Vibrato");
368
+ const accentEl = noteEl.querySelector(":scope > Accent");
369
+ return {
370
+ string: stringIndex,
371
+ fret,
372
+ pitchClass: pc,
373
+ noteName: note.name,
374
+ slide: propFlags(propsEl, "Slide"),
375
+ harmonic: propHType(propsEl, "HarmonicType"),
376
+ palmMute: propEnabled(propsEl, "PalmMuted") ?? false,
377
+ muted: propEnabled(propsEl, "Muted") ?? false,
378
+ letRing: letRingEl !== null,
379
+ bend,
380
+ tie: {
381
+ origin: tieEl ? tieEl.getAttribute("origin") === "true" : false,
382
+ destination: tieEl ? tieEl.getAttribute("destination") === "true" : false
383
+ },
384
+ vibrato: vibratoEl?.textContent?.trim() ?? null,
385
+ hammerOn: propEnabled(propsEl, "HopoOrigin") ?? false,
386
+ pullOff: propEnabled(propsEl, "HopoDestination") ?? false,
387
+ tapped: propEnabled(propsEl, "Tapped") ?? false,
388
+ accent: accentEl ? parseInt(accentEl.textContent?.trim() ?? "", 10) || null : null
389
+ };
390
+ }
391
+ function indexElements(container, childTag) {
392
+ const map = /* @__PURE__ */ new Map();
393
+ if (!container) return map;
394
+ const children = container.querySelectorAll(`:scope > ${childTag}`);
395
+ for (const child of children) {
396
+ const id = child.getAttribute("id");
397
+ if (id) map.set(id, child);
398
+ }
399
+ return map;
400
+ }
401
+ function splitIds(text) {
402
+ if (!text) return [];
403
+ return text.split(/\s+/).filter((s) => s.length > 0);
404
+ }
405
+ function buildTempoMap(masterTrackEl) {
406
+ const tempoMap = /* @__PURE__ */ new Map();
407
+ if (!masterTrackEl) return tempoMap;
408
+ const automationsEl = masterTrackEl.querySelector(":scope > Automations");
409
+ if (!automationsEl) return tempoMap;
410
+ const autos = automationsEl.querySelectorAll(":scope > Automation");
411
+ for (const auto of autos) {
412
+ const type = childText(auto, "Type");
413
+ if (type?.toLowerCase() !== "tempo") continue;
414
+ const barText = childText(auto, "Bar");
415
+ const valueText = childText(auto, "Value");
416
+ if (barText === null || valueText === null) continue;
417
+ const barIndex = parseInt(barText, 10);
418
+ const value = parseFloat(valueText);
419
+ if (!isNaN(barIndex) && !isNaN(value)) {
420
+ tempoMap.set(barIndex, value);
421
+ }
422
+ }
423
+ return tempoMap;
424
+ }
425
+ function tempoAtBar(barIndex, tempoMap, defaultTempo) {
426
+ for (let i = barIndex; i >= 0; i--) {
427
+ const t = tempoMap.get(i);
428
+ if (t !== void 0) return t;
429
+ }
430
+ return defaultTempo;
431
+ }
432
+ function gpifToTabSong(doc) {
433
+ const gpif = doc.querySelector("GPIF");
434
+ if (!gpif) throw new Error("Invalid GPIF XML: no <GPIF> root element");
435
+ const scoreEl = gpif.querySelector(":scope > Score");
436
+ const title = scoreEl ? childText(scoreEl, "Title") ?? "" : "";
437
+ const artist = scoreEl ? childText(scoreEl, "Artist") ?? "" : "";
438
+ const album = scoreEl ? childText(scoreEl, "Album") ?? "" : "";
439
+ const noteMap = indexElements(gpif.querySelector(":scope > Notes"), "Note");
440
+ const beatMap = indexElements(gpif.querySelector(":scope > Beats"), "Beat");
441
+ const voiceMap = indexElements(gpif.querySelector(":scope > Voices"), "Voice");
442
+ const barMap = indexElements(gpif.querySelector(":scope > Bars"), "Bar");
443
+ const rhythmMap = indexElements(gpif.querySelector(":scope > Rhythms"), "Rhythm");
444
+ const masterBarsEl = gpif.querySelector(":scope > MasterBars");
445
+ const masterBarEls = masterBarsEl ? Array.from(masterBarsEl.querySelectorAll(":scope > MasterBar")) : [];
446
+ const masterTrackEl = gpif.querySelector(":scope > MasterTrack");
447
+ const tempoMap = buildTempoMap(masterTrackEl);
448
+ let initialTempo = 120;
449
+ if (tempoMap.size > 0) {
450
+ initialTempo = tempoMap.get(0) ?? 120;
451
+ }
452
+ const tracksEl = gpif.querySelector(":scope > Tracks");
453
+ const trackEls = tracksEl ? Array.from(tracksEl.querySelectorAll(":scope > Track")) : [];
454
+ const tracks = trackEls.map(
455
+ (trackEl) => transformTrackElement(
456
+ trackEl,
457
+ masterBarEls,
458
+ barMap,
459
+ voiceMap,
460
+ beatMap,
461
+ noteMap,
462
+ rhythmMap,
463
+ tempoMap,
464
+ initialTempo
465
+ )
466
+ );
467
+ return { title, artist, album, tempo: initialTempo, tracks };
468
+ }
469
+ function transformTrackElement(trackEl, masterBarEls, barMap, voiceMap, beatMap, noteMap, rhythmMap, tempoMap, defaultTempo) {
470
+ const trackId = trackEl.getAttribute("id") ?? "0";
471
+ const propsEl = trackEl.querySelector(":scope > Properties");
472
+ let tuningPitches = [40, 45, 50, 55, 59, 64];
473
+ let capoFret = 0;
474
+ if (propsEl) {
475
+ const tuningProp = findProperty(propsEl, "Tuning");
476
+ if (tuningProp) {
477
+ const pitchesEl = tuningProp.querySelector("Pitches");
478
+ if (pitchesEl?.textContent) {
479
+ tuningPitches = parseTuningPitches(pitchesEl.textContent.trim());
480
+ }
481
+ }
482
+ const capoProp = findProperty(propsEl, "CapoFret");
483
+ if (capoProp) {
484
+ const fretEl = capoProp.querySelector("Fret");
485
+ if (fretEl?.textContent) {
486
+ const parsed = parseInt(fretEl.textContent.trim(), 10);
487
+ if (!isNaN(parsed)) capoFret = parsed;
488
+ }
489
+ }
490
+ }
491
+ const tuning = tuningPitches.map((midi) => noteFromPitchClass(midiToPitchClass(midi))).reverse();
492
+ const trackIndex = parseInt(trackId, 10);
493
+ let globalBeatIndex = 0;
494
+ const bars = [];
495
+ for (let mbIdx = 0; mbIdx < masterBarEls.length; mbIdx++) {
496
+ const masterBarEl = masterBarEls[mbIdx];
497
+ const barsText = childText(masterBarEl, "Bars");
498
+ const barIds = splitIds(barsText);
499
+ const barId = barIds[trackIndex] ?? barIds[0];
500
+ const barEl = barId ? barMap.get(barId) : void 0;
501
+ if (!barEl) {
502
+ bars.push(makeEmptyBar(mbIdx, masterBarEl));
503
+ continue;
504
+ }
505
+ const timeText = childText(masterBarEl, "Time");
506
+ const timeSig = parseTimeSignature(timeText);
507
+ const keyEl = masterBarEl.querySelector(":scope > Key");
508
+ const keySig = keyEl ? {
509
+ accidentalCount: parseInt(childText(keyEl, "AccidentalCount") ?? "0", 10),
510
+ mode: childText(keyEl, "Mode")?.toLowerCase() ?? "major"
511
+ } : null;
512
+ const sectionEl = masterBarEl.querySelector(":scope > Section");
513
+ const section = sectionEl ? {
514
+ letter: sectionEl.getAttribute("letter") ?? void 0,
515
+ text: sectionEl.getAttribute("text") ?? void 0
516
+ } : null;
517
+ const repeatEl = masterBarEl.querySelector(":scope > Repeat");
518
+ const repeatStart = repeatEl ? repeatEl.getAttribute("start") === "true" : false;
519
+ const repeatEnd = repeatEl ? repeatEl.getAttribute("end") === "true" : false;
520
+ const repeatCount = repeatEl ? parseInt(repeatEl.getAttribute("count") ?? "0", 10) || 0 : 0;
521
+ const voicesText = childText(barEl, "Voices");
522
+ const voiceIds = splitIds(voicesText);
523
+ const tabBeats = [];
524
+ const currentTempo = tempoAtBar(mbIdx, tempoMap, defaultTempo);
525
+ if (voiceIds.length > 0) {
526
+ const voiceEl = voiceMap.get(voiceIds[0]);
527
+ if (voiceEl) {
528
+ const voiceBeatsText = childText(voiceEl, "Beats");
529
+ const voiceBeatIds = splitIds(voiceBeatsText);
530
+ for (const beatId of voiceBeatIds) {
531
+ const beatEl = beatMap.get(beatId);
532
+ if (!beatEl) continue;
533
+ const rhythmRef = beatEl.querySelector(":scope > Rhythm")?.getAttribute("ref");
534
+ const rhythmEl = rhythmRef ? rhythmMap.get(rhythmRef) : void 0;
535
+ const notesText = childText(beatEl, "Notes");
536
+ const noteIds = splitIds(notesText);
537
+ const stringCount = tuningPitches.length;
538
+ const tabNotes = noteIds.map((nid) => noteMap.get(nid)).filter((n) => n !== void 0).map((n) => transformNoteElement(n, tuningPitches)).map((n) => ({ ...n, string: stringCount - 1 - n.string }));
539
+ const noteValueText = rhythmEl ? childText(rhythmEl, "NoteValue") : null;
540
+ const duration = noteValueText?.toLowerCase() ?? "quarter";
541
+ const tupletEl = rhythmEl?.querySelector(":scope > PrimaryTuplet");
542
+ const tuplet = tupletEl ? {
543
+ num: parseInt(tupletEl.getAttribute("num") ?? "1", 10) || 1,
544
+ den: parseInt(tupletEl.getAttribute("den") ?? "1", 10) || 1
545
+ } : null;
546
+ const dotEl = rhythmEl?.querySelector(":scope > AugmentationDot");
547
+ const dotCount = dotEl ? parseInt(dotEl.getAttribute("count") ?? "0", 10) || 0 : 0;
548
+ const isRest = tabNotes.length === 0;
549
+ const dynamicText = childText(beatEl, "Dynamic");
550
+ tabBeats.push({
551
+ index: globalBeatIndex++,
552
+ barIndex: mbIdx,
553
+ notes: tabNotes,
554
+ duration,
555
+ tuplet: tuplet && (tuplet.num !== 1 || tuplet.den !== 1) ? tuplet : null,
556
+ dotted: dotCount,
557
+ isRest,
558
+ dynamic: dynamicText,
559
+ tempo: currentTempo
560
+ });
561
+ }
562
+ }
563
+ }
564
+ bars.push({
565
+ index: mbIdx,
566
+ timeSignature: timeSig,
567
+ keySignature: keySig,
568
+ section,
569
+ beats: tabBeats,
570
+ repeatStart,
571
+ repeatEnd,
572
+ repeatCount
573
+ });
574
+ }
575
+ return {
576
+ id: trackId,
577
+ name: childText(trackEl, "Name") ?? "Track",
578
+ shortName: childText(trackEl, "ShortName") ?? "",
579
+ instrument: trackEl.querySelector(":scope > Instrument")?.getAttribute("ref") ?? null,
580
+ tuning,
581
+ capoFret,
582
+ bars
583
+ };
584
+ }
585
+ function makeEmptyBar(index, masterBarEl) {
586
+ const timeText = childText(masterBarEl, "Time");
587
+ return {
588
+ index,
589
+ timeSignature: parseTimeSignature(timeText),
590
+ keySignature: null,
591
+ section: null,
592
+ beats: [],
593
+ repeatStart: false,
594
+ repeatEnd: false,
595
+ repeatCount: 0
596
+ };
597
+ }
598
+ function parseGpxFile(data) {
599
+ const files = decodeGpxBinary(data);
600
+ const gpifXml = files.get("score.gpif");
601
+ if (!gpifXml) {
602
+ throw new Error("No score.gpif found in GPX archive");
603
+ }
604
+ const DOMParserImpl = getDOMParser();
605
+ const parser = new DOMParserImpl();
606
+ const doc = parser.parseFromString(gpifXml, "text/xml");
607
+ const parseError = doc.querySelector("parsererror");
608
+ if (parseError) {
609
+ throw new Error(`Failed to parse GPIF XML: ${parseError.textContent}`);
610
+ }
611
+ return gpifToTabSong(doc);
612
+ }
613
+
614
+ // src/gp5-parser.ts
615
+ var GP5Reader = class {
616
+ constructor(buffer) {
617
+ this.view = new DataView(buffer);
618
+ this.buf = new Uint8Array(buffer);
619
+ this.pos = 0;
620
+ this.byteLength = buffer.byteLength;
621
+ }
622
+ getPosition() {
623
+ return this.pos;
624
+ }
625
+ skip(n) {
626
+ this.pos += n;
627
+ }
628
+ readByte() {
629
+ const v = this.buf[this.pos];
630
+ this.pos += 1;
631
+ return v;
632
+ }
633
+ readSignedByte() {
634
+ const v = this.view.getInt8(this.pos);
635
+ this.pos += 1;
636
+ return v;
637
+ }
638
+ readBool() {
639
+ return this.readByte() !== 0;
640
+ }
641
+ readShort() {
642
+ const v = this.view.getInt16(this.pos, true);
643
+ this.pos += 2;
644
+ return v;
645
+ }
646
+ readInt() {
647
+ const v = this.view.getInt32(this.pos, true);
648
+ this.pos += 4;
649
+ return v;
650
+ }
651
+ readDouble() {
652
+ const v = this.view.getFloat64(this.pos, true);
653
+ this.pos += 8;
654
+ return v;
655
+ }
656
+ /** Reads IntByteSizeString: int(strLen+1) + byte(strLen) + chars. */
657
+ readIntByteSizeString() {
658
+ const totalSize = this.readInt();
659
+ const strLen = this.readByte();
660
+ const str = this.readChars(strLen);
661
+ const padding = Math.max(0, totalSize - 1 - strLen);
662
+ this.skip(padding);
663
+ return str;
664
+ }
665
+ /** Reads IntSizeString: int(len) + chars. */
666
+ readIntString() {
667
+ const len = this.readInt();
668
+ if (len <= 0) return "";
669
+ return this.readChars(len);
670
+ }
671
+ /** Reads ByteSizeString with fixed buffer length. */
672
+ readByteSizeString(fixedLen) {
673
+ const strLen = this.readByte();
674
+ const str = this.readChars(Math.min(strLen, fixedLen));
675
+ const remaining = fixedLen - Math.min(strLen, fixedLen);
676
+ this.skip(remaining);
677
+ return str;
678
+ }
679
+ readChars(length) {
680
+ const chars = [];
681
+ for (let i = 0; i < length; i++) {
682
+ chars.push(this.buf[this.pos + i]);
683
+ }
684
+ this.pos += length;
685
+ return String.fromCharCode(...chars);
686
+ }
687
+ };
688
+ function parseVersionString(versionStr) {
689
+ const match = versionStr.match(/v(\d+)\.(\d+)/);
690
+ if (!match) return { major: 5, minor: 10, patch: 0 };
691
+ return {
692
+ major: parseInt(match[1], 10),
693
+ minor: parseInt(match[2], 10),
694
+ patch: 0
695
+ };
696
+ }
697
+ function versionGreaterThan(v, major, minor, patch) {
698
+ if (v.major !== major) return v.major > major;
699
+ if (v.minor !== minor) return v.minor > minor;
700
+ return v.patch > patch;
701
+ }
702
+ var GP_DURATION_MAP = {
703
+ [-2]: "whole",
704
+ [-1]: "half",
705
+ [0]: "quarter",
706
+ [1]: "eighth",
707
+ [2]: "16th",
708
+ [3]: "32nd",
709
+ [4]: "64th",
710
+ [5]: "128th"
711
+ };
712
+ function gpDurationToDuration(value) {
713
+ return GP_DURATION_MAP[value] ?? "quarter";
714
+ }
715
+ var TUPLET_MAP = {
716
+ 3: { num: 3, den: 2 },
717
+ 5: { num: 5, den: 4 },
718
+ 6: { num: 6, den: 4 },
719
+ 7: { num: 7, den: 4 },
720
+ 9: { num: 9, den: 8 },
721
+ 10: { num: 10, den: 8 },
722
+ 11: { num: 11, den: 8 },
723
+ 12: { num: 12, den: 8 },
724
+ 13: { num: 13, den: 8 }
725
+ };
726
+ var HARMONIC_TYPE_MAP = {
727
+ 1: "Natural",
728
+ 2: "Artificial",
729
+ 3: "Tapped",
730
+ 4: "Pinch",
731
+ 5: "Semi"
732
+ };
733
+ function readInfo(r) {
734
+ const title = r.readIntByteSizeString();
735
+ const subtitle = r.readIntByteSizeString();
736
+ const artist = r.readIntByteSizeString();
737
+ const album = r.readIntByteSizeString();
738
+ r.readIntByteSizeString();
739
+ r.readIntByteSizeString();
740
+ r.readIntByteSizeString();
741
+ r.readIntByteSizeString();
742
+ r.readIntByteSizeString();
743
+ const noticeCount = r.readInt();
744
+ for (let i = 0; i < noticeCount; i++) {
745
+ r.readIntByteSizeString();
746
+ }
747
+ return { title, subtitle, artist, album };
748
+ }
749
+ function readLyrics(r) {
750
+ r.readInt();
751
+ for (let i = 0; i < 5; i++) {
752
+ r.readInt();
753
+ r.readIntString();
754
+ }
755
+ }
756
+ function readRSEMasterEffect(r, version) {
757
+ if (versionGreaterThan(version, 5, 0, 0)) {
758
+ r.readInt();
759
+ r.readInt();
760
+ readEqualizer(r, 11);
761
+ }
762
+ }
763
+ function readEqualizer(r, bands) {
764
+ for (let i = 0; i < bands; i++) {
765
+ r.readSignedByte();
766
+ }
767
+ }
768
+ function readPageSetup(r) {
769
+ r.readInt();
770
+ r.readInt();
771
+ r.readInt();
772
+ r.readInt();
773
+ r.readInt();
774
+ r.readInt();
775
+ r.readInt();
776
+ r.readShort();
777
+ for (let i = 0; i < 10; i++) {
778
+ r.readIntByteSizeString();
779
+ }
780
+ }
781
+ function readDirections(r) {
782
+ for (let i = 0; i < 19; i++) {
783
+ r.readShort();
784
+ }
785
+ }
786
+ function readMidiChannels(r) {
787
+ const channels = [];
788
+ for (let i = 0; i < 64; i++) {
789
+ const instrument = r.readInt();
790
+ const volume = r.readByte();
791
+ const balance = r.readByte();
792
+ const chorus = r.readByte();
793
+ const reverb = r.readByte();
794
+ const phaser = r.readByte();
795
+ const tremolo = r.readByte();
796
+ r.skip(2);
797
+ channels.push({ instrument, volume, balance, chorus, reverb, phaser, tremolo });
798
+ }
799
+ return channels;
800
+ }
801
+ function readMeasureHeaders(r, count, _version) {
802
+ const headers = [];
803
+ let prevNumerator = 4;
804
+ let prevDenominator = 4;
805
+ for (let i = 0; i < count; i++) {
806
+ if (i > 0) {
807
+ r.skip(1);
808
+ }
809
+ const flags = r.readByte();
810
+ let numerator = prevNumerator;
811
+ let denominator = prevDenominator;
812
+ if (flags & 1) {
813
+ numerator = r.readSignedByte();
814
+ }
815
+ if (flags & 2) {
816
+ denominator = r.readSignedByte();
817
+ }
818
+ const repeatOpen = (flags & 4) !== 0;
819
+ let repeatClose = -1;
820
+ if (flags & 8) {
821
+ repeatClose = r.readSignedByte();
822
+ if (repeatClose > 0) repeatClose -= 1;
823
+ }
824
+ let marker = null;
825
+ if (flags & 32) {
826
+ const name = r.readIntByteSizeString();
827
+ const colorR = r.readByte();
828
+ const colorG = r.readByte();
829
+ const colorB = r.readByte();
830
+ r.skip(1);
831
+ marker = { name, color: [colorR, colorG, colorB] };
832
+ }
833
+ let keySignature = 0;
834
+ let keyMode = 0;
835
+ if (flags & 64) {
836
+ keySignature = r.readSignedByte();
837
+ keyMode = r.readSignedByte();
838
+ }
839
+ const hasDoubleBar = (flags & 128) !== 0;
840
+ let repeatAlternative = 0;
841
+ if (flags & 16) {
842
+ repeatAlternative = r.readByte();
843
+ }
844
+ if (flags & 3) {
845
+ r.skip(4);
846
+ }
847
+ if (!(flags & 16)) {
848
+ r.skip(1);
849
+ }
850
+ const tripletFeel = r.readByte();
851
+ headers.push({
852
+ numerator,
853
+ denominator,
854
+ repeatOpen,
855
+ repeatClose,
856
+ repeatAlternative,
857
+ marker,
858
+ keySignature,
859
+ keyMode,
860
+ hasDoubleBar,
861
+ tripletFeel
862
+ });
863
+ prevNumerator = numerator;
864
+ prevDenominator = denominator;
865
+ }
866
+ return headers;
867
+ }
868
+ function readTrackHeaders(r, count, version) {
869
+ const tracks = [];
870
+ for (let i = 0; i < count; i++) {
871
+ if (i === 0 || versionGreaterThan(version, 5, 0, 0) === false) {
872
+ r.skip(1);
873
+ }
874
+ const flags1 = r.readByte();
875
+ const isPercussion = (flags1 & 1) !== 0;
876
+ const name = r.readByteSizeString(40);
877
+ const numStrings = r.readInt();
878
+ const tuning = [];
879
+ for (let s = 0; s < 7; s++) {
880
+ const val = r.readInt();
881
+ if (s < numStrings) tuning.push(val);
882
+ }
883
+ const port = r.readInt();
884
+ const channelIndex = r.readInt() - 1;
885
+ const effectChannel = r.readInt() - 1;
886
+ const fretCount = r.readInt();
887
+ const capoFret = r.readInt();
888
+ r.skip(4);
889
+ tracks.push({ name, isPercussion, numStrings, tuning, port, channelIndex, effectChannel, fretCount, capoFret });
890
+ r.readShort();
891
+ r.readByte();
892
+ r.readByte();
893
+ readTrackRSE(r, version);
894
+ }
895
+ if (versionGreaterThan(version, 5, 0, 0)) {
896
+ r.skip(1);
897
+ } else {
898
+ r.skip(2);
899
+ }
900
+ return tracks;
901
+ }
902
+ function readTrackRSE(r, version) {
903
+ r.readByte();
904
+ r.skip(12);
905
+ r.skip(12);
906
+ readRSEInstrument(r, version);
907
+ if (versionGreaterThan(version, 5, 0, 0)) {
908
+ readEqualizer(r, 4);
909
+ readRSEInstrumentEffect(r, version);
910
+ }
911
+ }
912
+ function readRSEInstrument(r, version) {
913
+ r.readInt();
914
+ r.readInt();
915
+ r.readInt();
916
+ if (versionGreaterThan(version, 5, 0, 0)) {
917
+ r.readInt();
918
+ } else {
919
+ r.readShort();
920
+ r.skip(1);
921
+ }
922
+ }
923
+ function readRSEInstrumentEffect(r, version) {
924
+ if (versionGreaterThan(version, 5, 0, 0)) {
925
+ r.readIntByteSizeString();
926
+ r.readIntByteSizeString();
927
+ }
928
+ }
929
+ function readMeasures(r, measureCount, trackCount, version) {
930
+ const allMeasures = [];
931
+ for (let t = 0; t < trackCount; t++) {
932
+ allMeasures.push([]);
933
+ }
934
+ for (let m = 0; m < measureCount; m++) {
935
+ for (let t = 0; t < trackCount; t++) {
936
+ const voice1Beats = readVoice(r, version);
937
+ const voice2Beats = readVoice(r, version);
938
+ r.readByte();
939
+ const beats = voice1Beats.length > 0 ? voice1Beats : voice2Beats;
940
+ allMeasures[t].push(beats);
941
+ }
942
+ }
943
+ return allMeasures;
944
+ }
945
+ function readVoice(r, version) {
946
+ const beatCount = r.readInt();
947
+ const beats = [];
948
+ for (let b = 0; b < beatCount; b++) {
949
+ beats.push(readBeat(r, version));
950
+ }
951
+ return beats;
952
+ }
953
+ function readBeat(r, version) {
954
+ const flags = r.readByte();
955
+ let isRest = false;
956
+ let isEmpty = false;
957
+ if (flags & 64) {
958
+ const status = r.readByte();
959
+ isEmpty = status === 0;
960
+ isRest = status === 2;
961
+ }
962
+ const durationValue = r.readSignedByte();
963
+ const duration = gpDurationToDuration(durationValue);
964
+ const dotted = (flags & 1) !== 0;
965
+ let tuplet = null;
966
+ if (flags & 32) {
967
+ const tupletValue = r.readInt();
968
+ tuplet = TUPLET_MAP[tupletValue] ?? null;
969
+ }
970
+ if (flags & 2) {
971
+ readChord(r, version);
972
+ }
973
+ if (flags & 4) {
974
+ r.readIntByteSizeString();
975
+ }
976
+ if (flags & 8) {
977
+ readBeatEffects(r, version);
978
+ }
979
+ if (flags & 16) {
980
+ readMixTableChange(r, version);
981
+ }
982
+ const stringFlags = r.readByte();
983
+ const notes = [];
984
+ for (let i = 6; i >= 0; i--) {
985
+ if (stringFlags & 1 << i) {
986
+ notes.push(readNote(r, version));
987
+ }
988
+ }
989
+ const flags2 = r.readShort();
990
+ if (flags2 & 2048) {
991
+ r.readByte();
992
+ }
993
+ return { duration, dotted, tuplet, isRest: isRest || isEmpty, isEmpty, notes };
994
+ }
995
+ function readChord(r, _version) {
996
+ const header = r.readByte();
997
+ if (header === 0) {
998
+ r.readIntByteSizeString();
999
+ const firstFret = r.readInt();
1000
+ if (firstFret !== 0) {
1001
+ for (let i = 0; i < 6; i++) r.readInt();
1002
+ }
1003
+ } else {
1004
+ r.skip(16);
1005
+ r.readByteSizeString(22);
1006
+ r.readByte();
1007
+ r.readByte();
1008
+ r.readByte();
1009
+ for (let i = 0; i < 6; i++) r.readInt();
1010
+ r.readByte();
1011
+ r.skip(5);
1012
+ r.skip(5);
1013
+ r.skip(5);
1014
+ r.skip(7);
1015
+ r.skip(1);
1016
+ r.skip(7);
1017
+ r.readBool();
1018
+ }
1019
+ }
1020
+ function readBeatEffects(r, _version) {
1021
+ const flags1 = r.readByte();
1022
+ const flags2 = r.readByte();
1023
+ if (flags1 & 32) {
1024
+ const slapEffect = r.readSignedByte();
1025
+ if (slapEffect === 0) {
1026
+ readBend(r);
1027
+ }
1028
+ }
1029
+ if (flags2 & 4) {
1030
+ readBend(r);
1031
+ }
1032
+ if (flags1 & 64) {
1033
+ r.readByte();
1034
+ r.readByte();
1035
+ }
1036
+ if (flags2 & 2) {
1037
+ r.readSignedByte();
1038
+ }
1039
+ }
1040
+ function readBend(r) {
1041
+ const type = r.readSignedByte();
1042
+ const value = r.readInt();
1043
+ const pointCount = r.readInt();
1044
+ const points = [];
1045
+ for (let i = 0; i < pointCount; i++) {
1046
+ const position = r.readInt();
1047
+ const pointValue = r.readInt();
1048
+ const vibrato = r.readBool();
1049
+ points.push({ position, value: pointValue, vibrato });
1050
+ }
1051
+ return { type, value, points };
1052
+ }
1053
+ function readMixTableChange(r, version) {
1054
+ r.readSignedByte();
1055
+ readRSEInstrument(r, version);
1056
+ if (!versionGreaterThan(version, 5, 0, 0)) {
1057
+ r.skip(1);
1058
+ }
1059
+ const volume = r.readSignedByte();
1060
+ const balance = r.readSignedByte();
1061
+ const chorus = r.readSignedByte();
1062
+ const reverb = r.readSignedByte();
1063
+ const phaser = r.readSignedByte();
1064
+ const tremolo = r.readSignedByte();
1065
+ r.readIntByteSizeString();
1066
+ const tempo = r.readInt();
1067
+ if (volume >= 0) r.readSignedByte();
1068
+ if (balance >= 0) r.readSignedByte();
1069
+ if (chorus >= 0) r.readSignedByte();
1070
+ if (reverb >= 0) r.readSignedByte();
1071
+ if (phaser >= 0) r.readSignedByte();
1072
+ if (tremolo >= 0) r.readSignedByte();
1073
+ if (tempo >= 0) {
1074
+ r.readSignedByte();
1075
+ if (versionGreaterThan(version, 5, 0, 0)) {
1076
+ r.readBool();
1077
+ }
1078
+ }
1079
+ r.readByte();
1080
+ r.readSignedByte();
1081
+ readRSEInstrumentEffect(r, version);
1082
+ }
1083
+ function readNote(r, version) {
1084
+ const flags = r.readByte();
1085
+ const heavyAccent = (flags & 2) !== 0;
1086
+ const accent = (flags & 64) !== 0;
1087
+ let isTied = false;
1088
+ let isDead = false;
1089
+ if (flags & 32) {
1090
+ const noteType = r.readByte();
1091
+ isTied = noteType === 2;
1092
+ isDead = noteType === 3;
1093
+ }
1094
+ let velocity = 8;
1095
+ if (flags & 16) {
1096
+ velocity = r.readSignedByte();
1097
+ }
1098
+ let fret = 0;
1099
+ if (flags & 32) {
1100
+ fret = r.readSignedByte();
1101
+ if (fret < 0 || fret > 99) fret = 0;
1102
+ }
1103
+ if (flags & 128) {
1104
+ r.readSignedByte();
1105
+ r.readSignedByte();
1106
+ }
1107
+ if (flags & 1) {
1108
+ r.readDouble();
1109
+ }
1110
+ r.readByte();
1111
+ let hammerOn = false;
1112
+ let letRing = false;
1113
+ let slide = null;
1114
+ let harmonic = null;
1115
+ let palmMute = false;
1116
+ let vibrato = false;
1117
+ let bend = null;
1118
+ let staccato = false;
1119
+ let trill = null;
1120
+ let tremoloPicking = null;
1121
+ if (flags & 8) {
1122
+ const result = readNoteEffects(r, version);
1123
+ hammerOn = result.hammerOn;
1124
+ letRing = result.letRing;
1125
+ slide = result.slide;
1126
+ harmonic = result.harmonic;
1127
+ palmMute = result.palmMute;
1128
+ vibrato = result.vibrato;
1129
+ bend = result.bend;
1130
+ staccato = result.staccato;
1131
+ trill = result.trill;
1132
+ tremoloPicking = result.tremoloPicking;
1133
+ }
1134
+ return {
1135
+ string: 0,
1136
+ // Will be set by caller based on string index
1137
+ fret,
1138
+ isTied,
1139
+ isDead,
1140
+ velocity,
1141
+ hammerOn,
1142
+ letRing,
1143
+ slide,
1144
+ harmonic,
1145
+ palmMute,
1146
+ vibrato,
1147
+ bend,
1148
+ staccato,
1149
+ heavyAccent,
1150
+ accent,
1151
+ trill,
1152
+ tremoloPicking
1153
+ };
1154
+ }
1155
+ function readNoteEffects(r, version) {
1156
+ const flags1 = r.readByte();
1157
+ const flags2 = r.readByte();
1158
+ let bend = null;
1159
+ let hammerOn = false;
1160
+ let letRing = false;
1161
+ let slide = null;
1162
+ let harmonic = null;
1163
+ let palmMute = false;
1164
+ let vibrato = false;
1165
+ let staccato = false;
1166
+ let trill = null;
1167
+ let tremoloPicking = null;
1168
+ if (flags1 & 1) {
1169
+ bend = readBend(r);
1170
+ }
1171
+ hammerOn = (flags1 & 2) !== 0;
1172
+ letRing = (flags1 & 8) !== 0;
1173
+ if (flags1 & 16) {
1174
+ readGraceNote(r, version);
1175
+ }
1176
+ staccato = (flags2 & 1) !== 0;
1177
+ palmMute = (flags2 & 2) !== 0;
1178
+ if (flags2 & 4) {
1179
+ tremoloPicking = r.readSignedByte();
1180
+ }
1181
+ if (flags2 & 8) {
1182
+ slide = r.readByte();
1183
+ }
1184
+ if (flags2 & 16) {
1185
+ const harmonicType = r.readSignedByte();
1186
+ harmonic = HARMONIC_TYPE_MAP[harmonicType] ?? null;
1187
+ if (harmonicType === 2) {
1188
+ r.readByte();
1189
+ r.readSignedByte();
1190
+ r.readByte();
1191
+ } else if (harmonicType === 3) {
1192
+ r.readByte();
1193
+ }
1194
+ }
1195
+ if (flags2 & 32) {
1196
+ const trillFret = r.readSignedByte();
1197
+ const trillPeriod = r.readSignedByte();
1198
+ trill = { fret: trillFret, period: trillPeriod };
1199
+ }
1200
+ vibrato = (flags2 & 64) !== 0;
1201
+ return { hammerOn, letRing, slide, harmonic, palmMute, vibrato, bend, staccato, trill, tremoloPicking };
1202
+ }
1203
+ function readGraceNote(r, version) {
1204
+ r.readByte();
1205
+ r.readByte();
1206
+ r.readByte();
1207
+ r.readByte();
1208
+ if (versionGreaterThan(version, 5, 0, 0)) {
1209
+ r.readByte();
1210
+ } else {
1211
+ r.readByte();
1212
+ }
1213
+ }
1214
+ function transformToTabSong(info, tempo, measureHeaders, trackHeaders, parsedMeasures, channels) {
1215
+ const tracks = trackHeaders.map((th, trackIdx) => {
1216
+ const tuningPitches = th.tuning;
1217
+ const tuning = tuningPitches.map((midi) => noteFromPitchClass(midiToPitchClass(midi))).reverse();
1218
+ let globalBeatIndex = 0;
1219
+ const bars = [];
1220
+ for (let mIdx = 0; mIdx < measureHeaders.length; mIdx++) {
1221
+ const mh = measureHeaders[mIdx];
1222
+ const beatsData = parsedMeasures[trackIdx]?.[mIdx] ?? [];
1223
+ const tabBeats = [];
1224
+ for (const beatData of beatsData) {
1225
+ if (beatData.isEmpty) continue;
1226
+ const stringCount = th.numStrings;
1227
+ const tabNotes = [];
1228
+ let noteStringIndex = 0;
1229
+ for (const noteData of beatData.notes) {
1230
+ const stringIdx = stringCount - 1 - noteStringIndex;
1231
+ const fret = noteData.fret;
1232
+ const openPitch = tuningPitches[noteStringIndex] ?? 0;
1233
+ const pc = ((openPitch + fret) % 12 + 12) % 12;
1234
+ const note = noteFromPitchClass(pc, false);
1235
+ let bendResult = null;
1236
+ if (noteData.bend) {
1237
+ const points = noteData.bend.points;
1238
+ const origin = points.length > 0 ? points[0].value / 100 : 0;
1239
+ const destination = points.length > 1 ? points[points.length - 1].value / 100 : 0;
1240
+ const middle = points.length > 2 ? points[Math.floor(points.length / 2)].value / 100 : 0;
1241
+ bendResult = { origin, destination, middle };
1242
+ }
1243
+ tabNotes.push({
1244
+ string: stringIdx,
1245
+ fret,
1246
+ pitchClass: pc,
1247
+ noteName: note.name,
1248
+ slide: noteData.slide,
1249
+ harmonic: noteData.harmonic,
1250
+ palmMute: noteData.palmMute,
1251
+ muted: noteData.isDead,
1252
+ letRing: noteData.letRing,
1253
+ bend: bendResult,
1254
+ tie: {
1255
+ origin: false,
1256
+ destination: noteData.isTied
1257
+ },
1258
+ vibrato: noteData.vibrato ? "slight" : null,
1259
+ hammerOn: noteData.hammerOn,
1260
+ pullOff: false,
1261
+ // GP5 uses hammerOn for both — context determines direction
1262
+ tapped: false,
1263
+ accent: noteData.accent ? 1 : noteData.heavyAccent ? 2 : null
1264
+ });
1265
+ noteStringIndex++;
1266
+ }
1267
+ tabBeats.push({
1268
+ index: globalBeatIndex++,
1269
+ barIndex: mIdx,
1270
+ notes: tabNotes,
1271
+ duration: beatData.duration,
1272
+ tuplet: beatData.tuplet,
1273
+ dotted: beatData.dotted ? 1 : 0,
1274
+ isRest: beatData.isRest && tabNotes.length === 0,
1275
+ dynamic: null,
1276
+ tempo
1277
+ });
1278
+ }
1279
+ const section = mh.marker ? { text: mh.marker.name } : null;
1280
+ bars.push({
1281
+ index: mIdx,
1282
+ timeSignature: { numerator: mh.numerator, denominator: mh.denominator },
1283
+ keySignature: mh.keySignature !== 0 ? { accidentalCount: mh.keySignature, mode: mh.keyMode === 1 ? "minor" : "major" } : null,
1284
+ section,
1285
+ beats: tabBeats,
1286
+ repeatStart: mh.repeatOpen,
1287
+ repeatEnd: mh.repeatClose >= 0,
1288
+ repeatCount: mh.repeatClose >= 0 ? mh.repeatClose : 0
1289
+ });
1290
+ }
1291
+ const ch = channels[th.channelIndex];
1292
+ const instrumentName = ch ? `MIDI ${ch.instrument}` : null;
1293
+ return {
1294
+ id: String(trackIdx),
1295
+ name: th.name,
1296
+ shortName: th.name.substring(0, 4),
1297
+ instrument: instrumentName,
1298
+ tuning,
1299
+ capoFret: th.capoFret,
1300
+ bars
1301
+ };
1302
+ });
1303
+ return {
1304
+ title: info.title || info.subtitle || "",
1305
+ artist: info.artist,
1306
+ album: info.album,
1307
+ tempo,
1308
+ tracks
1309
+ };
1310
+ }
1311
+ function parseGp5File(data) {
1312
+ const buf = new ArrayBuffer(data.byteLength);
1313
+ new Uint8Array(buf).set(data);
1314
+ const r = new GP5Reader(buf);
1315
+ const versionStr = r.readByteSizeString(30);
1316
+ const version = parseVersionString(versionStr);
1317
+ if (version.major !== 5) {
1318
+ throw new Error(`Unsupported Guitar Pro version: ${versionStr} (expected GP5)`);
1319
+ }
1320
+ const info = readInfo(r);
1321
+ readLyrics(r);
1322
+ readRSEMasterEffect(r, version);
1323
+ readPageSetup(r);
1324
+ r.readIntByteSizeString();
1325
+ const tempo = r.readInt();
1326
+ if (versionGreaterThan(version, 5, 0, 0)) {
1327
+ r.readBool();
1328
+ }
1329
+ r.readSignedByte();
1330
+ r.readInt();
1331
+ const channels = readMidiChannels(r);
1332
+ readDirections(r);
1333
+ r.readInt();
1334
+ const measureCount = r.readInt();
1335
+ const trackCount = r.readInt();
1336
+ const measureHeaders = readMeasureHeaders(r, measureCount, version);
1337
+ const trackHeaders = readTrackHeaders(r, trackCount, version);
1338
+ const parsedMeasures = readMeasures(r, measureCount, trackCount, version);
1339
+ return transformToTabSong(info, tempo, measureHeaders, trackHeaders, parsedMeasures, channels);
1340
+ }
1341
+
1342
+ // src/tab-parser.ts
1343
+ function readCentralDirectory(data, view) {
1344
+ let eocdOffset = -1;
1345
+ for (let i = data.byteLength - 22; i >= 0; i--) {
1346
+ if (view.getUint32(i, true) === 101010256) {
1347
+ eocdOffset = i;
1348
+ break;
1349
+ }
1350
+ }
1351
+ if (eocdOffset === -1) return [];
1352
+ const cdOffset = view.getUint32(eocdOffset + 16, true);
1353
+ const cdEntryCount = view.getUint16(eocdOffset + 10, true);
1354
+ const entries = [];
1355
+ let pos = cdOffset;
1356
+ for (let i = 0; i < cdEntryCount; i++) {
1357
+ if (pos + 46 > data.byteLength) break;
1358
+ if (view.getUint32(pos, true) !== 33639248) break;
1359
+ const compressionMethod = view.getUint16(pos + 10, true);
1360
+ const compressedSize = view.getUint32(pos + 20, true);
1361
+ const uncompressedSize = view.getUint32(pos + 24, true);
1362
+ const nameLength = view.getUint16(pos + 28, true);
1363
+ const extraLength = view.getUint16(pos + 30, true);
1364
+ const commentLength = view.getUint16(pos + 32, true);
1365
+ const localHeaderOffset = view.getUint32(pos + 42, true);
1366
+ const nameBytes = data.subarray(pos + 46, pos + 46 + nameLength);
1367
+ const fileName = String.fromCharCode(...nameBytes);
1368
+ entries.push({ fileName, compressionMethod, compressedSize, uncompressedSize, localHeaderOffset });
1369
+ pos += 46 + nameLength + extraLength + commentLength;
1370
+ }
1371
+ return entries;
1372
+ }
1373
+ function extractFileFromZip(data, targetName) {
1374
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1375
+ const entries = readCentralDirectory(data, view);
1376
+ const entry = entries.find((e) => e.fileName === targetName);
1377
+ if (!entry) return null;
1378
+ const localOffset = entry.localHeaderOffset;
1379
+ if (view.getUint32(localOffset, true) !== 67324752) return null;
1380
+ const localNameLength = view.getUint16(localOffset + 26, true);
1381
+ const localExtraLength = view.getUint16(localOffset + 28, true);
1382
+ const dataOffset = localOffset + 30 + localNameLength + localExtraLength;
1383
+ const compressedData = data.subarray(dataOffset, dataOffset + entry.compressedSize);
1384
+ if (entry.compressionMethod === 0) {
1385
+ return compressedData;
1386
+ } else if (entry.compressionMethod === 8) {
1387
+ return decompressDeflateSync(compressedData, entry.uncompressedSize);
1388
+ } else {
1389
+ throw new Error(`Unsupported ZIP compression method: ${entry.compressionMethod}`);
1390
+ }
1391
+ }
1392
+ function decompressDeflateSync(compressed, expectedSize) {
1393
+ return inflate(compressed, expectedSize);
1394
+ }
1395
+ function buildFixedLitLenTree() {
1396
+ const lengths = new Uint8Array(288);
1397
+ for (let i = 0; i <= 143; i++) lengths[i] = 8;
1398
+ for (let i = 144; i <= 255; i++) lengths[i] = 9;
1399
+ for (let i = 256; i <= 279; i++) lengths[i] = 7;
1400
+ for (let i = 280; i <= 287; i++) lengths[i] = 8;
1401
+ return buildHuffmanTable(lengths);
1402
+ }
1403
+ function buildFixedDistTree() {
1404
+ const lengths = new Uint8Array(32);
1405
+ lengths.fill(5);
1406
+ return buildHuffmanTable(lengths);
1407
+ }
1408
+ function buildHuffmanTable(codeLengths) {
1409
+ const maxBits = Math.max(...codeLengths);
1410
+ const count = new Uint16Array(maxBits + 1);
1411
+ for (const len of codeLengths) {
1412
+ if (len > 0) count[len]++;
1413
+ }
1414
+ const nextCode = new Uint16Array(maxBits + 1);
1415
+ let code = 0;
1416
+ for (let bits = 1; bits <= maxBits; bits++) {
1417
+ code = code + count[bits - 1] << 1;
1418
+ nextCode[bits] = code;
1419
+ }
1420
+ const codes = new Uint16Array(codeLengths.length);
1421
+ const lengths = new Uint8Array(codeLengths.length);
1422
+ for (let i = 0; i < codeLengths.length; i++) {
1423
+ const len = codeLengths[i];
1424
+ if (len > 0) {
1425
+ codes[i] = nextCode[len]++;
1426
+ lengths[i] = len;
1427
+ }
1428
+ }
1429
+ return { codes, lengths };
1430
+ }
1431
+ var BitReader = class {
1432
+ constructor(data) {
1433
+ this.data = data;
1434
+ this.bytePos = 0;
1435
+ this.bitPos = 0;
1436
+ }
1437
+ readBits(n) {
1438
+ let result = 0;
1439
+ for (let i = 0; i < n; i++) {
1440
+ if (this.bytePos >= this.data.length) return result;
1441
+ result |= (this.data[this.bytePos] >> this.bitPos & 1) << i;
1442
+ this.bitPos++;
1443
+ if (this.bitPos === 8) {
1444
+ this.bitPos = 0;
1445
+ this.bytePos++;
1446
+ }
1447
+ }
1448
+ return result;
1449
+ }
1450
+ alignToByte() {
1451
+ if (this.bitPos > 0) {
1452
+ this.bitPos = 0;
1453
+ this.bytePos++;
1454
+ }
1455
+ }
1456
+ readByte() {
1457
+ return this.data[this.bytePos++];
1458
+ }
1459
+ readBytes(n) {
1460
+ const result = this.data.subarray(this.bytePos, this.bytePos + n);
1461
+ this.bytePos += n;
1462
+ return result;
1463
+ }
1464
+ };
1465
+ function decodeSymbol(bits, table) {
1466
+ let code = 0;
1467
+ for (let len = 1; len <= 15; len++) {
1468
+ code = code << 1 | bits.readBits(1);
1469
+ for (let i = 0; i < table.codes.length; i++) {
1470
+ if (table.lengths[i] === len && table.codes[i] === code) {
1471
+ return i;
1472
+ }
1473
+ }
1474
+ }
1475
+ throw new Error("Invalid Huffman code");
1476
+ }
1477
+ var LENGTH_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
1478
+ var LENGTH_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
1479
+ var DIST_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
1480
+ var DIST_EXTRA = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
1481
+ var CL_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
1482
+ function inflate(compressed, expectedSize) {
1483
+ const bits = new BitReader(compressed);
1484
+ const output = new Uint8Array(expectedSize);
1485
+ let outPos = 0;
1486
+ let bfinal = 0;
1487
+ while (bfinal === 0) {
1488
+ bfinal = bits.readBits(1);
1489
+ const btype = bits.readBits(2);
1490
+ if (btype === 0) {
1491
+ bits.alignToByte();
1492
+ const len = bits.readByte() | bits.readByte() << 8;
1493
+ bits.readByte();
1494
+ bits.readByte();
1495
+ const block = bits.readBytes(len);
1496
+ output.set(block, outPos);
1497
+ outPos += len;
1498
+ } else {
1499
+ let litLenTree;
1500
+ let distTree;
1501
+ if (btype === 1) {
1502
+ litLenTree = buildFixedLitLenTree();
1503
+ distTree = buildFixedDistTree();
1504
+ } else if (btype === 2) {
1505
+ const hlit = bits.readBits(5) + 257;
1506
+ const hdist = bits.readBits(5) + 1;
1507
+ const hclen = bits.readBits(4) + 4;
1508
+ const clLengths = new Uint8Array(19);
1509
+ for (let i = 0; i < hclen; i++) {
1510
+ clLengths[CL_ORDER[i]] = bits.readBits(3);
1511
+ }
1512
+ const clTree = buildHuffmanTable(clLengths);
1513
+ const allLengths = new Uint8Array(hlit + hdist);
1514
+ let idx = 0;
1515
+ while (idx < hlit + hdist) {
1516
+ const sym = decodeSymbol(bits, clTree);
1517
+ if (sym < 16) {
1518
+ allLengths[idx++] = sym;
1519
+ } else if (sym === 16) {
1520
+ const repeat = bits.readBits(2) + 3;
1521
+ const prev = allLengths[idx - 1];
1522
+ for (let i = 0; i < repeat; i++) allLengths[idx++] = prev;
1523
+ } else if (sym === 17) {
1524
+ const repeat = bits.readBits(3) + 3;
1525
+ for (let i = 0; i < repeat; i++) allLengths[idx++] = 0;
1526
+ } else if (sym === 18) {
1527
+ const repeat = bits.readBits(7) + 11;
1528
+ for (let i = 0; i < repeat; i++) allLengths[idx++] = 0;
1529
+ }
1530
+ }
1531
+ litLenTree = buildHuffmanTable(allLengths.subarray(0, hlit));
1532
+ distTree = buildHuffmanTable(allLengths.subarray(hlit));
1533
+ } else {
1534
+ throw new Error(`Invalid DEFLATE block type: ${btype}`);
1535
+ }
1536
+ while (true) {
1537
+ const sym = decodeSymbol(bits, litLenTree);
1538
+ if (sym < 256) {
1539
+ output[outPos++] = sym;
1540
+ } else if (sym === 256) {
1541
+ break;
1542
+ } else {
1543
+ const lengthIdx = sym - 257;
1544
+ const length = LENGTH_BASE[lengthIdx] + bits.readBits(LENGTH_EXTRA[lengthIdx]);
1545
+ const distSym = decodeSymbol(bits, distTree);
1546
+ const distance = DIST_BASE[distSym] + bits.readBits(DIST_EXTRA[distSym]);
1547
+ for (let i = 0; i < length; i++) {
1548
+ output[outPos] = output[outPos - distance];
1549
+ outPos++;
1550
+ }
1551
+ }
1552
+ }
1553
+ }
1554
+ }
1555
+ return output.subarray(0, outPos);
1556
+ }
1557
+ function parseGp7File(data) {
1558
+ const gpifData = extractFileFromZip(data, "Content/score.gpif");
1559
+ if (!gpifData) {
1560
+ throw new Error("No Content/score.gpif found in GP7+ archive");
1561
+ }
1562
+ const decoder = new TextDecoder("utf-8");
1563
+ const gpifXml = decoder.decode(gpifData);
1564
+ const DOMParserImpl = getDOMParser();
1565
+ const parser = new DOMParserImpl();
1566
+ const doc = parser.parseFromString(gpifXml, "text/xml");
1567
+ const parseError = doc.querySelector("parsererror");
1568
+ if (parseError) {
1569
+ throw new Error(`Failed to parse GPIF XML: ${parseError.textContent}`);
1570
+ }
1571
+ return gpifToTabSong(doc);
1572
+ }
1573
+ function detectFormat(data, fileName) {
1574
+ if (data.length < 4) {
1575
+ throw new Error("File too small to be a valid Guitar Pro file");
1576
+ }
1577
+ const header = String.fromCharCode(data[0], data[1], data[2], data[3]);
1578
+ if (header === "BCFZ" || header === "BCFS") {
1579
+ return "gpx";
1580
+ }
1581
+ if (data[0] === 80 && data[1] === 75) {
1582
+ return "gp7";
1583
+ }
1584
+ const strLen = data[0];
1585
+ if (strLen > 10 && strLen < 50 && data.byteLength > strLen + 1) {
1586
+ const versionStr = String.fromCharCode(...Array.from(data.subarray(1, 1 + Math.min(strLen, 40))));
1587
+ if (versionStr.includes("GUITAR PRO")) {
1588
+ return "gp5";
1589
+ }
1590
+ }
1591
+ if (fileName) {
1592
+ const ext = fileName.toLowerCase();
1593
+ if (ext.endsWith(".gpx")) return "gpx";
1594
+ if (ext.endsWith(".gp5") || ext.endsWith(".gp4") || ext.endsWith(".gp3")) return "gp5";
1595
+ if (ext.endsWith(".gp")) return "gp7";
1596
+ }
1597
+ throw new Error("Unrecognized Guitar Pro file format");
1598
+ }
1599
+ function parseTabFile(data, fileName) {
1600
+ const format = detectFormat(data, fileName);
1601
+ switch (format) {
1602
+ case "gpx":
1603
+ return parseGpxFile(data);
1604
+ case "gp7":
1605
+ return parseGp7File(data);
1606
+ case "gp5":
1607
+ return parseGp5File(data);
1608
+ }
1609
+ }
1610
+ export {
1611
+ beatDurationMs,
1612
+ detectFormat,
1613
+ durationToBeats,
1614
+ gpifToTabSong,
1615
+ midiToPitchClass,
1616
+ noteFromPitchClass,
1617
+ parseGp5File,
1618
+ parseGpxFile,
1619
+ parseTabFile
1620
+ };
1621
+ //# sourceMappingURL=index.js.map