@coderline/alphatab 1.7.0-alpha.1517 → 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.1517 (develop, build 1517)
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.1517';
207
- VersionInfo.date = '2025-08-19T02:07:05.813Z';
208
- VersionInfo.commit = 'a8c49344e3fef6f72a38d5493a2e58223e2c777b';
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
@@ -8167,6 +8179,10 @@
8167
8179
  * Gets or sets the midi program to use.
8168
8180
  */
8169
8181
  this.program = 0;
8182
+ /**
8183
+ * The midi bank to use.
8184
+ */
8185
+ this.bank = 0;
8170
8186
  /**
8171
8187
  * Gets or sets the primary channel for all normal midi events.
8172
8188
  */
@@ -8561,6 +8577,12 @@
8561
8577
  */
8562
8578
  this.percussionArticulations = [];
8563
8579
  }
8580
+ /**
8581
+ * Gets whether this track is a percussion track.
8582
+ */
8583
+ get isPercussion() {
8584
+ return this.staves.some(s => s.isPercussion);
8585
+ }
8564
8586
  /**
8565
8587
  * Adds a new line break.
8566
8588
  * @param index The index of the bar before which a line break should happen.
@@ -10225,33 +10247,15 @@
10225
10247
  }
10226
10248
  return StaffMetaResult.KnownStaffMeta;
10227
10249
  case 'instrument':
10228
- this.sy = this.newSy();
10229
10250
  this._staffTuningApplied = false;
10230
- if (this.sy === AlphaTexSymbols.Number) {
10231
- const instrument = this.syData;
10232
- if (instrument >= 0 && instrument <= 127) {
10233
- this._currentTrack.playbackInfo.program = this.syData;
10234
- }
10235
- else {
10236
- this.error('instrument', AlphaTexSymbols.Number, false);
10237
- }
10238
- }
10239
- else if (this.sy === AlphaTexSymbols.String) {
10240
- const instrumentName = this.syData.toLowerCase();
10241
- if (instrumentName === 'percussion') {
10242
- for (const staff of this._currentTrack.staves) {
10243
- this.applyPercussionStaff(staff);
10244
- }
10245
- this._currentTrack.playbackInfo.primaryChannel = SynthConstants.PercussionChannel;
10246
- this._currentTrack.playbackInfo.secondaryChannel = SynthConstants.PercussionChannel;
10247
- }
10248
- else {
10249
- this._currentTrack.playbackInfo.program = GeneralMidi.getValue(instrumentName);
10250
- }
10251
- }
10252
- else {
10253
- 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);
10254
10257
  }
10258
+ this._currentTrack.playbackInfo.bank = this.syData;
10255
10259
  this.sy = this.newSy();
10256
10260
  return StaffMetaResult.KnownStaffMeta;
10257
10261
  case 'lyrics':
@@ -10362,6 +10366,35 @@
10362
10366
  return StaffMetaResult.UnknownStaffMeta;
10363
10367
  }
10364
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
+ }
10365
10398
  handleAccidentalMode() {
10366
10399
  this.sy = this.newSy();
10367
10400
  if (this.sy !== AlphaTexSymbols.String) {
@@ -10634,6 +10667,17 @@
10634
10667
  }
10635
10668
  this._score.stylesheet.perTrackMultiBarRest.add(this._currentTrack.index);
10636
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;
10637
10681
  default:
10638
10682
  this.error('track-properties', AlphaTexSymbols.String, false);
10639
10683
  break;
@@ -11434,6 +11478,17 @@
11434
11478
  automation.value = program;
11435
11479
  beat.automations.push(automation);
11436
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
+ }
11437
11492
  else if (syData === 'fermata') {
11438
11493
  this.sy = this.newSy();
11439
11494
  if (this.sy !== AlphaTexSymbols.String) {
@@ -11444,7 +11499,7 @@
11444
11499
  this.sy = this.newSy(true);
11445
11500
  if (this.sy === AlphaTexSymbols.Number) {
11446
11501
  fermata.length = this.syData;
11447
- this.sy = this.newSy(true);
11502
+ this.sy = this.newSy();
11448
11503
  }
11449
11504
  beat.fermata = fermata;
11450
11505
  return true;
@@ -11477,13 +11532,9 @@
11477
11532
  beat.beamingMode = BeatBeamingMode.ForceSplitOnSecondaryToNext;
11478
11533
  break;
11479
11534
  }
11480
- this.sy = this.newSy();
11481
- return true;
11482
11535
  }
11483
11536
  else if (syData === 'timer') {
11484
11537
  beat.showTimer = true;
11485
- this.sy = this.newSy();
11486
- return true;
11487
11538
  }
11488
11539
  else {
11489
11540
  // string didn't match any beat effect syntax
@@ -15043,6 +15094,7 @@
15043
15094
  this._trackCount = 0;
15044
15095
  this._playbackInfos = [];
15045
15096
  this._doubleBars = new Set();
15097
+ this._clefsPerTrack = new Map();
15046
15098
  this._keySignatures = new Map();
15047
15099
  this._beatTextChunksByTrack = new Map();
15048
15100
  this._directionLookup = new Map();
@@ -15427,26 +15479,74 @@
15427
15479
  mainStaff.capo = IOHelper.readInt32LE(this.data);
15428
15480
  newTrack.color = GpBinaryHelpers.gpReadColor(this.data, false);
15429
15481
  if (this._versionNumber >= 500) {
15430
- const staveFlags = this.data.readByte();
15431
- mainStaff.showTablature = (staveFlags & 0x01) !== 0;
15432
- mainStaff.showStandardNotation = (staveFlags & 0x02) !== 0;
15433
- 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;
15434
15486
  if (this._score.stylesheet.perTrackChordDiagramsOnTop === null) {
15435
15487
  this._score.stylesheet.perTrackChordDiagramsOnTop = new Map();
15436
15488
  }
15437
15489
  this._score.stylesheet.perTrackChordDiagramsOnTop.set(newTrack.index, showChordDiagramListOnTopOfScore);
15438
- // flags for
15490
+ // MIDI: Automatic
15491
+ // 0x01 -> always set (unknown)
15439
15492
  // 0x02 -> auto let ring
15440
15493
  // 0x04 -> auto brush
15441
15494
  this.data.readByte();
15442
- // unknown
15443
- 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
+ }
15444
15542
  }
15445
- // unknown
15446
- if (this._versionNumber >= 510) {
15447
- this.data.skip(4);
15448
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15449
- 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
+ }
15450
15550
  }
15451
15551
  }
15452
15552
  readBars() {
@@ -15462,6 +15562,9 @@
15462
15562
  if (mainStaff.isPercussion) {
15463
15563
  newBar.clef = Clef.Neutral;
15464
15564
  }
15565
+ else if (this._clefsPerTrack.has(track.index)) {
15566
+ newBar.clef = this._clefsPerTrack.get(track.index);
15567
+ }
15465
15568
  mainStaff.addBar(newBar);
15466
15569
  if (this._keySignatures.has(newBar.index)) {
15467
15570
  const newKeySignature = this._keySignatures.get(newBar.index);
@@ -15883,11 +15986,29 @@
15883
15986
  return 0;
15884
15987
  }
15885
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
+ }
15886
16005
  readMixTableChange(beat) {
15887
16006
  const tableChange = new MixTableChange();
15888
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
15889
16010
  if (this._versionNumber >= 500) {
15890
- this.data.skip(16); // Rse Info
16011
+ this.readRseBank();
15891
16012
  }
15892
16013
  tableChange.volume = IOHelper.readSInt8(this.data);
15893
16014
  tableChange.balance = IOHelper.readSInt8(this.data);
@@ -15899,7 +16020,7 @@
15899
16020
  tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
15900
16021
  }
15901
16022
  tableChange.tempo = IOHelper.readInt32LE(this.data);
15902
- // durations
16023
+ // durations (in number of beats)
15903
16024
  if (tableChange.volume >= 0) {
15904
16025
  this.data.readByte();
15905
16026
  }
@@ -16882,9 +17003,7 @@
16882
17003
  return writer.toArray();
16883
17004
  }
16884
17005
  static addHeaderAndFooter(binaryStylesheet, style, prefix, name) {
16885
- if (style.template !== undefined) {
16886
- binaryStylesheet.addValue(`${prefix}${name}`, style.template, DataType.String);
16887
- }
17006
+ binaryStylesheet.addValue(`${prefix}${name}`, style.template, DataType.String);
16888
17007
  binaryStylesheet.addValue(`${prefix}${name}Alignment`, style.textAlign, DataType.Integer);
16889
17008
  if (style.isVisible !== undefined) {
16890
17009
  binaryStylesheet.addValue(`${prefix}draw${name}`, style.isVisible, DataType.Boolean);
@@ -16918,6 +17037,7 @@
16918
17037
  this.path = '';
16919
17038
  this.role = '';
16920
17039
  this.program = 0;
17040
+ this.bank = 0;
16921
17041
  }
16922
17042
  get uniqueId() {
16923
17043
  return `${this.path};${this.name};${this.role}`;
@@ -17272,21 +17392,28 @@
17272
17392
  if (!type) {
17273
17393
  return;
17274
17394
  }
17275
- let automation = null;
17395
+ const newAutomations = [];
17276
17396
  switch (type) {
17277
17397
  case 'Tempo':
17278
- automation = Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference);
17398
+ newAutomations.push(Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference));
17279
17399
  break;
17280
17400
  case 'SyncPoint':
17281
- automation = new Automation();
17282
- automation.type = AutomationType.SyncPoint;
17283
- automation.isLinear = isLinear;
17284
- automation.ratioPosition = ratioPosition;
17285
- 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);
17286
17407
  break;
17287
17408
  case 'Sound':
17288
17409
  if (textValue && sounds && sounds.has(textValue)) {
17289
- 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);
17290
17417
  }
17291
17418
  break;
17292
17419
  case 'SustainPedal':
@@ -17314,15 +17441,19 @@
17314
17441
  }
17315
17442
  break;
17316
17443
  }
17317
- if (automation) {
17444
+ if (newAutomations.length) {
17318
17445
  if (text) {
17319
- automation.text = text;
17446
+ for (const a of newAutomations) {
17447
+ a.text = text;
17448
+ }
17320
17449
  }
17321
17450
  if (barIndex >= 0) {
17322
17451
  if (!automations.has(barIndex)) {
17323
17452
  automations.set(barIndex, []);
17324
17453
  }
17325
- automations.get(barIndex).push(automation);
17454
+ for (const a of newAutomations) {
17455
+ automations.get(barIndex).push(a);
17456
+ }
17326
17457
  }
17327
17458
  }
17328
17459
  }
@@ -17914,22 +18045,31 @@
17914
18045
  break;
17915
18046
  }
17916
18047
  }
17917
- if (sound.role === 'Factory' || track.playbackInfo.program === 0) {
17918
- track.playbackInfo.program = sound.program;
17919
- }
17920
18048
  if (!this._soundsByTrack.has(trackId)) {
17921
18049
  this._soundsByTrack.set(trackId, new Map());
18050
+ // apply first sound
18051
+ track.playbackInfo.program = sound.program;
18052
+ track.playbackInfo.bank = sound.bank;
17922
18053
  }
17923
18054
  this._soundsByTrack.get(trackId).set(sound.uniqueId, sound);
17924
18055
  }
17925
18056
  parseSoundMidi(sound, node) {
18057
+ let bankMsb = 0;
18058
+ let bankLsb = 0;
17926
18059
  for (const c of node.childElements()) {
17927
18060
  switch (c.localName) {
17928
18061
  case 'Program':
17929
18062
  sound.program = GpifParser.parseIntSafe(c.innerText, 0);
17930
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;
17931
18070
  }
17932
18071
  }
18072
+ sound.bank = ((bankMsb & 0x7f) << 7) | bankLsb;
17933
18073
  }
17934
18074
  parsePartSounding(trackId, track, node) {
17935
18075
  for (const c of node.childElements()) {
@@ -19383,7 +19523,12 @@
19383
19523
  for (const a of automations) {
19384
19524
  // NOTE: currently the automations of a bar are applied to the
19385
19525
  // first beat of a bar
19386
- 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
+ }
19387
19532
  }
19388
19533
  }
19389
19534
  }
@@ -20403,6 +20548,10 @@
20403
20548
  * The midi channel program to use when playing the note (-1 if using the default track program).
20404
20549
  */
