@coderline/alphatab 1.9.0-alpha.1767 → 1.9.0-alpha.1768

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * alphaTab v1.9.0-alpha.1767 (develop, build 1767)
2
+ * alphaTab v1.9.0-alpha.1768 (develop, build 1768)
3
3
  *
4
4
  * Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
203
203
  * @internal
204
204
  */
205
205
  class VersionInfo {
206
- static version = '1.9.0-alpha.1767';
207
- static date = '2026-04-09T03:22:29.010Z';
208
- static commit = '6c3194f5f54595c44b979b1fee6cf423b1c9ed15';
206
+ static version = '1.9.0-alpha.1768';
207
+ static date = '2026-04-10T03:35:57.433Z';
208
+ static commit = '759bd788fc094ab5cb4db380f6a7657e459c14bf';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -3826,7 +3826,6 @@ class Score {
3826
3826
  */
3827
3827
  class SynthConstants {
3828
3828
  static DefaultChannelCount = 16 + 1;
3829
- static MetronomeChannel = SynthConstants.DefaultChannelCount - 1;
3830
3829
  static MetronomeKey = 33;
3831
3830
  static AudioChannels = 2;
3832
3831
  static MinVolume = 0;
@@ -3850,6 +3849,10 @@ class SynthConstants {
3850
3849
  static DefaultPitchWheel = SynthConstants.MaxPitchWheel / 2;
3851
3850
  static MicroBufferCount = 32;
3852
3851
  static MicroBufferSize = 64;
3852
+ /**
3853
+ * approximately -60 dB, which is inaudible to humans
3854
+ */
3855
+ static AudibleLevelThreshold = 1e-3;
3853
3856
  }
3854
3857
 
3855
3858
  /**
@@ -23814,26 +23817,26 @@ class GpifParser {
23814
23817
  case 'HarmonicType':
23815
23818
  const htype = c.findChildElement('HType');
23816
23819
  if (htype) {
23817
- switch (htype.innerText) {
23818
- case 'NoHarmonic':
23820
+ switch (htype.innerText.toLowerCase()) {
23821
+ case 'noharmonic':
23819
23822
  note.harmonicType = HarmonicType.None;
23820
23823
  break;
23821
- case 'Natural':
23824
+ case 'natural':
23822
23825
  note.harmonicType = HarmonicType.Natural;
23823
23826
  break;
23824
- case 'Artificial':
23827
+ case 'artificial':
23825
23828
  note.harmonicType = HarmonicType.Artificial;
23826
23829
  break;
23827
- case 'Pinch':
23830
+ case 'pinch':
23828
23831
  note.harmonicType = HarmonicType.Pinch;
23829
23832
  break;
23830
- case 'Tap':
23833
+ case 'tap':
23831
23834
  note.harmonicType = HarmonicType.Tap;
23832
23835
  break;
23833
- case 'Semi':
23836
+ case 'semi':
23834
23837
  note.harmonicType = HarmonicType.Semi;
23835
23838
  break;
23836
- case 'Feedback':
23839
+ case 'feedback':
23837
23840
  note.harmonicType = HarmonicType.Feedback;
23838
23841
  break;
23839
23842
  }
@@ -39223,8 +39226,11 @@ class AlphaTabWorkerScoreRenderer {
39223
39226
  this.renderFinished.trigger(data.result);
39224
39227
  break;
39225
39228
  case 'alphaTab.postRenderFinished':
39226
- this.boundsLookup = BoundsLookup.fromJson(data.boundsLookup, this._api.score);
39227
- this.boundsLookup?.finish();
39229
+ const score = this._api.score;
39230
+ if (score && data.boundsLookup) {
39231
+ this.boundsLookup = BoundsLookup.fromJson(data.boundsLookup, this._api.score);
39232
+ this.boundsLookup?.finish();
39233
+ }
39228
39234
  this.postRenderFinished.trigger();
39229
39235
  break;
39230
39236
  case 'alphaTab.error':
@@ -39481,6 +39487,164 @@ class AudioElementBackingTrackSynthOutput {
39481
39487
  }
39482
39488
  }
39483
39489
 
39490
+ /**
39491
+ * @internal
39492
+ */
39493
+ class QueueItem {
39494
+ value;
39495
+ next;
39496
+ constructor(value) {
39497
+ this.value = value;
39498
+ }
39499
+ }
39500
+ /**
39501
+ * @internal
39502
+ */
39503
+ class Queue {
39504
+ _head;
39505
+ _tail;
39506
+ get isEmpty() {
39507
+ return this._head === undefined;
39508
+ }
39509
+ clear() {
39510
+ this._head = undefined;
39511
+ this._tail = undefined;
39512
+ }
39513
+ enqueue(item) {
39514
+ const queueItem = new QueueItem(item);
39515
+ if (this._tail) {
39516
+ // not empty -> add after tail
39517
+ this._tail.next = queueItem;
39518
+ this._tail = queueItem;
39519
+ }
39520
+ else {
39521
+ // empty -> new item takes head and tail
39522
+ this._head = queueItem;
39523
+ this._tail = queueItem;
39524
+ }
39525
+ }
39526
+ enqueueFront(item) {
39527
+ const queueItem = new QueueItem(item);
39528
+ queueItem.next = this._head;
39529
+ if (this._head) {
39530
+ this._head = queueItem;
39531
+ }
39532
+ else {
39533
+ this._head = queueItem;
39534
+ this._tail = queueItem;
39535
+ }
39536
+ }
39537
+ peek() {
39538
+ const head = this._head;
39539
+ if (!head) {
39540
+ return undefined;
39541
+ }
39542
+ return head.value;
39543
+ }
39544
+ dequeue() {
39545
+ const head = this._head;
39546
+ if (!head) {
39547
+ return undefined;
39548
+ }
39549
+ const newHead = head.next;
39550
+ this._head = newHead;
39551
+ // last item removed?
39552
+ if (!newHead) {
39553
+ this._tail = undefined;
39554
+ }
39555
+ return head.value;
39556
+ }
39557
+ }
39558
+
39559
+ /**
39560
+ * The options controlling how to export the audio.
39561
+ * @public
39562
+ */
39563
+ class AudioExportOptions {
39564
+ /**
39565
+ * The soundfonts to load and use for generating the audio.
39566
+ * If not provided, the already loaded soundfonts of the synthesizer will be used.
39567
+ * If no existing synthesizer is initialized, the generated audio might not contain any hearable audio.
39568
+ */
39569
+ soundFonts;
39570
+ /**
39571
+ * The output sample rate.
39572
+ * @default `44100`
39573
+ */
39574
+ sampleRate = 44100;
39575
+ /**
39576
+ * Whether to respect sync point information during export.
39577
+ * @default `true`
39578
+ * @remarks
39579
+ * If the song contains sync point information for synchronization with an external media,
39580
+ * this option allows controlling whether the synthesized audio is aligned with these points.
39581
+ *
39582
+ * This is useful when mixing the exported audio together with external media, keeping the same timing.
39583
+ *
39584
+ * Disable this option if you want the original/exact timing as per music sheet in the exported audio.
39585
+ */
39586
+ useSyncPoints = false;
39587
+ /**
39588
+ * The current master volume as percentage. (range: 0.0-3.0, default 1.0)
39589
+ */
39590
+ masterVolume = 1;
39591
+ /**
39592
+ * The metronome volume. (range: 0.0-3.0, default 0.0)
39593
+ */
39594
+ metronomeVolume = 0;
39595
+ /**
39596
+ * The range of the song that should be exported. Set this to null
39597
+ * to play the whole song.
39598
+ */
39599
+ playbackRange;
39600
+ /**
39601
+ * The volume for individual tracks as percentage (range: 0.0-3.0).
39602
+ * @remarks
39603
+ * The key is the track index, and the value is the relative volume.
39604
+ * The configured volume (as per data model) still applies, this is an additional volume control.
39605
+ * If no custom value is set, 100% is used.
39606
+ * No values from the currently active synthesizer are applied.
39607
+ *
39608
+ * The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
39609
+ */
39610
+ trackVolume = new Map();
39611
+ /**
39612
+ * The additional semitone pitch transpose to apply for individual tracks.
39613
+ * @remarks
39614
+ * The key is the track index, and the value is the number of semitones to apply.
39615
+ * No values from the currently active synthesizer are applied.
39616
+ *
39617
+ * The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
39618
+ */
39619
+ trackTranspositionPitches = new Map();
39620
+ }
39621
+ /**
39622
+ * Represents a single chunk of audio produced.
39623
+ * @public
39624
+ */
39625
+ class AudioExportChunk {
39626
+ /**
39627
+ * The generated samples for the requested chunk.
39628
+ */
39629
+ samples;
39630
+ /**
39631
+ * The current time position within the song in milliseconds.
39632
+ */
39633
+ currentTime = 0;
39634
+ /**
39635
+ * The total length of the song in milliseconds.
39636
+ */
39637
+ endTime = 0;
39638
+ /**
39639
+ * The current time position within the song in midi ticks.
39640
+ */
39641
+ currentTick = 0;
39642
+ /**
39643
+ * The total length of the song in midi ticks.
39644
+ */
39645
+ endTick = 0;
39646
+ }
39647
+
39484
39648
  // The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT,
39485
39649
  // developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont)
39486
39650
  // TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny
@@ -39594,6 +39758,7 @@ class MidiSequencerState {
39594
39758
  endTime = 0;
39595
39759
  currentTempo = 0;
39596
39760
  syncPointTempo = 0;
39761
+ metronomeChannel = SynthConstants.DefaultChannelCount - 1;
39597
39762
  }
39598
39763
  /**
39599
39764
  * This sequencer dispatches midi events to the synthesizer based on the current
@@ -39606,6 +39771,9 @@ class MidiFileSequencer {
39606
39771
  _mainState;
39607
39772
  _oneTimeState = null;
39608
39773
  _countInState = null;
39774
+ get metronomeChannel() {
39775
+ return this._mainState.metronomeChannel;
39776
+ }
39609
39777
  get isPlayingMain() {
39610
39778
  return this._currentState === this._mainState;
39611
39779
  }
@@ -39689,7 +39857,7 @@ class MidiFileSequencer {
39689
39857
  const metronomeVolume = this._synthesizer.metronomeVolume;
39690
39858
  this._synthesizer.noteOffAll(true);
39691
39859
  this._synthesizer.resetSoft();
39692
- this._synthesizer.setupMetronomeChannel(metronomeVolume);
39860
+ this._synthesizer.setupMetronomeChannel(this.metronomeChannel, metronomeVolume);
39693
39861
  }
39694
39862
  this._mainSilentProcess(timePosition);
39695
39863
  }
@@ -39741,6 +39909,7 @@ class MidiFileSequencer {
39741
39909
  let metronomeLengthInMillis = 0;
39742
39910
  let metronomeTick = midiFile.tickShift; // shift metronome to content
39743
39911
  let metronomeTime = 0.0;
39912
+ let maxChannel = 0;
39744
39913
  let previousTick = 0;
39745
39914
  for (const mEvent of midiFile.events) {
39746
39915
  const synthData = new SynthEvent(state.synthData.length, mEvent);
@@ -39782,6 +39951,9 @@ class MidiFileSequencer {
39782
39951
  if (!state.firstProgramEventPerChannel.has(channel)) {
39783
39952
  state.firstProgramEventPerChannel.set(channel, synthData);
39784
39953
  }
39954
+ if (channel > maxChannel) {
39955
+ maxChannel = channel;
39956
+ }
39785
39957
  const isPercussion = channel === SynthConstants.PercussionChannel;
39786
39958
  if (!isPercussion) {
39787
39959
  this.instrumentPrograms.add(programChange.program);
@@ -39793,6 +39965,9 @@ class MidiFileSequencer {
39793
39965
  if (isPercussion) {
39794
39966
  this.percussionKeys.add(noteOn.noteKey);
39795
39967
  }
39968
+ if (noteOn.channel > maxChannel) {
39969
+ maxChannel = noteOn.channel;
39970
+ }
39796
39971
  }
39797
39972
  }
39798
39973
  state.currentTempo = state.tempoChanges.length > 0 ? state.tempoChanges[0].bpm : bpm;
@@ -39808,6 +39983,7 @@ class MidiFileSequencer {
39808
39983
  });
39809
39984
  state.endTime = absTime;
39810
39985
  state.endTick = absTick;
39986
+ state.metronomeChannel = maxChannel + 1;
39811
39987
  return state;
39812
39988
  }
39813
39989
  fillMidiEventQueue() {
@@ -44013,6 +44189,11 @@ class Voice {
44013
44189
  if (dynamicGain) {
44014
44190
  noteGain = SynthHelper.decibelsToGain(this.noteGainDb + this.modLfo.level * tmpModLfoToVolume);
44015
44191
  }
44192
+ // Update EG.
44193
+ this.ampEnv.process(blockSamples, f.outSampleRate);
44194
+ if (updateModEnv) {
44195
+ this.modEnv.process(blockSamples, f.outSampleRate);
44196
+ }
44016
44197
  gainMono = noteGain * this.ampEnv.level;
44017
44198
  if (isMuted) {
44018
44199
  gainMono = 0;
@@ -44020,11 +44201,6 @@ class Voice {
44020
44201
  else {
44021
44202
  gainMono *= this.mixVolume;
44022
44203
  }
44023
- // Update EG.
44024
- this.ampEnv.process(blockSamples, f.outSampleRate);
44025
- if (updateModEnv) {
44026
- this.modEnv.process(blockSamples, f.outSampleRate);
44027
- }
44028
44204
  // Update LFOs.
44029
44205
  if (updateModLFO) {
44030
44206
  this.modLfo.process(blockSamples);
@@ -44103,7 +44279,12 @@ class Voice {
44103
44279
  }
44104
44280
  break;
44105
44281
  }
44106
- if (tmpSourceSamplePosition >= tmpSampleEndDbl || this.ampEnv.segment === VoiceEnvelopeSegment.Done) {
44282
+ const inaudible = this.ampEnv.segment === VoiceEnvelopeSegment.Release &&
44283
+ Math.abs(gainMono) < SynthConstants.AudibleLevelThreshold;
44284
+ if (tmpSourceSamplePosition >= tmpSampleEndDbl ||
44285
+ this.ampEnv.segment === VoiceEnvelopeSegment.Done ||
44286
+ // Check if voice is inaudible during release to terminate early
44287
+ inaudible) {
44107
44288
  this.kill();
44108
44289
  return;
44109
44290
  }
@@ -44118,75 +44299,6 @@ class Voice {
44118
44299
  }
44119
44300
  }
44120
44301
 
44121
- /**
44122
- * @internal
44123
- */
44124
- class QueueItem {
44125
- value;
44126
- next;
44127
- constructor(value) {
44128
- this.value = value;
44129
- }
44130
- }
44131
- /**
44132
- * @internal
44133
- */
44134
- class Queue {
44135
- _head;
44136
- _tail;
44137
- get isEmpty() {
44138
- return this._head === undefined;
44139
- }
44140
- clear() {
44141
- this._head = undefined;
44142
- this._tail = undefined;
44143
- }
44144
- enqueue(item) {
44145
- const queueItem = new QueueItem(item);
44146
- if (this._tail) {
44147
- // not empty -> add after tail
44148
- this._tail.next = queueItem;
44149
- this._tail = queueItem;
44150
- }
44151
- else {
44152
- // empty -> new item takes head and tail
44153
- this._head = queueItem;
44154
- this._tail = queueItem;
44155
- }
44156
- }
44157
- enqueueFront(item) {
44158
- const queueItem = new QueueItem(item);
44159
- queueItem.next = this._head;
44160
- if (this._head) {
44161
- this._head = queueItem;
44162
- }
44163
- else {
44164
- this._head = queueItem;
44165
- this._tail = queueItem;
44166
- }
44167
- }
44168
- peek() {
44169
- const head = this._head;
44170
- if (!head) {
44171
- return undefined;
44172
- }
44173
- return head.value;
44174
- }
44175
- dequeue() {
44176
- const head = this._head;
44177
- if (!head) {
44178
- return undefined;
44179
- }
44180
- const newHead = head.next;
44181
- this._head = newHead;
44182
- // last item removed?
44183
- if (!newHead) {
44184
- this._tail = undefined;
44185
- }
44186
- return head.value;
44187
- }
44188
- }
44189
-
44190
44302
  /**
44191
44303
  * Lists all midi controllers.
44192
44304
  * @public
@@ -44343,6 +44455,7 @@ class TinySoundFont {
44343
44455
  currentTempo = 0;
44344
44456
  timeSignatureNumerator = 0;
44345
44457
  timeSignatureDenominator = 0;
44458
+ _metronomeChannel = SynthConstants.DefaultChannelCount - 1;
44346
44459
  constructor(sampleRate) {
44347
44460
  this.outSampleRate = sampleRate;
44348
44461
  }
@@ -44447,8 +44560,8 @@ class TinySoundFont {
44447
44560
  while (!this._midiEventQueue.isEmpty) {
44448
44561
  const m = this._midiEventQueue.dequeue();
44449
44562
  if (m.isMetronome && this.metronomeVolume > 0) {
44450
- this.channelNoteOff(SynthConstants.MetronomeChannel, SynthConstants.MetronomeKey);
44451
- this.channelNoteOn(SynthConstants.MetronomeChannel, SynthConstants.MetronomeKey, 95 / 127);
44563
+ this.channelNoteOff(this._metronomeChannel, SynthConstants.MetronomeKey);
44564
+ this.channelNoteOn(this._metronomeChannel, SynthConstants.MetronomeKey, 95 / 127);
44452
44565
  }
44453
44566
  else if (m.event) {
44454
44567
  this.processMidiMessage(m.event);
@@ -44462,7 +44575,7 @@ class TinySoundFont {
44462
44575
  // channel is muted if it is either explicitley muted, or another channel is set to solo but not this one.
44463
44576
  // exception. metronome is implicitly added in solo
44464
44577
  const isChannelMuted = this._mutedChannels.has(channel) ||
44465
- (anySolo && channel !== SynthConstants.MetronomeChannel && !this._soloChannels.has(channel));
44578
+ (anySolo && channel !== this._metronomeChannel && !this._soloChannels.has(channel));
44466
44579
  if (!buffer) {
44467
44580
  voice.kill();
44468
44581
  }
@@ -44516,16 +44629,17 @@ class TinySoundFont {
44516
44629
  }
44517
44630
  }
44518
44631
  get metronomeVolume() {
44519
- return this.channelGetMixVolume(SynthConstants.MetronomeChannel);
44632
+ return this.channelGetMixVolume(this._metronomeChannel);
44520
44633
  }
44521
44634
  set metronomeVolume(value) {
44522
- this.setupMetronomeChannel(value);
44635
+ this.setupMetronomeChannel(this._metronomeChannel, value);
44523
44636
  }
44524
- setupMetronomeChannel(volume) {
44525
- this.channelSetMixVolume(SynthConstants.MetronomeChannel, volume);
44637
+ setupMetronomeChannel(channel, volume) {
44638
+ this._metronomeChannel = channel;
44639
+ this.channelSetMixVolume(channel, volume);
44526
44640
  if (volume > 0) {
44527
- this.channelSetVolume(SynthConstants.MetronomeChannel, 1);
44528
- this.channelSetPresetNumber(SynthConstants.MetronomeChannel, 0, true);
44641
+ this.channelSetVolume(channel, 1);
44642
+ this.channelSetPresetNumber(channel, 0, true);
44529
44643
  }
44530
44644
  }
44531
44645
  get masterVolume() {
@@ -45605,95 +45719,6 @@ class TinySoundFont {
45605
45719
  }
45606
45720
  }
45607
45721
 
45608
- /**
45609
- * The options controlling how to export the audio.
45610
- * @public
45611
- */
45612
- class AudioExportOptions {
45613
- /**
45614
- * The soundfonts to load and use for generating the audio.
45615
- * If not provided, the already loaded soundfonts of the synthesizer will be used.
45616
- * If no existing synthesizer is initialized, the generated audio might not contain any hearable audio.
45617
- */
45618
- soundFonts;
45619
- /**
45620
- * The output sample rate.
45621
- * @default `44100`
45622
- */
45623
- sampleRate = 44100;
45624
- /**
45625
- * Whether to respect sync point information during export.
45626
- * @default `true`
45627
- * @remarks
45628
- * If the song contains sync point information for synchronization with an external media,
45629
- * this option allows controlling whether the synthesized audio is aligned with these points.
45630
- *
45631
- * This is useful when mixing the exported audio together with external media, keeping the same timing.
45632
- *
45633
- * Disable this option if you want the original/exact timing as per music sheet in the exported audio.
45634
- */
45635
- useSyncPoints = false;
45636
- /**
45637
- * The current master volume as percentage. (range: 0.0-3.0, default 1.0)
45638
- */
45639
- masterVolume = 1;
45640
- /**
45641
- * The metronome volume. (range: 0.0-3.0, default 0.0)
45642
- */
45643
- metronomeVolume = 0;
45644
- /**
45645
- * The range of the song that should be exported. Set this to null
45646
- * to play the whole song.
45647
- */
45648
- playbackRange;
45649
- /**
45650
- * The volume for individual tracks as percentage (range: 0.0-3.0).
45651
- * @remarks
45652
- * The key is the track index, and the value is the relative volume.
45653
- * The configured volume (as per data model) still applies, this is an additional volume control.
45654
- * If no custom value is set, 100% is used.
45655
- * No values from the currently active synthesizer are applied.
45656
- *
45657
- * The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
45658
- */
45659
- trackVolume = new Map();
45660
- /**
45661
- * The additional semitone pitch transpose to apply for individual tracks.
45662
- * @remarks
45663
- * The key is the track index, and the value is the number of semitones to apply.
45664
- * No values from the currently active synthesizer are applied.
45665
- *
45666
- * The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
45667
- */
45668
- trackTranspositionPitches = new Map();
45669
- }
45670
- /**
45671
- * Represents a single chunk of audio produced.
45672
- * @public
45673
- */
45674
- class AudioExportChunk {
45675
- /**
45676
- * The generated samples for the requested chunk.
45677
- */
45678
- samples;
45679
- /**
45680
- * The current time position within the song in milliseconds.
45681
- */
45682
- currentTime = 0;
45683
- /**
45684
- * The total length of the song in milliseconds.
45685
- */
45686
- endTime = 0;
45687
- /**
45688
- * The current time position within the song in midi ticks.
45689
- */
45690
- currentTick = 0;
45691
- /**
45692
- * The total length of the song in midi ticks.
45693
- */
45694
- endTick = 0;
45695
- }
45696
-
45697
45722
  /**
45698
45723
  * This is the base class for synthesizer components which can be used to
45699
45724
  * play a {@link MidiFile} via a {@link ISynthOutput}.
@@ -45902,6 +45927,17 @@ class AlphaSynthBase {
45902
45927
  }
45903
45928
  this._notPlayedSamples += samples.length;
45904
45929
  this.output.addSamples(samples);
45930
+ // if the sequencer finished, we instantly force a noteOff on all
45931
+ // voices to complete playback and stop voices fast.
45932
+ // Doing this in the samplePlayed callback is too late as we might
45933
+ // continue generating audio for long-release notes (especially percussion like cymbals)
45934
+ // we still have checkForFinish which takes care of the counterpart
45935
+ // on the sample played area to ensure we seek back.
45936
+ // but thanks to this code we ensure the output will complete fast as we won't
45937
+ // be adding more samples beside a 0.1s ramp-down
45938
+ if (this.sequencer.isFinished) {
45939
+ this.synthesizer.noteOffAll(true);
45940
+ }
45905
45941
  }
45906
45942
  else {
45907
45943
  // Tell output that there is no data left for it.
@@ -45918,7 +45954,7 @@ class AlphaSynthBase {
45918
45954
  if (this._countInVolume > 0) {
45919
45955
  Logger.debug('AlphaSynth', 'Starting countin');
45920
45956
  this.sequencer.startCountIn();
45921
- this.synthesizer.setupMetronomeChannel(this._countInVolume);
45957
+ this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this._countInVolume);
45922
45958
  this.updateTimePosition(0, true);
45923
45959
  }
45924
45960
  this.output.play();
@@ -45930,7 +45966,7 @@ class AlphaSynthBase {
45930
45966
  this._stopOneTimeMidi();
45931
45967
  }
45932
45968
  Logger.debug('AlphaSynth', 'Starting playback');
45933
- this.synthesizer.setupMetronomeChannel(this.metronomeVolume);
45969
+ this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this.metronomeVolume);
45934
45970
  this._synthStopping = false;
45935
45971
  this.state = PlayerState.Playing;
45936
45972
  this.stateChanged.trigger(new PlayerStateChangedEventArgs(this.state, false));
@@ -46014,7 +46050,7 @@ class AlphaSynthBase {
46014
46050
  }
46015
46051
  _checkReadyForPlayback() {
46016
46052
  if (this.isReadyForPlayback) {
46017
- this.synthesizer.setupMetronomeChannel(this.metronomeVolume);
46053
+ this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this.metronomeVolume);
46018
46054
  const programs = this.sequencer.instrumentPrograms;
46019
46055
  const percussionKeys = this.sequencer.percussionKeys;
46020
46056
  let append = false;
@@ -46355,7 +46391,7 @@ class AlphaSynthAudioExporter {
46355
46391
  _generatedAudioCurrentTime = 0;
46356
46392
  _generatedAudioEndTime = 0;
46357
46393
  setup() {
46358
- this._synth.setupMetronomeChannel(this._synth.metronomeVolume);
46394
+ this._synth.setupMetronomeChannel(this._sequencer.metronomeChannel, this._synth.metronomeVolume);
46359
46395
  const syncPoints = this._sequencer.currentSyncPoints;
46360
46396
  const alphaTabEndTime = this._sequencer.currentEndTime;
46361
46397
  if (syncPoints.length === 0) {
@@ -46437,7 +46473,7 @@ class BackingTrackAudioSynthesizer {
46437
46473
  }
46438
46474
  loadPresets(_hydra, _instrumentPrograms, _percussionKeys, _append) {
46439
46475
  }
46440
- setupMetronomeChannel(_metronomeVolume) {
46476
+ setupMetronomeChannel(_metronomeChannel, _metronomeVolume) {
46441
46477
  }
46442
46478
  synthesizeSilent(_sampleCount) {
46443
46479
  this.fakeSynthesize();
@@ -47871,6 +47907,7 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
47871
47907
  _bufferTimeInMilliseconds = 0;
47872
47908
  _settings;
47873
47909
  _boundHandleMessage;
47910
+ _pendingEvents;
47874
47911
  constructor(settings) {
47875
47912
  super();
47876
47913
  this._settings = settings;
@@ -47898,6 +47935,13 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
47898
47935
  this.source.connect(this._worklet);
47899
47936
  this.source.start(0);
47900
47937
  this._worklet.connect(ctx.destination);
47938
+ const pending = this._pendingEvents;
47939
+ if (pending) {
47940
+ for (const e of pending) {
47941
+ this._worklet.port.postMessage(e);
47942
+ }
47943
+ this._pendingEvents = undefined;
47944
+ }
47901
47945
  }, (reason) => {
47902
47946
  Logger.error('WebAudio', `Audio Worklet creation failed: reason=${reason}`);
47903
47947
  });
@@ -47924,15 +47968,26 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
47924
47968
  this._worklet.disconnect();
47925
47969
  }
47926
47970
  this._worklet = null;
47971
+ this._pendingEvents = undefined;
47972
+ }
47973
+ _postWorkerMessage(message) {
47974
+ const worklet = this._worklet;
47975
+ if (worklet) {
47976
+ worklet.port.postMessage(message);
47977
+ }
47978
+ else {
47979
+ this._pendingEvents ??= [];
47980
+ this._pendingEvents.push(message);
47981
+ }
47927
47982
  }
47928
47983
  addSamples(f) {
47929
- this._worklet?.port.postMessage({
47984
+ this._postWorkerMessage({
47930
47985
  cmd: 'alphaSynth.output.addSamples',
47931
47986
  samples: Environment.prepareForPostMessage(f)
47932
47987
  });
47933
47988
  }
47934
47989
  resetSamples() {
47935
- this._worklet?.port.postMessage({
47990
+ this._postWorkerMessage({
47936
47991
  cmd: 'alphaSynth.output.resetSamples'
47937
47992
  });
47938
47993
  }
@@ -55805,6 +55860,9 @@ class AlphaTabApiBase {
55805
55860
  this._beatVisibilityChecker.bounds = this.boundsLookup;
55806
55861
  this._currentBeat = null;
55807
55862
  this._cursorUpdateTick(this._previousTick, false, 1, true, true);
55863
+ if (this._selectionStart) {
55864
+ this.highlightPlaybackRange(this._selectionStart.beat, this._selectionEnd.beat);
55865
+ }
55808
55866
  this.postRenderFinished.trigger();
55809
55867
  this.uiFacade.triggerEvent(this.container, 'postRenderFinished', null);
55810
55868
  }
@@ -69210,16 +69268,18 @@ class LineBarRenderer extends BarRendererBase {
69210
69268
  s = [];
69211
69269
  const zero = MusicFontSymbol.Tuplet0;
69212
69270
  if (num > 10) {
69213
- s.push((zero + Math.floor(num / 10)));
69214
- s.push((zero + (num - 10)));
69271
+ const tens = Math.floor(num / 10);
69272
+ s.push((zero + tens));
69273
+ s.push((zero + (num - 10 * tens)));
69215
69274
  }
69216
69275
  else {
69217
69276
  s.push((zero + num));
69218
69277
  }
69219
69278
  s.push(MusicFontSymbol.TupletColon);
69220
69279
  if (den > 10) {
69221
- s.push((zero + Math.floor(den / 10)));
69222
- s.push((zero + (den - 10)));
69280
+ const tens = Math.floor(den / 10);
69281
+ s.push((zero + tens));
69282
+ s.push((zero + (den - 10 * tens)));
69223
69283
  }
69224
69284
  else {
69225
69285
  s.push((zero + den));
@@ -72198,8 +72258,10 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
72198
72258
  const group = new GlyphGroup(0, 0);
72199
72259
  group.renderer = this.renderer;
72200
72260
  for (const note of this.container.beat.notes) {
72201
- const g = this._createBeatDot(sr.getNoteSteps(note), group);
72202
- g.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationEffects, note);
72261
+ if (note.isVisible) {
72262
+ const g = this._createBeatDot(sr.getNoteSteps(note), group);
72263
+ g.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationEffects, note);
72264
+ }
72203
72265
  }
72204
72266
  this.addEffect(group);
72205
72267
  }