@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.
package/dist/alphaTab.js CHANGED
@@ -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
  *
@@ -203,9 +203,9 @@
203
203
  print(`build date: ${VersionInfo.date}`);
204
204
  }
205
205
  }
206
- VersionInfo.version = '1.7.0-alpha.1515';
207
- VersionInfo.date = '2025-08-17T02:26:19.392Z';
208
- VersionInfo.commit = '23aaf549a408c97811ff01054a773c3fa2344bf0';
206
+ VersionInfo.version = '1.7.0-alpha.1522';
207
+ VersionInfo.date = '2025-08-24T02:21:16.522Z';
208
+ VersionInfo.commit = 'c80b4a66eae6b73cb2d7dacaaf74c8f9cce16318';
209
209
 
210
210
  /**
211
211
  * This public class provides names for all general midi instruments.
@@ -232,6 +232,14 @@
232
232
  static isGuitar(program) {
233
233
  return (program >= 24 && program <= 39) || program === 105 || program === 43;
234
234
  }
235
+ static isBass(program) {
236
+ return program >= 32 && program <= 39;
237
+ }
238
+ static bankToLsbMsb(bank) {
239
+ const lsb = bank & 0x7f;
240
+ const msb = (bank >> 7) & 0x7f;
241
+ return [lsb, msb];
242
+ }
235
243
  }
236
244
  GeneralMidi._values = new Map([
237
245
  ['acousticgrandpiano', 0],
@@ -1426,6 +1434,10 @@
1426
1434
  * A sync point for synchronizing the internal time axis with an external audio track.
1427
1435
  */
1428
1436
  AutomationType[AutomationType["SyncPoint"] = 4] = "SyncPoint";
1437
+ /**
1438
+ * Midi Bank change.
1439
+ */
1440
+ AutomationType[AutomationType["Bank"] = 4] = "Bank";
1429
1441
  })(AutomationType || (AutomationType = {}));
1430
1442
  /**
1431
1443
  * Represents the data of a sync point for synchronizing the internal time axis with
@@ -3393,6 +3405,49 @@
3393
3405
  MusicFontSymbol[MusicFontSymbol["FingeringCLower"] = 60700] = "FingeringCLower";
3394
3406
  })(MusicFontSymbol || (MusicFontSymbol = {}));
3395
3407
 
3408
+ /**
3409
+ * Defines all possible accidentals for notes.
3410
+ */
3411
+ var AccidentalType;
3412
+ (function (AccidentalType) {
3413
+ /**
3414
+ * No accidental
3415
+ */
3416
+ AccidentalType[AccidentalType["None"] = 0] = "None";
3417
+ /**
3418
+ * Naturalize
3419
+ */
3420
+ AccidentalType[AccidentalType["Natural"] = 1] = "Natural";
3421
+ /**
3422
+ * Sharp
3423
+ */
3424
+ AccidentalType[AccidentalType["Sharp"] = 2] = "Sharp";
3425
+ /**
3426
+ * Flat
3427
+ */
3428
+ AccidentalType[AccidentalType["Flat"] = 3] = "Flat";
3429
+ /**
3430
+ * Natural for smear bends
3431
+ */
3432
+ AccidentalType[AccidentalType["NaturalQuarterNoteUp"] = 4] = "NaturalQuarterNoteUp";
3433
+ /**
3434
+ * Sharp for smear bends
3435
+ */
3436
+ AccidentalType[AccidentalType["SharpQuarterNoteUp"] = 5] = "SharpQuarterNoteUp";
3437
+ /**
3438
+ * Flat for smear bends
3439
+ */
3440
+ AccidentalType[AccidentalType["FlatQuarterNoteUp"] = 6] = "FlatQuarterNoteUp";
3441
+ /**
3442
+ * Double Sharp, indicated by an 'x'
3443
+ */
3444
+ AccidentalType[AccidentalType["DoubleSharp"] = 7] = "DoubleSharp";
3445
+ /**
3446
+ * Double Flat, indicated by 'bb'
3447
+ */
3448
+ AccidentalType[AccidentalType["DoubleFlat"] = 8] = "DoubleFlat";
3449
+ })(AccidentalType || (AccidentalType = {}));
3450
+
3396
3451
  class TuningParseResult {
3397
3452
  constructor() {
3398
3453
  this.note = null;
@@ -3898,6 +3953,150 @@
3898
3953
  }
3899
3954
  return ModelUtils.allMusicFontSymbols;
3900
3955
  }
3956
+ /**
3957
+ * @internal
3958
+ */
3959
+ static flooredDivision(a, b) {
3960
+ return a - b * Math.floor(a / b);
3961
+ }
3962
+ // NOTE: haven't figured out yet what exact formula is applied when transposing key signatures
3963
+ // this table is simply created by checking the Guitar Pro behavior,
3964
+ // The table is organized as [<transpose>][<key signature>] to match the table above
3965
+ // it's also easier to read as we list every key signature per row, transposed by the same value
3966
+ // this gives typically just a shifted list according to the transpose (with some special treatments)
3967
+ /**
3968
+ * Converts the key transpose table to actual key signatures.
3969
+ * @param texts An array where every item indicates the number of accidentals and which accidental
3970
+ * placed for the key signature.
3971
+ *
3972
+ * e.g. 3# is 3-sharps -> KeySignature.A
3973
+ */
3974
+ static translateKeyTransposeTable(texts) {
3975
+ const keySignatures = [];
3976
+ for (const transpose of texts) {
3977
+ const transposeValues = [];
3978
+ keySignatures.push(transposeValues);
3979
+ for (const keySignatureText of transpose) {
3980
+ const keySignature =
3981
+ // digit
3982
+ (Number.parseInt(keySignatureText.charAt(0)) *
3983
+ // b -> negative, # positive
3984
+ (keySignatureText.charAt(1) === 'b' ? -1 : 1));
3985
+ transposeValues.push(keySignature);
3986
+ }
3987
+ }
3988
+ return keySignatures;
3989
+ }
3990
+ /**
3991
+ * Transposes the given key signature.
3992
+ * @internal
3993
+ * @param keySignature The key signature to transpose
3994
+ * @param transpose The number of semitones to transpose (+/- 0-11)
3995
+ * @returns
3996
+ */
3997
+ static transposeKey(keySignature, transpose) {
3998
+ if (transpose === 0) {
3999
+ return keySignature;
4000
+ }
4001
+ if (transpose < 0) {
4002
+ const lookup = ModelUtils.keyTransposeTable[-transpose];
4003
+ const keySignatureIndex = lookup.indexOf(keySignature);
4004
+ if (keySignatureIndex === -1) {
4005
+ return keySignature;
4006
+ }
4007
+ return (keySignatureIndex - 7);
4008
+ }
4009
+ else {
4010
+ return ModelUtils.keyTransposeTable[transpose][keySignature + 7];
4011
+ }
4012
+ }
4013
+ /**
4014
+ * @internal
4015
+ */
4016
+ static computeAccidental(keySignature, accidentalMode, noteValue, quarterBend, currentAccidental = null) {
4017
+ const ks = keySignature;
4018
+ const ksi = ks + 7;
4019
+ const index = noteValue % 12;
4020
+ const accidentalForKeySignature = ksi < 7 ? AccidentalType.Flat : AccidentalType.Sharp;
4021
+ const hasKeySignatureAccidentalSetForNote = ModelUtils.KeySignatureLookup[ksi][index];
4022
+ const hasNoteAccidentalWithinOctave = ModelUtils.AccidentalNotes[index];
4023
+ // the general logic is like this:
4024
+ // - we check if the key signature has an accidental defined
4025
+ // - we calculate which accidental a note needs according to its index in the octave
4026
+ // - if the accidental is already placed at this line, nothing needs to be done, otherwise we place it
4027
+ // - if there should not be an accidental, but there is one in the key signature, we clear it.
4028
+ // the exceptions are:
4029
+ // - for quarter bends we just place the corresponding accidental
4030
+ // - the accidental mode can enforce the accidentals for the note
4031
+ let accidentalToSet = AccidentalType.None;
4032
+ if (quarterBend) {
4033
+ accidentalToSet = hasNoteAccidentalWithinOctave ? accidentalForKeySignature : AccidentalType.Natural;
4034
+ switch (accidentalToSet) {
4035
+ case AccidentalType.Natural:
4036
+ accidentalToSet = AccidentalType.NaturalQuarterNoteUp;
4037
+ break;
4038
+ case AccidentalType.Sharp:
4039
+ accidentalToSet = AccidentalType.SharpQuarterNoteUp;
4040
+ break;
4041
+ case AccidentalType.Flat:
4042
+ accidentalToSet = AccidentalType.FlatQuarterNoteUp;
4043
+ break;
4044
+ }
4045
+ }
4046
+ else {
4047
+ // define which accidental should be shown ignoring what might be set on the KS already
4048
+ switch (accidentalMode) {
4049
+ case NoteAccidentalMode.ForceSharp:
4050
+ accidentalToSet = AccidentalType.Sharp;
4051
+ break;
4052
+ case NoteAccidentalMode.ForceDoubleSharp:
4053
+ accidentalToSet = AccidentalType.DoubleSharp;
4054
+ break;
4055
+ case NoteAccidentalMode.ForceFlat:
4056
+ accidentalToSet = AccidentalType.Flat;
4057
+ break;
4058
+ case NoteAccidentalMode.ForceDoubleFlat:
4059
+ accidentalToSet = AccidentalType.DoubleFlat;
4060
+ break;
4061
+ default:
4062
+ // if note has an accidental in the octave, we place a symbol
4063
+ // according to the Key Signature
4064
+ if (hasNoteAccidentalWithinOctave) {
4065
+ accidentalToSet = accidentalForKeySignature;
4066
+ }
4067
+ else if (hasKeySignatureAccidentalSetForNote) {
4068
+ // note does not get an accidental, but KS defines one -> Naturalize
4069
+ accidentalToSet = AccidentalType.Natural;
4070
+ }
4071
+ break;
4072
+ }
4073
+ // do we need an accidental on the note?
4074
+ if (accidentalToSet !== AccidentalType.None) {
4075
+ // if there is no accidental on the line, and the key signature has it set already, we clear it on the note
4076
+ if (currentAccidental != null) {
4077
+ if (currentAccidental === accidentalToSet) {
4078
+ accidentalToSet = AccidentalType.None;
4079
+ }
4080
+ }
4081
+ else if (hasKeySignatureAccidentalSetForNote && accidentalToSet === accidentalForKeySignature) {
4082
+ accidentalToSet = AccidentalType.None;
4083
+ }
4084
+ }
4085
+ else {
4086
+ // if we don't want an accidental, but there is already one applied, we place a naturalize accidental
4087
+ // and clear the registration
4088
+ if (currentAccidental !== null) {
4089
+ if (currentAccidental === AccidentalType.Natural) {
4090
+ accidentalToSet = AccidentalType.None;
4091
+ }
4092
+ else {
4093
+ accidentalToSet = AccidentalType.Natural;
4094
+ }
4095
+ }
4096
+ }
4097
+ }
4098
+ return accidentalToSet;
4099
+ }
3901
4100
  }
3902
4101
  ModelUtils.TuningLetters = new Set([
3903
4102
  0x43 /* C */, 0x44 /* D */, 0x45 /* E */, 0x46 /* F */, 0x47 /* G */, 0x41 /* A */, 0x42 /* B */, 0x63 /* c */,
@@ -3950,6 +4149,66 @@
3950
4149
  // Contrabass
3951
4150
  [43, -12]
3952
4151
  ]);