20405
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;
20406
20555
  /**
20407
20556
  * The volume to use when playing the note (-1 if using the default track volume).
20408
20557
  */
@@ -20901,6 +21050,9 @@
20901
21050
  if (trackInfo.firstArticulation.outputMidiProgram >= 0) {
20902
21051
  track.playbackInfo.program = trackInfo.firstArticulation.outputMidiProgram;
20903
21052
  }
21053
+ if (trackInfo.firstArticulation.outputMidiBank >= 0) {
21054
+ track.playbackInfo.bank = trackInfo.firstArticulation.outputMidiBank;
21055
+ }
20904
21056
  if (trackInfo.firstArticulation.outputBalance >= 0) {
20905
21057
  track.playbackInfo.balance = trackInfo.firstArticulation.outputBalance;
20906
21058
  }
@@ -20932,7 +21084,9 @@
20932
21084
  articulation.outputMidiChannel = Number.parseInt(c.innerText) - 1;
20933
21085
  break;
20934
21086
  // case 'midi-name': Ignored
20935
- // 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;
20936
21090
  case 'midi-program':
20937
21091
  articulation.outputMidiProgram = Number.parseInt(c.innerText) - 1;
20938
21092
  break;
@@ -21462,7 +21616,15 @@
21462
21616
  switch (c.localName) {
21463
21617
  // case 'midi-channel': Ignored
21464
21618
  // case 'midi-name': Ignored
21465
- // 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;
21466
21628
  case 'midi-program':
21467
21629
  if (!this._nextBeatAutomations) {
21468
21630
  this._nextBeatAutomations = [];
@@ -23733,11 +23895,15 @@
23733
23895
  }
23734
23896
 
23735
23897
  class EventEmitter {
23736
- constructor() {
23898
+ constructor(fireOnRegister = undefined) {
23737
23899
  this._listeners = [];
23900
+ this._fireOnRegister = fireOnRegister;
23738
23901
  }
23739
23902
  on(value) {
23740
23903
  this._listeners.push(value);
23904
+ if (this._fireOnRegister?.()) {
23905
+ value();
23906
+ }
23741
23907
  return () => {
23742
23908
  this.off(value);
23743
23909
  };
@@ -23755,11 +23921,18 @@
23755
23921
  * @partial
23756
23922
  */
23757
23923
  class EventEmitterOfT {
23758
- constructor() {
23924
+ constructor(fireOnRegister = undefined) {
23759
23925
  this._listeners = [];
23926
+ this._fireOnRegister = fireOnRegister;
23760
23927
  }
23761
23928
  on(value) {
23762
23929
  this._listeners.push(value);
23930
+ if (this._fireOnRegister) {
23931
+ const arg = this._fireOnRegister();
23932
+ if (arg !== null) {
23933
+ value(arg);
23934
+ }
23935
+ }
23763
23936
  return () => {
23764
23937
  this.off(value);
23765
23938
  };
@@ -30800,6 +30973,12 @@
30800
30973
  this.sequencer.playbackSpeed = value;
30801
30974
  this.timePosition = this.timePosition * (oldSpeed / value);
30802
30975
  }
30976
+ get loadedMidiInfo() {
30977
+ return this._loadedMidiInfo;
30978
+ }
30979
+ get currentPosition() {
30980
+ return this._currentPosition;
30981
+ }
30803
30982
  get tickPosition() {
30804
30983
  return this._tickPosition;
30805
30984
  }
@@ -30857,22 +31036,38 @@
30857
31036
  this._midiEventsPlayedFilter = new Set();
30858
31037
  this._notPlayedSamples = 0;
30859
31038
  this._synthStopping = false;
31039
+ this._currentPosition = new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
30860
31040
  this.isReady = false;
30861
31041
  this.state = PlayerState.Paused;
30862
31042
  this._loadedSoundFonts = [];
30863
- this.ready = new EventEmitter();
30864
31043
  this.readyForPlayback = new EventEmitter();
30865
31044
  this.finished = new EventEmitter();
30866
31045
  this.soundFontLoaded = new EventEmitter();
30867
31046
  this.soundFontLoadFailed = new EventEmitterOfT();
30868
- this.midiLoaded = new EventEmitterOfT();
30869
31047
  this.midiLoadFailed = new EventEmitterOfT();
30870
- this.stateChanged = new EventEmitterOfT();
30871
- this.positionChanged = new EventEmitterOfT();
30872
31048
  this.midiEventsPlayed = new EventEmitterOfT();
30873
- this.playbackRangeChanged = new EventEmitterOfT();
30874
31049
  Logger.debug('AlphaSynth', 'Initializing player');
30875
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
+ });
30876
31071
  Logger.debug('AlphaSynth', 'Creating output');
30877
31072
  this._output = output;
30878
31073
  Logger.debug('AlphaSynth', 'Creating synthesizer');
@@ -31050,7 +31245,8 @@
31050
31245
  Logger.debug('AlphaSynth', 'Loading midi from model');
31051
31246
  this.sequencer.loadMidi(midi);
31052
31247
  this._isMidiLoaded = true;
31053
- 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);
31054
31250
  Logger.debug('AlphaSynth', 'Midi successfully loaded');
31055
31251
  this.checkReadyForPlayback();
31056
31252
  this.tickPosition = 0;
@@ -31153,23 +31349,28 @@
31153
31349
  this.sequencer.resetOneTimeMidi();
31154
31350
  this.timePosition = this.sequencer.currentTime;
31155
31351
  }
31156
- updateTimePosition(timePosition, isSeek) {
31157
- // update the real positions
31158
- let currentTime = timePosition;
31159
- 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;
31160
31355
  let currentTick = this.sequencer.currentTimePositionToTickPosition(currentTime);
31161
- this._tickPosition = currentTick;
31162
31356
  const endTime = this.sequencer.currentEndTime;
31163
31357
  const endTick = this.sequencer.currentEndTick;
31164
- // on fade outs we can have some milliseconds longer, ensure we don't report this
31165
31358
  if (currentTime > endTime) {
31166
31359
  currentTime = endTime;
31167
31360
  currentTick = endTick;
31168
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;
31169
31369
  const mode = this.sequencer.isPlayingMain ? 'main' : this.sequencer.isPlayingCountIn ? 'count-in' : 'one-time';
31170
- 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})`);
31171
31371
  if (this.sequencer.isPlayingMain) {
31172
- 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);
31173
31374
  }
31174
31375
  // build events which were actually played
31175
31376
  if (isSeek) {
@@ -31177,7 +31378,7 @@
31177
31378
  }
31178
31379
  else {
31179
31380
  const playedEvents = [];
31180
- while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < currentTime) {
31381
+ while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < args.currentTime) {
31181
31382
  const synthEvent = this._playedEventsQueue.dequeue();
31182
31383
  playedEvents.push(synthEvent.event);
31183
31384
  }
@@ -36628,6 +36829,15 @@
36628
36829
  fillFromJson(json) {
36629
36830
  SettingsSerializer.fromJson(this, json);
36630
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
+ }
36631
36841
  }
36632
36842
 
36633
36843
  class SectionSerializer {
@@ -37845,6 +38055,7 @@
37845
38055
  o.set("balance", obj.balance);
37846
38056
  o.set("port", obj.port);
37847
38057
  o.set("program", obj.program);
38058
+ o.set("bank", obj.bank);
37848
38059
  o.set("primarychannel", obj.primaryChannel);
37849
38060
  o.set("secondarychannel", obj.secondaryChannel);
37850
38061
  o.set("ismute", obj.isMute);
@@ -37865,6 +38076,9 @@
37865
38076
  case "program":
37866
38077
  obj.program = v;
37867
38078
  return true;
38079
+ case "bank":
38080
+ obj.bank = v;
38081
+ return true;
37868
38082
  case "primarychannel":
37869
38083
  obj.primaryChannel = v;
37870
38084
  return true;
@@ -41523,6 +41737,11 @@
41523
41737
  this._programsPerChannel.set(channel, program);
41524
41738
  }
41525
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
+ }
41526
41745
  static buildTranspositionPitches(score, settings) {
41527
41746
  const transpositionPitches = new Map();
41528
41747
  for (const track of score.tracks) {
@@ -41550,6 +41769,7 @@
41550
41769
  // Set PitchBendRangeCoarse to 12
41551
41770
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryFine, 0);
41552
41771
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
41772
+ this.addBankChange(track, 0, channel, playbackInfo.bank);
41553
41773
  this.addProgramChange(track, 0, channel, playbackInfo.program);
41554
41774
  }
41555
41775
  /**
@@ -42845,6 +43065,10 @@
42845
43065
  this.addProgramChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, (automation.value | 0) & 0xff);
42846
43066
  this.addProgramChange(beat.voice.bar.staff.track, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.secondaryChannel, (automation.value | 0) & 0xff);
42847
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;
42848
43072
  case AutomationType.Balance:
42849
43073
  const balance = MidiFileGenerator.toChannelShort(automation.value);
42850
43074
  this._handler.addControlChange(beat.voice.bar.staff.track.index, beat.playbackStart + startMove, beat.voice.bar.staff.track.playbackInfo.primaryChannel, ControllerType.PanCoarse, balance);
@@ -43794,17 +44018,29 @@
43794
44018
  this._playbackSpeed = 1;
43795
44019
  this._isLooping = false;
43796
44020
  this._midiEventsPlayedFilter = [];
43797
- this.ready = new EventEmitter();
43798
- this.readyForPlayback = new EventEmitter();
43799
44021
  this.finished = new EventEmitter();
43800
44022
  this.soundFontLoaded = new EventEmitter();
43801
44023
  this.soundFontLoadFailed = new EventEmitterOfT();
43802
- this.midiLoaded = new EventEmitterOfT();
43803
44024
  this.midiLoadFailed = new EventEmitterOfT();
43804
- this.stateChanged = new EventEmitterOfT();
43805
- this.positionChanged = new EventEmitterOfT();
43806
44025
  this.midiEventsPlayed = new EventEmitterOfT();
43807
- 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
+ });
43808
44044
  }
43809
44045
  get instance() {
43810
44046
  return this._instance;
@@ -43826,10 +44062,14 @@
43826
44062
  newUnregister.push(value.finished.on(() => this.finished.trigger()));
43827
44063
  newUnregister.push(value.soundFontLoaded.on(() => this.soundFontLoaded.trigger()));
43828
44064
  newUnregister.push(value.soundFontLoadFailed.on(e => this.soundFontLoadFailed.trigger(e)));
43829
- newUnregister.push(value.midiLoaded.on(e => this.midiLoaded.trigger(e)));
44065
+ newUnregister.push(value.midiLoaded.on(e => {
44066
+ this.midiLoaded.trigger(e);
44067
+ }));
43830
44068
  newUnregister.push(value.midiLoadFailed.on(e => this.midiLoadFailed.trigger(e)));
43831
44069
  newUnregister.push(value.stateChanged.on(e => this.stateChanged.trigger(e)));
43832
- newUnregister.push(value.positionChanged.on(e => this.positionChanged.trigger(e)));
44070
+ newUnregister.push(value.positionChanged.on(e => {
44071
+ this.positionChanged.trigger(e);
44072
+ }));
43833
44073
  newUnregister.push(value.midiEventsPlayed.on(e => this.midiEventsPlayed.trigger(e)));
43834
44074
  newUnregister.push(value.playbackRangeChanged.on(e => this.playbackRangeChanged.trigger(e)));
43835
44075
  this._instanceEventUnregister = newUnregister;
@@ -43908,6 +44148,14 @@
43908
44148
  this._instance.playbackSpeed = value;
43909
44149
  }
43910
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
+ }
43911
44159
  get tickPosition() {
43912
44160
  return this._instance ? this._instance.tickPosition : 0;
43913
44161
  }
@@ -44249,83 +44497,6 @@
44249
44497
  this._previousStateForCursor = PlayerState.Paused;
44250
44498
  this._previousCursorCache = null;
44251
44499
  this._lastScroll = 0;
44252
- /**
44253
- * This event is fired when the played beat changed.
44254
- *
44255
- * @eventProperty
44256
- * @category Events - Player
44257
- * @since 0.9.4
44258
- *
44259
- * @example
44260
- * JavaScript
44261
- * ```js
44262
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44263
- * api.playedBeatChanged.on((beat) => {
44264
- * updateFretboard(beat);
44265
- * });
44266
- * ```
44267
- *
44268
- * @example
44269
- * C#
44270
- * ```cs
44271
- * var api = new AlphaTabApi<MyControl>(...);
44272
- * api.PlayedBeatChanged.On(beat =>
44273
- * {
44274
- * UpdateFretboard(beat);
44275
- * });
44276
- * ```
44277
- *
44278
- * @example
44279
- * Android
44280
- * ```kotlin
44281
- * val api = AlphaTabApi<MyControl>(...)
44282
- * api.playedBeatChanged.on { beat ->
44283
- * updateFretboard(beat)
44284
- * }
44285
- * ```
44286
- *
44287
- */
44288
- this.playedBeatChanged = new EventEmitterOfT();
44289
- /**
44290
- * This event is fired when the currently active beats across all tracks change.
44291
- *
44292
- * @remarks
44293
- * Unlike the {@link playedBeatChanged} event this event contains the beats of all tracks and voices independent of them being rendered.
44294
- *
44295
- * @eventProperty
44296
- * @category Events - Player
44297
- * @since 1.2.3
44298
- *
44299
- * @example
44300
- * JavaScript
44301
- * ```js
44302
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44303
- * api.activeBeatsChanged.on(args => {
44304
- * updateHighlights(args.activeBeats);
44305
- * });
44306
- * ```
44307
- *
44308
- * @example
44309
- * C#
44310
- * ```cs
44311
- * var api = new AlphaTabApi<MyControl>(...);
44312
- * api.ActiveBeatsChanged.On(args =>
44313
- * {
44314
- * UpdateHighlights(args.ActiveBeats);
44315
- * });
44316
- * ```
44317
- *
44318
- * @example
44319
- * Android
44320
- * ```kotlin
44321
- * val api = AlphaTabApi<MyControl>(...)
44322
- * api.activeBeatsChanged.on { args ->
44323
- * updateHighlights(args.activeBeats)
44324
- * }
44325
- * ```
44326
- *
44327
- */
44328
- this.activeBeatsChanged = new EventEmitterOfT();
44329
44500
  this._beatMouseDown = false;
44330
44501
  this._noteMouseDown = false;
44331
44502
  this._selectionStart = null;
@@ -44564,47 +44735,6 @@
44564
44735
  *
44565
44736
  */
44566
44737
  this.noteMouseUp = new EventEmitterOfT();
44567
- /**
44568
- * This event is fired whenever a new song is loaded.
44569
- * @remarks
44570
- * This event is fired whenever a new song is loaded or changing due to {@link renderScore} or {@link renderTracks} calls.
44571
- * It is fired after the transposition midi pitches from the settings were applied, but before any midi is generated or rendering is started.
44572
- * This allows any modification of the score before further processing.
44573
- *
44574
- * @eventProperty
44575
- * @category Events - Core
44576
- * @since 0.9.4
44577
- *
44578
- * @example
44579
- * JavaScript
44580
- * ```js
44581
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44582
- * api.scoreLoaded.on((score) => {
44583
- * updateSongInformationInUi(score);
44584
- * });
44585
- * ```
44586
- *
44587
- * @example
44588
- * C#
44589
- * ```cs
44590
- * var api = new AlphaTabApi<MyControl>(...);
44591
- * api.ScoreLoaded.On(score =>
44592
- * {
44593
- * UpdateSongInformationInUi(score);
44594
- * });
44595
- * ```
44596
- *
44597
- * @example
44598
- * Android
44599
- * ```kotlin
44600
- * val api = AlphaTabApi<MyControl>(...)
44601
- * api.scoreLoaded.on { score ->
44602
- * updateSongInformationInUi(score)
44603
- * }
44604
- * ```
44605
- *
44606
- */
44607
- this.scoreLoaded = new EventEmitterOfT();
44608
44738
  /**
44609
44739
  * This event is fired when alphaTab was resized and is about to rerender the music notation.
44610
44740
  * @remarks
@@ -44863,46 +44993,6 @@
44863
44993
  *
44864
44994
  */
44865
44995
  this.midiLoad = new EventEmitterOfT();
44866
- /**
44867
- * This event is fired when the Midi file needed for playback was loaded.
44868
- *
44869
- * @eventProperty
44870
- * @category Events - Player
44871
- * @since 0.9.4
44872
- *
44873
- * @example
44874
- * JavaScript
44875
- * ```js
44876
- * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
44877
- * api.midiLoaded.on(e => {
44878
- * hideGeneratingAudioIndicator();
44879
- * updateSongDuration(e.endTime);
44880
- * });
44881
- * ```
44882
- *
44883
- * @example
44884
- * C#
44885
- * ```cs
44886
- * var api = new AlphaTabApi<MyControl>(...);
44887
- * api.MidiLoaded.On(e =>
44888
- * {
44889
- * HideGeneratingAudioIndicator();
44890
- * UpdateSongDuration(e.EndTime);
44891
- * });
44892
- * ```
44893
- *
44894
- * @example
44895
- * Android
44896
- * ```kotlin
44897
- * val api = AlphaTabApi<MyControl>(...)
44898
- * api.midiLoaded.on { e ->
44899
- * hideGeneratingAudioIndicator()
44900
- * updateSongDuration(e.endTime)
44901
- * }
44902
- * ```
44903
- *
44904
- */
44905
- this.midiLoaded = new EventEmitterOfT();
44906
44996
  /**
44907
44997
  * This event is fired when a settings update was requested.
44908
44998
  *
@@ -44942,12 +45032,30 @@
44942
45032
  this.settingsUpdated = new EventEmitter();
44943
45033
  this.uiFacade = uiFacade;
44944
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
+ });
44945
45056
  uiFacade.initialize(this, settings);
44946
45057
  Logger.logLevel = this.settings.core.logLevel;
44947
- // backwards compatibility: remove in 2.0
44948
- if (this.settings.player.playerMode === exports.PlayerMode.Disabled && this.settings.player.enablePlayer) {
44949
- this.settings.player.playerMode = exports.PlayerMode.EnabledAutomatic;
44950
- }
45058
+ this.settings.handleBackwardsCompatibility();
44951
45059
  Environment.printEnvironmentInfo(false);
44952
45060
  this.canvasElement = uiFacade.createCanvasElement();
44953
45061
  this.container.appendChild(this.canvasElement);
@@ -45106,6 +45214,7 @@
45106
45214
  * ```
45107
45215
  */
45108
45216
  updateSettings() {
45217
+ this.settings.handleBackwardsCompatibility();
45109
45218
  const score = this.score;
45110
45219
  if (score) {
45111
45220
  ModelUtils.applyPitchOffsets(this.settings, score);
@@ -45913,6 +46022,22 @@
45913
46022
  set timePosition(value) {
45914
46023
  this._player.timePosition = value;
45915
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
+ }
45916
46041
  /**
45917
46042
  * The range of the song that should be played.
45918
46043
  * @remarks
@@ -48237,27 +48362,33 @@
48237
48362
  value: value
48238
48363
  });
48239
48364
  }
48365
+ get loadedMidiInfo() {
48366
+ return this.loadedMidiInfo;
48367
+ }
48368
+ get currentPosition() {
48369
+ return this._currentPosition;
48370
+ }
48240
48371
  get tickPosition() {
48241
- return this._tickPosition;
48372
+ return this._currentPosition.currentTick;
48242
48373
  }
48243
48374
  set tickPosition(value) {
48244
48375
  if (value < 0) {
48245
48376
  value = 0;
48246
48377
  }
48247
- 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);
48248
48379
  this._synth.postMessage({
48249
48380
  cmd: 'alphaSynth.setTickPosition',
48250
48381
  value: value
48251
48382
  });
48252
48383
  }
48253
48384
  get timePosition() {
48254
- return this._timePosition;
48385
+ return this._currentPosition.currentTime;
48255
48386
  }
48256
48387
  set timePosition(value) {
48257
48388
  if (value < 0) {
48258
48389
  value = 0;
48259
48390
  }
48260
- 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);
48261
48392
  this._synth.postMessage({
48262
48393
  cmd: 'alphaSynth.setTimePosition',
48263
48394
  value: value
@@ -48300,11 +48431,10 @@
48300
48431
  this._metronomeVolume = 0;
48301
48432
  this._countInVolume = 0;
48302
48433
  this._playbackSpeed = 0;
48303
- this._tickPosition = 0;
48304
- this._timePosition = 0;
48305
48434
  this._isLooping = false;
48306
48435
  this._playbackRange = null;
48307
48436
  this._midiEventsPlayedFilter = [];
48437
+ this._currentPosition = new PositionChangedEventArgs(0, 0, 0, 0, false, 120, 120);
48308
48438
  this.ready = new EventEmitter();
48309
48439
  this.readyForPlayback = new EventEmitter();
48310
48440
  this.finished = new EventEmitter();
@@ -48323,8 +48453,6 @@
48323
48453
  this._masterVolume = 0.0;
48324
48454
  this._metronomeVolume = 0.0;
48325
48455
  this._playbackSpeed = 0.0;
48326
- this._tickPosition = 0;
48327
- this._timePosition = 0.0;
48328
48456
  this._isLooping = false;
48329
48457
  this._playbackRange = null;
48330
48458
  this._output = player;
@@ -48459,9 +48587,8 @@
48459
48587
  this.checkReadyForPlayback();
48460
48588
  break;
48461
48589
  case 'alphaSynth.positionChanged':
48462
- this._timePosition = data.currentTime;
48463
- this._tickPosition = data.currentTick;
48464
- 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);
48465
48592
  break;
48466
48593
  case 'alphaSynth.midiEventsPlayed':
48467
48594
  this.midiEventsPlayed.trigger(new MidiEventsPlayedEventArgs(data.events.map(JsonConverter.jsObjectToMidiEvent)));
@@ -48485,7 +48612,8 @@
48485
48612
  break;
48486
48613
  case 'alphaSynth.midiLoaded':
48487
48614
  this.checkReadyForPlayback();
48488
- 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);
48489
48617
  break;
48490
48618
  case 'alphaSynth.midiLoadFailed':
48491
48619
  this.checkReadyForPlayback();
@@ -66628,6 +66756,10 @@
66628
66756
  track.score.stylesheet.perTrackMultiBarRest.has(track.index)) {
66629
66757
  writer.writeLine(` multibarrest`);
66630
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
+ }
66631
66763
  writer.outdent();
66632
66764
  writer.writeLine('}');
66633
66765
  writer.indent();
@@ -66686,13 +66818,7 @@
66686
66818
  writer.writeLine('|');
66687
66819
  }
66688
66820
  if (voiceIndex === 0) {
66689
- // Track meta on first bar
66690
66821
  let anyWritten = false;
66691
- if (bar.index === 0 && bar.staff.index === 0) {
66692
- const l = writer.tex.length;
66693
- this.writeTrackMetaTo(writer, bar.staff.track);
66694
- anyWritten = writer.tex.length > l;
66695
- }
66696
66822
  // Staff meta on first bar
66697
66823
  if (bar.index === 0) {
66698
66824
  const l = writer.tex.length;
@@ -66722,14 +66848,6 @@
66722
66848
  }
66723
66849
  writer.outdent();
66724
66850
  }
66725
- writeTrackMetaTo(writer, track) {
66726
- writer.writeSingleLineComment(`Track ${track.index + 1} Metadata`);
66727
- this.writePlaybackInfoTo(writer, track);
66728
- }
66729
- writePlaybackInfoTo(writer, track) {
66730
- const isPercussion = track.staves.some(s => s.isPercussion);
66731
- writer.writeMeta('instrument', isPercussion ? 'percussion' : GeneralMidi.getName(track.playbackInfo.program));
66732
- }
66733
66851
  writeStaffMetaTo(writer, staff) {
66734
66852
  writer.writeSingleLineComment(`Staff ${staff.index + 1} Metadata`);
66735
66853
  if (staff.capo !== 0) {
@@ -68352,15 +68470,16 @@
68352
68470
  }
68353
68471
  return GpifIconIds.SteelGuitar;
68354
68472
  }
68355
- writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, barIndex, program, ratioPosition = 0) {
68473
+ writeSoundAndAutomation(soundsNode, automationsNode, name, path, role, barIndex, program, bank, ratioPosition = 0) {
68356
68474
  const soundNode = soundsNode.addElement('Sound');
68357
68475
  soundNode.addElement('Name').setCData(name);
68358
68476
  soundNode.addElement('Label').setCData(name);
68359
68477
  soundNode.addElement('Path').setCData(path);
68360
68478
  soundNode.addElement('Role').setCData(role);
68361
68479
  const midi = soundNode.addElement('MIDI');
68362
- midi.addElement('LSB').innerText = '0';
68363
- 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();
68364
68483
  midi.addElement('Program').innerText = program.toString();
68365
68484
  const automationNode = automationsNode.addElement('Automation');
68366
68485
  automationNode.addElement('Type').innerText = 'Sound';
@@ -68378,21 +68497,26 @@
68378
68497
  const trackSoundPath = `Midi/${track.playbackInfo.program}`;
68379
68498
  const trackSoundRole = 'Factory';
68380
68499
  let trackSoundWritten = false;
68500
+ let bank = track.playbackInfo.bank;
68381
68501
  for (const staff of track.staves) {
68382
68502
  for (const bar of staff.bars) {
68383
68503
  for (const voice of bar.voices) {
68384
68504
  for (const beat of voice.beats) {
68385
68505
  const soundAutomation = beat.getAutomation(AutomationType.Instrument);
68386
68506
  const isTrackSound = bar.index === 0 && beat.index === 0;
68507
+ const bankAutomation = beat.getAutomation(AutomationType.Bank);
68508
+ if (bankAutomation) {
68509
+ bank = bankAutomation.value;
68510
+ }
68387
68511
  if (soundAutomation) {
68388
68512
  const name = isTrackSound ? trackSoundName : `ProgramChange_${beat.id}`;
68389
68513
  const path = isTrackSound ? trackSoundPath : `Midi/${soundAutomation.value}`;
68390
68514
  const role = isTrackSound ? trackSoundRole : 'User';
68391
68515
  if (!isTrackSound && !trackSoundWritten) {
68392
- 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);
68393
68517
  trackSoundWritten = true;
68394
68518
  }
68395
- 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);
68396
68520
  if (isTrackSound) {
68397
68521
  trackSoundWritten = true;
68398
68522
  }