@coderline/alphatab 1.7.0-alpha.1515 → 1.7.0-alpha.1522

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * alphaTab v1.7.0-alpha.1515 (develop, build 1515)
2
+ * alphaTab v1.7.0-alpha.1522 (develop, build 1522)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -197,9 +197,9 @@ class VersionInfo {
197
197
  print(`build date: ${VersionInfo.date}`);
198
198
  }
199
199
  }
200
- VersionInfo.version = '1.7.0-alpha.1515';
201
- VersionInfo.date = '2025-08-17T02:26:19.392Z';
202
- VersionInfo.commit = '23aaf549a408c97811ff01054a773c3fa2344bf0';
200
+ VersionInfo.version = '1.7.0-alpha.1522';
201
+ VersionInfo.date = '2025-08-24T02:21:16.522Z';
202
+ VersionInfo.commit = 'c80b4a66eae6b73cb2d7dacaaf74c8f9cce16318';
203
203
 
204
204
  /**
205
205
  * This public class provides names for all general midi instruments.
@@ -226,6 +226,14 @@ class GeneralMidi {
226
226
  static isGuitar(program) {
227
227
  return (program >= 24 && program <= 39) || program === 105 || program === 43;
228
228
  }
229
+ static isBass(program) {
230
+ return program >= 32 && program <= 39;
231
+ }
232
+ static bankToLsbMsb(bank) {
233
+ const lsb = bank & 0x7f;
234
+ const msb = (bank >> 7) & 0x7f;
235
+ return [lsb, msb];
236
+ }
229
237
  }
230
238
  GeneralMidi._values = new Map([
231
239
  ['acousticgrandpiano', 0],
@@ -1420,6 +1428,10 @@ var AutomationType;
1420
1428
  * A sync point for synchronizing the internal time axis with an external audio track.
1421
1429
  */
1422
1430
  AutomationType[AutomationType["SyncPoint"] = 4] = "SyncPoint";
1431
+ /**
1432
+ * Midi Bank change.
1433
+ */
1434
+ AutomationType[AutomationType["Bank"] = 4] = "Bank";
1423
1435
  })(AutomationType || (AutomationType = {}));
1424
1436
  /**
1425
1437
  * Represents the data of a sync point for synchronizing the internal time axis with
@@ -3387,6 +3399,49 @@ var MusicFontSymbol;
3387
3399
  MusicFontSymbol[MusicFontSymbol["FingeringCLower"] = 60700] = "FingeringCLower";
3388
3400
  })(MusicFontSymbol || (MusicFontSymbol = {}));
3389
3401
 
3402
+ /**
3403
+ * Defines all possible accidentals for notes.
3404
+ */
3405
+ var AccidentalType;
3406
+ (function (AccidentalType) {
3407
+ /**
3408
+ * No accidental
3409
+ */
3410
+ AccidentalType[AccidentalType["None"] = 0] = "None";
3411
+ /**
3412
+ * Naturalize
3413
+ */
3414
+ AccidentalType[AccidentalType["Natural"] = 1] = "Natural";
3415
+ /**
3416
+ * Sharp
3417
+ */
3418
+ AccidentalType[AccidentalType["Sharp"] = 2] = "Sharp";
3419
+ /**
3420
+ * Flat
3421
+ */
3422
+ AccidentalType[AccidentalType["Flat"] = 3] = "Flat";
3423
+ /**
3424
+ * Natural for smear bends
3425
+ */
3426
+ AccidentalType[AccidentalType["NaturalQuarterNoteUp"] = 4] = "NaturalQuarterNoteUp";
3427
+ /**
3428
+ * Sharp for smear bends
3429
+ */
3430
+ AccidentalType[AccidentalType["SharpQuarterNoteUp"] = 5] = "SharpQuarterNoteUp";
3431
+ /**
3432
+ * Flat for smear bends
3433
+ */
3434
+ AccidentalType[AccidentalType["FlatQuarterNoteUp"] = 6] = "FlatQuarterNoteUp";
3435
+ /**
3436
+ * Double Sharp, indicated by an 'x'
3437
+ */
3438
+ AccidentalType[AccidentalType["DoubleSharp"] = 7] = "DoubleSharp";
3439
+ /**
3440
+ * Double Flat, indicated by 'bb'
3441
+ */
3442
+ AccidentalType[AccidentalType["DoubleFlat"] = 8] = "DoubleFlat";
3443
+ })(AccidentalType || (AccidentalType = {}));
3444
+
3390
3445
  class TuningParseResult {
3391
3446
  constructor() {
3392
3447
  this.note = null;
@@ -3892,6 +3947,150 @@ class ModelUtils {
3892
3947
  }
3893
3948
  return ModelUtils.allMusicFontSymbols;
3894
3949
  }
3950
+ /**
3951
+ * @internal
3952
+ */
3953
+ static flooredDivision(a, b) {
3954
+ return a - b * Math.floor(a / b);
3955
+ }
3956
+ // NOTE: haven't figured out yet what exact formula is applied when transposing key signatures
3957
+ // this table is simply created by checking the Guitar Pro behavior,
3958
+ // The table is organized as [<transpose>][<key signature>] to match the table above
3959
+ // it's also easier to read as we list every key signature per row, transposed by the same value
3960
+ // this gives typically just a shifted list according to the transpose (with some special treatments)
3961
+ /**
3962
+ * Converts the key transpose table to actual key signatures.
3963
+ * @param texts An array where every item indicates the number of accidentals and which accidental
3964
+ * placed for the key signature.
3965
+ *
3966
+ * e.g. 3# is 3-sharps -> KeySignature.A
3967
+ */
3968
+ static translateKeyTransposeTable(texts) {
3969
+ const keySignatures = [];
3970
+ for (const transpose of texts) {
3971
+ const transposeValues = [];
3972
+ keySignatures.push(transposeValues);
3973
+ for (const keySignatureText of transpose) {
3974
+ const keySignature =
3975
+ // digit
3976
+ (Number.parseInt(keySignatureText.charAt(0)) *
3977
+ // b -> negative, # positive
3978
+ (keySignatureText.charAt(1) === 'b' ? -1 : 1));
3979
+ transposeValues.push(keySignature);
3980
+ }
3981
+ }
3982
+ return keySignatures;
3983
+ }
3984
+ /**
3985
+ * Transposes the given key signature.
3986
+ * @internal
3987
+ * @param keySignature The key signature to transpose
3988
+ * @param transpose The number of semitones to transpose (+/- 0-11)
3989
+ * @returns
3990
+ */
3991
+ static transposeKey(keySignature, transpose) {
3992
+ if (transpose === 0) {
3993
+ return keySignature;
3994
+ }
3995
+ if (transpose < 0) {
3996
+ const lookup = ModelUtils.keyTransposeTable[-transpose];
3997
+ const keySignatureIndex = lookup.indexOf(keySignature);
3998
+ if (keySignatureIndex === -1) {
3999
+ return keySignature;
4000
+ }
4001
+ return (keySignatureIndex - 7);
4002
+ }
4003
+ else {
4004
+ return ModelUtils.keyTransposeTable[transpose][keySignature + 7];
4005
+ }
4006
+ }
4007
+ /**
4008
+ * @internal
4009
+ */
4010
+ static computeAccidental(keySignature, accidentalMode, noteValue, quarterBend, currentAccidental = null) {
4011
+ const ks = keySignature;
4012
+ const ksi = ks + 7;
4013
+ const index = noteValue % 12;
4014
+ const accidentalForKeySignature = ksi < 7 ? AccidentalType.Flat : AccidentalType.Sharp;
4015
+ const hasKeySignatureAccidentalSetForNote = ModelUtils.KeySignatureLookup[ksi][index];
4016
+ const hasNoteAccidentalWithinOctave = ModelUtils.AccidentalNotes[index];
4017
+ // the general logic is like this:
4018
+ // - we check if the key signature has an accidental defined
4019
+ // - we calculate which accidental a note needs according to its index in the octave
4020
+ // - if the accidental is already placed at this line, nothing needs to be done, otherwise we place it
4021
+ // - if there should not be an accidental, but there is one in the key signature, we clear it.
4022
+ // the exceptions are:
4023
+ // - for quarter bends we just place the corresponding accidental
4024
+ // - the accidental mode can enforce the accidentals for the note
4025
+ let accidentalToSet = AccidentalType.None;
4026
+ if (quarterBend) {
4027
+ accidentalToSet = hasNoteAccidentalWithinOctave ? accidentalForKeySignature : AccidentalType.Natural;
4028
+ switch (accidentalToSet) {
4029
+ case AccidentalType.Natural:
4030
+ accidentalToSet = AccidentalType.NaturalQuarterNoteUp;
4031
+ break;
4032
+ case AccidentalType.Sharp:
4033
+ accidentalToSet = AccidentalType.SharpQuarterNoteUp;
4034
+ break;
4035
+ case AccidentalType.Flat:
4036
+ accidentalToSet = AccidentalType.FlatQuarterNoteUp;
4037
+ break;
4038
+ }
4039
+ }
4040
+ else {
4041
+ // define which accidental should be shown ignoring what might be set on the KS already
4042
+ switch (accidentalMode) {
4043
+ case NoteAccidentalMode.ForceSharp:
4044
+ accidentalToSet = AccidentalType.Sharp;
4045
+ break;
4046
+ case NoteAccidentalMode.ForceDoubleSharp:
4047
+ accidentalToSet = AccidentalType.DoubleSharp;
4048
+ break;
4049
+ case NoteAccidentalMode.ForceFlat:
4050
+ accidentalToSet = AccidentalType.Flat;
4051
+ break;
4052
+ case NoteAccidentalMode.ForceDoubleFlat:
4053
+ accidentalToSet = AccidentalType.DoubleFlat;
4054
+ break;
4055
+ default:
4056
+ // if note has an accidental in the octave, we place a symbol
4057
+ // according to the Key Signature
4058
+ if (hasNoteAccidentalWithinOctave) {
4059
+ accidentalToSet = accidentalForKeySignature;
4060
+ }
4061
+ else if (hasKeySignatureAccidentalSetForNote) {
4062
+ // note does not get an accidental, but KS defines one -> Naturalize
4063
+ accidentalToSet = AccidentalType.Natural;
4064
+ }
4065
+ break;
4066
+ }
4067
+ // do we need an accidental on the note?
4068
+ if (accidentalToSet !== AccidentalType.None) {
4069
+ // if there is no accidental on the line, and the key signature has it set already, we clear it on the note
4070
+ if (currentAccidental != null) {
4071
+ if (currentAccidental === accidentalToSet) {
4072
+ accidentalToSet = AccidentalType.None;
4073
+ }
4074
+ }
4075
+ else if (hasKeySignatureAccidentalSetForNote && accidentalToSet === accidentalForKeySignature) {
4076
+ accidentalToSet = AccidentalType.None;
4077
+ }
4078
+ }
4079
+ else {
4080
+ // if we don't want an accidental, but there is already one applied, we place a naturalize accidental
4081
+ // and clear the registration
4082
+ if (currentAccidental !== null) {
4083
+ if (currentAccidental === AccidentalType.Natural) {
4084
+ accidentalToSet = AccidentalType.None;
4085
+ }
4086
+ else {
4087
+ accidentalToSet = AccidentalType.Natural;
4088
+ }
4089
+ }
4090
+ }
4091
+ }
4092
+ return accidentalToSet;
4093
+ }
3895
4094
  }