4152
+ /**
4153
+ * @internal
4154
+ */
4155
+ ModelUtils.keyTransposeTable = ModelUtils.translateKeyTransposeTable([
4156
+ /* Cb Gb Db Ab Eb Bb F C G D A E B F C# */
4157
+ /* C 0 */ ['7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#'],
4158
+ /* Db 1 */ ['2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b', '1b', '0#'],
4159
+ /* D 2 */ ['3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#'],
4160
+ /* Eb 3 */ ['4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b'],
4161
+ /* E 4 */ ['1#', '2#', '3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#'],
4162
+ /* F 5 */ ['6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b'],
4163
+ /* Gb 6 */ ['1b', '0#', '1#', '2#', '3#', '4#', '7b', '6#', '7#', '4b', '3b', '2b', '1b', '0#', '1#'],
4164
+ /* G 7 */ ['4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#'],
4165
+ /* Ab 8 */ ['3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b', '1b'],
4166
+ /* A 9 */ ['2#', '3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#'],
4167
+ /* Bb 10 */ ['5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b'],
4168
+ /* B 11 */ ['0#', '1#', '2#', '3#', '4#', '7b', '6b', '6#', '4b', '3b', '2b', '1b', '0#', '1#', '2#']
4169
+ ]);
4170
+ /**
4171
+ * a lookup list containing an info whether the notes within an octave
4172
+ * need an accidental rendered. the accidental symbol is determined based on the type of key signature.
4173
+ */
4174
+ ModelUtils.KeySignatureLookup = [
4175
+ // Flats (where the value is true, a flat accidental is required for the notes)
4176
+ [true, true, true, true, true, true, true, true, true, true, true, true],
4177
+ [true, true, true, true, true, false, true, true, true, true, true, true],
4178
+ [false, true, true, true, true, false, true, true, true, true, true, true],
4179
+ [false, true, true, true, true, false, false, false, true, true, true, true],
4180
+ [false, false, false, true, true, false, false, false, true, true, true, true],
4181
+ [false, false, false, true, true, false, false, false, false, false, true, true],
4182
+ [false, false, false, false, false, false, false, false, false, false, true, true],
4183
+ // natural
4184
+ [false, false, false, false, false, false, false, false, false, false, false, false],
4185
+ // sharps (where the value is true, a flat accidental is required for the notes)
4186
+ [false, false, false, false, false, true, true, false, false, false, false, false],
4187
+ [true, true, false, false, false, true, true, false, false, false, false, false],
4188
+ [true, true, false, false, false, true, true, true, true, false, false, false],
4189
+ [true, true, true, true, false, true, true, true, true, false, false, false],
4190
+ [true, true, true, true, false, true, true, true, true, true, true, false],
4191
+ [true, true, true, true, true, true, true, true, true, true, true, false],
4192
+ [true, true, true, true, true, true, true, true, true, true, true, true]
4193
+ ];
4194
+ /**
4195
+ * Contains the list of notes within an octave have accidentals set.
4196
+ * @internal
4197
+ */
4198
+ ModelUtils.AccidentalNotes = [
4199
+ false,
4200
+ true,
4201
+ false,
4202
+ true,
4203
+ false,
4204
+ false,
4205
+ true,
4206
+ false,
4207
+ true,
4208
+ false,
4209
+ true,
4210
+ false
4211
+ ];
3953
4212
 
3954
4213
  /**
3955
4214
  * Lists all types of pick strokes.
@@ -7920,6 +8179,10 @@
7920
8179
  * Gets or sets the midi program to use.
7921
8180
  */
7922
8181
  this.program = 0;
8182
+ /**
8183
+ * The midi bank to use.
8184
+ */
8185
+ this.bank = 0;
7923
8186
  /**
7924
8187
  * Gets or sets the primary channel for all normal midi events.
7925
8188
  */
@@ -7952,7 +8215,7 @@
7952
8215
  static getTextPartsForTuning(tuning, octaveShift = -1) {
7953
8216
  const octave = (tuning / 12) | 0;
7954
8217
  const note = tuning % 12;
7955
- const notes = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
8218
+ const notes = Tuning.noteNames;
7956
8219
  return [notes[note], (octave + octaveShift).toString()];
7957
8220
  }
7958
8221
  /**
@@ -8091,8 +8354,7 @@
8091
8354
  Tuning._fiveStrings = [];
8092
8355
  Tuning._fourStrings = [];
8093
8356
  Tuning._defaultTunings = new Map();
8094
- Tuning.defaultAccidentals = ['', '#', '', '#', '', '', '#', '', '#', '', '#', ''];
8095
- Tuning.defaultSteps = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'];
8357
+ Tuning.noteNames = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
8096
8358
  Tuning.initialize();
8097
8359
 
8098
8360
  /**
@@ -8315,6 +8577,12 @@
8315
8577
  */
8316
8578
  this.percussionArticulations = [];
8317
8579
  }
8580
+ /**
8581
+ * Gets whether this track is a percussion track.
8582
+ */
8583
+ get isPercussion() {
8584
+ return this.staves.some(s => s.isPercussion);
8585
+ }
8318
8586
  /**
8319
8587
  * Adds a new line break.
8320
8588
  * @param index The index of the bar before which a line break should happen.
@@ -9018,8 +9286,7 @@
9018
9286
  }
9019
9287
  // unicode handling
9020
9288
  // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types-string-type
9021
- if (IOHelper.isLeadingSurrogate(previousCodepoint) &&
9022
- IOHelper.isTrailingSurrogate(codepoint)) {
9289
+ if (IOHelper.isLeadingSurrogate(previousCodepoint) && IOHelper.isTrailingSurrogate(codepoint)) {
9023
9290
  codepoint = (previousCodepoint - 0xd800) * 0x400 + (codepoint - 0xdc00) + 0x10000;
9024
9291
  s += String.fromCodePoint(codepoint);
9025
9292
  }
@@ -9454,10 +9721,10 @@
9454
9721
  case 'bass':
9455
9722
  return Clef.F4;
9456
9723
  case 'c3':
9457
- case 'tenor':
9724
+ case 'alto':
9458
9725
  return Clef.C3;
9459
9726
  case 'c4':
9460
- case 'alto':
9727
+ case 'tenor':
9461
9728
  return Clef.C4;
9462
9729
  case 'n':
9463
9730
  case 'neutral':
@@ -9474,6 +9741,8 @@
9474
9741
  */
9475
9742
  parseClefFromInt(i) {
9476
9743
  switch (i) {
9744
+ case 0:
9745
+ return Clef.Neutral;
9477
9746
  case 43:
9478
9747
  return Clef.G2;
9479
9748
  case 65:
@@ -9978,33 +10247,15 @@
9978
10247
  }
9979
10248
  return StaffMetaResult.KnownStaffMeta;
9980
10249
  case 'instrument':
9981
- this.sy = this.newSy();
9982
10250
  this._staffTuningApplied = false;
9983
- if (this.sy === AlphaTexSymbols.Number) {
9984
- const instrument = this.syData;
9985
- if (instrument >= 0 && instrument <= 127) {
9986
- this._currentTrack.playbackInfo.program = this.syData;
9987
- }
9988
- else {
9989
- this.error('instrument', AlphaTexSymbols.Number, false);
9990
- }
9991
- }
9992
- else if (this.sy === AlphaTexSymbols.String) {
9993
- const instrumentName = this.syData.toLowerCase();
9994
- if (instrumentName === 'percussion') {
9995
- for (const staff of this._currentTrack.staves) {
9996
- this.applyPercussionStaff(staff);
9997
- }
9998
- this._currentTrack.playbackInfo.primaryChannel = SynthConstants.PercussionChannel;
9999
- this._currentTrack.playbackInfo.secondaryChannel = SynthConstants.PercussionChannel;
10000
- }
10001
- else {
10002
- this._currentTrack.playbackInfo.program = GeneralMidi.getValue(instrumentName);
10003
- }
10004
- }
10005
- else {
10006
- this.error('instrument', AlphaTexSymbols.Number, true);
10251
+ this.readTrackInstrument();
10252
+ return StaffMetaResult.KnownStaffMeta;
10253
+ case 'bank':
10254
+ this.sy = this.newSy();
10255
+ if (this.sy !== AlphaTexSymbols.Number) {
10256
+ this.error('bank', AlphaTexSymbols.Number, true);
10007
10257
  }
10258
+ this._currentTrack.playbackInfo.bank = this.syData;
10008
10259
  this.sy = this.newSy();
10009
10260
  return StaffMetaResult.KnownStaffMeta;
10010
10261
  case 'lyrics':
@@ -10115,6 +10366,35 @@
10115
10366
  return StaffMetaResult.UnknownStaffMeta;
10116
10367
  }
10117
10368
  }
10369
+ readTrackInstrument() {
10370
+ this.sy = this.newSy();
10371
+ if (this.sy === AlphaTexSymbols.Number) {
10372
+ const instrument = this.syData;
10373
+ if (instrument >= 0 && instrument <= 127) {
10374
+ this._currentTrack.playbackInfo.program = this.syData;
10375
+ }
10376
+ else {
10377
+ this.error('instrument', AlphaTexSymbols.Number, false);
10378
+ }
10379
+ }
10380
+ else if (this.sy === AlphaTexSymbols.String) {
10381
+ const instrumentName = this.syData.toLowerCase();
10382
+ if (instrumentName === 'percussion') {
10383
+ for (const staff of this._currentTrack.staves) {
10384
+ this.applyPercussionStaff(staff);
10385
+ }
10386
+ this._currentTrack.playbackInfo.primaryChannel = SynthConstants.PercussionChannel;
10387
+ this._currentTrack.playbackInfo.secondaryChannel = SynthConstants.PercussionChannel;
10388
+ }
10389
+ else {
10390
+ this._currentTrack.playbackInfo.program = GeneralMidi.getValue(instrumentName);
10391
+ }
10392
+ }
10393
+ else {
10394
+ this.error('instrument', AlphaTexSymbols.Number, true);
10395
+ }
10396
+ this.sy = this.newSy();
10397
+ }
10118
10398
  handleAccidentalMode() {
10119
10399
  this.sy = this.newSy();
10120
10400
  if (this.sy !== AlphaTexSymbols.String) {
@@ -10387,6 +10667,17 @@
10387
10667
  }
10388
10668
  this._score.stylesheet.perTrackMultiBarRest.add(this._currentTrack.index);
10389
10669
  break;
10670
+ case 'instrument':
10671
+ this.readTrackInstrument();
10672
+ break;
10673
+ case 'bank':
10674
+ this.sy = this.newSy();
10675
+ if (this.sy !== AlphaTexSymbols.Number) {
10676
+ this.error('bank', AlphaTexSymbols.Number, true);
10677
+ }
10678
+ this._currentTrack.playbackInfo.bank = this.syData;
10679
+ this.sy = this.newSy();
10680
+ break;
10390
10681
  default:
10391
10682
  this.error('track-properties', AlphaTexSymbols.String, false);
10392
10683
  break;
@@ -10731,6 +11022,25 @@
10731
11022
  }
10732
11023
  beat.text = this.syData;
10733
11024
  }
11025
+ else if (syData === 'lyrics') {
11026
+ this.sy = this.newSy();
11027
+ let lyricsLine = 0;
11028
+ if (this.sy === AlphaTexSymbols.Number) {
11029
+ lyricsLine = this.syData;
11030
+ this.sy = this.newSy();
11031
+ }
11032
+ if (this.sy !== AlphaTexSymbols.String) {
11033
+ this.error('lyrics', AlphaTexSymbols.String, true);
11034
+ return false;
11035
+ }
11036
+ if (!beat.lyrics) {
11037
+ beat.lyrics = [];
11038
+ }
11039
+ while (beat.lyrics.length <= lyricsLine) {
11040
+ beat.lyrics.push('');
11041
+ }
11042
+ beat.lyrics[lyricsLine] = this.syData;
11043
+ }
10734
11044
  else if (syData === 'dd') {
10735
11045
  beat.dots = 2;
10736
11046
  }
@@ -11168,6 +11478,17 @@
11168
11478
  automation.value = program;
11169
11479
  beat.automations.push(automation);
11170
11480
  }
11481
+ else if (syData === 'bank') {
11482
+ this.sy = this.newSy();
11483
+ if (this.sy !== AlphaTexSymbols.Number) {
11484
+ this.error('bank-change', AlphaTexSymbols.Number, true);
11485
+ }
11486
+ const automation = new Automation();
11487
+ automation.isLinear = false;
11488
+ automation.type = AutomationType.Bank;
11489
+ automation.value = this.syData;
11490
+ beat.automations.push(automation);
11491
+ }
11171
11492
  else if (syData === 'fermata') {
11172
11493
  this.sy = this.newSy();
11173
11494
  if (this.sy !== AlphaTexSymbols.String) {
@@ -11178,7 +11499,7 @@
11178
11499
  this.sy = this.newSy(true);
11179
11500
  if (this.sy === AlphaTexSymbols.Number) {
11180
11501
  fermata.length = this.syData;
11181
- this.sy = this.newSy(true);
11502
+ this.sy = this.newSy();
11182
11503
  }
11183
11504
  beat.fermata = fermata;
11184
11505
  return true;
@@ -11211,13 +11532,9 @@
11211
11532
  beat.beamingMode = BeatBeamingMode.ForceSplitOnSecondaryToNext;
11212
11533
  break;
11213
11534
  }
11214
- this.sy = this.newSy();
11215
- return true;
11216
11535
  }
11217
11536
  else if (syData === 'timer') {
11218
11537
  beat.showTimer = true;
11219
- this.sy = this.newSy();
11220
- return true;
11221
11538
  }
