@coderline/alphatab 1.9.0-alpha.1883 → 1.9.0-alpha.1891
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/alphaTab.core.min.mjs +2 -2
- package/dist/alphaTab.core.mjs +464 -102
- package/dist/alphaTab.d.ts +6 -0
- package/dist/alphaTab.js +464 -102
- package/dist/alphaTab.min.js +2 -2
- package/dist/alphaTab.min.mjs +1 -1
- package/dist/alphaTab.mjs +1 -1
- package/dist/alphaTab.worker.min.mjs +1 -1
- package/dist/alphaTab.worker.mjs +1 -1
- package/dist/alphaTab.worklet.min.mjs +1 -1
- package/dist/alphaTab.worklet.mjs +1 -1
- package/package.json +2 -2
package/dist/alphaTab.core.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.9.0-alpha.
|
|
2
|
+
* alphaTab v1.9.0-alpha.1891 (develop, build 1891)
|
|
3
3
|
*
|
|
4
4
|
* Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
|
|
5
5
|
*
|
|
@@ -186,9 +186,9 @@ class AlphaTabError extends Error {
|
|
|
186
186
|
* @internal
|
|
187
187
|
*/
|
|
188
188
|
class VersionInfo {
|
|
189
|
-
static version = "1.9.0-alpha.
|
|
190
|
-
static date = "2026-08-
|
|
191
|
-
static commit = "
|
|
189
|
+
static version = "1.9.0-alpha.1891";
|
|
190
|
+
static date = "2026-08-10T02:28:09.977Z";
|
|
191
|
+
static commit = "2a460d7c4b6d879465fe23382adc67bf6fd3196d";
|
|
192
192
|
static print(print) {
|
|
193
193
|
print(`alphaTab ${VersionInfo.version}`);
|
|
194
194
|
print(`commit: ${VersionInfo.commit}`);
|
|
@@ -3134,7 +3134,7 @@ class GraceGroup {
|
|
|
3134
3134
|
this.beats.push(beat);
|
|
3135
3135
|
}
|
|
3136
3136
|
finish() {
|
|
3137
|
-
if (this.beats.length > 0) this.id = `${this.beats[0].
|
|
3137
|
+
if (this.beats.length > 0) this.id = `${this.beats[0].voice.bar.id}_${this.beats[0].voice.index}_${this.beats[0].absoluteDisplayStart}`;
|
|
3138
3138
|
}
|
|
3139
3139
|
}
|
|
3140
3140
|
//#endregion
|
|
@@ -4139,6 +4139,20 @@ class ModelUtils {
|
|
|
4139
4139
|
}
|
|
4140
4140
|
return headerFooterStyle;
|
|
4141
4141
|
}
|
|
4142
|
+
static backfillStaffVoices(staff, targetVoiceCount) {
|
|
4143
|
+
for (let bi = 0; bi < staff.bars.length - 1; bi++) {
|
|
4144
|
+
const priorBar = staff.bars[bi];
|
|
4145
|
+
while (priorBar.voices.length < targetVoiceCount) ModelUtils.appendPlaceholderVoice(priorBar);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
static appendPlaceholderVoice(bar) {
|
|
4149
|
+
const voice = new Voice$1();
|
|
4150
|
+
bar.addVoice(voice);
|
|
4151
|
+
const beat = new Beat();
|
|
4152
|
+
beat.isEmpty = true;
|
|
4153
|
+
beat.duration = Duration.Quarter;
|
|
4154
|
+
voice.addBeat(beat);
|
|
4155
|
+
}
|
|
4142
4156
|
/**
|
|
4143
4157
|
* Performs some general consolidations of inconsistencies on the given score like
|
|
4144
4158
|
* missing bars, beats, duplicated midi channels etc
|
|
@@ -4189,13 +4203,7 @@ class ModelUtils {
|
|
|
4189
4203
|
bar.keySignature = bar.previousBar.keySignature;
|
|
4190
4204
|
bar.keySignatureType = bar.previousBar.keySignatureType;
|
|
4191
4205
|
}
|
|
4192
|
-
for (let i = 0; i < voiceCount; i++)
|
|
4193
|
-
const v = new Voice$1();
|
|
4194
|
-
bar.addVoice(v);
|
|
4195
|
-
const emptyBeat = new Beat();
|
|
4196
|
-
emptyBeat.isEmpty = true;
|
|
4197
|
-
v.addBeat(emptyBeat);
|
|
4198
|
-
}
|
|
4206
|
+
for (let i = 0; i < voiceCount; i++) ModelUtils.appendPlaceholderVoice(bar);
|
|
4199
4207
|
}
|
|
4200
4208
|
}
|
|
4201
4209
|
}
|
|
@@ -4942,6 +4950,11 @@ class ModelUtils {
|
|
|
4942
4950
|
const ksi = keySignature + 7;
|
|
4943
4951
|
return keySignatureType === KeySignatureType.Minor ? ModelUtils._minorKeySignatureTonicDegrees[ksi] : ModelUtils._majorKeySignatureTonicDegrees[ksi];
|
|
4944
4952
|
}
|
|
4953
|
+
/** True iff the staff's first note isn't stringed. Empty staves return false. */
|
|
4954
|
+
static staffNotesAreNotStringed(staff) {
|
|
4955
|
+
for (const bar of staff.bars) for (const voice of bar.voices) for (const beat of voice.beats) for (const note of beat.notes) return !note.isStringed;
|
|
4956
|
+
return false;
|
|
4957
|
+
}
|
|
4945
4958
|
}
|
|
4946
4959
|
//#endregion
|
|
4947
4960
|
//#region src/model/PickStroke.ts
|
|
@@ -6332,7 +6345,10 @@ class Note {
|
|
|
6332
6345
|
else realValue += this.harmonicPitch;
|
|
6333
6346
|
return realValue;
|
|
6334
6347
|
}
|
|
6335
|
-
if (this.isPercussion)
|
|
6348
|
+
if (this.isPercussion) {
|
|
6349
|
+
const art = PercussionMapper.getArticulation(this);
|
|
6350
|
+
return art !== null ? art.outputMidiNumber : this.percussionArticulation;
|
|
6351
|
+
}
|
|
6336
6352
|
if (this.isStringed) return this.fret + this.stringTuning - transpositionPitch;
|
|
6337
6353
|
if (this.isPiano) return this.octave * 12 + this.tone - transpositionPitch;
|
|
6338
6354
|
return 0;
|
|
@@ -12855,6 +12871,18 @@ class Track {
|
|
|
12855
12871
|
staff.track = this;
|
|
12856
12872
|
this.staves.push(staff);
|
|
12857
12873
|
}
|
|
12874
|
+
/**
|
|
12875
|
+
* Returns the index of {@link articulation} in {@link percussionArticulations},
|
|
12876
|
+
* appending it (deduplicated by `uniqueId`) when not yet present. Callers store the
|
|
12877
|
+
* returned index in {@link Note.percussionArticulation}.
|
|
12878
|
+
*/
|
|
12879
|
+
getOrRegisterPercussionArticulation(articulation) {
|
|
12880
|
+
const uniqueId = articulation.uniqueId;
|
|
12881
|
+
for (let i = 0; i < this.percussionArticulations.length; i++) if (this.percussionArticulations[i].uniqueId === uniqueId) return i;
|
|
12882
|
+
const index = this.percussionArticulations.length;
|
|
12883
|
+
this.percussionArticulations.push(articulation);
|
|
12884
|
+
return index;
|
|
12885
|
+
}
|
|
12858
12886
|
finish(settings, sharedDataBag = null) {
|
|
12859
12887
|
if (!this.shortName) {
|
|
12860
12888
|
this.shortName = this.name;
|
|
@@ -18350,11 +18378,38 @@ class Gp3To5Importer extends ScoreImporter {
|
|
|
18350
18378
|
readVoice(track, bar) {
|
|
18351
18379
|
const beatCount = IOHelper.readInt32LE(this.data);
|
|
18352
18380
|
if (beatCount === 0) return;
|
|
18381
|
+
const currentVoiceCount = bar.index === 0 ? 1 : bar.previousBar.voices.length;
|
|
18382
|
+
if (bar.voices.length === 0 || currentVoiceCount === 2) {
|
|
18383
|
+
this._readAndChainVoice(track, bar, beatCount);
|
|
18384
|
+
return;
|
|
18385
|
+
}
|
|
18386
|
+
if (beatCount === 1 && this._skipEmptyVoice()) return;
|
|
18387
|
+
ModelUtils.backfillStaffVoices(bar.staff, 2);
|
|
18388
|
+
this._readAndChainVoice(track, bar, beatCount);
|
|
18389
|
+
}
|
|
18390
|
+
_readAndChainVoice(track, bar, beatCount) {
|
|
18353
18391
|
const newVoice = new Voice$1();
|
|
18354
18392
|
bar.addVoice(newVoice);
|
|
18355
18393
|
this._ensureLoopBoundary(beatCount, Gp3To5Importer._maxBeatCount, "beat count");
|
|
18356
18394
|
for (let i = 0; i < beatCount; i++) this.readBeat(track, bar, newVoice);
|
|
18357
18395
|
}
|
|
18396
|
+
/**
|
|
18397
|
+
* Attempts to skip a fully empty voice.
|
|
18398
|
+
* @returns true if we detected an empty voice, false if the beat was not empty and a full voice has to be read.
|
|
18399
|
+
*/
|
|
18400
|
+
_skipEmptyVoice() {
|
|
18401
|
+
const startOfVoice = this.data.position;
|
|
18402
|
+
const flags = this.data.readByte();
|
|
18403
|
+
let isEmpty = false;
|
|
18404
|
+
if ((flags & 64) !== 0) isEmpty = (this.data.readByte() & 2) === 0;
|
|
18405
|
+
if (!isEmpty) {
|
|
18406
|
+
this.data.position = startOfVoice;
|
|
18407
|
+
return false;
|
|
18408
|
+
}
|
|
18409
|
+
this.data.skip(2);
|
|
18410
|
+
if (this._versionNumber >= 500) this.data.skip(2);
|
|
18411
|
+
return true;
|
|
18412
|
+
}
|
|
18358
18413
|
readBeat(track, bar, voice) {
|
|
18359
18414
|
const newBeat = new Beat();
|
|
18360
18415
|
const flags = this.data.readByte();
|
|
@@ -18702,7 +18757,9 @@ class Gp3To5Importer extends ScoreImporter {
|
|
|
18702
18757
|
beat.addNote(newNote);
|
|
18703
18758
|
if ((flags & 8) !== 0) this.readNoteEffects(track, voice, beat, newNote);
|
|
18704
18759
|
if (bar.staff.isPercussion) {
|
|
18705
|
-
|
|
18760
|
+
const midi = Gp3To5Importer._gp5PercussionInstrumentMap.has(newNote.fret) ? Gp3To5Importer._gp5PercussionInstrumentMap.get(newNote.fret) : newNote.fret;
|
|
18761
|
+
const knownArticulation = PercussionMapper.getArticulationById(midi);
|
|
18762
|
+
if (knownArticulation !== null) newNote.percussionArticulation = bar.staff.track.getOrRegisterPercussionArticulation(knownArticulation);
|
|
18706
18763
|
newNote.fret = NaN;
|
|
18707
18764
|
}
|
|
18708
18765
|
if (swapAccidentals) {
|
|
@@ -19470,6 +19527,140 @@ class BackingTrack {
|
|
|
19470
19527
|
rawAudioFile;
|
|
19471
19528
|
}
|
|
19472
19529
|
//#endregion
|
|
19530
|
+
//#region src/model/FingeringAssigner.ts
|
|
19531
|
+
/**
|
|
19532
|
+
* Cost-function weights for {@link FingeringAssigner}. Defaults tuned for
|
|
19533
|
+
* six-string guitar.
|
|
19534
|
+
* @internal
|
|
19535
|
+
*/
|
|
19536
|
+
class FingeringOptions {
|
|
19537
|
+
preferredHandPosition = 5;
|
|
19538
|
+
/** Negative = prefer open strings. */
|
|
19539
|
+
openStringBonus = -1;
|
|
19540
|
+
highFretPenaltyWeight = .5;
|
|
19541
|
+
negativeFretPenaltyWeight = 3;
|
|
19542
|
+
/** Soft; heavy weight prefers distinct strings but permits collisions
|
|
19543
|
+
* for chords with more notes than strings. */
|
|
19544
|
+
collisionPenalty = 100;
|
|
19545
|
+
/** Negative = cluster chord notes on neighbouring strings. */
|
|
19546
|
+
adjacentStringBonus = -1.5;
|
|
19547
|
+
/** Negative = repeated pitches stay on the same string across beats. */
|
|
19548
|
+
stringContinuityBonus = -.75;
|
|
19549
|
+
/** EWMA weight for the hand-position anchor:
|
|
19550
|
+
* `hand = α·hand + (1−α)·newHand`. */
|
|
19551
|
+
handPositionMomentum = .7;
|
|
19552
|
+
}
|
|
19553
|
+
/**
|
|
19554
|
+
* Assigns (string, fret) to a stream of beats via greedy hand-position
|
|
19555
|
+
* hysteresis. One instance per (staff, voice); mutates notes in place.
|
|
19556
|
+
* Not thread-safe.
|
|
19557
|
+
* @internal
|
|
19558
|
+
*/
|
|
19559
|
+
class FingeringAssigner {
|
|
19560
|
+
static _maxStrings = 30;
|
|
19561
|
+
_tuning;
|
|
19562
|
+
_capo;
|
|
19563
|
+
_transpositionPitch;
|
|
19564
|
+
_options;
|
|
19565
|
+
_handPosition;
|
|
19566
|
+
_lastStringByMidi;
|
|
19567
|
+
_sortedIdx;
|
|
19568
|
+
/**
|
|
19569
|
+
* @param tuning High-to-low MIDI pitches (matches {@link Staff.tuning}). 1..30 entries.
|
|
19570
|
+
*/
|
|
19571
|
+
constructor(tuning, capo, transpositionPitch, options) {
|
|
19572
|
+
if (tuning.length < 1 || tuning.length > FingeringAssigner._maxStrings) throw new Error(`FingeringAssigner requires 1..${FingeringAssigner._maxStrings} strings, got tuning.length=${tuning.length}`);
|
|
19573
|
+
this._tuning = tuning;
|
|
19574
|
+
this._capo = capo;
|
|
19575
|
+
this._transpositionPitch = transpositionPitch;
|
|
19576
|
+
this._options = options ?? new FingeringOptions();
|
|
19577
|
+
this._handPosition = this._options.preferredHandPosition;
|
|
19578
|
+
this._lastStringByMidi = /* @__PURE__ */ new Uint8Array(128);
|
|
19579
|
+
this._sortedIdx = /* @__PURE__ */ new Int32Array(16);
|
|
19580
|
+
}
|
|
19581
|
+
/** Reset the hand-position anchor and per-pitch continuity memory. */
|
|
19582
|
+
reset() {
|
|
19583
|
+
this._handPosition = this._options.preferredHandPosition;
|
|
19584
|
+
this._lastStringByMidi.fill(0);
|
|
19585
|
+
}
|
|
19586
|
+
/** Assigns `(string, fret)` to notes that don't already carry both. */
|
|
19587
|
+
assign(beat) {
|
|
19588
|
+
const notes = beat.notes;
|
|
19589
|
+
const K = notes.length;
|
|
19590
|
+
if (K === 0) return;
|
|
19591
|
+
if (this._sortedIdx.length < K) this._sortedIdx = new Int32Array(K);
|
|
19592
|
+
const sortedIdx = this._sortedIdx;
|
|
19593
|
+
let n = 0;
|
|
19594
|
+
for (let i = 0; i < K; i++) {
|
|
19595
|
+
const note = notes[i];
|
|
19596
|
+
if (note.isStringed) continue;
|
|
19597
|
+
if (note.isPercussion) {
|
|
19598
|
+
const art = PercussionMapper.getArticulation(note);
|
|
19599
|
+
if (art !== null) {
|
|
19600
|
+
if (Number.isNaN(note.string)) note.string = Math.max(1, Math.min(6, 7 - art.staffLine));
|
|
19601
|
+
if (Number.isNaN(note.fret)) note.fret = art.outputMidiNumber;
|
|
19602
|
+
}
|
|
19603
|
+
continue;
|
|
19604
|
+
}
|
|
19605
|
+
const tieOrigin = note.tieOrigin;
|
|
19606
|
+
if (note.isTieDestination && tieOrigin !== null && tieOrigin.isStringed) {
|
|
19607
|
+
note.string = tieOrigin.string;
|
|
19608
|
+
note.fret = tieOrigin.fret;
|
|
19609
|
+
continue;
|
|
19610
|
+
}
|
|
19611
|
+
let j = n;
|
|
19612
|
+
const noteValue = note.realValue;
|
|
19613
|
+
while (j > 0 && notes[sortedIdx[j - 1]].realValue > noteValue) {
|
|
19614
|
+
sortedIdx[j] = sortedIdx[j - 1];
|
|
19615
|
+
j--;
|
|
19616
|
+
}
|
|
19617
|
+
sortedIdx[j] = i;
|
|
19618
|
+
n++;
|
|
19619
|
+
}
|
|
19620
|
+
if (n === 0) return;
|
|
19621
|
+
const N = this._tuning.length;
|
|
19622
|
+
const opts = this._options;
|
|
19623
|
+
let usedStrings = 0;
|
|
19624
|
+
let newHand = -1;
|
|
19625
|
+
for (let k = 0; k < n; k++) {
|
|
19626
|
+
const note = notes[sortedIdx[k]];
|
|
19627
|
+
const realValue = note.realValue;
|
|
19628
|
+
const target = realValue + this._transpositionPitch;
|
|
19629
|
+
const continuityString = realValue >= 0 && realValue < 128 ? this._lastStringByMidi[realValue] : 0;
|
|
19630
|
+
let bestString = 1;
|
|
19631
|
+
let bestFret = 0;
|
|
19632
|
+
let bestCost = Number.POSITIVE_INFINITY;
|
|
19633
|
+
for (let s = 1; s <= N; s++) {
|
|
19634
|
+
const fret = target - (this._capo + this._tuning[N - s]);
|
|
19635
|
+
const distanceCost = Math.abs(fret - this._handPosition);
|
|
19636
|
+
const openBonus = fret === 0 ? opts.openStringBonus : 0;
|
|
19637
|
+
const negFretPenalty = fret < 0 ? -fret * opts.negativeFretPenaltyWeight : 0;
|
|
19638
|
+
const highFretPenalty = fret > 12 ? (fret - 12) * opts.highFretPenaltyWeight : 0;
|
|
19639
|
+
const collisionCost = (usedStrings & 1 << s - 1) !== 0 ? opts.collisionPenalty : 0;
|
|
19640
|
+
const leftUsed = s > 1 && (usedStrings & 1 << s - 2) !== 0;
|
|
19641
|
+
const rightUsed = s < N && (usedStrings & 1 << s) !== 0;
|
|
19642
|
+
const adjacencyBonus = leftUsed || rightUsed ? opts.adjacentStringBonus : 0;
|
|
19643
|
+
const continuityBonus = s === continuityString ? opts.stringContinuityBonus : 0;
|
|
19644
|
+
const cost = distanceCost + openBonus + negFretPenalty + highFretPenalty + collisionCost + adjacencyBonus + continuityBonus;
|
|
19645
|
+
if (cost < bestCost) {
|
|
19646
|
+
bestCost = cost;
|
|
19647
|
+
bestString = s;
|
|
19648
|
+
bestFret = fret;
|
|
19649
|
+
}
|
|
19650
|
+
}
|
|
19651
|
+
note.string = bestString;
|
|
19652
|
+
note.fret = bestFret;
|
|
19653
|
+
usedStrings |= 1 << bestString - 1;
|
|
19654
|
+
if (realValue >= 0 && realValue < 128) this._lastStringByMidi[realValue] = bestString;
|
|
19655
|
+
if (bestFret > 0 && (newHand < 0 || bestFret < newHand)) newHand = bestFret;
|
|
19656
|
+
}
|
|
19657
|
+
if (newHand >= 0) {
|
|
19658
|
+
const alpha = opts.handPositionMomentum;
|
|
19659
|
+
this._handPosition = alpha * this._handPosition + (1 - alpha) * newHand;
|
|
19660
|
+
}
|
|
19661
|
+
}
|
|
19662
|
+
}
|
|
19663
|
+
//#endregion
|
|
19473
19664
|
//#region src/importer/GpifParser.ts
|
|
19474
19665
|
/**
|
|
19475
19666
|
* This structure represents a duration within a gpif
|
|
@@ -19537,6 +19728,8 @@ class GpifParser {
|
|
|
19537
19728
|
_articulationByName;
|
|
19538
19729
|
_skipApplyLyrics = false;
|
|
19539
19730
|
_backingTrackPadding = 0;
|
|
19731
|
+
/** Marks the input as a Guitar Pro 6 file. Also auto-detected from the GPIF header. */
|
|
19732
|
+
isGp6 = false;
|
|
19540
19733
|
_doubleBars = /* @__PURE__ */ new Set();
|
|
19541
19734
|
_keySignatures = /* @__PURE__ */ new Map();
|
|
19542
19735
|
loadAsset;
|
|
@@ -19557,7 +19750,7 @@ class GpifParser {
|
|
|
19557
19750
|
this._rhythmById = /* @__PURE__ */ new Map();
|
|
19558
19751
|
this._notesOfBeat = /* @__PURE__ */ new Map();
|
|
19559
19752
|
this._noteById = /* @__PURE__ */ new Map();
|
|
19560
|
-
this._tappedNotes = /* @__PURE__ */ new
|
|
19753
|
+
this._tappedNotes = /* @__PURE__ */ new Set();
|
|
19561
19754
|
this._lyricsByTrack = /* @__PURE__ */ new Map();
|
|
19562
19755
|
this._soundsByTrack = /* @__PURE__ */ new Map();
|
|
19563
19756
|
this._skipApplyLyrics = false;
|
|
@@ -19570,6 +19763,7 @@ class GpifParser {
|
|
|
19570
19763
|
this._parseDom(dom);
|
|
19571
19764
|
this._buildModel();
|
|
19572
19765
|
ModelUtils.consolidate(this.score);
|
|
19766
|
+
if (this.isGp6) this._assignFingeringForGp6();
|
|
19573
19767
|
this.score.finish(settings);
|
|
19574
19768
|
if (!this._skipApplyLyrics && this._lyricsByTrack.size > 0) for (const [t, lyrics] of this._lyricsByTrack) this._tracksById.get(t).applyLyrics(lyrics);
|
|
19575
19769
|
}
|
|
@@ -19579,6 +19773,12 @@ class GpifParser {
|
|
|
19579
19773
|
if (root.localName === "GPIF") {
|
|
19580
19774
|
this.score = new Score();
|
|
19581
19775
|
for (const n of root.childElements()) switch (n.localName) {
|
|
19776
|
+
case "GPVersion":
|
|
19777
|
+
if (n.innerText === "6") this.isGp6 = true;
|
|
19778
|
+
break;
|
|
19779
|
+
case "Encoding":
|
|
19780
|
+
if (n.findChildElement("EncodingDescription")?.innerText === "GP6") this.isGp6 = true;
|
|
19781
|
+
break;
|
|
19582
19782
|
case "Score":
|
|
19583
19783
|
this._parseScoreNode(n);
|
|
19584
19784
|
break;
|
|
@@ -21078,7 +21278,7 @@ class GpifParser {
|
|
|
21078
21278
|
variation = GpifParser._parseIntSafe(c.findChildElement("Variation")?.innerText, 0);
|
|
21079
21279
|
break;
|
|
21080
21280
|
case "Tapped":
|
|
21081
|
-
this._tappedNotes.
|
|
21281
|
+
this._tappedNotes.add(noteId);
|
|
21082
21282
|
break;
|
|
21083
21283
|
case "HarmonicType":
|
|
21084
21284
|
const htype = c.findChildElement("HType");
|
|
@@ -21266,6 +21466,84 @@ class GpifParser {
|
|
|
21266
21466
|
}
|
|
21267
21467
|
this._rhythmById.set(rhythmId, rhythm);
|
|
21268
21468
|
}
|
|
21469
|
+
_keyFor(trackId, base) {
|
|
21470
|
+
if (!this._transposeKeySignaturePerTrack.has(trackId)) return base;
|
|
21471
|
+
return [ModelUtils.transposeKey(base[0], this._transposeKeySignaturePerTrack.get(trackId)), base[1]];
|
|
21472
|
+
}
|
|
21473
|
+
_equalizeVoiceCount(staff, bar, staffVoiceLimits) {
|
|
21474
|
+
const previousMax = staffVoiceLimits.get(staff) ?? 0;
|
|
21475
|
+
const target = Math.max(previousMax, bar.voices.length, 1);
|
|
21476
|
+
if (target > previousMax) {
|
|
21477
|
+
staffVoiceLimits.set(staff, target);
|
|
21478
|
+
ModelUtils.backfillStaffVoices(staff, target);
|
|
21479
|
+
}
|
|
21480
|
+
while (bar.voices.length < target) ModelUtils.appendPlaceholderVoice(bar);
|
|
21481
|
+
}
|
|
21482
|
+
_attachVoiceToBar(bar, voiceId, staff) {
|
|
21483
|
+
const voice = this._voiceById.get(voiceId);
|
|
21484
|
+
bar.addVoice(voice);
|
|
21485
|
+
const beatIds = this._beatsOfVoice.get(voiceId);
|
|
21486
|
+
if (!beatIds) return;
|
|
21487
|
+
for (const beatId of beatIds) if (beatId !== GpifParser._invalidId) this._attachBeatToVoice(voice, beatId, staff);
|
|
21488
|
+
}
|
|
21489
|
+
_attachBeatToVoice(voice, beatId, staff) {
|
|
21490
|
+
const beat = BeatCloner.clone(this._beatById.get(beatId));
|
|
21491
|
+
voice.addBeat(beat);
|
|
21492
|
+
const rhythmId = this._rhythmOfBeat.get(beatId);
|
|
21493
|
+
const rhythm = this._rhythmById.get(rhythmId);
|
|
21494
|
+
beat.duration = rhythm.value;
|
|
21495
|
+
beat.dots = rhythm.dots;
|
|
21496
|
+
beat.tupletNumerator = rhythm.tupletNumerator;
|
|
21497
|
+
beat.tupletDenominator = rhythm.tupletDenominator;
|
|
21498
|
+
const noteIds = this._notesOfBeat.get(beatId);
|
|
21499
|
+
if (!noteIds) return;
|
|
21500
|
+
for (const noteId of noteIds) if (noteId !== GpifParser._invalidId) this._attachNoteToBeat(beat, noteId, staff);
|
|
21501
|
+
}
|
|
21502
|
+
_attachNoteToBeat(beat, noteId, staff) {
|
|
21503
|
+
const note = NoteCloner.clone(this._noteById.get(noteId));
|
|
21504
|
+
if (staff.isPercussion) note.fret = NaN;
|
|
21505
|
+
else note.percussionArticulation = NaN;
|
|
21506
|
+
beat.addNote(note);
|
|
21507
|
+
if (this._tappedNotes.has(noteId)) beat.tap = true;
|
|
21508
|
+
if (staff.isPercussion && note.percussionArticulation >= 0) {
|
|
21509
|
+
const trackArticulations = staff.track.percussionArticulations;
|
|
21510
|
+
let known = null;
|
|
21511
|
+
if (note.percussionArticulation < trackArticulations.length) known = trackArticulations[note.percussionArticulation];
|
|
21512
|
+
else known = PercussionMapper.getArticulationById(note.percussionArticulation);
|
|
21513
|
+
if (known !== null) note.percussionArticulation = staff.track.getOrRegisterPercussionArticulation(known);
|
|
21514
|
+
}
|
|
21515
|
+
}
|
|
21516
|
+
_assignFingeringForGp6() {
|
|
21517
|
+
for (const track of this.score.tracks) for (const staff of track.staves) {
|
|
21518
|
+
const isPercussion = staff.isPercussion;
|
|
21519
|
+
const isPitchedOnly = !isPercussion && staff.stringTuning.tunings.length === 0 && ModelUtils.staffNotesAreNotStringed(staff);
|
|
21520
|
+
if (!isPercussion && !isPitchedOnly) continue;
|
|
21521
|
+
if (isPercussion) staff.stringTuning.tunings = [
|
|
21522
|
+
0,
|
|
21523
|
+
0,
|
|
21524
|
+
0,
|
|
21525
|
+
0,
|
|
21526
|
+
0,
|
|
21527
|
+
0
|
|
21528
|
+
];
|
|
21529
|
+
else {
|
|
21530
|
+
const fallback = staff.index === 0 ? Tuning.getDefaultTuningFor(6) : Tuning.getDefaultTuningFor(5);
|
|
21531
|
+
if (fallback !== null) {
|
|
21532
|
+
staff.stringTuning.tunings = fallback.tunings.slice();
|
|
21533
|
+
staff.stringTuning.name = fallback.name;
|
|
21534
|
+
}
|
|
21535
|
+
}
|
|
21536
|
+
const assignersByVoiceIndex = /* @__PURE__ */ new Map();
|
|
21537
|
+
for (const bar of staff.bars) for (const voice of bar.voices) {
|
|
21538
|
+
let assigner = assignersByVoiceIndex.get(voice.index);
|
|
21539
|
+
if (assigner === void 0) {
|
|
21540
|
+
assigner = new FingeringAssigner(staff.stringTuning.tunings, staff.capo, staff.transpositionPitch);
|
|
21541
|
+
assignersByVoiceIndex.set(voice.index, assigner);
|
|
21542
|
+
}
|
|
21543
|
+
for (const beat of voice.beats) assigner.assign(beat);
|
|
21544
|
+
}
|
|
21545
|
+
}
|
|
21546
|
+
}
|
|
21269
21547
|
_buildModel() {
|
|
21270
21548
|
for (let i = 0, j = this._masterBars.length; i < j; i++) {
|
|
21271
21549
|
const masterBar = this._masterBars[i];
|
|
@@ -21283,66 +21561,46 @@ class GpifParser {
|
|
|
21283
21561
|
this.score.addTrack(track);
|
|
21284
21562
|
trackIndexToTrackId.push(trackId);
|
|
21285
21563
|
}
|
|
21564
|
+
const staffVoiceLimits = /* @__PURE__ */ new Map();
|
|
21286
21565
|
let keySignature;
|
|
21287
21566
|
for (const barIds of this._barsOfMasterBar) {
|
|
21288
21567
|
let staffIndex = 0;
|
|
21289
21568
|
let trackIndex = 0;
|
|
21290
|
-
keySignature = [KeySignature.C, KeySignatureType.Major];
|
|
21291
|
-
if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[0])) keySignature = [ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[0])), keySignature[1]];
|
|
21569
|
+
keySignature = this._keyFor(trackIndexToTrackId[0], [KeySignature.C, KeySignatureType.Major]);
|
|
21292
21570
|
for (let barIndex = 0; barIndex < barIds.length && trackIndex < this.score.tracks.length; barIndex++) {
|
|
21293
21571
|
const barId = barIds[barIndex];
|
|
21294
|
-
if (barId
|
|
21295
|
-
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
|
|
21301
|
-
|
|
21302
|
-
|
|
21303
|
-
|
|
21304
|
-
|
|
21305
|
-
|
|
21306
|
-
|
|
21307
|
-
|
|
21308
|
-
|
|
21309
|
-
|
|
21310
|
-
|
|
21311
|
-
|
|
21312
|
-
|
|
21313
|
-
|
|
21314
|
-
|
|
21315
|
-
const rhythm = this._rhythmById.get(rhythmId);
|
|
21316
|
-
beat.duration = rhythm.value;
|
|
21317
|
-
beat.dots = rhythm.dots;
|
|
21318
|
-
beat.tupletNumerator = rhythm.tupletNumerator;
|
|
21319
|
-
beat.tupletDenominator = rhythm.tupletDenominator;
|
|
21320
|
-
if (this._notesOfBeat.has(beatId)) {
|
|
21321
|
-
for (const noteId of this._notesOfBeat.get(beatId)) if (noteId !== GpifParser._invalidId) {
|
|
21322
|
-
const note = NoteCloner.clone(this._noteById.get(noteId));
|
|
21323
|
-
if (staff.isPercussion) note.fret = NaN;
|
|
21324
|
-
else note.percussionArticulation = NaN;
|
|
21325
|
-
beat.addNote(note);
|
|
21326
|
-
if (this._tappedNotes.has(noteId)) beat.tap = true;
|
|
21327
|
-
}
|
|
21328
|
-
}
|
|
21329
|
-
}
|
|
21572
|
+
if (barId === GpifParser._invalidId) {
|
|
21573
|
+
trackIndex++;
|
|
21574
|
+
continue;
|
|
21575
|
+
}
|
|
21576
|
+
const bar = this._barsById.get(barId);
|
|
21577
|
+
const track = this.score.tracks[trackIndex];
|
|
21578
|
+
const staff = track.staves[staffIndex];
|
|
21579
|
+
staff.addBar(bar);
|
|
21580
|
+
const masterBarIndex = staff.bars.length - 1;
|
|
21581
|
+
if (this._keySignatures.has(masterBarIndex)) keySignature = this._keyFor(trackIndexToTrackId[trackIndex], this._keySignatures.get(masterBarIndex));
|
|
21582
|
+
bar.keySignature = keySignature[0];
|
|
21583
|
+
bar.keySignatureType = keySignature[1];
|
|
21584
|
+
if (this._doubleBars.has(bar.masterBar)) bar.barLineRight = BarLineStyle.LightLight;
|
|
21585
|
+
const voiceIds = this._voicesOfBar.get(barId);
|
|
21586
|
+
if (voiceIds) {
|
|
21587
|
+
let pendingPlaceholders = 0;
|
|
21588
|
+
for (const voiceId of voiceIds) if (voiceId === GpifParser._invalidId) pendingPlaceholders++;
|
|
21589
|
+
else {
|
|
21590
|
+
while (pendingPlaceholders > 0) {
|
|
21591
|
+
ModelUtils.appendPlaceholderVoice(bar);
|
|
21592
|
+
pendingPlaceholders--;
|
|
21330
21593
|
}
|
|
21331
|
-
|
|
21332
|
-
const voice = new Voice$1();
|
|
21333
|
-
bar.addVoice(voice);
|
|
21334
|
-
const beat = new Beat();
|
|
21335
|
-
beat.isEmpty = true;
|
|
21336
|
-
beat.duration = Duration.Quarter;
|
|
21337
|
-
voice.addBeat(beat);
|
|
21594
|
+
this._attachVoiceToBar(bar, voiceId, staff);
|
|
21338
21595
|
}
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21342
|
-
|
|
21343
|
-
|
|
21344
|
-
|
|
21345
|
-
}
|
|
21596
|
+
}
|
|
21597
|
+
this._equalizeVoiceCount(staff, bar, staffVoiceLimits);
|
|
21598
|
+
staffIndex++;
|
|
21599
|
+
if (staffIndex >= track.staves.length) {
|
|
21600
|
+
trackIndex++;
|
|
21601
|
+
staffIndex = 0;
|
|
21602
|
+
}
|
|
21603
|
+
keySignature = trackIndex < trackIndexToTrackId.length ? this._keyFor(trackIndexToTrackId[trackIndex], [KeySignature.C, KeySignatureType.Major]) : [KeySignature.C, KeySignatureType.Major];
|
|
21346
21604
|
}
|
|
21347
21605
|
}
|
|
21348
21606
|
for (const trackId of this._tracksMapping) {
|
|
@@ -21875,6 +22133,7 @@ class GpxImporter extends ScoreImporter {
|
|
|
21875
22133
|
if (!xml) throw new UnsupportedFormatError("No score.gpif found in GPX");
|
|
21876
22134
|
Logger.debug(this.name, "Start Parsing score.gpif");
|
|
21877
22135
|
const gpifParser = new GpifParser();
|
|
22136
|
+
gpifParser.isGp6 = true;
|
|
21878
22137
|
gpifParser.parseXml(xml, this.settings);
|
|
21879
22138
|
Logger.debug(this.name, "score.gpif parsed");
|
|
21880
22139
|
const score = gpifParser.score;
|
|
@@ -22212,6 +22471,16 @@ class TrackInfo {
|
|
|
22212
22471
|
}
|
|
22213
22472
|
}
|
|
22214
22473
|
/**
|
|
22474
|
+
* Tracks how raw MusicXML voice numbers on a staff map into alphaTab's local
|
|
22475
|
+
* (dense, 0-based) voice slots. Collapses MuseScore's sparse `staff*4+local`
|
|
22476
|
+
* convention (staff 2 → voices 5..8) into dense per-staff indices.
|
|
22477
|
+
* @internal
|
|
22478
|
+
*/
|
|
22479
|
+
class StaffVoicePacking {
|
|
22480
|
+
mapping = /* @__PURE__ */ new Map();
|
|
22481
|
+
sortedRawVoices = [];
|
|
22482
|
+
}
|
|
22483
|
+
/**
|
|
22215
22484
|
* @internal
|
|
22216
22485
|
*/
|
|
22217
22486
|
class MusicXmlImporter extends ScoreImporter {
|
|
@@ -22219,6 +22488,7 @@ class MusicXmlImporter extends ScoreImporter {
|
|
|
22219
22488
|
_idToTrackInfo = /* @__PURE__ */ new Map();
|
|
22220
22489
|
_indexToTrackInfo = /* @__PURE__ */ new Map();
|
|
22221
22490
|
_staffToContext = /* @__PURE__ */ new Map();
|
|
22491
|
+
_staffVoicePacking = /* @__PURE__ */ new Map();
|
|
22222
22492
|
_currentBarNumberDisplayPart;
|
|
22223
22493
|
_currentBarNumberDisplayBar;
|
|
22224
22494
|
_divisionsPerQuarterNote = 1;
|
|
@@ -23595,14 +23865,38 @@ class MusicXmlImporter extends ScoreImporter {
|
|
|
23595
23865
|
}
|
|
23596
23866
|
return staff.bars[masterBar.index];
|
|
23597
23867
|
}
|
|
23598
|
-
|
|
23599
|
-
let
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23868
|
+
_resolveAndPlaceVoice(staff, rawVoice, bar) {
|
|
23869
|
+
let packing;
|
|
23870
|
+
if (this._staffVoicePacking.has(staff)) packing = this._staffVoicePacking.get(staff);
|
|
23871
|
+
else {
|
|
23872
|
+
packing = new StaffVoicePacking();
|
|
23873
|
+
this._staffVoicePacking.set(staff, packing);
|
|
23603
23874
|
}
|
|
23604
|
-
if (
|
|
23605
|
-
|
|
23875
|
+
if (packing.mapping.has(rawVoice)) return bar.voices[packing.mapping.get(rawVoice)];
|
|
23876
|
+
let newVoiceNumber = Number.parseInt(rawVoice, 10);
|
|
23877
|
+
if (Number.isNaN(newVoiceNumber)) {
|
|
23878
|
+
Logger.warning("MusicXML", "Voices need to be specified as numbers");
|
|
23879
|
+
newVoiceNumber = 0;
|
|
23880
|
+
}
|
|
23881
|
+
let insertPos = packing.sortedRawVoices.length;
|
|
23882
|
+
for (let i = 0; i < packing.sortedRawVoices.length; i++) {
|
|
23883
|
+
const existing = Number.parseInt(packing.sortedRawVoices[i], 10);
|
|
23884
|
+
if ((Number.isNaN(existing) ? 0 : existing) > newVoiceNumber) {
|
|
23885
|
+
insertPos = i;
|
|
23886
|
+
break;
|
|
23887
|
+
}
|
|
23888
|
+
}
|
|
23889
|
+
packing.sortedRawVoices.splice(insertPos, 0, rawVoice);
|
|
23890
|
+
for (const b of staff.bars) {
|
|
23891
|
+
b.voices.splice(insertPos, 0, new Voice$1());
|
|
23892
|
+
for (let i = insertPos; i < b.voices.length; i++) {
|
|
23893
|
+
b.voices[i].index = i;
|
|
23894
|
+
b.voices[i].bar = b;
|
|
23895
|
+
}
|
|
23896
|
+
}
|
|
23897
|
+
packing.mapping.clear();
|
|
23898
|
+
for (let i = 0; i < packing.sortedRawVoices.length; i++) packing.mapping.set(packing.sortedRawVoices[i], i);
|
|
23899
|
+
return bar.voices[insertPos];
|
|
23606
23900
|
}
|
|
23607
23901
|
_parseNote(element, masterBar, track) {
|
|
23608
23902
|
let beat = null;
|
|
@@ -23611,7 +23905,7 @@ class MusicXmlImporter extends ScoreImporter {
|
|
|
23611
23905
|
let beamMode = null;
|
|
23612
23906
|
let isChord = false;
|
|
23613
23907
|
let staffIndex = 0;
|
|
23614
|
-
let
|
|
23908
|
+
let voiceRaw = "1";
|
|
23615
23909
|
let durationInTicks = -1;
|
|
23616
23910
|
let beatDuration = null;
|
|
23617
23911
|
let dots = 0;
|
|
@@ -23639,7 +23933,7 @@ class MusicXmlImporter extends ScoreImporter {
|
|
|
23639
23933
|
return;
|
|
23640
23934
|
}
|
|
23641
23935
|
const bar = this._getOrCreateBar(staff, masterBar);
|
|
23642
|
-
const voice = this.
|
|
23936
|
+
const voice = this._resolveAndPlaceVoice(staff, voiceRaw, bar);
|
|
23643
23937
|
const actualMusicalPosition = voice.beats.length === 0 ? 0 : voice.beats[voice.beats.length - 1].displayEnd;
|
|
23644
23938
|
let gap = this._musicalPosition - actualMusicalPosition;
|
|
23645
23939
|
if (gap > 0) {
|
|
@@ -23741,13 +24035,11 @@ class MusicXmlImporter extends ScoreImporter {
|
|
|
23741
24035
|
case "instrument":
|
|
23742
24036
|
instrumentId = c.getAttribute("id", "");
|
|
23743
24037
|
break;
|
|
23744
|
-
case "voice":
|
|
23745
|
-
|
|
23746
|
-
|
|
23747
|
-
Logger.warning("MusicXML", "Voices need to be specified as numbers");
|
|
23748
|
-
voiceIndex = 0;
|
|
23749
|
-
} else voiceIndex = voiceIndex - 1;
|
|
24038
|
+
case "voice": {
|
|
24039
|
+
const trimmed = c.innerText.trim();
|
|
24040
|
+
voiceRaw = trimmed.length > 0 ? trimmed : "1";
|
|
23750
24041
|
break;
|
|
24042
|
+
}
|
|
23751
24043
|
case "type":
|
|
23752
24044
|
beatDuration = this._parseBeatDuration(c);
|
|
23753
24045
|
break;
|
|
@@ -23833,7 +24125,7 @@ class MusicXmlImporter extends ScoreImporter {
|
|
|
23833
24125
|
if (instrumentId !== null && trackInfo.isUnpitchedInstrument(instrumentId)) note.percussionArticulation = trackInfo.getOrCreateArticulation(instrumentId, note);
|
|
23834
24126
|
else if (note.beat.voice.bar.staff.isPercussion) {
|
|
23835
24127
|
const knownArticulation = PercussionMapper.getArticulationById(note.displayValue);
|
|
23836
|
-
if (knownArticulation) note.percussionArticulation = knownArticulation
|
|
24128
|
+
if (knownArticulation) note.percussionArticulation = track.getOrRegisterPercussionArticulation(knownArticulation);
|
|
23837
24129
|
}
|
|
23838
24130
|
}
|
|
23839
24131
|
_parsePlay(element, note) {
|
|
@@ -68852,9 +69144,11 @@ class GpifSoundMapper {
|
|
|
68852
69144
|
class GpifWriter {
|
|
68853
69145
|
static _sampleRate = 44100;
|
|
68854
69146
|
_rhythmIdLookup = /* @__PURE__ */ new Map();
|
|
69147
|
+
_tuningByStaff = /* @__PURE__ */ new Map();
|
|
68855
69148
|
writeXml(score) {
|
|
68856
69149
|
const xmlDocument = new XmlDocument();
|
|
68857
69150
|
this._rhythmIdLookup = /* @__PURE__ */ new Map();
|
|
69151
|
+
this._tuningByStaff = /* @__PURE__ */ new Map();
|
|
68858
69152
|
this._writeDom(xmlDocument, score);
|
|
68859
69153
|
return xmlDocument.toFormattedString("", true);
|
|
68860
69154
|
}
|
|
@@ -68883,15 +69177,30 @@ class GpifWriter {
|
|
|
68883
69177
|
const beats = gpif.addElement("Beats");
|
|
68884
69178
|
const notes = gpif.addElement("Notes");
|
|
68885
69179
|
const rhythms = gpif.addElement("Rhythms");
|
|
68886
|
-
for (const tracks of score.tracks) for (const staff of tracks.staves)
|
|
68887
|
-
|
|
68888
|
-
|
|
68889
|
-
|
|
68890
|
-
|
|
68891
|
-
|
|
68892
|
-
|
|
69180
|
+
for (const tracks of score.tracks) for (const staff of tracks.staves) {
|
|
69181
|
+
const needsFingering = ModelUtils.staffNotesAreNotStringed(staff);
|
|
69182
|
+
const assignersByVoiceIndex = needsFingering ? /* @__PURE__ */ new Map() : null;
|
|
69183
|
+
const stringedTuning = needsFingering ? this._tuningByStaff.get(staff) : null;
|
|
69184
|
+
const savedTunings = staff.tuning;
|
|
69185
|
+
if (needsFingering && stringedTuning !== null && savedTunings.length === 0) staff.stringTuning.tunings = stringedTuning.slice();
|
|
69186
|
+
for (const bar of staff.bars) {
|
|
69187
|
+
const activeVoices = this._writeBarNode(bars, bar);
|
|
69188
|
+
for (const voice of activeVoices) {
|
|
69189
|
+
let assigner = null;
|
|
69190
|
+
if (assignersByVoiceIndex !== null && stringedTuning !== null) if (assignersByVoiceIndex.has(voice.index)) assigner = assignersByVoiceIndex.get(voice.index);
|
|
69191
|
+
else {
|
|
69192
|
+
assigner = new FingeringAssigner(stringedTuning, staff.capo, staff.transpositionPitch);
|
|
69193
|
+
assignersByVoiceIndex.set(voice.index, assigner);
|
|
69194
|
+
}
|
|
69195
|
+
this._writeVoiceNode(voices, voice);
|
|
69196
|
+
for (const beat of voice.beats) {
|
|
69197
|
+
if (assigner !== null) assigner.assign(beat);
|
|
69198
|
+
this._writeBeatNode(beats, beat, rhythms);
|
|
69199
|
+
for (const note of beat.notes) this._writeNoteNode(notes, note);
|
|
69200
|
+
}
|
|
68893
69201
|
}
|
|
68894
69202
|
}
|
|
69203
|
+
staff.stringTuning.tunings = savedTunings;
|
|
68895
69204
|
}
|
|
68896
69205
|
}
|
|
68897
69206
|
_writeAssets(parent, score) {
|
|
@@ -68997,13 +69306,18 @@ class GpifWriter {
|
|
|
68997
69306
|
const properties = parent.addElement("Properties");
|
|
68998
69307
|
this._writeConcertPitch(properties, note);
|
|
68999
69308
|
this._writeTransposedPitch(properties, note);
|
|
69000
|
-
if (note.
|
|
69309
|
+
if (note.isPercussion) {
|
|
69310
|
+
const art = PercussionMapper.getArticulation(note);
|
|
69311
|
+
const midi = art !== null ? art.outputMidiNumber : 0;
|
|
69312
|
+
this._writeSimplePropertyNode(properties, "String", "String", (note.string - 1).toString());
|
|
69313
|
+
this._writeSimplePropertyNode(properties, "Fret", "Fret", midi.toString());
|
|
69314
|
+
this._writeSimplePropertyNode(properties, "Midi", "Number", midi.toString());
|
|
69315
|
+
} else if (note.isStringed) {
|
|
69001
69316
|
this._writeSimplePropertyNode(properties, "String", "String", (note.string - 1).toString());
|
|
69002
69317
|
this._writeSimplePropertyNode(properties, "Fret", "Fret", note.fret.toString());
|
|
69003
69318
|
this._writeSimplePropertyNode(properties, "Midi", "Number", note.realValue.toString());
|
|
69004
69319
|
if (note.showStringNumber) this._writeSimplePropertyNode(properties, "ShowStringNumber", "Enable", null);
|
|
69005
69320
|
}
|
|
69006
|
-
if (note.isPercussion) this._writeSimplePropertyNode(properties, "String", "String", (note.string - 1).toString());
|
|
69007
69321
|
if (note.isPiano) {
|
|
69008
69322
|
this._writeSimplePropertyNode(properties, "Octave", "Number", note.octave.toString());
|
|
69009
69323
|
this._writeSimplePropertyNode(properties, "Tone", "Step", note.tone.toString());
|
|
@@ -69065,7 +69379,7 @@ class GpifWriter {
|
|
|
69065
69379
|
if (slideFlags > 0) this._writeSimplePropertyNode(properties, "Slide", "Flags", slideFlags.toString());
|
|
69066
69380
|
}
|
|
69067
69381
|
_writeTransposedPitch(properties, note) {
|
|
69068
|
-
if (note.isPercussion) this._writePitch(properties, "
|
|
69382
|
+
if (note.isPercussion) this._writePitch(properties, "TransposedPitch", "C", "-1", "");
|
|
69069
69383
|
else this._writePitchForValue(properties, "TransposedPitch", note.displayValueWithoutBend, note.accidentalMode, note.beat.voice.bar.keySignature);
|
|
69070
69384
|
}
|
|
69071
69385
|
_writeConcertPitch(properties, note) {
|
|
@@ -69470,7 +69784,7 @@ class GpifWriter {
|
|
|
69470
69784
|
initialTempoAutomation.addElement("Bar").innerText = "0";
|
|
69471
69785
|
initialTempoAutomation.addElement("Position").innerText = "0";
|
|
69472
69786
|
initialTempoAutomation.addElement("Visible").innerText = "true";
|
|
69473
|
-
initialTempoAutomation.addElement("Value").innerText = `${score.tempo} 2`;
|
|
69787
|
+
initialTempoAutomation.addElement("Value").innerText = `${score.tempo | 0} 2`;
|
|
69474
69788
|
if (score.tempoLabel) initialTempoAutomation.addElement("Text").innerText = score.tempoLabel;
|
|
69475
69789
|
}
|
|
69476
69790
|
const initialSyncPoint = score.masterBars[0].syncPoints ? score.masterBars[0].syncPoints.find((p) => p.ratioPosition === 0 && p.syncPointValue.barOccurence === 0) : void 0;
|
|
@@ -69485,7 +69799,7 @@ class GpifWriter {
|
|
|
69485
69799
|
tempoAutomation.addElement("Bar").innerText = mb.index.toString();
|
|
69486
69800
|
tempoAutomation.addElement("Position").innerText = automation.ratioPosition.toString();
|
|
69487
69801
|
tempoAutomation.addElement("Visible").innerText = automation.isVisible ? "true" : "false";
|
|
69488
|
-
tempoAutomation.addElement("Value").innerText = `${automation.value} 2`;
|
|
69802
|
+
tempoAutomation.addElement("Value").innerText = `${automation.value | 0} 2`;
|
|
69489
69803
|
if (automation.text) tempoAutomation.addElement("Text").innerText = automation.text;
|
|
69490
69804
|
}
|
|
69491
69805
|
if (mb.syncPoints) for (const syncPoint of mb.syncPoints) {
|
|
@@ -69621,14 +69935,35 @@ class GpifWriter {
|
|
|
69621
69935
|
const properties = parent.addElement("Staff").addElement("Properties");
|
|
69622
69936
|
this._writeSimplePropertyNode(properties, "CapoFret", "Fret", staff.capo.toString());
|
|
69623
69937
|
this._writeSimplePropertyNode(properties, "FretCount", "Fret", "24");
|
|
69624
|
-
|
|
69938
|
+
let tuning = staff.tuning;
|
|
69939
|
+
let tuningName = staff.tuningName;
|
|
69940
|
+
if (tuning.length === 0) {
|
|
69941
|
+
if (staff.isPercussion) {
|
|
69942
|
+
tuning = [
|
|
69943
|
+
0,
|
|
69944
|
+
0,
|
|
69945
|
+
0,
|
|
69946
|
+
0,
|
|
69947
|
+
0,
|
|
69948
|
+
0
|
|
69949
|
+
];
|
|
69950
|
+
tuningName = "";
|
|
69951
|
+
} else if (ModelUtils.staffNotesAreNotStringed(staff)) {
|
|
69952
|
+
const staffTuning = staff.index === 0 ? Tuning.getDefaultTuningFor(6) : Tuning.getDefaultTuningFor(5);
|
|
69953
|
+
tuning = staffTuning.tunings;
|
|
69954
|
+
tuningName = staffTuning.name;
|
|
69955
|
+
}
|
|
69956
|
+
}
|
|
69957
|
+
this._tuningByStaff.set(staff, tuning);
|
|
69958
|
+
if (tuning.length > 0) {
|
|
69625
69959
|
const tuningProperty = properties.addElement("Property");
|
|
69626
69960
|
tuningProperty.attributes.set("name", "Tuning");
|
|
69627
|
-
tuningProperty.addElement("Pitches").innerText =
|
|
69628
|
-
tuningProperty.addElement("Label").setCData(
|
|
69629
|
-
tuningProperty.addElement("LabelVisible").innerText =
|
|
69961
|
+
tuningProperty.addElement("Pitches").innerText = tuning.slice().reverse().join(" ");
|
|
69962
|
+
tuningProperty.addElement("Label").setCData(tuningName);
|
|
69963
|
+
tuningProperty.addElement("LabelVisible").innerText = tuningName ? "true" : "false";
|
|
69630
69964
|
tuningProperty.addElement("Flat");
|
|
69631
|
-
|
|
69965
|
+
if (staff.isPercussion) tuningProperty.addElement("Instrument").innerText = "Undefined";
|
|
69966
|
+
else switch (tuning.length) {
|
|
69632
69967
|
case 3:
|
|
69633
69968
|
tuningProperty.addElement("Instrument").innerText = "Shamisen";
|
|
69634
69969
|
break;
|
|
@@ -70001,10 +70336,37 @@ class GpifWriter {
|
|
|
70001
70336
|
_writeBarNode(parent, bar) {
|
|
70002
70337
|
const barNode = parent.addElement("Bar");
|
|
70003
70338
|
barNode.attributes.set("id", bar.id.toString());
|
|
70004
|
-
|
|
70339
|
+
const activeVoices = [];
|
|
70340
|
+
const slots = [
|
|
70341
|
+
"-1",
|
|
70342
|
+
"-1",
|
|
70343
|
+
"-1",
|
|
70344
|
+
"-1"
|
|
70345
|
+
];
|
|
70346
|
+
const overflowVoices = [];
|
|
70347
|
+
for (let i = 0; i < bar.voices.length; i++) {
|
|
70348
|
+
const v = bar.voices[i];
|
|
70349
|
+
if (i < 4) {
|
|
70350
|
+
if (!v.isEmpty) {
|
|
70351
|
+
slots[i] = v.id.toString();
|
|
70352
|
+
activeVoices.push(v);
|
|
70353
|
+
}
|
|
70354
|
+
} else if (!v.isEmpty) overflowVoices.push(v);
|
|
70355
|
+
}
|
|
70356
|
+
let dropped = 0;
|
|
70357
|
+
for (const v of overflowVoices) {
|
|
70358
|
+
const freeSlot = slots.indexOf("-1");
|
|
70359
|
+
if (freeSlot >= 0) {
|
|
70360
|
+
slots[freeSlot] = v.id.toString();
|
|
70361
|
+
activeVoices.push(v);
|
|
70362
|
+
} else dropped++;
|
|
70363
|
+
}
|
|
70364
|
+
if (dropped > 0) Logger.warning("GpifWriter", `Bar ${bar.id} has ${activeVoices.length + dropped} non-empty voices; Guitar Pro supports max 4. Dropping ${dropped}.`);
|
|
70365
|
+
barNode.addElement("Voices").innerText = slots.join(" ");
|
|
70005
70366
|
barNode.addElement("Clef").innerText = Clef[bar.clef];
|
|
70006
70367
|
if (bar.clefOttava !== Ottavia.Regular) barNode.addElement("Ottavia").innerText = Ottavia[bar.clefOttava].substr(1);
|
|
70007
70368
|
if (bar.simileMark !== SimileMark.None) barNode.addElement("SimileMark").innerText = SimileMark[bar.simileMark];
|
|
70369
|
+
return activeVoices;
|
|
70008
70370
|
}
|
|
70009
70371
|
_writeVoiceNode(parent, voice) {
|
|
70010
70372
|
if (voice.isEmpty) return;
|