3896
4095
  ModelUtils.TuningLetters = new Set([
3897
4096
  0x43 /* C */, 0x44 /* D */, 0x45 /* E */, 0x46 /* F */, 0x47 /* G */, 0x41 /* A */, 0x42 /* B */, 0x63 /* c */,
@@ -3944,6 +4143,66 @@ ModelUtils.displayTranspositionPitches = new Map([
3944
4143
  // Contrabass
3945
4144
  [43, -12]
3946
4145
  ]);
4146
+ /**
4147
+ * @internal
4148
+ */
4149
+ ModelUtils.keyTransposeTable = ModelUtils.translateKeyTransposeTable([
4150
+ /* Cb Gb Db Ab Eb Bb F C G D A E B F C# */
4151
+ /* C 0 */ ['7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#'],
4152
+ /* Db 1 */ ['2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b', '1b', '0#'],
4153
+ /* D 2 */ ['3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#'],
4154
+ /* Eb 3 */ ['4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b'],
4155
+ /* E 4 */ ['1#', '2#', '3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#'],
4156
+ /* F 5 */ ['6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b'],
4157
+ /* Gb 6 */ ['1b', '0#', '1#', '2#', '3#', '4#', '7b', '6#', '7#', '4b', '3b', '2b', '1b', '0#', '1#'],
4158
+ /* G 7 */ ['4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#'],
4159
+ /* Ab 8 */ ['3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b', '1b'],
4160
+ /* A 9 */ ['2#', '3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#'],
4161
+ /* Bb 10 */ ['5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b'],
4162
+ /* B 11 */ ['0#', '1#', '2#', '3#', '4#', '7b', '6b', '6#', '4b', '3b', '2b', '1b', '0#', '1#', '2#']
4163
+ ]);
4164
+ /**
4165
+ * a lookup list containing an info whether the notes within an octave
4166
+ * need an accidental rendered. the accidental symbol is determined based on the type of key signature.
4167
+ */
4168
+ ModelUtils.KeySignatureLookup = [
4169
+ // Flats (where the value is true, a flat accidental is required for the notes)
4170
+ [true, true, true, true, true, true, true, true, true, true, true, true],
4171
+ [true, true, true, true, true, false, true, true, true, true, true, true],
4172
+ [false, true, true, true, true, false, true, true, true, true, true, true],
4173
+ [false, true, true, true, true, false, false, false, true, true, true, true],
4174
+ [false, false, false, true, true, false, false, false, true, true, true, true],
4175
+ [false, false, false, true, true, false, false, false, false, false, true, true],
4176
+ [false, false, false, false, false, false, false, false, false, false, true, true],
4177
+ // natural
4178
+ [false, false, false, false, false, false, false, false, false, false, false, false],
4179
+ // sharps (where the value is true, a flat accidental is required for the notes)
4180
+ [false, false, false, false, false, true, true, false, false, false, false, false],
4181
+ [true, true, false, false, false, true, true, false, false, false, false, false],
4182
+ [true, true, false, false, false, true, true, true, true, false, false, false],
4183
+ [true, true, true, true, false, true, true, true, true, false, false, false],
4184
+ [true, true, true, true, false, true, true, true, true, true, true, false],
4185
+ [true, true, true, true, true, true, true, true, true, true, true, false],
4186
+ [true, true, true, true, true, true, true, true, true, true, true, true]
4187
+ ];
4188
+ /**
4189
+ * Contains the list of notes within an octave have accidentals set.
4190
+ * @internal
4191
+ */
4192
+ ModelUtils.AccidentalNotes = [
4193
+ false,
4194
+ true,
4195
+ false,
4196
+ true,
4197
+ false,
4198
+ false,
4199
+ true,
4200
+ false,
4201
+ true,
4202
+ false,
4203
+ true,
4204
+ false
4205
+ ];
3947
4206
 
3948
4207
  /**
3949
4208
  * Lists all types of pick strokes.
@@ -7914,6 +8173,10 @@ class PlaybackInformation {
7914
8173
  * Gets or sets the midi program to use.
7915
8174
  */
7916
8175
  this.program = 0;
8176
+ /**
8177
+ * The midi bank to use.
8178
+ */
8179
+ this.bank = 0;
7917
8180
  /**
7918
8181
  * Gets or sets the primary channel for all normal midi events.
7919
8182
  */
@@ -7946,7 +8209,7 @@ class Tuning {
7946
8209
  static getTextPartsForTuning(tuning, octaveShift = -1) {
7947
8210
  const octave = (tuning / 12) | 0;
7948
8211
  const note = tuning % 12;
7949
- const notes = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
8212
+ const notes = Tuning.noteNames;
7950
8213
  return [notes[note], (octave + octaveShift).toString()];
7951
8214
  }
7952
8215
  /**
@@ -8085,8 +8348,7 @@ Tuning._sixStrings = [];
8085
8348
  Tuning._fiveStrings = [];
8086
8349
  Tuning._fourStrings = [];
8087
8350
  Tuning._defaultTunings = new Map();
8088
- Tuning.defaultAccidentals = ['', '#', '', '#', '', '', '#', '', '#', '', '#', ''];
8089
- Tuning.defaultSteps = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'];
8351
+ Tuning.noteNames = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
8090
8352
  Tuning.initialize();
8091
8353
 
8092
8354
  /**
@@ -8309,6 +8571,12 @@ class Track {
8309
8571
  */
8310
8572
  this.percussionArticulations = [];
8311
8573
  }
8574
+ /**
8575
+ * Gets whether this track is a percussion track.
8576
+ */
8577
+ get isPercussion() {
8578
+ return this.staves.some(s => s.isPercussion);
8579
+ }
8312
8580
  /**
8313
8581
  * Adds a new line break.
8314
8582
  * @param index The index of the bar before which a line break should happen.
@@ -9012,8 +9280,7 @@ class AlphaTexLexer {
9012
9280
  }
9013
9281
  // unicode handling
9014
9282
  // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types-string-type
9015
- if (IOHelper.isLeadingSurrogate(previousCodepoint) &&
9016
- IOHelper.isTrailingSurrogate(codepoint)) {
9283
+ if (IOHelper.isLeadingSurrogate(previousCodepoint) && IOHelper.isTrailingSurrogate(codepoint)) {
9017
9284
  codepoint = (previousCodepoint - 0xd800) * 0x400 + (codepoint - 0xdc00) + 0x10000;
9018
9285
  s += String.fromCodePoint(codepoint);
9019
9286
  }
@@ -9448,10 +9715,10 @@ class AlphaTexImporter extends ScoreImporter {
9448
9715
  case 'bass':
9449
9716
  return Clef.F4;
9450
9717
  case 'c3':
9451
- case 'tenor':
9718
+ case 'alto':
9452
9719
  return Clef.C3;
9453
9720
  case 'c4':
9454
- case 'alto':
9721
+ case 'tenor':
9455
9722
  return Clef.C4;
9456
9723
  case 'n':
9457
9724
  case 'neutral':
@@ -9468,6 +9735,8 @@ class AlphaTexImporter extends ScoreImporter {
9468
9735
  */
9469
9736
  parseClefFromInt(i) {
9470
9737
  switch (i) {
9738
+ case 0:
9739
+ return Clef.Neutral;
9471
9740
  case 43:
9472
9741
  return Clef.G2;
9473
9742
  case 65:
@@ -9972,33 +10241,15 @@ class AlphaTexImporter extends ScoreImporter {
9972
10241
  }
9973
10242
  return StaffMetaResult.KnownStaffMeta;
9974
10243
  case 'instrument':
9975
- this.sy = this.newSy();
9976
10244
  this._staffTuningApplied = false;
9977
- if (this.sy === AlphaTexSymbols.Number) {
9978
- const instrument = this.syData;
9979
- if (instrument >= 0 && instrument <= 127) {
9980
- this._currentTrack.playbackInfo.program = this.syData;
9981
- }
9982
- else {
9983
- this.error('instrument', AlphaTexSymbols.Number, false);
9984
- }
9985
- }
9986
- else if (this.sy === AlphaTexSymbols.String) {
9987
- const instrumentName = this.syData.toLowerCase();
9988
- if (instrumentName === 'percussion') {
9989
- for (const staff of this._currentTrack.staves) {
9990
- this.applyPercussionStaff(staff);
9991
- }
9992
- this._currentTrack.playbackInfo.primaryChannel = SynthConstants.PercussionChannel;
9993
- this._currentTrack.playbackInfo.secondaryChannel = SynthConstants.PercussionChannel;
9994
- }
9995
- else {
9996
- this._currentTrack.playbackInfo.program = GeneralMidi.getValue(instrumentName);
9997
- }
9998
- }
9999
- else {
10000
- this.error('instrument', AlphaTexSymbols.Number, true);
10245
+ this.readTrackInstrument();
10246
+ return StaffMetaResult.KnownStaffMeta;
10247
+ case 'bank':
10248
+ this.sy = this.newSy();
10249
+ if (this.sy !== AlphaTexSymbols.Number) {
10250
+ this.error('bank', AlphaTexSymbols.Number, true);
10001
10251
  }
10252
+ this._currentTrack.playbackInfo.bank = this.syData;
10002
10253
  this.sy = this.newSy();
10003
10254
  return StaffMetaResult.KnownStaffMeta;
10004
10255
  case 'lyrics':
@@ -10109,6 +10360,35 @@ class AlphaTexImporter extends ScoreImporter {
10109
10360
  return StaffMetaResult.UnknownStaffMeta;
10110
10361
  }
10111
10362
  }
10363
+ readTrackInstrument() {
10364
+ this.sy = this.newSy();
10365
+ if (this.sy === AlphaTexSymbols.Number) {
10366
+ const instrument = this.syData;
10367
+ if (instrument >= 0 && instrument <= 127) {
10368
+ this._currentTrack.playbackInfo.program = this.syData;
10369
+ }
10370
+ else {
10371
+ this.error('instrument', AlphaTexSymbols.Number, false);
10372
+ }
10373
+ }
10374
+ else if (this.sy === AlphaTexSymbols.String) {
10375
+ const instrumentName = this.syData.toLowerCase();
10376
+ if (instrumentName === 'percussion') {
10377
+ for (const staff of this._currentTrack.staves) {
10378
+ this.applyPercussionStaff(staff);
10379
+ }
10380
+ this._currentTrack.playbackInfo.primaryChannel = SynthConstants.PercussionChannel;
10381
+ this._currentTrack.playbackInfo.secondaryChannel = SynthConstants.PercussionChannel;
10382
+ }
10383
+ else {
10384
+ this._currentTrack.playbackInfo.program = GeneralMidi.getValue(instrumentName);
10385
+ }
10386
+ }
10387
+ else {
10388
+ this.error('instrument', AlphaTexSymbols.Number, true);
10389
+ }
10390
+ this.sy = this.newSy();
10391
+ }
10112
10392
  handleAccidentalMode() {
10113
10393
  this.sy = this.newSy();
10114
10394
  if (this.sy !== AlphaTexSymbols.String) {
@@ -10381,6 +10661,17 @@ class AlphaTexImporter extends ScoreImporter {
10381
10661
  }
10382
10662
  this._score.stylesheet.perTrackMultiBarRest.add(this._currentTrack.index);
10383
10663
  break;
10664
+ case 'instrument':
10665
+ this.readTrackInstrument();
10666
+ break;
10667
+ case 'bank':
10668
+ this.sy = this.newSy();
10669
+ if (this.sy !== AlphaTexSymbols.Number) {
10670
+ this.error('bank', AlphaTexSymbols.Number, true);
10671
+ }
10672
+ this._currentTrack.playbackInfo.bank = this.syData;
10673
+ this.sy = this.newSy();
10674
+ break;
10384
10675
  default:
10385
10676
  this.error('track-properties', AlphaTexSymbols.String, false);
10386
10677
  break;
@@ -10725,6 +11016,25 @@ class AlphaTexImporter extends ScoreImporter {
10725
11016
  }
10726
11017
  beat.text = this.syData;
10727
11018
  }
11019
+ else if (syData === 'lyrics') {
11020
+ this.sy = this.newSy();
11021
+ let lyricsLine = 0;
11022
+ if (this.sy === AlphaTexSymbols.Number) {
11023
+ lyricsLine = this.syData;
11024
+ this.sy = this.newSy();
11025
+ }
11026
+ if (this.sy !== AlphaTexSymbols.String) {
11027
+ this.error('lyrics', AlphaTexSymbols.String, true);
11028
+ return false;
11029
+ }
11030
+ if (!beat.lyrics) {
11031
+ beat.lyrics = [];
11032
+ }
11033
+ while (beat.lyrics.length <= lyricsLine) {
11034
+ beat.lyrics.push('');
11035
+ }
11036
+ beat.lyrics[lyricsLine] = this.syData;
11037
+ }
10728
11038
  else if (syData === 'dd') {
10729
11039
  beat.dots = 2;
10730
11040
  }
@@ -11162,6 +11472,17 @@ class AlphaTexImporter extends ScoreImporter {
11162
11472
  automation.value = program;
11163
11473
  beat.automations.push(automation);
11164
11474
  }
11475
+ else if (syData === 'bank') {
11476
+ this.sy = this.newSy();
11477
+ if (this.sy !== AlphaTexSymbols.Number) {
11478
+ this.error('bank-change', AlphaTexSymbols.Number, true);
11479
+ }
11480
+ const automation = new Automation();
11481
+ automation.isLinear = false;
11482
+ automation.type = AutomationType.Bank;
11483
+ automation.value = this.syData;
11484
+ beat.automations.push(automation);
11485
+ }
11165
11486
  else if (syData === 'fermata') {
11166
11487
  this.sy = this.newSy();
11167
11488
  if (this.sy !== AlphaTexSymbols.String) {
@@ -11172,7 +11493,7 @@ class AlphaTexImporter extends ScoreImporter {
11172
11493
  this.sy = this.newSy(true);
11173
11494
  if (this.sy === AlphaTexSymbols.Number) {
11174
11495
  fermata.length = this.syData;
11175
- this.sy = this.newSy(true);
11496
+ this.sy = this.newSy();
11176
11497
  }
11177
11498
  beat.fermata = fermata;
11178
11499
  return true;
@@ -11205,13 +11526,9 @@ class AlphaTexImporter extends ScoreImporter {
11205
11526
  beat.beamingMode = BeatBeamingMode.ForceSplitOnSecondaryToNext;
11206
11527
  break;
11207
11528
  }
11208
- this.sy = this.newSy();
11209
- return true;
11210
11529
  }
11211
11530
  else if (syData === 'timer') {
11212
11531
  beat.showTimer = true;
11213
- this.sy = this.newSy();
11214
- return true;
11215
11532
  }
11216
11533
  else {
11217
11534
  // string didn't match any beat effect syntax
@@ -14771,6 +15088,7 @@ class Gp3To5Importer extends ScoreImporter {
14771
15088
  this._trackCount = 0;
14772
15089
  this._playbackInfos = [];
14773
15090
  this._doubleBars = new Set();
15091
+ this._clefsPerTrack = new Map();
14774
15092
  this._keySignatures = new Map();
14775
15093
  this._beatTextChunksByTrack = new Map();
14776
15094
  this._directionLookup = new Map();
@@ -15155,26 +15473,74 @@ class Gp3To5Importer extends ScoreImporter {
15155
15473
  mainStaff.capo = IOHelper.readInt32LE(this.data);
15156
15474
  newTrack.color = GpBinaryHelpers.gpReadColor(this.data, false);
15157
15475
  if (this._versionNumber >= 500) {
15158
- const staveFlags = this.data.readByte();
15159
- mainStaff.showTablature = (staveFlags & 0x01) !== 0;
15160
- mainStaff.showStandardNotation = (staveFlags & 0x02) !== 0;
15161
- const showChordDiagramListOnTopOfScore = (staveFlags & 0x64) !== 0;
15476
+ const staffFlags = this.data.readByte();
15477
+ mainStaff.showTablature = (staffFlags & 0x01) !== 0;
15478
+ mainStaff.showStandardNotation = (staffFlags & 0x02) !== 0;
15479
+ const showChordDiagramListOnTopOfScore = (staffFlags & 0x64) !== 0;
15162
15480
  if (this._score.stylesheet.perTrackChordDiagramsOnTop === null) {
15163
15481
  this._score.stylesheet.perTrackChordDiagramsOnTop = new Map();
15164
15482
  }
15165
15483
  this._score.stylesheet.perTrackChordDiagramsOnTop.set(newTrack.index, showChordDiagramListOnTopOfScore);
15166
- // flags for
15484
+ // MIDI: Automatic
15485
+ // 0x01 -> always set (unknown)
15167
15486
  // 0x02 -> auto let ring
15168
15487
  // 0x04 -> auto brush
15169
15488
  this.data.readByte();
15170
- // unknown
15171
- this.data.skip(43);
15489
+ // RSE: Auto-Accentuation on the Beat
15490
+ // 0 - None
15491
+ // 1 - Very Soft
15492
+ // 2 - Soft
15493
+ // 3 - Medium
15494
+ // 4 - Strong
15495
+ // 5 - Very Strong
15496
+ this.data.readByte();
15497
+ newTrack.playbackInfo.bank = this.data.readByte();
15498
+ // RSE: Human Playing (1 byte, 0-100%)
15499
+ this.data.readByte();
15500
+ // `12` for all tunings which have bass clefs
15501
+ const clefMode = IOHelper.readInt32LE(this.data);
15502
+ if (clefMode === 12) {
15503
+ this._clefsPerTrack.set(index, Clef.F4);
15504
+ }
15505
+ else {
15506
+ this._clefsPerTrack.set(index, Clef.G2);
15507
+ }
15508
+ // Unknown, no UI setting seem to affect this
15509
+ IOHelper.readInt32LE(this.data);
15510
+ // Unknown, no UI setting seem to affect this
15511
+ // typically: 100
15512
+ IOHelper.readInt32LE(this.data);
15513
+ // Unknown, no UI setting seem to affect this
15514
+ // typically: 1 2 3 4 5 6 7 8 9 10
15515
+ this.data.skip(10);
15516
+ // Unknown, no UI setting seem to affect this
15517
+ // typically: 255
15518
+ this.data.readByte();
15519
+ // Unknown, no UI setting seem to affect this
15520
+ // typically: 3
15521
+ this.data.readByte();
15522
+ this.readRseBank();
15523
+ // this.data.skip(42);
15524
+ if (this._versionNumber >= 510) {
15525
+ // RSE: 3-band EQ
15526
+ // 1 byte Low
15527
+ // 1 byte Mid
15528
+ // 1 byte High
15529
+ // 1 byte PRE
15530
+ this.data.skip(4);
15531
+ // RSE: effect name
15532
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15533
+ // RSE: effect category
15534
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15535
+ }
15172
15536
  }
15173
- // unknown
15174
- if (this._versionNumber >= 510) {
15175
- this.data.skip(4);
15176
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15177
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15537
+ else {
15538
+ if (GeneralMidi.isBass(newTrack.playbackInfo.program)) {
15539
+ this._clefsPerTrack.set(index, Clef.F4);
15540
+ }
15541
+ else {
15542
+ this._clefsPerTrack.set(index, Clef.G2);
15543
+ }
15178
15544
  }
15179
15545
  }
15180
15546
  readBars() {
@@ -15190,6 +15556,9 @@ class Gp3To5Importer extends ScoreImporter {
15190
15556
  if (mainStaff.isPercussion) {
15191
15557
  newBar.clef = Clef.Neutral;
15192
15558
  }
15559
+ else if (this._clefsPerTrack.has(track.index)) {
15560
+ newBar.clef = this._clefsPerTrack.get(track.index);
15561
+ }
15193
15562
  mainStaff.addBar(newBar);
15194
15563
  if (this._keySignatures.has(newBar.index)) {
15195
15564
  const newKeySignature = this._keySignatures.get(newBar.index);
@@ -15611,11 +15980,29 @@ class Gp3To5Importer extends ScoreImporter {
15611
15980
  return 0;
15612
15981
  }
15613
15982
  }
15983
+ readRseBank() {
15984
+ // RSE Banks on disk are having filenames like this: 033_2_002.ini
15985
+ // 033 is the RSE instrument (selected in the instrument list)
15986
+ // 2 seem to be some sort of style variation. e.g. bass has multiple variations (1,2), guitars have only 1, percussion has only 0
15987
+ // likely for organizational purposes
15988
+ // 002 is the effective soundbank (selected in the soundbank dropdown)
15989
+ // RSE Instrument
15990
+ this.data.skip(4); // IOHelper.readInt32LE(this.data);
15991
+ // RSE Style/Variation
15992
+ this.data.skip(4); //IOHelper.readInt32LE(this.data);
15993
+ // RSE Soundbank
15994
+ this.data.skip(4); //IOHelper.readInt32LE(this.data);
15995
+ // Unknown, no UI setting seem to affect this
15996
+ // typically: -1
15997
+ this.data.skip(4); //IOHelper.readInt32LE(this.data);
15998
+ }
15614
15999
  readMixTableChange(beat) {
15615
16000
  const tableChange = new MixTableChange();
15616
16001
  tableChange.instrument = IOHelper.readSInt8(this.data);
16002
+ // NOTE: The UI shows a Midi Bank selection, but this information is not stored in the file
16003
+ // when reopening the file the bank is always 0
15617
16004
  if (this._versionNumber >= 500) {
15618
- this.data.skip(16); // Rse Info
16005
+ this.readRseBank();
15619
16006
  }
15620
16007
  tableChange.volume = IOHelper.readSInt8(this.data);
15621
16008
  tableChange.balance = IOHelper.readSInt8(this.data);
@@ -15627,7 +16014,7 @@ class Gp3To5Importer extends ScoreImporter {
15627
16014
  tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15628
16015
  }
15629
16016
  tableChange.tempo = IOHelper.readInt32LE(this.data);
15630
- // durations
16017
+ // durations (in number of beats)
15631
16018
  if (tableChange.volume >= 0) {
15632
16019
  this.data.readByte();
15633
16020
  }
@@ -15757,11 +16144,11 @@ class Gp3To5Importer extends ScoreImporter {
15757
16144
  newNote.fret = -1;
15758
16145
  }
15759
16146
  if (swapAccidentals) {
15760
- const accidental = Tuning.defaultAccidentals[newNote.realValueWithoutHarmonic % 12];
15761
- if (accidental === '#') {
16147
+ const accidental = ModelUtils.computeAccidental(bar.keySignature, NoteAccidentalMode.Default, newNote.realValueWithoutHarmonic, false);
16148
+ if (accidental === AccidentalType.Sharp) {
15762
16149
  newNote.accidentalMode = NoteAccidentalMode.ForceFlat;
15763
16150
  }
15764
- else if (accidental === 'b') {
16151
+ else if (accidental === AccidentalType.Flat) {
15765
16152
  newNote.accidentalMode = NoteAccidentalMode.ForceSharp;
15766
16153
  }
15767
16154
  }
@@ -16610,9 +16997,7 @@ class BinaryStylesheet {
16610
16997
  return writer.toArray();
16611
16998
  }
16612
16999
  static addHeaderAndFooter(binaryStylesheet, style, prefix, name) {
16613
- if (style.template !== undefined) {
16614
- binaryStylesheet.addValue(`${prefix}${name}`, style.template, DataType.String);
16615
- }
17000
+ binaryStylesheet.addValue(`${prefix}${name}`, style.template, DataType.String);
16616
17001
  binaryStylesheet.addValue(`${prefix}${name}Alignment`, style.textAlign, DataType.Integer);
16617
17002
  if (style.isVisible !== undefined) {
16618
17003
  binaryStylesheet.addValue(`${prefix}draw${name}`, style.isVisible, DataType.Boolean);
@@ -16646,6 +17031,7 @@ class GpifSound {
16646
17031
  this.path = '';
16647
17032
  this.role = '';
16648
17033
  this.program = 0;
17034
+ this.bank = 0;
16649
17035
  }
16650
17036
  get uniqueId() {
16651
17037
  return `${this.path};${this.name};${this.role}`;
@@ -16661,6 +17047,7 @@ class GpifParser {
16661
17047
  this._backingTrackPadding = 0;
16662
17048
  this._doubleBars = new Set();
16663
17049
  this._keySignatures = new Map();
17050
+ this._transposeKeySignaturePerTrack = new Map();
16664
17051
  }
16665
17052
  parseXml(xml, settings) {
16666
17053
  this._masterTrackAutomations = new Map();
@@ -16891,7 +17278,8 @@ class GpifParser {
16891
17278
  assetId = c.innerText;
16892
17279
  break;
16893
17280
  case 'FramePadding':
16894
- this._backingTrackPadding = GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate * 1000;
17281
+ this._backingTrackPadding =
17282
+ (GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate) * 1000;
16895
17283
  break;
16896
17284
  }
16897
17285
  }
@@ -16998,21 +17386,28 @@ class GpifParser {
16998
17386
  if (!type) {
16999
17387
  return;
17000
17388
  }
17001
- let automation = null;
17389
+ const newAutomations = [];
17002
17390
  switch (type) {
17003
17391
  case 'Tempo':
17004
- automation = Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference);
17392
+ newAutomations.push(Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference));
17005
17393
  break;
17006
17394
  case 'SyncPoint':
17007
- automation = new Automation();
17008
- automation.type = AutomationType.SyncPoint;
17009
- automation.isLinear = isLinear;
17010
- automation.ratioPosition = ratioPosition;
17011
- automation.syncPointValue = syncPointValue;
17395
+ const syncPoint = new Automation();
17396
+ syncPoint.type = AutomationType.SyncPoint;
17397
+ syncPoint.isLinear = isLinear;
17398
+ syncPoint.ratioPosition = ratioPosition;
17399
+ syncPoint.syncPointValue = syncPointValue;
17400
+ newAutomations.push(syncPoint);
17012
17401
  break;
17013
17402
  case 'Sound':
17014
17403
  if (textValue && sounds && sounds.has(textValue)) {
17015
- automation = Automation.buildInstrumentAutomation(isLinear, ratioPosition, sounds.get(textValue).program);
17404
+ const bankChange = new Automation();
17405
+ bankChange.type = AutomationType.Bank;
17406
+ bankChange.ratioPosition = ratioPosition;
17407
+ bankChange.value = sounds.get(textValue).bank;
17408
+ newAutomations.push(bankChange);
17409
+ const programChange = Automation.buildInstrumentAutomation(isLinear, ratioPosition, sounds.get(textValue).program);
17410
+ newAutomations.push(programChange);
17016
17411
  }
17017
17412
  break;
17018
17413
  case 'SustainPedal':
@@ -17040,15 +17435,19 @@ class GpifParser {
17040
17435
  }
17041
17436
  break;
17042
17437
  }
17043
- if (automation) {
17438
+ if (newAutomations.length) {
17044
17439
  if (text) {
17045
- automation.text = text;
17440
+ for (const a of newAutomations) {
17441
+ a.text = text;
17442
+ }
17046
17443
  }
17047
17444
  if (barIndex >= 0) {
17048
17445
  if (!automations.has(barIndex)) {
17049
17446
  automations.set(barIndex, []);
17050
17447
  }
17051
- automations.get(barIndex).push(automation);
17448
+ for (const a of newAutomations) {
17449
+ automations.get(barIndex).push(a);
17450
+ }
17052
17451
  }
17053
17452
  }
17054
17453
  }
@@ -17127,13 +17526,13 @@ class GpifParser {
17127
17526
  track.playbackInfo.isMute = state === 'Mute';
17128
17527
  break;
17129
17528
  case 'PartSounding':
17130
- this.parsePartSounding(track, c);
17529
+ this.parsePartSounding(trackId, track, c);
17131
17530
  break;
17132
17531
  case 'Staves':
17133
17532
  this.parseStaves(track, c);
17134
17533
  break;
17135
17534
  case 'Transpose':
17136
- this.parseTranspose(track, c);
17535
+ this.parseTranspose(trackId, track, c);
17137
17536
  break;
17138
17537
  case 'RSE':
17139
17538
  this.parseRSE(track, c);
@@ -17640,24 +18039,33 @@ class GpifParser {
17640
18039
  break;
17641
18040
  }
17642
18041
  }
17643
- if (sound.role === 'Factory' || track.playbackInfo.program === 0) {
17644
- track.playbackInfo.program = sound.program;
17645
- }
17646
18042
  if (!this._soundsByTrack.has(trackId)) {
17647
18043
  this._soundsByTrack.set(trackId, new Map());
18044
+ // apply first sound
18045
+ track.playbackInfo.program = sound.program;
18046
+ track.playbackInfo.bank = sound.bank;
17648
18047
  }
17649
18048
  this._soundsByTrack.get(trackId).set(sound.uniqueId, sound);
17650
18049
  }
17651
18050
  parseSoundMidi(sound, node) {
18051
+ let bankMsb = 0;
18052
+ let bankLsb = 0;
17652
18053
  for (const c of node.childElements()) {
17653
18054
  switch (c.localName) {
17654
18055
  case 'Program':
17655
18056
  sound.program = GpifParser.parseIntSafe(c.innerText, 0);
17656
18057
  break;
18058
+ case 'MSB': // coarse
18059
+ bankMsb = GpifParser.parseIntSafe(c.innerText, 0);
18060
+ break;
18061
+ case 'LSB': // Fine
18062
+ bankLsb = GpifParser.parseIntSafe(c.innerText, 0);
18063
+ break;
17657
18064
  }
17658
18065
  }
18066
+ sound.bank = ((bankMsb & 0x7f) << 7) | bankLsb;
17659
18067
  }
17660
- parsePartSounding(track, node) {
18068
+ parsePartSounding(trackId, track, node) {
17661
18069
  for (const c of node.childElements()) {
17662
18070
  switch (c.localName) {
17663
18071
  case 'TranspositionPitch':
@@ -17665,10 +18073,14 @@ class GpifParser {
17665
18073
  staff.displayTranspositionPitch = GpifParser.parseIntSafe(c.innerText, 0);
17666
18074
  }
17667
18075
  break;
18076
+ case 'NominalKey':
18077
+ const transposeIndex = Math.max(0, Tuning.noteNames.indexOf(c.innerText));
18078
+ this._transposeKeySignaturePerTrack.set(trackId, transposeIndex);
18079
+ break;
17668
18080
  }
17669
18081
  }
17670
18082
  }
17671
- parseTranspose(track, node) {
18083
+ parseTranspose(trackId, track, node) {
17672
18084
  let octave = 0;
17673
18085
  let chromatic = 0;
17674
18086
  for (const c of node.childElements()) {
@@ -17681,9 +18093,14 @@ class GpifParser {
17681
18093
  break;
17682
18094
  }
17683
18095
  }
18096
+ const pitch = octave * 12 + chromatic;
17684
18097
  for (const staff of track.staves) {
17685
- staff.displayTranspositionPitch = octave * 12 + chromatic;
18098
+ staff.displayTranspositionPitch = pitch;
17686
18099
  }
18100
+ // the chromatic transpose also causes an alternative key signature to be adjusted
18101
+ // In Guitar Pro this feature is hidden in the track properties (more -> Transposition tonality -> 'C played as:' ).
18102
+ const transposeIndex = ModelUtils.flooredDivision(pitch, 12);
18103
+ this._transposeKeySignaturePerTrack.set(trackId, transposeIndex);
17687
18104
  }
17688
18105
  parseRSE(track, node) {
17689
18106
  for (const c of node.childElements()) {
@@ -18647,6 +19064,7 @@ class GpifParser {
18647
19064
  // GP6 had percussion as element+variation
18648
19065
  let element = -1;
18649
19066
  let variation = -1;
19067
+ let hasTransposedPitch = false;
18650
19068
  for (const c of node.childElements()) {
18651
19069
  switch (c.localName) {
18652
19070
  case 'Property':
@@ -18727,7 +19145,15 @@ class GpifParser {
18727
19145
  note.tone = GpifParser.parseIntSafe(c.findChildElement('Step')?.innerText, 0);
18728
19146
  break;
18729
19147
  case 'ConcertPitch':
19148
+ if (!hasTransposedPitch) {
19149
+ this.parseConcertPitch(c, note);
19150
+ }
19151
+ break;
19152
+ case 'TransposedPitch':
19153
+ // clear potential value from concert pitch
19154
+ note.accidentalMode = NoteAccidentalMode.Default;
18730
19155
  this.parseConcertPitch(c, note);
19156
+ hasTransposedPitch = true;
18731
19157
  break;
18732
19158
  case 'Bended':
18733
19159
  isBended = true;
@@ -18941,20 +19367,29 @@ class GpifParser {
18941
19367
  lastMasterBar.isDoubleBar = false;
18942
19368
  }
18943
19369
  // add tracks to score
19370
+ const trackIndexToTrackId = [];
18944
19371
  for (const trackId of this._tracksMapping) {
18945
19372
  if (!trackId) {
18946
19373
  continue;
18947
19374
  }
18948
19375
  const track = this._tracksById.get(trackId);
18949
19376
  this.score.addTrack(track);
19377
+ trackIndexToTrackId.push(trackId);
18950
19378
  }
18951
19379
  // process all masterbars
18952
19380
  let keySignature;
18953
19381
  for (const barIds of this._barsOfMasterBar) {
18954
19382
  // add all bars of masterbar vertically to all tracks
18955
19383
  let staffIndex = 0;
19384
+ let trackIndex = 0;
18956
19385
  keySignature = [KeySignature.C, KeySignatureType.Major];
18957
- for (let barIndex = 0, trackIndex = 0; barIndex < barIds.length && trackIndex < this.score.tracks.length; barIndex++) {
19386
+ if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[0])) {
19387
+ keySignature = [
19388
+ ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[0])),
19389
+ keySignature[1]
19390
+ ];
19391
+ }
19392
+ for (let barIndex = 0; barIndex < barIds.length && trackIndex < this.score.tracks.length; barIndex++) {
18958
19393
  const barId = barIds[barIndex];
18959
19394
  if (barId !== GpifParser.InvalidId) {
18960
19395
  const bar = this._barsById.get(barId);
@@ -18964,6 +19399,12 @@ class GpifParser {
18964
19399
  const masterBarIndex = staff.bars.length - 1;
18965
19400
  if (this._keySignatures.has(masterBarIndex)) {
18966
19401
  keySignature = this._keySignatures.get(masterBarIndex);
19402
+ if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[trackIndex])) {
19403
+ keySignature = [
19404
+ ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[trackIndex])),
19405
+ keySignature[1]
19406
+ ];
19407
+ }
18967
19408
  }
18968
19409
  bar.keySignature = keySignature[0];
18969
19410
  bar.keySignatureType = keySignature[1];
@@ -19030,11 +19471,17 @@ class GpifParser {
19030
19471
  if (staffIndex === track.staves.length - 1) {
19031
19472
  trackIndex++;
19032
19473
  staffIndex = 0;
19033
- keySignature = [KeySignature.C, KeySignatureType.Major];
19034
19474
  }
19035
19475
  else {
19036
19476
  staffIndex++;
19037
- keySignature = [KeySignature.C, KeySignatureType.Major];
19477
+ }
19478
+ keySignature = [KeySignature.C, KeySignatureType.Major];
19479
+ if (trackIndex < trackIndexToTrackId.length &&
19480
+ this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[trackIndex])) {
19481
+ keySignature = [
19482
+ ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[trackIndex])),
19483
+ keySignature[1]
19484
+ ];
19038
19485
  }
19039
19486
  }
19040
19487
  else {
@@ -19070,7 +19517,12 @@ class GpifParser {
19070
19517
  for (const a of automations) {
19071
19518
  // NOTE: currently the automations of a bar are applied to the
19072
19519
  // first beat of a bar
19073
- beat.automations.push(a);
19520
+ const skip =
19521
+ // skip bank automations if they are 0 at start
19522
+ a.type === AutomationType.Bank && a.value === 0 && bar.index === 0;
19523
+ if (!skip) {
19524
+ beat.automations.push(a);
19525
+ }
19074
19526
  }
19075
19527
  }
19076
19528
  }
@@ -19122,7 +19574,7 @@ GpifParser.BendPointPositionFactor = BendPoint.MaxPosition / 100.0;
19122
19574
  */
19123
19575
  GpifParser.BendPointValueFactor = 1 / 25.0;
19124
19576
  // tests have shown that Guitar Pro seem to always work with 44100hz for the frame offsets,
19125
- // they are NOT using the sample rate of the input file.
19577
+ // they are NOT using the sample rate of the input file.
19126
19578
  // Downsampling a 44100hz ogg to 8000hz and using it in as audio track resulted in the same frame offset when placing sync points.
19127
19579
  GpifParser.SampleRate = 44100;
19128
19580
 
@@ -19817,49 +20269,6 @@ class GpxImporter extends ScoreImporter {
19817
20269
  }
19818
20270
  }
19819
20271
 
19820
- /**
19821
- * Defines all possible accidentals for notes.
19822
- */
19823
- var AccidentalType;
19824
- (function (AccidentalType) {
19825
- /**
19826
- * No accidental
19827
- */
19828
- AccidentalType[AccidentalType["None"] = 0] = "None";
19829
- /**
19830
- * Naturalize
19831
- */
19832
- AccidentalType[AccidentalType["Natural"] = 1] = "Natural";
19833
- /**
19834
- * Sharp
19835
- */
19836
- AccidentalType[AccidentalType["Sharp"] = 2] = "Sharp";
19837
- /**
19838
- * Flat
19839
- */
19840
- AccidentalType[AccidentalType["Flat"] = 3] = "Flat";
19841
- /**
19842
- * Natural for smear bends
19843
- */
19844
- AccidentalType[AccidentalType["NaturalQuarterNoteUp"] = 4] = "NaturalQuarterNoteUp";
19845
- /**
19846
- * Sharp for smear bends
19847
- */
19848
- AccidentalType[AccidentalType["SharpQuarterNoteUp"] = 5] = "SharpQuarterNoteUp";
19849
- /**
19850
- * Flat for smear bends
19851
- */
19852
- AccidentalType[AccidentalType["FlatQuarterNoteUp"] = 6] = "FlatQuarterNoteUp";
19853
- /**
19854
- * Double Sharp, indicated by an 'x'
19855
- */
19856
- AccidentalType[AccidentalType["DoubleSharp"] = 7] = "DoubleSharp";
19857
- /**
19858
- * Double Flat, indicated by 'bb'
19859
- */
19860
- AccidentalType[AccidentalType["DoubleFlat"] = 8] = "DoubleFlat";
19861
- })(AccidentalType || (AccidentalType = {}));
19862
-
19863
20272
  class BeatLines {
19864
20273
  constructor() {
19865
20274
  this.maxLine = -1e3;
@@ -19962,90 +20371,6 @@ class AccidentalHelper {
19962
20371
  }
19963
20372
  return line;
19964
20373
  }
19965
- static computeAccidental(keySignature, accidentalMode, noteValue, quarterBend, currentAccidental = null) {
19966
- const ks = keySignature;
19967
- const ksi = ks + 7;
19968
- const index = noteValue % 12;
19969
- const accidentalForKeySignature = ksi < 7 ? AccidentalType.Flat : AccidentalType.Sharp;
19970
- const hasKeySignatureAccidentalSetForNote = AccidentalHelper.KeySignatureLookup[ksi][index];
19971
- const hasNoteAccidentalWithinOctave = AccidentalHelper.AccidentalNotes[index];
19972
- // the general logic is like this:
19973
- // - we check if the key signature has an accidental defined
19974
- // - we calculate which accidental a note needs according to its index in the octave
19975
- // - if the accidental is already placed at this line, nothing needs to be done, otherwise we place it
19976
- // - if there should not be an accidental, but there is one in the key signature, we clear it.
19977
- // the exceptions are:
19978
- // - for quarter bends we just place the corresponding accidental
19979
- // - the accidental mode can enforce the accidentals for the note
19980
- let accidentalToSet = AccidentalType.None;
19981
- if (quarterBend) {
19982
- accidentalToSet = hasNoteAccidentalWithinOctave ? accidentalForKeySignature : AccidentalType.Natural;
19983
- switch (accidentalToSet) {
19984
- case AccidentalType.Natural:
19985
- accidentalToSet = AccidentalType.NaturalQuarterNoteUp;
19986
- break;
19987
- case AccidentalType.Sharp:
19988
- accidentalToSet = AccidentalType.SharpQuarterNoteUp;
19989
- break;
19990
- case AccidentalType.Flat:
19991
- accidentalToSet = AccidentalType.FlatQuarterNoteUp;
19992
- break;
19993
- }
19994
- }
19995
- else {
19996
- // define which accidental should be shown ignoring what might be set on the KS already
19997
- switch (accidentalMode) {
19998
- case NoteAccidentalMode.ForceSharp:
19999
- accidentalToSet = AccidentalType.Sharp;
20000
- break;
20001
- case NoteAccidentalMode.ForceDoubleSharp:
20002
- accidentalToSet = AccidentalType.DoubleSharp;
20003
- break;
20004
- case NoteAccidentalMode.ForceFlat:
20005
- accidentalToSet = AccidentalType.Flat;
20006
- break;
20007
- case NoteAccidentalMode.ForceDoubleFlat:
20008
- accidentalToSet = AccidentalType.DoubleFlat;
20009
- break;
20010
- default:
20011
- // if note has an accidental in the octave, we place a symbol
20012
- // according to the Key Signature
20013
- if (hasNoteAccidentalWithinOctave) {
20014
- accidentalToSet = accidentalForKeySignature;
20015
- }
20016
- else if (hasKeySignatureAccidentalSetForNote) {
20017
- // note does not get an accidental, but KS defines one -> Naturalize
20018
- accidentalToSet = AccidentalType.Natural;
20019
- }
20020
- break;
20021
- }
20022
- // do we need an accidental on the note?
20023
- if (accidentalToSet !== AccidentalType.None) {
20024
- // if there is no accidental on the line, and the key signature has it set already, we clear it on the note
20025
- if (currentAccidental != null) {
20026
- if (currentAccidental === accidentalToSet) {
20027
- accidentalToSet = AccidentalType.None;
20028
- }
20029
- }
20030
- else if (hasKeySignatureAccidentalSetForNote && accidentalToSet === accidentalForKeySignature) {
20031
- accidentalToSet = AccidentalType.None;
20032
- }
20033
- }
20034
- else {
20035
- // if we don't want an accidental, but there is already one applied, we place a naturalize accidental
20036
- // and clear the registration
20037
- if (currentAccidental !== null) {
20038
- if (currentAccidental === AccidentalType.Natural) {
20039
- accidentalToSet = AccidentalType.None;
20040
- }
20041
- else {
20042
- accidentalToSet = AccidentalType.Natural;
20043
- }
20044
- }
20045
- }
20046
- }
20047
- return accidentalToSet;
20048
- }
20049
20374
  getAccidental(noteValue, quarterBend, relatedBeat, isHelperNote, note = null) {
20050
20375
  let steps = 0;
20051
20376
  let accidentalToSet = AccidentalType.None;
@@ -20059,7 +20384,7 @@ class AccidentalHelper {
20059
20384
  const currentAccidental = this._registeredAccidentals.has(steps)
20060
20385
  ? this._registeredAccidentals.get(steps)
20061
20386
  : null;
20062
- accidentalToSet = AccidentalHelper.computeAccidental(this._bar.keySignature, accidentalMode, noteValue, quarterBend, currentAccidental);
20387
+ accidentalToSet = ModelUtils.computeAccidental(this._bar.keySignature, accidentalMode, noteValue, quarterBend, currentAccidental);
20063
20388
  let skipAccidental = false;
20064
20389
  switch (accidentalToSet) {
20065
20390
  case AccidentalType.NaturalQuarterNoteUp:
@@ -20173,48 +20498,6 @@ class AccidentalHelper {
20173
20498
  return 0;
20174
20499
  }
20175
20500
  }
20176
- /**
20177
- * a lookup list containing an info whether the notes within an octave
20178
- * need an accidental rendered. the accidental symbol is determined based on the type of key signature.
20179
- */
20180
- AccidentalHelper.KeySignatureLookup = [
20181
- // Flats (where the value is true, a flat accidental is required for the notes)
20182
- [true, true, true, true, true, true, true, true, true, true, true, true],
20183
- [true, true, true, true, true, false, true, true, true, true, true, true],
20184
- [false, true, true, true, true, false, true, true, true, true, true, true],
20185
- [false, true, true, true, true, false, false, false, true, true, true, true],
20186
- [false, false, false, true, true, false, false, false, true, true, true, true],
20187
- [false, false, false, true, true, false, false, false, false, false, true, true],
20188
- [false, false, false, false, false, false, false, false, false, false, true, true],
20189
- // natural
20190
- [false, false, false, false, false, false, false, false, false, false, false, false],
20191
- // sharps (where the value is true, a flat accidental is required for the notes)
20192
- [false, false, false, false, false, true, true, false, false, false, false, false],
20193
- [true, true, false, false, false, true, true, false, false, false, false, false],
20194
- [true, true, false, false, false, true, true, true, true, false, false, false],
20195
- [true, true, true, true, false, true, true, true, true, false, false, false],
20196
- [true, true, true, true, false, true, true, true, true, true, true, false],
20197
- [true, true, true, true, true, true, true, true, true, true, true, false],
20198
- [true, true, true, true, true, true, true, true, true, true, true, true]
20199
- ];
20200
- /**
20201
- * Contains the list of notes within an octave have accidentals set.
20202
- */
20203
- // prettier-ignore
20204
- AccidentalHelper.AccidentalNotes = [
20205
- false,
20206
- true,
20207
- false,
20208
- true,
20209
- false,
20210
- false,
20211
- true,
20212
- false,
20213
- true,
20214
- false,
20215
- true,
20216
- false
20217
- ];
20218
20501
  /**
20219
20502
  * We always have 7 steps per octave.
20220
20503
  * (by a step the offsets inbetween score lines is meant,
@@ -20259,6 +20542,10 @@ class InstrumentArticulationWithPlaybackInfo extends InstrumentArticulation {
20259
20542
  * The midi channel program to use when playing the note (-1 if using the default track program).
20260
20543
  */
20261
20544
  this.outputMidiProgram = -1;
20545
+ /**
20546
+ * The midi bank to use when playing the note (-1 if using the default track bank).
20547
+ */
20548
+ this.outputMidiBank = -1;
20262
20549
  /**
20263
20550
  * The volume to use when playing the note (-1 if using the default track volume).
20264
20551
  */
@@ -20757,6 +21044,9 @@ class MusicXmlImporter extends ScoreImporter {
20757
21044
  if (trackInfo.firstArticulation.outputMidiProgram >= 0) {
20758
21045
  track.playbackInfo.program = trackInfo.firstArticulation.outputMidiProgram;
20759
21046
  }
21047
+ if (trackInfo.firstArticulation.outputMidiBank >= 0) {
21048
+ track.playbackInfo.bank = trackInfo.firstArticulation.outputMidiBank;
21049
+ }
20760
21050
  if (trackInfo.firstArticulation.outputBalance >= 0) {
20761
21051
  track.playbackInfo.balance = trackInfo.firstArticulation.outputBalance;
20762
21052
  }
@@ -20788,7 +21078,9 @@ class MusicXmlImporter extends ScoreImporter {
20788
21078
  articulation.outputMidiChannel = Number.parseInt(c.innerText) - 1;
20789
21079
  break;
20790
21080
  // case 'midi-name': Ignored
20791
- // case 'midi-bank': Not supported (https://github.com/CoderLine/alphaTab/issues/1986)
21081
+ case 'midi-bank':
21082
+ articulation.outputMidiBank = Number.parseInt(c.innerText) - 1;
21083
+ break;
20792
21084
  case 'midi-program':
20793
21085
  articulation.outputMidiProgram = Number.parseInt(c.innerText) - 1;
20794
21086
  break;
@@ -21318,7 +21610,15 @@ class MusicXmlImporter extends ScoreImporter {
21318
21610
  switch (c.localName) {
21319
21611
  // case 'midi-channel': Ignored
21320
21612
  // case 'midi-name': Ignored
21321
- // case 'midi-bank': Ignored
21613
+ case 'midi-bank':
21614
+ if (!this._nextBeatAutomations) {
21615
+ this._nextBeatAutomations = [];
21616
+ }
21617
+ automation = new Automation();
21618
+ automation.type = AutomationType.Bank;
21619
+ automation.value = Number.parseInt(c.innerText) - 1;
21620
+ this._nextBeatAutomations.push(automation);
21621
+ break;
21322
21622
  case 'midi-program':
21323
21623
  if (!this._nextBeatAutomations) {
21324
21624
  this._nextBeatAutomations = [];
@@ -23589,11 +23889,15 @@ class CircularSampleBuffer {
23589
23889
  }
23590
23890
 
23591
23891
  class EventEmitter {
23592
- constructor() {
23892
+ constructor(fireOnRegister = undefined) {
23593
23893
  this._listeners = [];
23894
+ this._fireOnRegister = fireOnRegister;
23594
23895
  }
23595
23896
  on(value) {
23596
23897
  this._listeners.push(value);
23898
+ if (this._fireOnRegister?.()) {
23899
+ value();
23900
+ }
23597
23901
  return () => {
23598
23902
  this.off(value);
23599
23903
  };
@@ -23611,11 +23915,18 @@ class EventEmitter {
23611
23915
  * @partial
23612
23916
  */
23613
23917
  class EventEmitterOfT {
23614
- constructor() {
23918
+ constructor(fireOnRegister = undefined) {
23615
23919
  this._listeners = [];
23920
+ this._fireOnRegister = fireOnRegister;
23616
23921
  }
23617
23922
  on(value) {
23618
23923
  this._listeners.push(value);
23924
+ if (this._fireOnRegister) {
23925
+ const arg = this._fireOnRegister();
23926
+ if (arg !== null) {
23927
+ value(arg);
23928
+ }
23929
+ }
23619
23930
  return () => {
23620
23931
  this.off(value);
23621
23932
  };
@@ -30656,6 +30967,12 @@ class AlphaSynthBase {
30656
30967
  this.sequencer.playbackSpeed = value;
30657
30968
  this.timePosition = this.timePosition * (oldSpeed / value);
30658
30969
  }
30970
+ get loadedMidiInfo() {
30971
+ return this._loadedMidiInfo;
30972
+ }
30973
+ get currentPosition() {
30974
+ return this._currentPosition;
30975
+ }
30659
30976
  get tickPosition() {
30660
30977
  return this._tickPosition;
30661
30978
  }
@@ -30713,22 +31030,38 @@ class AlphaSynthBase {
30713
31030
  this._midiEventsPlayedFilter = new Set();
30714
31031
  this._notPlayedSamples = 0;
30715
31032
  this._synthStopping = false;
31033
+ this._currentPosition = new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
30716
31034
  this.isReady = false;
30717
31035
  this.state = PlayerState.Paused;
30718
31036
  this._loadedSoundFonts = [];
30719
- this.ready = new EventEmitter();
30720
31037
  this.readyForPlayback = new EventEmitter();
30721
31038
  this.finished = new EventEmitter();
30722
31039
  this.soundFontLoaded = new EventEmitter();
30723
31040
  this.soundFontLoadFailed = new EventEmitterOfT();
30724
- this.midiLoaded = new EventEmitterOfT();
30725
31041
  this.midiLoadFailed = new EventEmitterOfT();
30726
- this.stateChanged = new EventEmitterOfT();
30727
- this.positionChanged = new EventEmitterOfT();
30728
31042
  this.midiEventsPlayed = new EventEmitterOfT();
30729
- this.playbackRangeChanged = new EventEmitterOfT();
30730
31043
  Logger.debug('AlphaSynth', 'Initializing player');
30731
31044
  this.state = PlayerState.Paused;
31045
+ this.ready = new EventEmitter(() => this.isReady);
31046
+ this.readyForPlayback = new EventEmitter(() => this.isReadyForPlayback);
31047
+ this.midiLoaded = new EventEmitterOfT(() => {
31048
+ if (this._loadedMidiInfo) {
31049
+ return this._loadedMidiInfo;
31050
+ }
31051
+ return null;
31052
+ });
31053
+ this.stateChanged = new EventEmitterOfT(() => {
31054
+ return new PlayerStateChangedEventArgs(this.state, false);
31055
+ });
31056
+ this.positionChanged = new EventEmitterOfT(() => {
31057
+ return this._currentPosition;
31058
+ });
31059
+ this.playbackRangeChanged = new EventEmitterOfT(() => {
31060
+ if (this.playbackRange) {
31061
+ return new PlaybackRangeChangedEventArgs(this.playbackRange);
31062
+ }
31063
+ return null;
31064
+ });
30732
31065
  Logger.debug('AlphaSynth', 'Creating output');
30733
31066
  this._output = output;
30734
31067
  Logger.debug('AlphaSynth', 'Creating synthesizer');
@@ -30906,7 +31239,8 @@ class AlphaSynthBase {
30906
31239
  Logger.debug('AlphaSynth', 'Loading midi from model');
30907
31240
  this.sequencer.loadMidi(midi);
30908
31241
  this._isMidiLoaded = true;
30909
- this.midiLoaded.trigger(new PositionChangedEventArgs(0, this.sequencer.currentEndTime, 0, this.sequencer.currentEndTick, false, this.sequencer.currentTempo, this.sequencer.modifiedTempo));
31242
+ this._loadedMidiInfo = new PositionChangedEventArgs(0, this.sequencer.currentEndTime, 0, this.sequencer.currentEndTick, false, this.sequencer.currentTempo, this.sequencer.modifiedTempo);
31243
+ this.midiLoaded.trigger(this._loadedMidiInfo);
30910
31244
  Logger.debug('AlphaSynth', 'Midi successfully loaded');
30911
31245
  this.checkReadyForPlayback();
30912
31246
  this.tickPosition = 0;
@@ -31009,23 +31343,28 @@ class AlphaSynthBase {
31009
31343
  this.sequencer.resetOneTimeMidi();
31010
31344
  this.timePosition = this.sequencer.currentTime;
31011
31345
  }
31012
- updateTimePosition(timePosition, isSeek) {
31013
- // update the real positions
31014
- let currentTime = timePosition;
31015
- this._timePosition = currentTime;
31346
+ createPositionChangedEventArgs(isSeek) {
31347
+ // on fade outs we can have some milliseconds longer, ensure we don't report this
31348
+ let currentTime = this._timePosition;
31016
31349
  let currentTick = this.sequencer.currentTimePositionToTickPosition(currentTime);
31017
- this._tickPosition = currentTick;
31018
31350
  const endTime = this.sequencer.currentEndTime;
31019
31351
  const endTick = this.sequencer.currentEndTick;
31020
- // on fade outs we can have some milliseconds longer, ensure we don't report this
31021
31352
  if (currentTime > endTime) {
31022
31353
  currentTime = endTime;
31023
31354
  currentTick = endTick;
31024
31355
  }
31356
+ return new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek, this.sequencer.currentTempo, this.sequencer.modifiedTempo);
31357
+ }
31358
+ updateTimePosition(timePosition, isSeek) {
31359
+ // update the real positions
31360
+ this._timePosition = timePosition;
31361
+ const args = this.createPositionChangedEventArgs(isSeek);
31362
+ this._tickPosition = args.currentTick;
31025
31363
  const mode = this.sequencer.isPlayingMain ? 'main' : this.sequencer.isPlayingCountIn ? 'count-in' : 'one-time';
31026
- Logger.debug('AlphaSynth', `Position changed: (time: ${currentTime}/${endTime}, tick: ${currentTick}/${endTick}, Active Voices: ${this.synthesizer.activeVoiceCount} (${mode}), Tempo original: ${this.sequencer.currentTempo}, Tempo modified: ${this.sequencer.modifiedTempo})`);
31364
+ Logger.debug('AlphaSynth', `Position changed: (time: ${args.currentTime}/${args.endTime}, tick: ${args.currentTick}/${args.endTick}, Active Voices: ${this.synthesizer.activeVoiceCount} (${mode}), Tempo original: ${this.sequencer.currentTempo}, Tempo modified: ${this.sequencer.modifiedTempo})`);
31027
31365
  if (this.sequencer.isPlayingMain) {
31028
- this.positionChanged.trigger(new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek, this.sequencer.currentTempo, this.sequencer.modifiedTempo));
31366
+ this._currentPosition = args;
31367
+ this.positionChanged.trigger(args);
31029
31368
  }
31030
31369
  // build events which were actually played
31031
31370
  if (isSeek) {
@@ -31033,7 +31372,7 @@ class AlphaSynthBase {
31033
31372
  }
31034
31373
  else {
31035
31374
  const playedEvents = [];
31036
- while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < currentTime) {
31375
+ while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < args.currentTime) {
31037
31376
  const synthEvent = this._playedEventsQueue.dequeue();
31038
31377
  playedEvents.push(synthEvent.event);
31039
31378
  }
@@ -36484,6 +36823,15 @@ class Settings {
36484
36823
  fillFromJson(json) {
36485
36824
  SettingsSerializer.fromJson(this, json);
36486
36825
  }
36826
+ /**
36827
+ * handles backwards compatibility aspects on the settings, removed in 2.0
36828
+ * @internal
36829
+ */
36830
+ handleBackwardsCompatibility() {
36831
+ if (this.player.playerMode === PlayerMode.Disabled && this.player.enablePlayer) {
36832
+ this.player.playerMode = PlayerMode.EnabledAutomatic;
36833
+ }
36834
+ }
36487
36835
  }
36488
36836
 
36489
36837
  class SectionSerializer {
@@ -37701,6 +38049,7 @@ class PlaybackInformationSerializer {
37701
38049
  o.set("balance", obj.balance);
37702
38050
  o.set("port", obj.port);
37703
38051
  o.set("program", obj.program);
38052
+ o.set("bank", obj.bank);
37704
38053
  o.set("primarychannel", obj.primaryChannel);
37705
38054
  o.set("secondarychannel", obj.secondaryChannel);
37706
38055
  o.set("ismute", obj.isMute);
@@ -37721,6 +38070,9 @@ class PlaybackInformationSerializer {
37721
38070
  case "program":
37722
38071
  obj.program = v;
37723
38072
  return true;
38073
+ case "bank":
38074
+ obj.bank = v;
38075
+ return true;
37724
38076
  case "primarychannel":
37725
38077
  obj.primaryChannel = v;
37726
38078
  return true;
@@ -41379,6 +41731,11 @@ class MidiFileGenerator {
41379
41731
  this._programsPerChannel.set(channel, program);
41380
41732
  }
41381
41733
  }
41734
+ addBankChange(track, tick, channel, bank) {
41735
+ const lsbMsb = GeneralMidi.bankToLsbMsb(bank);
41736
+ this._handler.addControlChange(track.index, tick, channel, ControllerType.BankSelectCoarse, lsbMsb[1]);
41737
+ this._handler.addControlChange(track.index, tick, channel, ControllerType.BankSelectFine, lsbMsb[0]);
41738
+ }
41382
41739
  static buildTranspositionPitches(score, settings) {
41383
41740
  const transpositionPitches = new Map();
41384
41741
  for (const track of score.tracks) {
@@ -41406,6 +41763,7 @@ class MidiFileGenerator {
41406
41763
  // Set PitchBendRangeCoarse to 12
41407
41764
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryFine, 0);
41408
41765
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
41766
+ this.addBankChange(track, 0, channel, playbackInfo.bank);
41409
41767
  this.addProgramChange(track, 0, channel, playbackInfo.program);
41410
41768
  }
41411
41769
  /**
@@ -42701,6 +43059,10 @@ class MidiFileGenerator {
42701
43059
  this.addProgramChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, (automation.value | 0) & 0xff);
42702
43060
  this.addProgramChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.secondaryChannel, (automation.value | 0) & 0xff);
42703
43061
  break;
43062
+ case AutomationType.Bank:
43063
+ this.addBankChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, automation.value);
43064
+ this.addBankChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.secondaryChannel, automation.value);
43065
+ break;
42704
43066
  case AutomationType.Balance:
42705
43067
  const balance = MidiFileGenerator.toChannelShort(automation.value);
42706
43068
  this._handler.addControlChange(beat.voice.bar.staff.track.index, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, ControllerType.PanCoarse, balance);
@@ -43650,17 +44012,29 @@ class AlphaSynthWrapper {
43650
44012
  this._playbackSpeed = 1;
43651
44013
  this._isLooping = false;
43652
44014
  this._midiEventsPlayedFilter = [];
43653
- this.ready = new EventEmitter();
43654
- this.readyForPlayback = new EventEmitter();
43655
44015
  this.finished = new EventEmitter();
43656
44016
  this.soundFontLoaded = new EventEmitter();
43657
44017
  this.soundFontLoadFailed = new EventEmitterOfT();
43658
- this.midiLoaded = new EventEmitterOfT();
43659
44018
  this.midiLoadFailed = new EventEmitterOfT();
43660
- this.stateChanged = new EventEmitterOfT();
43661
- this.positionChanged = new EventEmitterOfT();
43662
44019
  this.midiEventsPlayed = new EventEmitterOfT();
43663
- this.playbackRangeChanged = new EventEmitterOfT();
44020
+ this.ready = new EventEmitter(() => this.isReady);
44021
+ this.readyForPlayback = new EventEmitter(() => this.isReadyForPlayback);
44022
+ this.midiLoaded = new EventEmitterOfT(() => {
44023
+ return this._instance?.loadedMidiInfo ?? null;
44024
+ });
44025
+ this.stateChanged = new EventEmitterOfT(() => {
44026
+ return new PlayerStateChangedEventArgs(this.state, false);
44027
+ });
44028
+ this.positionChanged = new EventEmitterOfT(() => {
44029
+ return this.currentPosition;
44030
+ });
44031
+ this.playbackRangeChanged = new EventEmitterOfT(() => {
44032
+ const range = this.playbackRange;
44033
+ if (range) {
44034
+ return new PlaybackRangeChangedEventArgs(range);
44035
+ }
44036
+ return null;
44037
+ });
43664
44038
  }
43665
44039
  get instance() {
43666
44040
  return this._instance;
@@ -43682,10 +44056,14 @@ class AlphaSynthWrapper {
43682
44056
  newUnregister.push(value.finished.on(() => this.finished.trigger()));
43683
44057
  newUnregister.push(value.soundFontLoaded.on(() => this.soundFontLoaded.trigger()));
43684
44058
  newUnregister.push(value.soundFontLoadFailed.on(e => this.soundFontLoadFailed.trigger(e)));
43685
- newUnregister.push(value.midiLoaded.on(e => this.midiLoaded.trigger(e)));
44059
+ newUnregister.push(value.midiLoaded.on(e => {
44060
+ this.midiLoaded.trigger(e);
44061
+ }));
43686
44062
  newUnregister.push(value.midiLoadFailed.on(e => this.midiLoadFailed.trigger(e)));
43687
44063
  newUnregister.push(value.stateChanged.on(e => this.stateChanged.trigger(e)));
43688
- newUnregister.push(value.positionChanged.on(e => this.positionChanged.trigger(e)));
44064
+ newUnregister.push(value.positionChanged.on(e => {
44065
+ this.positionChanged.trigger(e);
44066
+ }));
43689
44067
  newUnregister.push(value.midiEventsPlayed.on(e => this.midiEventsPlayed.trigger(e)));
43690
44068
  newUnregister.push(value.playbackRangeChanged.on(e => this.playbackRangeChanged.trigger(e)));
43691
44069
  this._instanceEventUnregister = newUnregister;
@@ -43764,6 +44142,14 @@ class AlphaSynthWrapper {
43764
44142
  this._instance.playbackSpeed = value;
43765
44143
  }
43766
44144
  }
44145
+ get loadedMidiInfo() {
44146
+ return this._instance ? this._instance.loadedMidiInfo : undefined;
44147
+ }
44148
+ get currentPosition() {
44149
+ return this._instance
44150
+ ? this._instance.currentPosition
44151
+ : new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
44152
+ }
43767
44153
  get tickPosition() {
43768
44154
  return this._instance ? this._instance.tickPosition : 0;
43769
44155
  }
@@ -44105,83 +44491,6 @@ class AlphaTabApiBase {
44105
44491
  this._previousStateForCursor = PlayerState.Paused;
44106
44492
  this._previousCursorCache = null;
44107
44493
  this._lastScroll = 0;
44108
- /**
44109
- * This event is fired when the played beat changed.
44110
- *
44111
- * @eventProperty
44112
- * @category Events - Player
44113
- * @since 0.9.4
44114
- *
44115
- * @example
44116
- * JavaScript
44117
- * ```js
44118
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44119
- * api.playedBeatChanged.on((beat) => {
44120
- * updateFretboard(beat);
44121
- * });
44122
- * ```
44123
- *
44124
- * @example
44125
- * C#
44126
- * ```cs
44127
- * var api = new AlphaTabApi<MyControl>(...);
44128
- * api.PlayedBeatChanged.On(beat =>
44129
- * {
44130
- * UpdateFretboard(beat);
44131
- * });
44132
- * ```
44133
- *
44134
- * @example
44135
- * Android
44136
- * ```kotlin
44137
- * val api = AlphaTabApi<MyControl>(...)
44138
- * api.playedBeatChanged.on { beat ->
44139
- * updateFretboard(beat)
44140
- * }
44141
- * ```
44142
- *
44143
- */
44144
- this.playedBeatChanged = new EventEmitterOfT();
44145
- /**
44146
- * This event is fired when the currently active beats across all tracks change.
44147
- *
44148
- * @remarks
44149
- * Unlike the {@link playedBeatChanged} event this event contains the beats of all tracks and voices independent of them being rendered.
44150
- *
44151
- * @eventProperty
44152
- * @category Events - Player
44153
- * @since 1.2.3
44154
- *
44155
- * @example
44156
- * JavaScript
44157
- * ```js
44158
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44159
- * api.activeBeatsChanged.on(args => {
44160
- * updateHighlights(args.activeBeats);
44161
- * });
44162
- * ```
44163
- *
44164
- * @example
44165
- * C#
44166
- * ```cs
44167
- * var api = new AlphaTabApi<MyControl>(...);
44168
- * api.ActiveBeatsChanged.On(args =>
44169
- * {
44170
- * UpdateHighlights(args.ActiveBeats);
44171
- * });
44172
- * ```
44173
- *
44174
- * @example
44175
- * Android
44176
- * ```kotlin
44177
- * val api = AlphaTabApi<MyControl>(...)
44178
- * api.activeBeatsChanged.on { args ->
44179
- * updateHighlights(args.activeBeats)
44180
- * }
44181
- * ```
44182
- *
44183
- */
44184
- this.activeBeatsChanged = new EventEmitterOfT();
44185
44494
  this._beatMouseDown = false;
44186
44495
  this._noteMouseDown = false;
44187
44496
  this._selectionStart = null;
@@ -44420,47 +44729,6 @@ class AlphaTabApiBase {
44420
44729
  *
44421
44730
  */
44422
44731
  this.noteMouseUp = new EventEmitterOfT();
44423
- /**
44424
- * This event is fired whenever a new song is loaded.
44425
- * @remarks
44426
- * This event is fired whenever a new song is loaded or changing due to {@link renderScore} or {@link renderTracks} calls.
44427
- * It is fired after the transposition midi pitches from the settings were applied, but before any midi is generated or rendering is started.
44428
- * This allows any modification of the score before further processing.
44429
- *
44430
- * @eventProperty
44431
- * @category Events - Core
44432
- * @since 0.9.4
44433
- *
44434
- * @example
44435
- * JavaScript
44436
- * ```js
44437
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44438
- * api.scoreLoaded.on((score) => {
44439
- * updateSongInformationInUi(score);
44440
- * });
44441
- * ```
44442
- *
44443
- * @example
44444
- * C#
44445
- * ```cs
44446
- * var api = new AlphaTabApi<MyControl>(...);
44447
- * api.ScoreLoaded.On(score =>
44448
- * {
44449
- * UpdateSongInformationInUi(score);
44450
- * });
44451
- * ```
44452
- *
44453
- * @example
44454
- * Android
44455
- * ```kotlin
44456
- * val api = AlphaTabApi<MyControl>(...)
44457
- * api.scoreLoaded.on { score ->
44458
- * updateSongInformationInUi(score)
44459
- * }
44460
- * ```
44461
- *
44462
- */
44463
- this.scoreLoaded = new EventEmitterOfT();
44464
44732
  /**
44465
44733
  * This event is fired when alphaTab was resized and is about to rerender the music notation.
44466
44734
  * @remarks
@@ -44719,46 +44987,6 @@ class AlphaTabApiBase {
44719
44987
  *
44720
44988
  */
44721
44989
  this.midiLoad = new EventEmitterOfT();
44722
- /**
44723
- * This event is fired when the Midi file needed for playback was loaded.
44724
- *
44725
- * @eventProperty
44726
- * @category Events - Player
44727
- * @since 0.9.4
44728
- *
44729
- * @example
44730
- * JavaScript
44731
- * ```js
44732
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44733
- * api.midiLoaded.on(e => {
44734
- * hideGeneratingAudioIndicator();
44735
- * updateSongDuration(e.endTime);
44736
- * });
44737
- * ```
44738
- *
44739
- * @example
44740
- * C#
44741
- * ```cs
44742
- * var api = new AlphaTabApi<MyControl>(...);
44743
- * api.MidiLoaded.On(e =>
44744
- * {
44745
- * HideGeneratingAudioIndicator();
44746
- * UpdateSongDuration(e.EndTime);
44747
- * });
44748
- * ```
44749
- *
44750
- * @example
44751
- * Android
44752
- * ```kotlin
44753
- * val api = AlphaTabApi<MyControl>(...)
44754
- * api.midiLoaded.on { e ->
44755
- * hideGeneratingAudioIndicator()
44756
- * updateSongDuration(e.endTime)
44757
- * }
44758
- * ```
44759
- *
44760
- */
44761
- this.midiLoaded = new EventEmitterOfT();
44762
44990
  /**
44763
44991
  * This event is fired when a settings update was requested.
44764
44992
  *
@@ -44798,12 +45026,30 @@ class AlphaTabApiBase {
44798
45026
  this.settingsUpdated = new EventEmitter();
44799
45027
  this.uiFacade = uiFacade;
44800
45028
  this.container = uiFacade.rootContainer;
45029
+ this.activeBeatsChanged = new EventEmitterOfT(() => {
45030
+ if (this._player.state === PlayerState.Playing && this._currentBeat) {
45031
+ return new ActiveBeatsChangedEventArgs(this._currentBeat.beatLookup.highlightedBeats.map(h => h.beat));
45032
+ }
45033
+ return null;
45034
+ });
45035
+ this.playedBeatChanged = new EventEmitterOfT(() => {
45036
+ if (this._player.state === PlayerState.Playing && this._currentBeat) {
45037
+ return this._currentBeat.beat;
45038
+ }
45039
+ return null;
45040
+ });
45041
+ this.scoreLoaded = new EventEmitterOfT(() => {
45042
+ if (this._score) {
45043
+ return this._score;
45044
+ }
45045
+ return null;
45046
+ });
45047
+ this.midiLoaded = new EventEmitterOfT(() => {
45048
+ return this._player.loadedMidiInfo ?? null;
45049
+ });
44801
45050
  uiFacade.initialize(this, settings);
44802
45051
  Logger.logLevel = this.settings.core.logLevel;
44803
- // backwards compatibility: remove in 2.0
44804
- if (this.settings.player.playerMode === PlayerMode.Disabled && this.settings.player.enablePlayer) {
44805
- this.settings.player.playerMode = PlayerMode.EnabledAutomatic;
44806
- }
45052
+ this.settings.handleBackwardsCompatibility();
44807
45053
  Environment.printEnvironmentInfo(false);
44808
45054
  this.canvasElement = uiFacade.createCanvasElement();
44809
45055
  this.container.appendChild(this.canvasElement);
@@ -44962,6 +45208,7 @@ class AlphaTabApiBase {
44962
45208
  * ```
44963
45209
  */
44964
45210
  updateSettings() {
45211
+ this.settings.handleBackwardsCompatibility();
44965
45212
  const score = this.score;
44966
45213
  if (score) {
44967
45214
  ModelUtils.applyPitchOffsets(this.settings, score);
@@ -45769,6 +46016,22 @@ class AlphaTabApiBase {
45769
46016
  set timePosition(value) {
45770
46017
  this._player.timePosition = value;
45771
46018
  }
46019
+ /**
46020
+ * The total length of the song in midi ticks.
46021
+ * @category Properties - Player
46022
+ * @since 1.6.2
46023
+ */
46024
+ get endTick() {
46025
+ return this._player.currentPosition.endTick;
46026
+ }
46027
+ /**
46028
+ * The total length of the song in milliseconds.
46029
+ * @category Properties - Player
46030
+ * @since 1.6.2
46031
+ */
46032
+ get endTime() {
46033
+ return this._player.currentPosition.endTime;
46034
+ }
45772
46035
  /**
45773
46036
  * The range of the song that should be played.
45774
46037
  * @remarks
@@ -48093,27 +48356,33 @@ class AlphaSynthWebWorkerApi {
48093
48356
  value: value
48094
48357
  });
48095
48358
  }
48359
+ get loadedMidiInfo() {
48360
+ return this.loadedMidiInfo;
48361
+ }
48362
+ get currentPosition() {
48363
+ return this._currentPosition;
48364
+ }
48096
48365
  get tickPosition() {
48097
- return this._tickPosition;
48366
+ return this._currentPosition.currentTick;
48098
48367
  }
48099
48368
  set tickPosition(value) {
48100
48369
  if (value < 0) {
48101
48370
  value = 0;
48102
48371
  }
48103
- this._tickPosition = value;
48372
+ this._currentPosition = new PositionChangedEventArgs(this._currentPosition.currentTime, this._currentPosition.endTime, value, this._currentPosition.endTick, true, this._currentPosition.originalTempo, this._currentPosition.modifiedTempo);
48104
48373
  this._synth.postMessage({
48105
48374
  cmd: 'alphaSynth.setTickPosition',
48106
48375
  value: value
48107
48376
  });
48108
48377
  }
48109
48378
  get timePosition() {
48110
- return this._timePosition;
48379
+ return this._currentPosition.currentTime;
48111
48380
  }
48112
48381
  set timePosition(value) {
48113
48382
  if (value < 0) {
48114
48383
  value = 0;
48115
48384
  }
48116
- this._timePosition = value;
48385
+ this._currentPosition = new PositionChangedEventArgs(value, this._currentPosition.endTime, this._currentPosition.currentTick, this._currentPosition.endTick, true, this._currentPosition.originalTempo, this._currentPosition.modifiedTempo);
48117
48386
  this._synth.postMessage({
48118
48387
  cmd: 'alphaSynth.setTimePosition',
48119
48388
  value: value
@@ -48156,11 +48425,10 @@ class AlphaSynthWebWorkerApi {
48156
48425
  this._metronomeVolume = 0;
48157
48426
  this._countInVolume = 0;
48158
48427
  this._playbackSpeed = 0;
48159
- this._tickPosition = 0;
48160
- this._timePosition = 0;
48161
48428
  this._isLooping = false;
48162
48429
  this._playbackRange = null;
48163
48430
  this._midiEventsPlayedFilter = [];
48431
+ this._currentPosition = new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
48164
48432
  this.ready = new EventEmitter();
48165
48433
  this.readyForPlayback = new EventEmitter();
48166
48434
  this.finished = new EventEmitter();
@@ -48179,8 +48447,6 @@ class AlphaSynthWebWorkerApi {
48179
48447
  this._masterVolume = 0.0;
48180
48448
  this._metronomeVolume = 0.0;
48181
48449
  this._playbackSpeed = 0.0;
48182
- this._tickPosition = 0;
48183
- this._timePosition = 0.0;
48184
48450
  this._isLooping = false;
48185
48451
  this._playbackRange = null;
48186
48452
  this._output = player;
@@ -48315,9 +48581,8 @@ class AlphaSynthWebWorkerApi {
48315
48581
  this.checkReadyForPlayback();
48316
48582
  break;
48317
48583
  case 'alphaSynth.positionChanged':
48318
- this._timePosition = data.currentTime;
48319
- this._tickPosition = data.currentTick;
48320
- this.positionChanged.trigger(new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo));
48584
+ this._currentPosition = new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo);
48585
+ this.positionChanged.trigger(this._currentPosition);
48321
48586
  break;
48322
48587
  case 'alphaSynth.midiEventsPlayed':
48323
48588
  this.midiEventsPlayed.trigger(new MidiEventsPlayedEventArgs(data.events.map(JsonConverter.jsObjectToMidiEvent)));
@@ -48341,7 +48606,8 @@ class AlphaSynthWebWorkerApi {
48341
48606
  break;
48342
48607
  case 'alphaSynth.midiLoaded':
48343
48608
  this.checkReadyForPlayback();
48344
- this.midiLoaded.trigger(new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo));
48609
+ this._loadedMidiInfo = new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo);
48610
+ this.midiLoaded.trigger(this._loadedMidiInfo);
48345
48611
  break;
48346
48612
  case 'alphaSynth.midiLoadFailed':
48347
48613
  this.checkReadyForPlayback();
@@ -59053,7 +59319,7 @@ class NumberedBeatPreNotesGlyph extends BeatGlyphBase {
59053
59319
  // - when the standard notation naturalizes the accidental from the key signature, the numbered notation has the reversed accidental
59054
59320
  const accidentalMode = note ? note.accidentalMode : NoteAccidentalMode.Default;
59055
59321
  const noteValue = AccidentalHelper.getNoteValue(note);
59056
- let accidentalToSet = AccidentalHelper.computeAccidental(this.renderer.bar.keySignature, accidentalMode, noteValue, note.hasQuarterToneOffset);
59322
+ let accidentalToSet = ModelUtils.computeAccidental(this.renderer.bar.keySignature, accidentalMode, noteValue, note.hasQuarterToneOffset);
59057
59323
  if (accidentalToSet === AccidentalType.Natural) {
59058
59324
  const ks = this.renderer.bar.keySignature;
59059
59325
  const ksi = ks + 7;
@@ -59212,7 +59478,7 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
59212
59478
  ? AccidentalHelper.FlatNoteSteps
59213
59479
  : AccidentalHelper.SharpNoteSteps;
59214
59480
  let steps = stepList[index] + 1;
59215
- const hasAccidental = AccidentalHelper.AccidentalNotes[index];
59481
+ const hasAccidental = ModelUtils.AccidentalNotes[index];
59216
59482
  if (hasAccidental &&
59217
59483
  !this.container.preNotes.isNaturalizeAccidental) {
59218
59484
  if (ksi < 7) {
@@ -66425,6 +66691,30 @@ class AlphaTexExporter extends ScoreExporter {
66425
66691
  if (stylesheet.useSystemSignSeparator) {
66426
66692
  writer.writeMeta('useSystemSignSeparator');
66427
66693
  }
66694
+ if (stylesheet.multiTrackMultiBarRest) {
66695
+ writer.writeMeta('multiBarRest');
66696
+ }
66697
+ if (stylesheet.singleTrackTrackNamePolicy !==
66698
+ AlphaTexExporter.DefaultScore.stylesheet.singleTrackTrackNamePolicy) {
66699
+ writer.writeMeta('singleTrackTrackNamePolicy', TrackNamePolicy[stylesheet.singleTrackTrackNamePolicy]);
66700
+ }
66701
+ if (stylesheet.multiTrackTrackNamePolicy !== AlphaTexExporter.DefaultScore.stylesheet.multiTrackTrackNamePolicy) {
66702
+ writer.writeMeta('multiTrackTrackNamePolicy', TrackNamePolicy[stylesheet.multiTrackTrackNamePolicy]);
66703
+ }
66704
+ if (stylesheet.firstSystemTrackNameMode !== AlphaTexExporter.DefaultScore.stylesheet.firstSystemTrackNameMode) {
66705
+ writer.writeMeta('firstSystemTrackNameMode', TrackNameMode[stylesheet.firstSystemTrackNameMode]);
66706
+ }
66707
+ if (stylesheet.otherSystemsTrackNameMode !== AlphaTexExporter.DefaultScore.stylesheet.otherSystemsTrackNameMode) {
66708
+ writer.writeMeta('otherSystemsTrackNameMode', TrackNameMode[stylesheet.otherSystemsTrackNameMode]);
66709
+ }
66710
+ if (stylesheet.firstSystemTrackNameOrientation !==
66711
+ AlphaTexExporter.DefaultScore.stylesheet.firstSystemTrackNameOrientation) {
66712
+ writer.writeMeta('firstSystemTrackNameOrientation', TrackNameOrientation[stylesheet.firstSystemTrackNameOrientation]);
66713
+ }
66714
+ if (stylesheet.otherSystemsTrackNameOrientation !==
66715
+ AlphaTexExporter.DefaultScore.stylesheet.otherSystemsTrackNameOrientation) {
66716
+ writer.writeMeta('otherSystemsTrackNameOrientation', TrackNameOrientation[stylesheet.otherSystemsTrackNameOrientation]);
66717
+ }
66428
66718
  }
66429
66719
  writeTrackTo(writer, track) {
66430
66720
  writer.write('\\track ');
@@ -66460,6 +66750,10 @@ class AlphaTexExporter extends ScoreExporter {
66460
66750
  track.score.stylesheet.perTrackMultiBarRest.has(track.index)) {
66461
66751
  writer.writeLine(` multibarrest`);
66462
66752
  }
66753
+ writer.writeLine(` instrument ${track.isPercussion ? 'percussion' : GeneralMidi.getName(track.playbackInfo.program)}`);
66754
+ if (track.playbackInfo.bank > 0) {
66755
+ writer.writeLine(` bank ${track.playbackInfo.bank}`);
66756
+ }
66463
66757
  writer.outdent();
66464
66758
  writer.writeLine('}');
66465
66759
  writer.indent();
@@ -66518,13 +66812,7 @@ class AlphaTexExporter extends ScoreExporter {
66518
66812
  writer.writeLine('|');
66519
66813
  }
66520
66814
  if (voiceIndex === 0) {
66521
- // Track meta on first bar
66522
66815
  let anyWritten = false;
66523
- if (bar.index === 0 && bar.staff.index === 0) {
66524
- const l = writer.tex.length;
66525
- this.writeTrackMetaTo(writer, bar.staff.track);
66526
- anyWritten = writer.tex.length > l;
66527
- }
66528
66816
  // Staff meta on first bar
66529
66817
  if (bar.index === 0) {
66530
66818
  const l = writer.tex.length;
@@ -66554,14 +66842,6 @@ class AlphaTexExporter extends ScoreExporter {
66554
66842
  }
66555
66843
  writer.outdent();
66556
66844
  }
66557
- writeTrackMetaTo(writer, track) {
66558
- writer.writeSingleLineComment(`Track ${track.index + 1} Metadata`);
66559
- this.writePlaybackInfoTo(writer, track);
66560
- }
66561
- writePlaybackInfoTo(writer, track) {
66562
- const isPercussion = track.staves.some(s => s.isPercussion);
66563
- writer.writeMeta('instrument', isPercussion ? 'percussion' : GeneralMidi.getName(track.playbackInfo.program));
66564
- }
66565
66845
  writeStaffMetaTo(writer, staff) {
66566
66846
  writer.writeSingleLineComment(`Staff ${staff.index + 1} Metadata`);
66567
66847
  if (staff.capo !== 0) {
@@ -66594,6 +66874,7 @@ class AlphaTexExporter extends ScoreExporter {
66594
66874
  if (staff.displayTranspositionPitch !== defaultTransposition) {
66595
66875
  writer.writeMeta('displaytranspose', `${-staff.displayTranspositionPitch}`);
66596
66876
  }
66877
+ writer.writeMeta('accidentals', 'auto');
66597
66878
  if (staff.chords != null) {
66598
66879
  for (const [_, chord] of staff.chords) {
66599
66880
  this.writeChordTo(writer, chord);
@@ -66961,6 +67242,18 @@ class AlphaTexExporter extends ScoreExporter {
66961
67242
  writer.writeGroupItem('txt ');
66962
67243
  writer.writeString(beat.text);
66963
67244
  }
67245
+ if (beat.lyrics != null && beat.lyrics.length > 0) {
67246
+ if (beat.lyrics.length > 1) {
67247
+ for (let i = 0; i < beat.lyrics.length; i++) {
67248
+ writer.writeGroupItem(`lyrics ${i} `);
67249
+ writer.writeString(beat.lyrics[i]);
67250
+ }
67251
+ }
67252
+ else {
67253
+ writer.writeGroupItem('lyrics ');
67254
+ writer.writeString(beat.lyrics[0]);
67255
+ }
67256
+ }
66964
67257
  switch (beat.graceType) {
66965
67258
  case GraceType.OnBeat:
66966
67259
  writer.writeGroupItem('gr ob');
@@ -67595,7 +67888,7 @@ class GpifWriter {
67595
67888
  this.writePitch(properties, 'ConcertPitch', 'C', '-1', '');
67596
67889
  }
67597
67890
  else {
67598
- this.writePitchForValue(properties, 'TransposedPitch', note.displayValueWithoutBend, note.accidentalMode);
67891
+ this.writePitchForValue(properties, 'TransposedPitch', note.displayValueWithoutBend, note.accidentalMode, note.beat.voice.bar.keySignature);
67599
67892
  }
67600
67893
  }
67601
67894
  writeConcertPitch(properties, note) {
@@ -67603,10 +67896,10 @@ class GpifWriter {
67603
67896
  this.writePitch(properties, 'ConcertPitch', 'C', '-1', '');
67604
67897
  }
67605
67898
  else {
67606
- this.writePitchForValue(properties, 'ConcertPitch', note.realValueWithoutHarmonic, note.accidentalMode);
67899
+ this.writePitchForValue(properties, 'ConcertPitch', note.realValueWithoutHarmonic, note.accidentalMode, note.beat.voice.bar.keySignature);
67607
67900
  }
67608
67901
  }
67609
- writePitchForValue(properties, propertyName, value, accidentalMode) {
67902
+ writePitchForValue(properties, propertyName, value, accidentalMode, keySignature) {
67610
67903
  let index = 0;
67611
67904
  let octave = 0;
67612
67905
  let step = '';
@@ -67614,8 +67907,25 @@ class GpifWriter {
67614
67907
  const updateParts = () => {
67615
67908
  index = value % 12;
67616
67909
  octave = (value / 12) | 0;
67617
- step = Tuning.defaultSteps[index];
67618
- accidental = Tuning.defaultAccidentals[index];
67910
+ step = GpifWriter.defaultSteps[index];
67911
+ switch (ModelUtils.computeAccidental(keySignature, NoteAccidentalMode.Default, value, false)) {
67912
+ case AccidentalType.None:
67913
+ case AccidentalType.Natural:
67914
+ accidental = '';
67915
+ break;
67916
+ case AccidentalType.Sharp:
67917
+ accidental = '#';
67918
+ break;
67919
+ case AccidentalType.Flat:
67920
+ accidental = 'b';
67921
+ break;
67922
+ case AccidentalType.DoubleSharp:
67923
+ accidental = 'x';
67924
+ break;
67925
+ case AccidentalType.DoubleFlat:
67926
+ accidental = 'bb';
67927
+ break;
67928
+ }
67619
67929
  };
67620
67930
  updateParts();
67621
67931
  switch (accidentalMode) {
@@ -68090,10 +68400,12 @@ class GpifWriter {
68090
68400
  const value = syncPointAutomation.addElement('Value');
68091
68401
  value.addElement('BarIndex').innerText = mb.index.toString();
68092
68402
  value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue.barOccurence.toString();
68093
- value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint).syncBpm.toString();
68403
+ value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value
68404
+ .get(syncPoint)
68405
+ .syncBpm.toString();
68094
68406
  value.addElement('OriginalTempo').innerText = score.tempo.toString();
68095
- let frameOffset = (((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
68096
- GpifWriter.SampleRate);
68407
+ let frameOffset = ((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
68408
+ GpifWriter.SampleRate;
68097
68409
  frameOffset = Math.floor(frameOffset + 0.5);
68098
68410
  value.addElement('FrameOffset').innerText = frameOffset.toString();
68099
68411
  }
@@ -68152,15 +68464,16 @@ class GpifWriter {
68152
68464
  }
68153
68465
  return GpifIconIds.SteelGuitar;
68154
68466
  }
68155
- writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, barIndex, program, ratioPosition = 0) {
68467
+ writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, barIndex, program, bank, ratioPosition = 0) {
68156
68468
  const soundNode = soundsNode.addElement('Sound');
68157
68469
  soundNode.addElement('Name').setCData(name);
68158
68470
  soundNode.addElement('Label').setCData(name);
68159
68471
  soundNode.addElement('Path').setCData(path);
68160
68472
  soundNode.addElement('Role').setCData(role);
68161
68473
  const midi = soundNode.addElement('MIDI');
68162
- midi.addElement('LSB').innerText = '0';
68163
- midi.addElement('MSB').innerText = '0';
68474
+ const lsbMsb = GeneralMidi.bankToLsbMsb(bank);
68475
+ midi.addElement('LSB').innerText = lsbMsb[0].toString();
68476
+ midi.addElement('MSB').innerText = lsbMsb[1].toString();
68164
68477
  midi.addElement('Program').innerText = program.toString();
68165
68478
  const automationNode = automationsNode.addElement('Automation');
68166
68479
  automationNode.addElement('Type').innerText = 'Sound';
@@ -68178,21 +68491,26 @@ class GpifWriter {
68178
68491
  const trackSoundPath = `Midi/${track.playbackInfo.program}`;
68179
68492
  const trackSoundRole = 'Factory';
68180
68493
  let trackSoundWritten = false;
68494
+ let bank = track.playbackInfo.bank;
68181
68495
  for (const staff of track.staves) {
68182
68496
  for (const bar of staff.bars) {
68183
68497
  for (const voice of bar.voices) {
68184
68498
  for (const beat of voice.beats) {
68185
68499
  const soundAutomation = beat.getAutomation(AutomationType.Instrument);
68186
68500
  const isTrackSound = bar.index === 0 && beat.index === 0;
68501
+ const bankAutomation = beat.getAutomation(AutomationType.Bank);
68502
+ if (bankAutomation) {
68503
+ bank = bankAutomation.value;
68504
+ }
68187
68505
  if (soundAutomation) {
68188
68506
  const name = isTrackSound ? trackSoundName : `ProgramChange_${beat.id}`;
68189
68507
  const path = isTrackSound ? trackSoundPath : `Midi/${soundAutomation.value}`;
68190
68508
  const role = isTrackSound ? trackSoundRole : 'User';
68191
68509
  if (!isTrackSound && !trackSoundWritten) {
68192
- this.writeSoundAndAutomation(soundsNode, automationsNode, trackSoundName, trackSoundPath, trackSoundRole, track.staves[0].bars[0].index, track.playbackInfo.program);
68510
+ this.writeSoundAndAutomation(soundsNode, automationsNode, trackSoundName, trackSoundPath, trackSoundRole, track.staves[0].bars[0].index, track.playbackInfo.program, track.playbackInfo.bank);
68193
68511
  trackSoundWritten = true;
68194
68512
  }
68195
- this.writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, bar.index, soundAutomation.value, soundAutomation.ratioPosition);
68513
+ this.writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, bar.index, soundAutomation.value, bank, soundAutomation.ratioPosition);
68196
68514
  if (isTrackSound) {
68197
68515
  trackSoundWritten = true;
68198
68516
  }
@@ -68575,8 +68893,13 @@ class GpifWriter {
68575
68893
  writeMasterBarNode(parent, masterBar) {
68576
68894
  const masterBarNode = parent.addElement('MasterBar');
68577
68895
  const key = masterBarNode.addElement('Key');
68578
- key.addElement('AccidentalCount').innerText = masterBar.keySignature.toString();
68579
- key.addElement('Mode').innerText = KeySignatureType[masterBar.keySignatureType];
68896
+ let keySignature = masterBar.score.tracks[0].staves[0].bars[masterBar.index].keySignature;
68897
+ const keySignatureType = masterBar.score.tracks[0].staves[0].bars[masterBar.index].keySignatureType;
68898
+ // reverse transpose
68899
+ const transposeIndex = ModelUtils.flooredDivision(masterBar.score.tracks[0].staves[0].displayTranspositionPitch, 12);
68900
+ keySignature = ModelUtils.transposeKey(keySignature, -transposeIndex);
68901
+ key.addElement('AccidentalCount').innerText = keySignature.toString();
68902
+ key.addElement('Mode').innerText = KeySignatureType[keySignatureType];
68580
68903
  key.addElement('Sharps').innerText = 'Sharps';
68581
68904
  masterBarNode.addElement('Time').innerText =
68582
68905
  `${masterBar.timeSignatureNumerator}/${masterBar.timeSignatureDenominator}`;
@@ -68886,6 +69209,7 @@ GpifWriter.MidiProgramInfoLookup = new Map([
68886
69209
  [127, new GpifMidiProgramInfo(GpifIconIds.Fx, 'Timpani')]
68887
69210
  ]);
68888
69211
  GpifWriter.DrumKitProgramInfo = new GpifMidiProgramInfo(GpifIconIds.PercussionKit, 'Drums', 'drumKit');
69212
+ GpifWriter.defaultSteps = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'];
68889
69213
 
68890
69214
  /**
68891
69215
  * CRC-32 with reversed data and unreversed output
@@ -70764,6 +71088,7 @@ const _barrel$2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty(
70764
71088
  SustainPedalMarker,
70765
71089
  get SustainPedalMarkerType () { return SustainPedalMarkerType; },
70766
71090
  SyncPointData,
71091
+ get TechniqueSymbolPlacement () { return TechniqueSymbolPlacement; },
70767
71092
  Track,
70768
71093
  get TrackNameMode () { return TrackNameMode; },
70769
71094
  get TrackNameOrientation () { return TrackNameOrientation; },