11222
11539
  else {
11223
11540
  // string didn't match any beat effect syntax
@@ -14777,6 +15094,7 @@
14777
15094
  this._trackCount = 0;
14778
15095
  this._playbackInfos = [];
14779
15096
  this._doubleBars = new Set();
15097
+ this._clefsPerTrack = new Map();
14780
15098
  this._keySignatures = new Map();
14781
15099
  this._beatTextChunksByTrack = new Map();
14782
15100
  this._directionLookup = new Map();
@@ -15161,26 +15479,74 @@
15161
15479
  mainStaff.capo = IOHelper.readInt32LE(this.data);
15162
15480
  newTrack.color = GpBinaryHelpers.gpReadColor(this.data, false);
15163
15481
  if (this._versionNumber >= 500) {
15164
- const staveFlags = this.data.readByte();
15165
- mainStaff.showTablature = (staveFlags & 0x01) !== 0;
15166
- mainStaff.showStandardNotation = (staveFlags & 0x02) !== 0;
15167
- const showChordDiagramListOnTopOfScore = (staveFlags & 0x64) !== 0;
15482
+ const staffFlags = this.data.readByte();
15483
+ mainStaff.showTablature = (staffFlags & 0x01) !== 0;
15484
+ mainStaff.showStandardNotation = (staffFlags & 0x02) !== 0;
15485
+ const showChordDiagramListOnTopOfScore = (staffFlags & 0x64) !== 0;
15168
15486
  if (this._score.stylesheet.perTrackChordDiagramsOnTop === null) {
15169
15487
  this._score.stylesheet.perTrackChordDiagramsOnTop = new Map();
15170
15488
  }
15171
15489
  this._score.stylesheet.perTrackChordDiagramsOnTop.set(newTrack.index, showChordDiagramListOnTopOfScore);
15172
- // flags for
15490
+ // MIDI: Automatic
15491
+ // 0x01 -> always set (unknown)
15173
15492
  // 0x02 -> auto let ring
15174
15493
  // 0x04 -> auto brush
15175
15494
  this.data.readByte();
15176
- // unknown
15177
- this.data.skip(43);
15495
+ // RSE: Auto-Accentuation on the Beat
15496
+ // 0 - None
15497
+ // 1 - Very Soft
15498
+ // 2 - Soft
15499
+ // 3 - Medium
15500
+ // 4 - Strong
15501
+ // 5 - Very Strong
15502
+ this.data.readByte();
15503
+ newTrack.playbackInfo.bank = this.data.readByte();
15504
+ // RSE: Human Playing (1 byte, 0-100%)
15505
+ this.data.readByte();
15506
+ // `12` for all tunings which have bass clefs
15507
+ const clefMode = IOHelper.readInt32LE(this.data);
15508
+ if (clefMode === 12) {
15509
+ this._clefsPerTrack.set(index, Clef.F4);
15510
+ }
15511
+ else {
15512
+ this._clefsPerTrack.set(index, Clef.G2);
15513
+ }
15514
+ // Unknown, no UI setting seem to affect this
15515
+ IOHelper.readInt32LE(this.data);
15516
+ // Unknown, no UI setting seem to affect this
15517
+ // typically: 100
15518
+ IOHelper.readInt32LE(this.data);
15519
+ // Unknown, no UI setting seem to affect this
15520
+ // typically: 1 2 3 4 5 6 7 8 9 10
15521
+ this.data.skip(10);
15522
+ // Unknown, no UI setting seem to affect this
15523
+ // typically: 255
15524
+ this.data.readByte();
15525
+ // Unknown, no UI setting seem to affect this
15526
+ // typically: 3
15527
+ this.data.readByte();
15528
+ this.readRseBank();
15529
+ // this.data.skip(42);
15530
+ if (this._versionNumber >= 510) {
15531
+ // RSE: 3-band EQ
15532
+ // 1 byte Low
15533
+ // 1 byte Mid
15534
+ // 1 byte High
15535
+ // 1 byte PRE
15536
+ this.data.skip(4);
15537
+ // RSE: effect name
15538
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15539
+ // RSE: effect category
15540
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15541
+ }
15178
15542
  }
15179
- // unknown
15180
- if (this._versionNumber >= 510) {
15181
- this.data.skip(4);
15182
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15183
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15543
+ else {
15544
+ if (GeneralMidi.isBass(newTrack.playbackInfo.program)) {
15545
+ this._clefsPerTrack.set(index, Clef.F4);
15546
+ }
15547
+ else {
15548
+ this._clefsPerTrack.set(index, Clef.G2);
15549
+ }
15184
15550
  }
15185
15551
  }
15186
15552
  readBars() {
@@ -15196,6 +15562,9 @@
15196
15562
  if (mainStaff.isPercussion) {
15197
15563
  newBar.clef = Clef.Neutral;
15198
15564
  }
15565
+ else if (this._clefsPerTrack.has(track.index)) {
15566
+ newBar.clef = this._clefsPerTrack.get(track.index);
15567
+ }
15199
15568
  mainStaff.addBar(newBar);
15200
15569
  if (this._keySignatures.has(newBar.index)) {
15201
15570
  const newKeySignature = this._keySignatures.get(newBar.index);
@@ -15617,11 +15986,29 @@
15617
15986
  return 0;
15618
15987
  }
15619
15988
  }
15989
+ readRseBank() {
15990
+ // RSE Banks on disk are having filenames like this: 033_2_002.ini
15991
+ // 033 is the RSE instrument (selected in the instrument list)
15992
+ // 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
15993
+ // likely for organizational purposes
15994
+ // 002 is the effective soundbank (selected in the soundbank dropdown)
15995
+ // RSE Instrument
15996
+ this.data.skip(4); // IOHelper.readInt32LE(this.data);
15997
+ // RSE Style/Variation
15998
+ this.data.skip(4); //IOHelper.readInt32LE(this.data);
15999
+ // RSE Soundbank
16000
+ this.data.skip(4); //IOHelper.readInt32LE(this.data);
16001
+ // Unknown, no UI setting seem to affect this
16002
+ // typically: -1
16003
+ this.data.skip(4); //IOHelper.readInt32LE(this.data);
16004
+ }
15620
16005
  readMixTableChange(beat) {
15621
16006
  const tableChange = new MixTableChange();
15622
16007
  tableChange.instrument = IOHelper.readSInt8(this.data);
16008
+ // NOTE: The UI shows a Midi Bank selection, but this information is not stored in the file
16009
+ // when reopening the file the bank is always 0
15623
16010
  if (this._versionNumber >= 500) {
15624
- this.data.skip(16); // Rse Info
16011
+ this.readRseBank();
15625
16012
  }
15626
16013
  tableChange.volume = IOHelper.readSInt8(this.data);
15627
16014
  tableChange.balance = IOHelper.readSInt8(this.data);
@@ -15633,7 +16020,7 @@
15633
16020
  tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15634
16021
  }
15635
16022
  tableChange.tempo = IOHelper.readInt32LE(this.data);
15636
- // durations
16023
+ // durations (in number of beats)
15637
16024
  if (tableChange.volume >= 0) {
15638
16025
  this.data.readByte();
15639
16026
  }
@@ -15763,11 +16150,11 @@
15763
16150
  newNote.fret = -1;
15764
16151
  }
15765
16152
  if (swapAccidentals) {
15766
- const accidental = Tuning.defaultAccidentals[newNote.realValueWithoutHarmonic % 12];
15767
- if (accidental === '#') {
16153
+ const accidental = ModelUtils.computeAccidental(bar.keySignature, NoteAccidentalMode.Default, newNote.realValueWithoutHarmonic, false);
16154
+ if (accidental === AccidentalType.Sharp) {
15768
16155
  newNote.accidentalMode = NoteAccidentalMode.ForceFlat;
15769
16156
  }
15770
- else if (accidental === 'b') {
16157
+ else if (accidental === AccidentalType.Flat) {
15771
16158
  newNote.accidentalMode = NoteAccidentalMode.ForceSharp;
15772
16159
  }
15773
16160
  }
@@ -16616,9 +17003,7 @@
16616
17003
  return writer.toArray();
16617
17004
  }
16618
17005
  static addHeaderAndFooter(binaryStylesheet, style, prefix, name) {
16619
- if (style.template !== undefined) {
16620
- binaryStylesheet.addValue(`${prefix}${name}`, style.template, DataType.String);
16621
- }
17006
+ binaryStylesheet.addValue(`${prefix}${name}`, style.template, DataType.String);
16622
17007
  binaryStylesheet.addValue(`${prefix}${name}Alignment`, style.textAlign, DataType.Integer);
16623
17008
  if (style.isVisible !== undefined) {
16624
17009
  binaryStylesheet.addValue(`${prefix}draw${name}`, style.isVisible, DataType.Boolean);
@@ -16652,6 +17037,7 @@
16652
17037
  this.path = '';
16653
17038
  this.role = '';
16654
17039
  this.program = 0;
17040
+ this.bank = 0;
16655
17041
  }
16656
17042
  get uniqueId() {
16657
17043
  return `${this.path};${this.name};${this.role}`;
@@ -16667,6 +17053,7 @@
16667
17053
  this._backingTrackPadding = 0;
16668
17054
  this._doubleBars = new Set();
16669
17055
  this._keySignatures = new Map();
17056
+ this._transposeKeySignaturePerTrack = new Map();
16670
17057
  }
16671
17058
  parseXml(xml, settings) {
16672
17059
  this._masterTrackAutomations = new Map();
@@ -16897,7 +17284,8 @@
16897
17284
  assetId = c.innerText;
16898
17285
  break;
16899
17286
  case 'FramePadding':
16900
- this._backingTrackPadding = GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate * 1000;
17287
+ this._backingTrackPadding =
17288
+ (GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate) * 1000;
16901
17289
  break;
16902
17290
  }
16903
17291
  }
@@ -17004,21 +17392,28 @@
17004
17392
  if (!type) {
17005
17393
  return;
17006
17394
  }
17007
- let automation = null;
17395
+ const newAutomations = [];
17008
17396
  switch (type) {
17009
17397
  case 'Tempo':
17010
- automation = Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference);
17398
+ newAutomations.push(Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference));
17011
17399
  break;
17012
17400
  case 'SyncPoint':
17013
- automation = new Automation();
17014
- automation.type = AutomationType.SyncPoint;
17015
- automation.isLinear = isLinear;
17016
- automation.ratioPosition = ratioPosition;
17017
- automation.syncPointValue = syncPointValue;
17401
+ const syncPoint = new Automation();
17402
+ syncPoint.type = AutomationType.SyncPoint;
17403
+ syncPoint.isLinear = isLinear;
17404
+ syncPoint.ratioPosition = ratioPosition;
17405
+ syncPoint.syncPointValue = syncPointValue;
17406
+ newAutomations.push(syncPoint);
17018
17407
  break;
17019
17408
  case 'Sound':
17020
17409
  if (textValue && sounds && sounds.has(textValue)) {
17021
- automation = Automation.buildInstrumentAutomation(isLinear, ratioPosition, sounds.get(textValue).program);
17410
+ const bankChange = new Automation();
17411
+ bankChange.type = AutomationType.Bank;
17412
+ bankChange.ratioPosition = ratioPosition;
17413
+ bankChange.value = sounds.get(textValue).bank;
17414
+ newAutomations.push(bankChange);
17415
+ const programChange = Automation.buildInstrumentAutomation(isLinear, ratioPosition, sounds.get(textValue).program);
17416
+ newAutomations.push(programChange);
17022
17417
  }
17023
17418
  break;
17024
17419
  case 'SustainPedal':
@@ -17046,15 +17441,19 @@
17046
17441
  }
17047
17442
  break;
17048
17443
  }
17049
- if (automation) {
17444
+ if (newAutomations.length) {
17050
17445
  if (text) {
17051
- automation.text = text;
17446
+ for (const a of newAutomations) {
17447
+ a.text = text;
17448
+ }
17052
17449
  }
17053
17450
  if (barIndex >= 0) {
17054
17451
  if (!automations.has(barIndex)) {
17055
17452
  automations.set(barIndex, []);
17056
17453
  }
17057
- automations.get(barIndex).push(automation);
17454
+ for (const a of newAutomations) {
17455
+ automations.get(barIndex).push(a);
17456
+ }
17058
17457
  }
17059
17458
  }
17060
17459
  }
@@ -17133,13 +17532,13 @@
17133
17532
  track.playbackInfo.isMute = state === 'Mute';
17134
17533
  break;
17135
17534
  case 'PartSounding':
17136
- this.parsePartSounding(track, c);
17535
+ this.parsePartSounding(trackId, track, c);
17137
17536
  break;
17138
17537
  case 'Staves':
17139
17538
  this.parseStaves(track, c);
17140
17539
  break;
17141
17540
  case 'Transpose':
17142
- this.parseTranspose(track, c);
17541
+ this.parseTranspose(trackId, track, c);
17143
17542
  break;
17144
17543
  case 'RSE':
17145
17544
  this.parseRSE(track, c);
@@ -17646,24 +18045,33 @@
17646
18045
  break;
17647
18046
  }
17648
18047
  }
17649
- if (sound.role === 'Factory' || track.playbackInfo.program === 0) {
17650
- track.playbackInfo.program = sound.program;
17651
- }
17652
18048
  if (!this._soundsByTrack.has(trackId)) {
17653
18049
  this._soundsByTrack.set(trackId, new Map());
18050
+ // apply first sound
18051
+ track.playbackInfo.program = sound.program;
18052
+ track.playbackInfo.bank = sound.bank;
17654
18053
  }
17655
18054
  this._soundsByTrack.get(trackId).set(sound.uniqueId, sound);
17656
18055
  }
17657
18056
  parseSoundMidi(sound, node) {
18057
+ let bankMsb = 0;
18058
+ let bankLsb = 0;
17658
18059
  for (const c of node.childElements()) {
17659
18060
  switch (c.localName) {
17660
18061
  case 'Program':
17661
18062
  sound.program = GpifParser.parseIntSafe(c.innerText, 0);
17662
18063
  break;
18064
+ case 'MSB': // coarse
18065
+ bankMsb = GpifParser.parseIntSafe(c.innerText, 0);
18066
+ break;
18067
+ case 'LSB': // Fine
18068
+ bankLsb = GpifParser.parseIntSafe(c.innerText, 0);
18069
+ break;
17663
18070
  }
17664
18071
  }
18072
+ sound.bank = ((bankMsb & 0x7f) << 7) | bankLsb;
17665
18073
  }
17666
- parsePartSounding(track, node) {
18074
+ parsePartSounding(trackId, track, node) {
17667
18075
  for (const c of node.childElements()) {
17668
18076
  switch (c.localName) {
17669
18077
  case 'TranspositionPitch':
@@ -17671,10 +18079,14 @@
17671
18079
  staff.displayTranspositionPitch = GpifParser.parseIntSafe(c.innerText, 0);
17672
18080
  }
17673
18081
  break;
18082
+ case 'NominalKey':
18083
+ const transposeIndex = Math.max(0, Tuning.noteNames.indexOf(c.innerText));
18084
+ this._transposeKeySignaturePerTrack.set(trackId, transposeIndex);
18085
+ break;
17674
18086
  }
17675
18087
  }
17676
18088
  }
17677
- parseTranspose(track, node) {
18089
+ parseTranspose(trackId, track, node) {
17678
18090
  let octave = 0;
17679
18091
  let chromatic = 0;
17680
18092
  for (const c of node.childElements()) {
@@ -17687,9 +18099,14 @@
17687
18099
  break;
17688
18100
  }
17689
18101
  }
18102
+ const pitch = octave * 12 + chromatic;
17690
18103
  for (const staff of track.staves) {
17691
- staff.displayTranspositionPitch = octave * 12 + chromatic;
18104
+ staff.displayTranspositionPitch = pitch;
17692
18105
  }
18106
+ // the chromatic transpose also causes an alternative key signature to be adjusted
18107
+ // In Guitar Pro this feature is hidden in the track properties (more -> Transposition tonality -> 'C played as:' ).
18108
+ const transposeIndex = ModelUtils.flooredDivision(pitch, 12);
18109
+ this._transposeKeySignaturePerTrack.set(trackId, transposeIndex);
17693
18110
  }
17694
18111
  parseRSE(track, node) {
17695
18112
  for (const c of node.childElements()) {
@@ -18653,6 +19070,7 @@
18653
19070
  // GP6 had percussion as element+variation
18654
19071
  let element = -1;
18655
19072
  let variation = -1;
19073
+ let hasTransposedPitch = false;
18656
19074
  for (const c of node.childElements()) {
18657
19075
  switch (c.localName) {
18658
19076
  case 'Property':
@@ -18733,7 +19151,15 @@
18733
19151
  note.tone = GpifParser.parseIntSafe(c.findChildElement('Step')?.innerText, 0);
18734
19152
  break;
18735
19153
  case 'ConcertPitch':
19154
+ if (!hasTransposedPitch) {
19155
+ this.parseConcertPitch(c, note);
19156
+ }
19157
+ break;
19158
+ case 'TransposedPitch':
19159
+ // clear potential value from concert pitch
19160
+ note.accidentalMode = NoteAccidentalMode.Default;
18736
19161
  this.parseConcertPitch(c, note);
19162
+ hasTransposedPitch = true;
18737
19163
  break;
18738
19164
  case 'Bended':
18739
19165
  isBended = true;
@@ -18947,20 +19373,29 @@
18947
19373
  lastMasterBar.isDoubleBar = false;
18948
19374
  }
18949
19375
  // add tracks to score
19376
+ const trackIndexToTrackId = [];
18950
19377
  for (const trackId of this._tracksMapping) {
18951
19378
  if (!trackId) {
18952
19379
  continue;
18953
19380
  }
18954
19381
  const track = this._tracksById.get(trackId);
18955
19382
  this.score.addTrack(track);
19383
+ trackIndexToTrackId.push(trackId);
18956
19384
  }
18957
19385
  // process all masterbars
18958
19386
  let keySignature;
18959
19387
  for (const barIds of this._barsOfMasterBar) {
18960
19388
  // add all bars of masterbar vertically to all tracks
18961
19389
  let staffIndex = 0;
19390
+ let trackIndex = 0;
18962
19391
  keySignature = [KeySignature.C, KeySignatureType.Major];
18963
- for (let barIndex = 0, trackIndex = 0; barIndex < barIds.length && trackIndex < this.score.tracks.length; barIndex++) {
19392
+ if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[0])) {
19393
+ keySignature = [
19394
+ ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[0])),
19395
+ keySignature[1]
19396
+ ];
19397
+ }
19398
+ for (let barIndex = 0; barIndex < barIds.length && trackIndex < this.score.tracks.length; barIndex++) {
18964
19399
  const barId = barIds[barIndex];
18965
19400
  if (barId !== GpifParser.InvalidId) {
18966
19401
  const bar = this._barsById.get(barId);
@@ -18970,6 +19405,12 @@
18970
19405
  const masterBarIndex = staff.bars.length - 1;
18971
19406
  if (this._keySignatures.has(masterBarIndex)) {
18972
19407
  keySignature = this._keySignatures.get(masterBarIndex);
19408
+ if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[trackIndex])) {
19409
+ keySignature = [
19410
+ ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[trackIndex])),
19411
+ keySignature[1]
19412
+ ];
19413
+ }
18973
19414
  }
18974
19415
  bar.keySignature = keySignature[0];
18975
19416
  bar.keySignatureType = keySignature[1];
@@ -19036,11 +19477,17 @@
19036
19477
  if (staffIndex === track.staves.length - 1) {
19037
19478
  trackIndex++;
19038
19479
  staffIndex = 0;
19039
- keySignature = [KeySignature.C, KeySignatureType.Major];
19040
19480
  }
19041
19481
  else {
19042
19482
  staffIndex++;
19043
- keySignature = [KeySignature.C, KeySignatureType.Major];
19483
+ }
19484
+ keySignature = [KeySignature.C, KeySignatureType.Major];
19485
+ if (trackIndex < trackIndexToTrackId.length &&
19486
+ this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[trackIndex])) {
19487
+ keySignature = [
19488
+ ModelUtils.transposeKey(keySignature[0], this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[trackIndex])),
19489
+ keySignature[1]
19490
+ ];
19044
19491
  }
19045
19492
  }
19046
19493
  else {
@@ -19076,7 +19523,12 @@
19076
19523
  for (const a of automations) {
19077
19524
  // NOTE: currently the automations of a bar are applied to the
19078
19525
  // first beat of a bar
19079
- beat.automations.push(a);
19526
+ const skip =
19527
+ // skip bank automations if they are 0 at start
19528
+ a.type === AutomationType.Bank && a.value === 0 && bar.index === 0;
19529
+ if (!skip) {
19530
+ beat.automations.push(a);
19531
+ }
19080
19532
  }
19081
19533
  }
19082
19534
  }
@@ -19128,7 +19580,7 @@
19128
19580
  */
19129
19581
  GpifParser.BendPointValueFactor = 1 / 25.0;
19130
19582
  // tests have shown that Guitar Pro seem to always work with 44100hz for the frame offsets,
19131
- // they are NOT using the sample rate of the input file.
19583
+ // they are NOT using the sample rate of the input file.
19132
19584
  // Downsampling a 44100hz ogg to 8000hz and using it in as audio track resulted in the same frame offset when placing sync points.
19133
19585
  GpifParser.SampleRate = 44100;
19134
19586
 
@@ -19823,49 +20275,6 @@
19823
20275
  }
19824
20276
  }
19825
20277
 
19826
- /**
19827
- * Defines all possible accidentals for notes.
19828
- */
19829
- var AccidentalType;
19830
- (function (AccidentalType) {
19831
- /**
19832
- * No accidental
19833
- */
19834
- AccidentalType[AccidentalType["None"] = 0] = "None";
19835
- /**
19836
- * Naturalize
19837
- */
19838
- AccidentalType[AccidentalType["Natural"] = 1] = "Natural";
19839
- /**
19840
- * Sharp
19841
- */
19842
- AccidentalType[AccidentalType["Sharp"] = 2] = "Sharp";
19843
- /**
19844
- * Flat
19845
- */
19846
- AccidentalType[AccidentalType["Flat"] = 3] = "Flat";
19847
- /**
19848
- * Natural for smear bends
19849
- */
19850
- AccidentalType[AccidentalType["NaturalQuarterNoteUp"] = 4] = "NaturalQuarterNoteUp";
19851
- /**
19852
- * Sharp for smear bends
19853
- */
19854
- AccidentalType[AccidentalType["SharpQuarterNoteUp"] = 5] = "SharpQuarterNoteUp";
19855
- /**
19856
- * Flat for smear bends
19857
- */
19858
- AccidentalType[AccidentalType["FlatQuarterNoteUp"] = 6] = "FlatQuarterNoteUp";
19859
- /**
19860
- * Double Sharp, indicated by an 'x'
19861
- */
19862
- AccidentalType[AccidentalType["DoubleSharp"] = 7] = "DoubleSharp";
19863
- /**
19864
- * Double Flat, indicated by 'bb'
19865
- */
19866
- AccidentalType[AccidentalType["DoubleFlat"] = 8] = "DoubleFlat";
19867
- })(AccidentalType || (AccidentalType = {}));
19868
-
19869
20278
  class BeatLines {
19870
20279
  constructor() {
19871
20280
  this.maxLine = -1e3;
@@ -19968,90 +20377,6 @@
19968
20377
  }
19969
20378
  return line;
19970
20379
  }
19971
- static computeAccidental(keySignature, accidentalMode, noteValue, quarterBend, currentAccidental = null) {
19972
- const ks = keySignature;
19973
- const ksi = ks + 7;
19974
- const index = noteValue % 12;
19975
- const accidentalForKeySignature = ksi < 7 ? AccidentalType.Flat : AccidentalType.Sharp;
19976
- const hasKeySignatureAccidentalSetForNote = AccidentalHelper.KeySignatureLookup[ksi][index];
19977
- const hasNoteAccidentalWithinOctave = AccidentalHelper.AccidentalNotes[index];
19978
- // the general logic is like this:
19979
- // - we check if the key signature has an accidental defined
19980
- // - we calculate which accidental a note needs according to its index in the octave
19981
- // - if the accidental is already placed at this line, nothing needs to be done, otherwise we place it
19982
- // - if there should not be an accidental, but there is one in the key signature, we clear it.
19983
- // the exceptions are:
19984
- // - for quarter bends we just place the corresponding accidental
19985
- // - the accidental mode can enforce the accidentals for the note
19986
- let accidentalToSet = AccidentalType.None;
19987
- if (quarterBend) {
19988
- accidentalToSet = hasNoteAccidentalWithinOctave ? accidentalForKeySignature : AccidentalType.Natural;
19989
- switch (accidentalToSet) {
19990
- case AccidentalType.Natural:
19991
- accidentalToSet = AccidentalType.NaturalQuarterNoteUp;
19992
- break;
19993
- case AccidentalType.Sharp:
19994
- accidentalToSet = AccidentalType.SharpQuarterNoteUp;
19995
- break;
19996
- case AccidentalType.Flat:
19997
- accidentalToSet = AccidentalType.FlatQuarterNoteUp;
19998
- break;
19999
- }
20000
- }
20001
- else {
20002
- // define which accidental should be shown ignoring what might be set on the KS already
20003
- switch (accidentalMode) {
20004
- case NoteAccidentalMode.ForceSharp:
20005
- accidentalToSet = AccidentalType.Sharp;
20006
- break;
20007
- case NoteAccidentalMode.ForceDoubleSharp:
20008
- accidentalToSet = AccidentalType.DoubleSharp;
20009
- break;
20010
- case NoteAccidentalMode.ForceFlat:
20011
- accidentalToSet = AccidentalType.Flat;
20012
- break;
20013
- case NoteAccidentalMode.ForceDoubleFlat:
20014
- accidentalToSet = AccidentalType.DoubleFlat;
20015
- break;
20016
- default:
20017
- // if note has an accidental in the octave, we place a symbol
20018
- // according to the Key Signature
20019
- if (hasNoteAccidentalWithinOctave) {
20020
- accidentalToSet = accidentalForKeySignature;
20021
- }
20022
- else if (hasKeySignatureAccidentalSetForNote) {
20023
- // note does not get an accidental, but KS defines one -> Naturalize
20024
- accidentalToSet = AccidentalType.Natural;
20025
- }
20026
- break;
20027
- }
20028
- // do we need an accidental on the note?
20029
- if (accidentalToSet !== AccidentalType.None) {
20030
- // if there is no accidental on the line, and the key signature has it set already, we clear it on the note
20031
- if (currentAccidental != null) {
20032
- if (currentAccidental === accidentalToSet) {
20033
- accidentalToSet = AccidentalType.None;
20034
- }
20035
- }
20036
- else if (hasKeySignatureAccidentalSetForNote && accidentalToSet === accidentalForKeySignature) {
20037
- accidentalToSet = AccidentalType.None;
20038
- }
20039
- }
20040
- else {
20041
- // if we don't want an accidental, but there is already one applied, we place a naturalize accidental
20042
- // and clear the registration
20043
- if (currentAccidental !== null) {
20044
- if (currentAccidental === AccidentalType.Natural) {
20045
- accidentalToSet = AccidentalType.None;
20046
- }
20047
- else {
20048
- accidentalToSet = AccidentalType.Natural;
20049
- }
20050
- }
20051
- }
20052
- }
20053
- return accidentalToSet;
20054
- }
20055
20380
  getAccidental(noteValue, quarterBend, relatedBeat, isHelperNote, note = null) {
20056
20381
  let steps = 0;
20057
20382
  let accidentalToSet = AccidentalType.None;
@@ -20065,7 +20390,7 @@
20065
20390
  const currentAccidental = this._registeredAccidentals.has(steps)
20066
20391
  ? this._registeredAccidentals.get(steps)
20067
20392
  : null;
20068
- accidentalToSet = AccidentalHelper.computeAccidental(this._bar.keySignature, accidentalMode, noteValue, quarterBend, currentAccidental);
20393
+ accidentalToSet = ModelUtils.computeAccidental(this._bar.keySignature, accidentalMode, noteValue, quarterBend, currentAccidental);
20069
20394
  let skipAccidental = false;
20070
20395
  switch (accidentalToSet) {
20071
20396
  case AccidentalType.NaturalQuarterNoteUp:
@@ -20179,48 +20504,6 @@
20179
20504
  return 0;
20180
20505
  }
20181
20506
  }
20182
- /**
20183
- * a lookup list containing an info whether the notes within an octave
20184
- * need an accidental rendered. the accidental symbol is determined based on the type of key signature.
20185
- */
20186
- AccidentalHelper.KeySignatureLookup = [
20187
- // Flats (where the value is true, a flat accidental is required for the notes)
20188
- [true, true, true, true, true, true, true, true, true, true, true, true],
20189
- [true, true, true, true, true, false, true, true, true, true, true, true],
20190
- [false, true, true, true, true, false, true, true, true, true, true, true],
20191
- [false, true, true, true, true, false, false, false, true, true, true, true],
20192
- [false, false, false, true, true, false, false, false, true, true, true, true],
20193
- [false, false, false, true, true, false, false, false, false, false, true, true],
20194
- [false, false, false, false, false, false, false, false, false, false, true, true],
20195
- // natural
20196
- [false, false, false, false, false, false, false, false, false, false, false, false],
20197
- // sharps (where the value is true, a flat accidental is required for the notes)
20198
- [false, false, false, false, false, true, true, false, false, false, false, false],
20199
- [true, true, false, false, false, true, true, false, false, false, false, false],
20200
- [true, true, false, false, false, true, true, true, true, false, false, false],
20201
- [true, true, true, true, false, true, true, true, true, false, false, false],
20202
- [true, true, true, true, false, true, true, true, true, true, true, false],
20203
- [true, true, true, true, true, true, true, true, true, true, true, false],
20204
- [true, true, true, true, true, true, true, true, true, true, true, true]
20205
- ];
20206
- /**
20207
- * Contains the list of notes within an octave have accidentals set.
20208
- */
20209
- // prettier-ignore
20210
- AccidentalHelper.AccidentalNotes = [
20211
- false,
20212
- true,
20213
- false,
20214
- true,
20215
- false,
20216
- false,
20217
- true,
20218
- false,
20219
- true,
20220
- false,
20221
- true,
20222
- false
20223
- ];
20224
20507
  /**
20225
20508
  * We always have 7 steps per octave.
20226
20509
  * (by a step the offsets inbetween score lines is meant,
@@ -20265,6 +20548,10 @@
20265
20548
  * The midi channel program to use when playing the note (-1 if using the default track program).
20266
20549
  */
20267
20550
  this.outputMidiProgram = -1;
20551
+ /**
20552
+ * The midi bank to use when playing the note (-1 if using the default track bank).
20553
+ */
20554
+ this.outputMidiBank = -1;
20268
20555
  /**
20269
20556
  * The volume to use when playing the note (-1 if using the default track volume).
20270
20557
  */
@@ -20763,6 +21050,9 @@
20763
21050
  if (trackInfo.firstArticulation.outputMidiProgram >= 0) {
20764
21051
  track.playbackInfo.program = trackInfo.firstArticulation.outputMidiProgram;
20765
21052
  }
21053
+ if (trackInfo.firstArticulation.outputMidiBank >= 0) {
21054
+ track.playbackInfo.bank = trackInfo.firstArticulation.outputMidiBank;
21055
+ }
20766
21056
  if (trackInfo.firstArticulation.outputBalance >= 0) {
20767
21057
  track.playbackInfo.balance = trackInfo.firstArticulation.outputBalance;
20768
21058
  }
@@ -20794,7 +21084,9 @@
20794
21084
  articulation.outputMidiChannel = Number.parseInt(c.innerText) - 1;
20795
21085
  break;
20796
21086
  // case 'midi-name': Ignored
20797
- // case 'midi-bank': Not supported (https://github.com/CoderLine/alphaTab/issues/1986)
21087
+ case 'midi-bank':
21088
+ articulation.outputMidiBank = Number.parseInt(c.innerText) - 1;
21089
+ break;
20798
21090
  case 'midi-program':
20799
21091
  articulation.outputMidiProgram = Number.parseInt(c.innerText) - 1;
20800
21092
  break;
@@ -21324,7 +21616,15 @@
21324
21616
  switch (c.localName) {
21325
21617
  // case 'midi-channel': Ignored
21326
21618
  // case 'midi-name': Ignored
21327
- // case 'midi-bank': Ignored
21619
+ case 'midi-bank':
21620
+ if (!this._nextBeatAutomations) {
21621
+ this._nextBeatAutomations = [];
21622
+ }
21623
+ automation = new Automation();
21624
+ automation.type = AutomationType.Bank;
21625
+ automation.value = Number.parseInt(c.innerText) - 1;
21626
+ this._nextBeatAutomations.push(automation);
21627
+ break;
21328
21628
  case 'midi-program':
21329
21629
  if (!this._nextBeatAutomations) {
21330
21630
  this._nextBeatAutomations = [];
@@ -23595,11 +23895,15 @@
23595
23895
  }
23596
23896
 
23597
23897
  class EventEmitter {
23598
- constructor() {
23898
+ constructor(fireOnRegister = undefined) {
23599
23899
  this._listeners = [];
23900
+ this._fireOnRegister = fireOnRegister;
23600
23901
  }
23601
23902
  on(value) {
23602
23903
  this._listeners.push(value);
23904
+ if (this._fireOnRegister?.()) {
23905
+ value();
23906
+ }
23603
23907
  return () => {
23604
23908
  this.off(value);
23605
23909
  };
@@ -23617,11 +23921,18 @@
23617
23921
  * @partial
23618
23922
  */
23619
23923
  class EventEmitterOfT {
23620
- constructor() {
23924
+ constructor(fireOnRegister = undefined) {
23621
23925
  this._listeners = [];
23926
+ this._fireOnRegister = fireOnRegister;
23622
23927
  }
23623
23928
  on(value) {
23624
23929
  this._listeners.push(value);
23930
+ if (this._fireOnRegister) {
23931
+ const arg = this._fireOnRegister();
23932
+ if (arg !== null) {
23933
+ value(arg);
23934
+ }
23935
+ }
23625
23936
  return () => {
23626
23937
  this.off(value);
23627
23938
  };
@@ -30662,6 +30973,12 @@
30662
30973
  this.sequencer.playbackSpeed = value;
30663
30974
  this.timePosition = this.timePosition * (oldSpeed / value);
30664
30975
  }
30976
+ get loadedMidiInfo() {
30977
+ return this._loadedMidiInfo;
30978
+ }
30979
+ get currentPosition() {
30980
+ return this._currentPosition;
30981
+ }
30665
30982
  get tickPosition() {
30666
30983
  return this._tickPosition;
30667
30984
  }
@@ -30719,22 +31036,38 @@
30719
31036
  this._midiEventsPlayedFilter = new Set();
30720
31037
  this._notPlayedSamples = 0;
30721
31038
  this._synthStopping = false;
31039
+ this._currentPosition = new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
30722
31040
  this.isReady = false;
30723
31041
  this.state = PlayerState.Paused;
30724
31042
  this._loadedSoundFonts = [];
30725
- this.ready = new EventEmitter();
30726
31043
  this.readyForPlayback = new EventEmitter();
30727
31044
  this.finished = new EventEmitter();
30728
31045
  this.soundFontLoaded = new EventEmitter();
30729
31046
  this.soundFontLoadFailed = new EventEmitterOfT();
30730
- this.midiLoaded = new EventEmitterOfT();
30731
31047
  this.midiLoadFailed = new EventEmitterOfT();
30732
- this.stateChanged = new EventEmitterOfT();
30733
- this.positionChanged = new EventEmitterOfT();
30734
31048
  this.midiEventsPlayed = new EventEmitterOfT();
30735
- this.playbackRangeChanged = new EventEmitterOfT();
30736
31049
  Logger.debug('AlphaSynth', 'Initializing player');
30737
31050
  this.state = PlayerState.Paused;
31051
+ this.ready = new EventEmitter(() => this.isReady);
31052
+ this.readyForPlayback = new EventEmitter(() => this.isReadyForPlayback);
31053
+ this.midiLoaded = new EventEmitterOfT(() => {
31054
+ if (this._loadedMidiInfo) {
31055
+ return this._loadedMidiInfo;
31056
+ }
31057
+ return null;
31058
+ });
31059
+ this.stateChanged = new EventEmitterOfT(() => {
31060
+ return new PlayerStateChangedEventArgs(this.state, false);
31061
+ });
31062
+ this.positionChanged = new EventEmitterOfT(() => {
31063
+ return this._currentPosition;
31064
+ });
31065
+ this.playbackRangeChanged = new EventEmitterOfT(() => {
31066
+ if (this.playbackRange) {
31067
+ return new PlaybackRangeChangedEventArgs(this.playbackRange);
31068
+ }
31069
+ return null;
31070
+ });
30738
31071
  Logger.debug('AlphaSynth', 'Creating output');
30739
31072
  this._output = output;
30740
31073
  Logger.debug('AlphaSynth', 'Creating synthesizer');
@@ -30912,7 +31245,8 @@
30912
31245
  Logger.debug('AlphaSynth', 'Loading midi from model');
30913
31246
  this.sequencer.loadMidi(midi);
30914
31247
  this._isMidiLoaded = true;
30915
- this.midiLoaded.trigger(new PositionChangedEventArgs(0, this.sequencer.currentEndTime, 0, this.sequencer.currentEndTick, false, this.sequencer.currentTempo, this.sequencer.modifiedTempo));
31248
+ this._loadedMidiInfo = new PositionChangedEventArgs(0, this.sequencer.currentEndTime, 0, this.sequencer.currentEndTick, false, this.sequencer.currentTempo, this.sequencer.modifiedTempo);
31249
+ this.midiLoaded.trigger(this._loadedMidiInfo);
30916
31250
  Logger.debug('AlphaSynth', 'Midi successfully loaded');
30917
31251
  this.checkReadyForPlayback();
30918
31252
  this.tickPosition = 0;
@@ -31015,23 +31349,28 @@
31015
31349
  this.sequencer.resetOneTimeMidi();
31016
31350
  this.timePosition = this.sequencer.currentTime;
31017
31351
  }
31018
- updateTimePosition(timePosition, isSeek) {
31019
- // update the real positions
31020
- let currentTime = timePosition;
31021
- this._timePosition = currentTime;
31352
+ createPositionChangedEventArgs(isSeek) {
31353
+ // on fade outs we can have some milliseconds longer, ensure we don't report this
31354
+ let currentTime = this._timePosition;
31022
31355
  let currentTick = this.sequencer.currentTimePositionToTickPosition(currentTime);
31023
- this._tickPosition = currentTick;
31024
31356
  const endTime = this.sequencer.currentEndTime;
31025
31357
  const endTick = this.sequencer.currentEndTick;
31026
- // on fade outs we can have some milliseconds longer, ensure we don't report this
31027
31358
  if (currentTime > endTime) {
31028
31359
  currentTime = endTime;
31029
31360
  currentTick = endTick;
31030
31361
  }
31362
+ return new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek, this.sequencer.currentTempo, this.sequencer.modifiedTempo);
31363
+ }
31364
+ updateTimePosition(timePosition, isSeek) {
31365
+ // update the real positions
31366
+ this._timePosition = timePosition;
31367
+ const args = this.createPositionChangedEventArgs(isSeek);
31368
+ this._tickPosition = args.currentTick;
31031
31369
  const mode = this.sequencer.isPlayingMain ? 'main' : this.sequencer.isPlayingCountIn ? 'count-in' : 'one-time';
31032
- 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})`);
31370
+ 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})`);
31033
31371
  if (this.sequencer.isPlayingMain) {
31034
- this.positionChanged.trigger(new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek, this.sequencer.currentTempo, this.sequencer.modifiedTempo));
31372
+ this._currentPosition = args;
31373
+ this.positionChanged.trigger(args);
31035
31374
  }
31036
31375
  // build events which were actually played
31037
31376
  if (isSeek) {
@@ -31039,7 +31378,7 @@
31039
31378
  }
31040
31379
  else {
31041
31380
  const playedEvents = [];
31042
- while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < currentTime) {
31381
+ while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < args.currentTime) {
31043
31382
  const synthEvent = this._playedEventsQueue.dequeue();
31044
31383
  playedEvents.push(synthEvent.event);
31045
31384
  }
@@ -36490,6 +36829,15 @@
36490
36829
  fillFromJson(json) {
36491
36830
  SettingsSerializer.fromJson(this, json);
36492
36831
  }
36832
+ /**
36833
+ * handles backwards compatibility aspects on the settings, removed in 2.0
36834
+ * @internal
36835
+ */
36836
+ handleBackwardsCompatibility() {
36837
+ if (this.player.playerMode === exports.PlayerMode.Disabled && this.player.enablePlayer) {
36838
+ this.player.playerMode = exports.PlayerMode.EnabledAutomatic;
36839
+ }
36840
+ }
36493
36841
  }
36494
36842
 
36495
36843
  class SectionSerializer {
@@ -37707,6 +38055,7 @@
37707
38055
  o.set("balance", obj.balance);
37708
38056
  o.set("port", obj.port);
37709
38057
  o.set("program", obj.program);
38058
+ o.set("bank", obj.bank);
37710
38059
  o.set("primarychannel", obj.primaryChannel);
37711
38060
  o.set("secondarychannel", obj.secondaryChannel);
37712
38061
  o.set("ismute", obj.isMute);
@@ -37727,6 +38076,9 @@
37727
38076
  case "program":
37728
38077
  obj.program = v;
37729
38078
  return true;
38079
+ case "bank":
38080
+ obj.bank = v;
38081
+ return true;
37730
38082
  case "primarychannel":
37731
38083
  obj.primaryChannel = v;
37732
38084
  return true;
@@ -41385,6 +41737,11 @@
41385
41737
  this._programsPerChannel.set(channel, program);
41386
41738
  }
41387
41739
  }
41740
+ addBankChange(track, tick, channel, bank) {
41741
+ const lsbMsb = GeneralMidi.bankToLsbMsb(bank);
41742
+ this._handler.addControlChange(track.index, tick, channel, ControllerType.BankSelectCoarse, lsbMsb[1]);
41743
+ this._handler.addControlChange(track.index, tick, channel, ControllerType.BankSelectFine, lsbMsb[0]);
41744
+ }
41388
41745
  static buildTranspositionPitches(score, settings) {
41389
41746
  const transpositionPitches = new Map();
41390
41747
  for (const track of score.tracks) {
@@ -41412,6 +41769,7 @@
41412
41769
  // Set PitchBendRangeCoarse to 12
41413
41770
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryFine, 0);
41414
41771
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
41772
+ this.addBankChange(track, 0, channel, playbackInfo.bank);
41415
41773
  this.addProgramChange(track, 0, channel, playbackInfo.program);
41416
41774
  }
41417
41775
  /**
@@ -42707,6 +43065,10 @@
42707
43065
  this.addProgramChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, (automation.value | 0) & 0xff);
42708
43066
  this.addProgramChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.secondaryChannel, (automation.value | 0) & 0xff);
42709
43067
  break;
43068
+ case AutomationType.Bank:
43069
+ this.addBankChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, automation.value);
43070
+ this.addBankChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.secondaryChannel, automation.value);
43071
+ break;
42710
43072
  case AutomationType.Balance:
42711
43073
  const balance = MidiFileGenerator.toChannelShort(automation.value);
42712
43074
  this._handler.addControlChange(beat.voice.bar.staff.track.index, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, ControllerType.PanCoarse, balance);
@@ -43656,17 +44018,29 @@
43656
44018
  this._playbackSpeed = 1;
43657
44019
  this._isLooping = false;
43658
44020
  this._midiEventsPlayedFilter = [];
43659
- this.ready = new EventEmitter();
43660
- this.readyForPlayback = new EventEmitter();
43661
44021
  this.finished = new EventEmitter();
43662
44022
  this.soundFontLoaded = new EventEmitter();
43663
44023
  this.soundFontLoadFailed = new EventEmitterOfT();
43664
- this.midiLoaded = new EventEmitterOfT();
43665
44024
  this.midiLoadFailed = new EventEmitterOfT();
43666
- this.stateChanged = new EventEmitterOfT();
43667
- this.positionChanged = new EventEmitterOfT();
43668
44025
  this.midiEventsPlayed = new EventEmitterOfT();
43669
- this.playbackRangeChanged = new EventEmitterOfT();
44026
+ this.ready = new EventEmitter(() => this.isReady);
44027
+ this.readyForPlayback = new EventEmitter(() => this.isReadyForPlayback);
44028
+ this.midiLoaded = new EventEmitterOfT(() => {
44029
+ return this._instance?.loadedMidiInfo ?? null;
44030
+ });
44031
+ this.stateChanged = new EventEmitterOfT(() => {
44032
+ return new PlayerStateChangedEventArgs(this.state, false);
44033
+ });
44034
+ this.positionChanged = new EventEmitterOfT(() => {
44035
+ return this.currentPosition;
44036
+ });
44037
+ this.playbackRangeChanged = new EventEmitterOfT(() => {
44038
+ const range = this.playbackRange;
44039
+ if (range) {
44040
+ return new PlaybackRangeChangedEventArgs(range);
44041
+ }
44042
+ return null;
44043
+ });
43670
44044
  }
43671
44045
  get instance() {
43672
44046
  return this._instance;
@@ -43688,10 +44062,14 @@
43688
44062
  newUnregister.push(value.finished.on(() => this.finished.trigger()));
43689
44063
  newUnregister.push(value.soundFontLoaded.on(() => this.soundFontLoaded.trigger()));
43690
44064
  newUnregister.push(value.soundFontLoadFailed.on(e => this.soundFontLoadFailed.trigger(e)));
43691
- newUnregister.push(value.midiLoaded.on(e => this.midiLoaded.trigger(e)));
44065
+ newUnregister.push(value.midiLoaded.on(e => {
44066
+ this.midiLoaded.trigger(e);
44067
+ }));
43692
44068
  newUnregister.push(value.midiLoadFailed.on(e => this.midiLoadFailed.trigger(e)));
43693
44069
  newUnregister.push(value.stateChanged.on(e => this.stateChanged.trigger(e)));
43694
- newUnregister.push(value.positionChanged.on(e => this.positionChanged.trigger(e)));
44070
+ newUnregister.push(value.positionChanged.on(e => {
44071
+ this.positionChanged.trigger(e);
44072
+ }));
43695
44073
  newUnregister.push(value.midiEventsPlayed.on(e => this.midiEventsPlayed.trigger(e)));
43696
44074
  newUnregister.push(value.playbackRangeChanged.on(e => this.playbackRangeChanged.trigger(e)));
43697
44075
  this._instanceEventUnregister = newUnregister;
@@ -43770,6 +44148,14 @@
43770
44148
  this._instance.playbackSpeed = value;
43771
44149
  }
43772
44150
  }
44151
+ get loadedMidiInfo() {
44152
+ return this._instance ? this._instance.loadedMidiInfo : undefined;
44153
+ }
44154
+ get currentPosition() {
44155
+ return this._instance
44156
+ ? this._instance.currentPosition
44157
+ : new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
44158
+ }
43773
44159
  get tickPosition() {
43774
44160
  return this._instance ? this._instance.tickPosition : 0;
43775
44161
  }
@@ -44111,83 +44497,6 @@
44111
44497
  this._previousStateForCursor = PlayerState.Paused;
44112
44498
  this._previousCursorCache = null;
44113
44499
  this._lastScroll = 0;
44114
- /**
44115
- * This event is fired when the played beat changed.
44116
- *
44117
- * @eventProperty
44118
- * @category Events - Player
44119
- * @since 0.9.4
44120
- *
44121
- * @example
44122
- * JavaScript
44123
- * ```js
44124
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44125
- * api.playedBeatChanged.on((beat) => {
44126
- * updateFretboard(beat);
44127
- * });
44128
- * ```
44129
- *
44130
- * @example
44131
- * C#
44132
- * ```cs
44133
- * var api = new AlphaTabApi<MyControl>(...);
44134
- * api.PlayedBeatChanged.On(beat =>
44135
- * {
44136
- * UpdateFretboard(beat);
44137
- * });
44138
- * ```
44139
- *
44140
- * @example
44141
- * Android
44142
- * ```kotlin
44143
- * val api = AlphaTabApi<MyControl>(...)
44144
- * api.playedBeatChanged.on { beat ->
44145
- * updateFretboard(beat)
44146
- * }
44147
- * ```
44148
- *
44149
- */
44150
- this.playedBeatChanged = new EventEmitterOfT();
44151
- /**
44152
- * This event is fired when the currently active beats across all tracks change.
44153
- *
44154
- * @remarks
44155
- * Unlike the {@link playedBeatChanged} event this event contains the beats of all tracks and voices independent of them being rendered.
44156
- *
44157
- * @eventProperty
44158
- * @category Events - Player
44159
- * @since 1.2.3
44160
- *
44161
- * @example
44162
- * JavaScript
44163
- * ```js
44164
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44165
- * api.activeBeatsChanged.on(args => {
44166
- * updateHighlights(args.activeBeats);
44167
- * });
44168
- * ```
44169
- *
44170
- * @example
44171
- * C#
44172
- * ```cs
44173
- * var api = new AlphaTabApi<MyControl>(...);
44174
- * api.ActiveBeatsChanged.On(args =>
44175
- * {
44176
- * UpdateHighlights(args.ActiveBeats);
44177
- * });
44178
- * ```
44179
- *
44180
- * @example
44181
- * Android
44182
- * ```kotlin
44183
- * val api = AlphaTabApi<MyControl>(...)
44184
- * api.activeBeatsChanged.on { args ->
44185
- * updateHighlights(args.activeBeats)
44186
- * }
44187
- * ```
44188
- *
44189
- */
44190
- this.activeBeatsChanged = new EventEmitterOfT();
44191
44500
  this._beatMouseDown = false;
44192
44501
  this._noteMouseDown = false;
44193
44502
  this._selectionStart = null;
@@ -44426,47 +44735,6 @@
44426
44735
  *
44427
44736
  */
44428
44737
  this.noteMouseUp = new EventEmitterOfT();
44429
- /**
44430
- * This event is fired whenever a new song is loaded.
44431
- * @remarks
44432
- * This event is fired whenever a new song is loaded or changing due to {@link renderScore} or {@link renderTracks} calls.
44433
- * It is fired after the transposition midi pitches from the settings were applied, but before any midi is generated or rendering is started.
44434
- * This allows any modification of the score before further processing.
44435
- *
44436
- * @eventProperty
44437
- * @category Events - Core
44438
- * @since 0.9.4
44439
- *
44440
- * @example
44441
- * JavaScript
44442
- * ```js
44443
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44444
- * api.scoreLoaded.on((score) => {
44445
- * updateSongInformationInUi(score);
44446
- * });
44447
- * ```
44448
- *
44449
- * @example
44450
- * C#
44451
- * ```cs
44452
- * var api = new AlphaTabApi<MyControl>(...);
44453
- * api.ScoreLoaded.On(score =>
44454
- * {
44455
- * UpdateSongInformationInUi(score);
44456
- * });
44457
- * ```
44458
- *
44459
- * @example
44460
- * Android
44461
- * ```kotlin
44462
- * val api = AlphaTabApi<MyControl>(...)
44463
- * api.scoreLoaded.on { score ->
44464
- * updateSongInformationInUi(score)
44465
- * }
44466
- * ```
44467
- *
44468
- */
44469
- this.scoreLoaded = new EventEmitterOfT();
44470
44738
  /**
44471
44739
  * This event is fired when alphaTab was resized and is about to rerender the music notation.
44472
44740
  * @remarks
@@ -44725,46 +44993,6 @@
44725
44993
  *
44726
44994
  */
44727
44995
  this.midiLoad = new EventEmitterOfT();
44728
- /**
44729
- * This event is fired when the Midi file needed for playback was loaded.
44730
- *
44731
- * @eventProperty
44732
- * @category Events - Player
44733
- * @since 0.9.4
44734
- *
44735
- * @example
44736
- * JavaScript
44737
- * ```js
44738
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44739
- * api.midiLoaded.on(e => {
44740
- * hideGeneratingAudioIndicator();
44741
- * updateSongDuration(e.endTime);
44742
- * });
44743
- * ```
44744
- *
44745
- * @example
44746
- * C#
44747
- * ```cs
44748
- * var api = new AlphaTabApi<MyControl>(...);
44749
- * api.MidiLoaded.On(e =>
44750
- * {
44751
- * HideGeneratingAudioIndicator();
44752
- * UpdateSongDuration(e.EndTime);
44753
- * });
44754
- * ```
44755
- *
44756
- * @example
44757
- * Android
44758
- * ```kotlin
44759
- * val api = AlphaTabApi<MyControl>(...)
44760
- * api.midiLoaded.on { e ->
44761
- * hideGeneratingAudioIndicator()
44762
- * updateSongDuration(e.endTime)
44763
- * }
44764
- * ```
44765
- *
44766
- */
44767
- this.midiLoaded = new EventEmitterOfT();
44768
44996
  /**
44769
44997
  * This event is fired when a settings update was requested.
44770
44998
  *
@@ -44804,12 +45032,30 @@
44804
45032
  this.settingsUpdated = new EventEmitter();
44805
45033
  this.uiFacade = uiFacade;
44806
45034
  this.container = uiFacade.rootContainer;
45035
+ this.activeBeatsChanged = new EventEmitterOfT(() => {
45036
+ if (this._player.state === PlayerState.Playing && this._currentBeat) {
45037
+ return new ActiveBeatsChangedEventArgs(this._currentBeat.beatLookup.highlightedBeats.map(h => h.beat));
45038
+ }
45039
+ return null;
45040
+ });
45041
+ this.playedBeatChanged = new EventEmitterOfT(() => {
45042
+ if (this._player.state === PlayerState.Playing && this._currentBeat) {
45043
+ return this._currentBeat.beat;
45044
+ }
45045
+ return null;
45046
+ });
45047
+ this.scoreLoaded = new EventEmitterOfT(() => {
45048
+ if (this._score) {
45049
+ return this._score;
45050
+ }
45051
+ return null;
45052
+ });
45053
+ this.midiLoaded = new EventEmitterOfT(() => {
45054
+ return this._player.loadedMidiInfo ?? null;
45055
+ });
44807
45056
  uiFacade.initialize(this, settings);
44808
45057
  Logger.logLevel = this.settings.core.logLevel;
44809
- // backwards compatibility: remove in 2.0
44810
- if (this.settings.player.playerMode === exports.PlayerMode.Disabled && this.settings.player.enablePlayer) {
44811
- this.settings.player.playerMode = exports.PlayerMode.EnabledAutomatic;
44812
- }
45058
+ this.settings.handleBackwardsCompatibility();
44813
45059
  Environment.printEnvironmentInfo(false);
44814
45060
  this.canvasElement = uiFacade.createCanvasElement();
44815
45061
  this.container.appendChild(this.canvasElement);
@@ -44968,6 +45214,7 @@
44968
45214
  * ```
44969
45215
  */
44970
45216
  updateSettings() {
45217
+ this.settings.handleBackwardsCompatibility();
44971
45218
  const score = this.score;
44972
45219
  if (score) {
44973
45220
  ModelUtils.applyPitchOffsets(this.settings, score);
@@ -45775,6 +46022,22 @@
45775
46022
  set timePosition(value) {
45776
46023
  this._player.timePosition = value;
45777
46024
  }
46025
+ /**
46026
+ * The total length of the song in midi ticks.
46027
+ * @category Properties - Player
46028
+ * @since 1.6.2
46029
+ */
46030
+ get endTick() {
46031
+ return this._player.currentPosition.endTick;
46032
+ }
46033
+ /**
46034
+ * The total length of the song in milliseconds.
46035
+ * @category Properties - Player
46036
+ * @since 1.6.2
46037
+ */
46038
+ get endTime() {
46039
+ return this._player.currentPosition.endTime;
46040
+ }
45778
46041
  /**
45779
46042
  * The range of the song that should be played.
45780
46043
  * @remarks
@@ -48099,27 +48362,33 @@
48099
48362
  value: value
48100
48363
  });
48101
48364
  }
48365
+ get loadedMidiInfo() {
48366
+ return this.loadedMidiInfo;
48367
+ }
48368
+ get currentPosition() {
48369
+ return this._currentPosition;
48370
+ }
48102
48371
  get tickPosition() {
48103
- return this._tickPosition;
48372
+ return this._currentPosition.currentTick;
48104
48373
  }
48105
48374
  set tickPosition(value) {
48106
48375
  if (value < 0) {
48107
48376
  value = 0;
48108
48377
  }
48109
- this._tickPosition = value;
48378
+ this._currentPosition = new PositionChangedEventArgs(this._currentPosition.currentTime, this._currentPosition.endTime, value, this._currentPosition.endTick, true, this._currentPosition.originalTempo, this._currentPosition.modifiedTempo);
48110
48379
  this._synth.postMessage({
48111
48380
  cmd: 'alphaSynth.setTickPosition',
48112
48381
  value: value
48113
48382
  });
48114
48383
  }
48115
48384
  get timePosition() {
48116
- return this._timePosition;
48385
+ return this._currentPosition.currentTime;
48117
48386
  }
48118
48387
  set timePosition(value) {
48119
48388
  if (value < 0) {
48120
48389
  value = 0;
48121
48390
  }
48122
- this._timePosition = value;
48391
+ this._currentPosition = new PositionChangedEventArgs(value, this._currentPosition.endTime, this._currentPosition.currentTick, this._currentPosition.endTick, true, this._currentPosition.originalTempo, this._currentPosition.modifiedTempo);
48123
48392
  this._synth.postMessage({
48124
48393
  cmd: 'alphaSynth.setTimePosition',
48125
48394
  value: value
@@ -48162,11 +48431,10 @@
48162
48431
  this._metronomeVolume = 0;
48163
48432
  this._countInVolume = 0;
48164
48433
  this._playbackSpeed = 0;
48165
- this._tickPosition = 0;
48166
- this._timePosition = 0;
48167
48434
  this._isLooping = false;
48168
48435
  this._playbackRange = null;
48169
48436
  this._midiEventsPlayedFilter = [];
48437
+ this._currentPosition = new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
48170
48438
  this.ready = new EventEmitter();
48171
48439
  this.readyForPlayback = new EventEmitter();
48172
48440
  this.finished = new EventEmitter();
@@ -48185,8 +48453,6 @@
48185
48453
  this._masterVolume = 0.0;
48186
48454
  this._metronomeVolume = 0.0;
48187
48455
  this._playbackSpeed = 0.0;
48188
- this._tickPosition = 0;
48189
- this._timePosition = 0.0;
48190
48456
  this._isLooping = false;
48191
48457
  this._playbackRange = null;
48192
48458
  this._output = player;
@@ -48321,9 +48587,8 @@
48321
48587
  this.checkReadyForPlayback();
48322
48588
  break;
48323
48589
  case 'alphaSynth.positionChanged':
48324
- this._timePosition = data.currentTime;
48325
- this._tickPosition = data.currentTick;
48326
- this.positionChanged.trigger(new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo));
48590
+ this._currentPosition = new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo);
48591
+ this.positionChanged.trigger(this._currentPosition);
48327
48592
  break;
48328
48593
  case 'alphaSynth.midiEventsPlayed':
48329
48594
  this.midiEventsPlayed.trigger(new MidiEventsPlayedEventArgs(data.events.map(JsonConverter.jsObjectToMidiEvent)));
@@ -48347,7 +48612,8 @@
48347
48612
  break;
48348
48613
  case 'alphaSynth.midiLoaded':
48349
48614
  this.checkReadyForPlayback();
48350
- this.midiLoaded.trigger(new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo));
48615
+ this._loadedMidiInfo = new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek, data.originalTempo, data.modifiedTempo);
48616
+ this.midiLoaded.trigger(this._loadedMidiInfo);
48351
48617
  break;
48352
48618
  case 'alphaSynth.midiLoadFailed':
48353
48619
  this.checkReadyForPlayback();
@@ -59059,7 +59325,7 @@
59059
59325
  // - when the standard notation naturalizes the accidental from the key signature, the numbered notation has the reversed accidental
59060
59326
  const accidentalMode = note ? note.accidentalMode : NoteAccidentalMode.Default;
59061
59327
  const noteValue = AccidentalHelper.getNoteValue(note);
59062
- let accidentalToSet = AccidentalHelper.computeAccidental(this.renderer.bar.keySignature, accidentalMode, noteValue, note.hasQuarterToneOffset);
59328
+ let accidentalToSet = ModelUtils.computeAccidental(this.renderer.bar.keySignature, accidentalMode, noteValue, note.hasQuarterToneOffset);
59063
59329
  if (accidentalToSet === AccidentalType.Natural) {
59064
59330
  const ks = this.renderer.bar.keySignature;
59065
59331
  const ksi = ks + 7;
@@ -59218,7 +59484,7 @@
59218
59484
  ? AccidentalHelper.FlatNoteSteps
59219
59485
  : AccidentalHelper.SharpNoteSteps;
59220
59486
  let steps = stepList[index] + 1;
59221
- const hasAccidental = AccidentalHelper.AccidentalNotes[index];
59487
+ const hasAccidental = ModelUtils.AccidentalNotes[index];
59222
59488
  if (hasAccidental &&
59223
59489
  !this.container.preNotes.isNaturalizeAccidental) {
59224
59490
  if (ksi < 7) {
@@ -66431,6 +66697,30 @@
66431
66697
  if (stylesheet.useSystemSignSeparator) {
66432
66698
  writer.writeMeta('useSystemSignSeparator');
66433
66699
  }
66700
+ if (stylesheet.multiTrackMultiBarRest) {
66701
+ writer.writeMeta('multiBarRest');
66702
+ }
66703
+ if (stylesheet.singleTrackTrackNamePolicy !==
66704
+ AlphaTexExporter.DefaultScore.stylesheet.singleTrackTrackNamePolicy) {
66705
+ writer.writeMeta('singleTrackTrackNamePolicy', TrackNamePolicy[stylesheet.singleTrackTrackNamePolicy]);
66706
+ }
66707
+ if (stylesheet.multiTrackTrackNamePolicy !== AlphaTexExporter.DefaultScore.stylesheet.multiTrackTrackNamePolicy) {
66708
+ writer.writeMeta('multiTrackTrackNamePolicy', TrackNamePolicy[stylesheet.multiTrackTrackNamePolicy]);
66709
+ }
66710
+ if (stylesheet.firstSystemTrackNameMode !== AlphaTexExporter.DefaultScore.stylesheet.firstSystemTrackNameMode) {
66711
+ writer.writeMeta('firstSystemTrackNameMode', TrackNameMode[stylesheet.firstSystemTrackNameMode]);
66712
+ }
66713
+ if (stylesheet.otherSystemsTrackNameMode !== AlphaTexExporter.DefaultScore.stylesheet.otherSystemsTrackNameMode) {
66714
+ writer.writeMeta('otherSystemsTrackNameMode', TrackNameMode[stylesheet.otherSystemsTrackNameMode]);
66715
+ }
66716
+ if (stylesheet.firstSystemTrackNameOrientation !==
66717
+ AlphaTexExporter.DefaultScore.stylesheet.firstSystemTrackNameOrientation) {
66718
+ writer.writeMeta('firstSystemTrackNameOrientation', TrackNameOrientation[stylesheet.firstSystemTrackNameOrientation]);
66719
+ }
66720
+ if (stylesheet.otherSystemsTrackNameOrientation !==
66721
+ AlphaTexExporter.DefaultScore.stylesheet.otherSystemsTrackNameOrientation) {
66722
+ writer.writeMeta('otherSystemsTrackNameOrientation', TrackNameOrientation[stylesheet.otherSystemsTrackNameOrientation]);
66723
+ }
66434
66724
  }
66435
66725
  writeTrackTo(writer, track) {
66436
66726
  writer.write('\\track ');
@@ -66466,6 +66756,10 @@
66466
66756
  track.score.stylesheet.perTrackMultiBarRest.has(track.index)) {
66467
66757
  writer.writeLine(` multibarrest`);
66468
66758
  }
66759
+ writer.writeLine(` instrument ${track.isPercussion ? 'percussion' : GeneralMidi.getName(track.playbackInfo.program)}`);
66760
+ if (track.playbackInfo.bank > 0) {
66761
+ writer.writeLine(` bank ${track.playbackInfo.bank}`);
66762
+ }
66469
66763
  writer.outdent();
66470
66764
  writer.writeLine('}');
66471
66765
  writer.indent();
@@ -66524,13 +66818,7 @@
66524
66818
  writer.writeLine('|');
66525
66819
  }
66526
66820
  if (voiceIndex === 0) {
66527
- // Track meta on first bar
66528
66821
  let anyWritten = false;
66529
- if (bar.index === 0 && bar.staff.index === 0) {
66530
- const l = writer.tex.length;
66531
- this.writeTrackMetaTo(writer, bar.staff.track);
66532
- anyWritten = writer.tex.length > l;
66533
- }
66534
66822
  // Staff meta on first bar
66535
66823
  if (bar.index === 0) {
66536
66824
  const l = writer.tex.length;
@@ -66560,14 +66848,6 @@
66560
66848
  }
66561
66849
  writer.outdent();
66562
66850
  }
66563
- writeTrackMetaTo(writer, track) {
66564
- writer.writeSingleLineComment(`Track ${track.index + 1} Metadata`);
66565
- this.writePlaybackInfoTo(writer, track);
66566
- }
66567
- writePlaybackInfoTo(writer, track) {
66568
- const isPercussion = track.staves.some(s => s.isPercussion);
66569
- writer.writeMeta('instrument', isPercussion ? 'percussion' : GeneralMidi.getName(track.playbackInfo.program));
66570
- }
66571
66851
  writeStaffMetaTo(writer, staff) {
66572
66852
  writer.writeSingleLineComment(`Staff ${staff.index + 1} Metadata`);
66573
66853
  if (staff.capo !== 0) {
@@ -66600,6 +66880,7 @@
66600
66880
  if (staff.displayTranspositionPitch !== defaultTransposition) {
66601
66881
  writer.writeMeta('displaytranspose', `${-staff.displayTranspositionPitch}`);
66602
66882
  }
66883
+ writer.writeMeta('accidentals', 'auto');
66603
66884
  if (staff.chords != null) {
66604
66885
  for (const [_, chord] of staff.chords) {
66605
66886
  this.writeChordTo(writer, chord);
@@ -66967,6 +67248,18 @@
66967
67248
  writer.writeGroupItem('txt ');
66968
67249
  writer.writeString(beat.text);
66969
67250
  }
67251
+ if (beat.lyrics != null && beat.lyrics.length > 0) {
67252
+ if (beat.lyrics.length > 1) {
67253
+ for (let i = 0; i < beat.lyrics.length; i++) {
67254
+ writer.writeGroupItem(`lyrics ${i} `);
67255
+ writer.writeString(beat.lyrics[i]);
67256
+ }
67257
+ }
67258
+ else {
67259
+ writer.writeGroupItem('lyrics ');
67260
+ writer.writeString(beat.lyrics[0]);
67261
+ }
67262
+ }
66970
67263
  switch (beat.graceType) {
66971
67264
  case GraceType.OnBeat:
66972
67265
  writer.writeGroupItem('gr ob');
@@ -67601,7 +67894,7 @@
67601
67894
  this.writePitch(properties, 'ConcertPitch', 'C', '-1', '');
67602
67895
  }
67603
67896
  else {
67604
- this.writePitchForValue(properties, 'TransposedPitch', note.displayValueWithoutBend, note.accidentalMode);
67897
+ this.writePitchForValue(properties, 'TransposedPitch', note.displayValueWithoutBend, note.accidentalMode, note.beat.voice.bar.keySignature);
67605
67898
  }
67606
67899
  }
67607
67900
  writeConcertPitch(properties, note) {
@@ -67609,10 +67902,10 @@
67609
67902
  this.writePitch(properties, 'ConcertPitch', 'C', '-1', '');
67610
67903
  }
67611
67904
  else {
67612
- this.writePitchForValue(properties, 'ConcertPitch', note.realValueWithoutHarmonic, note.accidentalMode);
67905
+ this.writePitchForValue(properties, 'ConcertPitch', note.realValueWithoutHarmonic, note.accidentalMode, note.beat.voice.bar.keySignature);
67613
67906
  }
67614
67907
  }
67615
- writePitchForValue(properties, propertyName, value, accidentalMode) {
67908
+ writePitchForValue(properties, propertyName, value, accidentalMode, keySignature) {
67616
67909
  let index = 0;
67617
67910
  let octave = 0;
67618
67911
  let step = '';
@@ -67620,8 +67913,25 @@
67620
67913
  const updateParts = () => {
67621
67914
  index = value % 12;
67622
67915
  octave = (value / 12) | 0;
67623
- step = Tuning.defaultSteps[index];
67624
- accidental = Tuning.defaultAccidentals[index];
67916
+ step = GpifWriter.defaultSteps[index];
67917
+ switch (ModelUtils.computeAccidental(keySignature, NoteAccidentalMode.Default, value, false)) {
67918
+ case AccidentalType.None:
67919
+ case AccidentalType.Natural:
67920
+ accidental = '';
67921
+ break;
67922
+ case AccidentalType.Sharp:
67923
+ accidental = '#';
67924
+ break;
67925
+ case AccidentalType.Flat:
67926
+ accidental = 'b';
67927
+ break;
67928
+ case AccidentalType.DoubleSharp:
67929
+ accidental = 'x';
67930
+ break;
67931
+ case AccidentalType.DoubleFlat:
67932
+ accidental = 'bb';
67933
+ break;
67934
+ }
67625
67935
  };
67626
67936
  updateParts();
67627
67937
  switch (accidentalMode) {
@@ -68096,10 +68406,12 @@
68096
68406
  const value = syncPointAutomation.addElement('Value');
68097
68407
  value.addElement('BarIndex').innerText = mb.index.toString();
68098
68408
  value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue.barOccurence.toString();
68099
- value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint).syncBpm.toString();
68409
+ value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value
68410
+ .get(syncPoint)
68411
+ .syncBpm.toString();
68100
68412
  value.addElement('OriginalTempo').innerText = score.tempo.toString();
68101
- let frameOffset = (((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
68102
- GpifWriter.SampleRate);
68413
+ let frameOffset = ((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
68414
+ GpifWriter.SampleRate;
68103
68415
  frameOffset = Math.floor(frameOffset + 0.5);
68104
68416
  value.addElement('FrameOffset').innerText = frameOffset.toString();
68105
68417
  }
@@ -68158,15 +68470,16 @@
68158
68470
  }
68159
68471
  return GpifIconIds.SteelGuitar;
68160
68472
  }
68161
- writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, barIndex, program, ratioPosition = 0) {
68473
+ writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, barIndex, program, bank, ratioPosition = 0) {
68162
68474
  const soundNode = soundsNode.addElement('Sound');
68163
68475
  soundNode.addElement('Name').setCData(name);
68164
68476
  soundNode.addElement('Label').setCData(name);
68165
68477
  soundNode.addElement('Path').setCData(path);
68166
68478
  soundNode.addElement('Role').setCData(role);
68167
68479
  const midi = soundNode.addElement('MIDI');
68168
- midi.addElement('LSB').innerText = '0';
68169
- midi.addElement('MSB').innerText = '0';
68480
+ const lsbMsb = GeneralMidi.bankToLsbMsb(bank);
68481
+ midi.addElement('LSB').innerText = lsbMsb[0].toString();
68482
+ midi.addElement('MSB').innerText = lsbMsb[1].toString();
68170
68483
  midi.addElement('Program').innerText = program.toString();
68171
68484
  const automationNode = automationsNode.addElement('Automation');
68172
68485
  automationNode.addElement('Type').innerText = 'Sound';
@@ -68184,21 +68497,26 @@
68184
68497
  const trackSoundPath = `Midi/${track.playbackInfo.program}`;
68185
68498
  const trackSoundRole = 'Factory';
68186
68499
  let trackSoundWritten = false;
68500
+ let bank = track.playbackInfo.bank;
68187
68501
  for (const staff of track.staves) {
68188
68502
  for (const bar of staff.bars) {
68189
68503
  for (const voice of bar.voices) {
68190
68504
  for (const beat of voice.beats) {
68191
68505
  const soundAutomation = beat.getAutomation(AutomationType.Instrument);
68192
68506
  const isTrackSound = bar.index === 0 && beat.index === 0;
68507
+ const bankAutomation = beat.getAutomation(AutomationType.Bank);
68508
+ if (bankAutomation) {
68509
+ bank = bankAutomation.value;
68510
+ }
68193
68511
  if (soundAutomation) {
68194
68512
  const name = isTrackSound ? trackSoundName : `ProgramChange_${beat.id}`;
68195
68513
  const path = isTrackSound ? trackSoundPath : `Midi/${soundAutomation.value}`;
68196
68514
  const role = isTrackSound ? trackSoundRole : 'User';
68197
68515
  if (!isTrackSound && !trackSoundWritten) {
68198
- this.writeSoundAndAutomation(soundsNode, automationsNode, trackSoundName, trackSoundPath, trackSoundRole, track.staves[0].bars[0].index, track.playbackInfo.program);
68516
+ this.writeSoundAndAutomation(soundsNode, automationsNode, trackSoundName, trackSoundPath, trackSoundRole, track.staves[0].bars[0].index, track.playbackInfo.program, track.playbackInfo.bank);
68199
68517
  trackSoundWritten = true;
68200
68518
  }
68201
- this.writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, bar.index, soundAutomation.value, soundAutomation.ratioPosition);
68519
+ this.writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, bar.index, soundAutomation.value, bank, soundAutomation.ratioPosition);
68202
68520
  if (isTrackSound) {
68203
68521
  trackSoundWritten = true;
68204
68522
  }
@@ -68581,8 +68899,13 @@
68581
68899
  writeMasterBarNode(parent, masterBar) {
68582
68900
  const masterBarNode = parent.addElement('MasterBar');
68583
68901
  const key = masterBarNode.addElement('Key');
68584
- key.addElement('AccidentalCount').innerText = masterBar.keySignature.toString();
68585
- key.addElement('Mode').innerText = KeySignatureType[masterBar.keySignatureType];
68902
+ let keySignature = masterBar.score.tracks[0].staves[0].bars[masterBar.index].keySignature;
68903
+ const keySignatureType = masterBar.score.tracks[0].staves[0].bars[masterBar.index].keySignatureType;
68904
+ // reverse transpose
68905
+ const transposeIndex = ModelUtils.flooredDivision(masterBar.score.tracks[0].staves[0].displayTranspositionPitch, 12);
68906
+ keySignature = ModelUtils.transposeKey(keySignature, -transposeIndex);
68907
+ key.addElement('AccidentalCount').innerText = keySignature.toString();
68908
+ key.addElement('Mode').innerText = KeySignatureType[keySignatureType];
68586
68909
  key.addElement('Sharps').innerText = 'Sharps';
68587
68910
  masterBarNode.addElement('Time').innerText =
68588
68911
  `${masterBar.timeSignatureNumerator}/${masterBar.timeSignatureDenominator}`;
@@ -68892,6 +69215,7 @@
68892
69215
  [127, new GpifMidiProgramInfo(GpifIconIds.Fx, 'Timpani')]
68893
69216
  ]);
68894
69217
  GpifWriter.DrumKitProgramInfo = new GpifMidiProgramInfo(GpifIconIds.PercussionKit, 'Drums', 'drumKit');
69218
+ GpifWriter.defaultSteps = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'];
68895
69219
 
68896
69220
  /**
68897
69221
  * CRC-32 with reversed data and unreversed output
@@ -70770,6 +71094,7 @@
70770
71094
  SustainPedalMarker,
70771
71095
  get SustainPedalMarkerType () { return SustainPedalMarkerType; },
70772
71096
  SyncPointData,
71097
+ get TechniqueSymbolPlacement () { return TechniqueSymbolPlacement; },
70773
71098
  Track,
70774
71099
  get TrackNameMode () { return TrackNameMode; },
70775
71100
  get TrackNameOrientation () { return TrackNameOrientation; },