@coderline/alphatab 1.6.0-alpha.1444 → 1.6.0-alpha.1448

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.6.0-alpha.1444 (develop, build 1444)
2
+ * alphaTab v1.6.0-alpha.1448 (develop, build 1448)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -22539,7 +22539,6 @@
22539
22539
  this.division = MidiUtils.QuarterTime;
22540
22540
  this.eventIndex = 0;
22541
22541
  this.currentTime = 0;
22542
- this.currentTick = 0;
22543
22542
  this.syncPointIndex = 0;
22544
22543
  this.playbackRange = null;
22545
22544
  this.playbackRangeStartTime = 0;
@@ -22547,7 +22546,7 @@
22547
22546
  this.endTick = 0;
22548
22547
  this.endTime = 0;
22549
22548
  this.currentTempo = 0;
22550
- this.modifiedTempo = 0;
22549
+ this.syncPointTempo = 0;
22551
22550
  }
22552
22551
  }
22553
22552
  /**
@@ -22604,7 +22603,13 @@
22604
22603
  return this._currentState.currentTempo;
22605
22604
  }
22606
22605
  get modifiedTempo() {
22607
- return this._currentState.modifiedTempo * this.playbackSpeed;
22606
+ return this._currentState.syncPointTempo * this.playbackSpeed;
22607
+ }
22608
+ get syncPointTempo() {
22609
+ return this._currentState.syncPointTempo;
22610
+ }
22611
+ get currentSyncPoints() {
22612
+ return this._currentState.syncPoints;
22608
22613
  }
22609
22614
  mainSeek(timePosition) {
22610
22615
  // map to speed=1
@@ -22628,7 +22633,7 @@
22628
22633
  this._mainState.syncPointIndex = 0;
22629
22634
  this._mainState.tempoChangeIndex = 0;
22630
22635
  this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
22631
- this._mainState.modifiedTempo =
22636
+ this._mainState.syncPointTempo =
22632
22637
  this._mainState.syncPoints.length > 0
22633
22638
  ? this._mainState.syncPoints[0].syncBpm
22634
22639
  : this._mainState.currentTempo;
@@ -22741,7 +22746,7 @@
22741
22746
  }
22742
22747
  }
22743
22748
  state.currentTempo = state.tempoChanges.length > 0 ? state.tempoChanges[0].bpm : bpm;
22744
- state.modifiedTempo = state.currentTempo;
22749
+ state.syncPointTempo = state.currentTempo;
22745
22750
  state.synthData.sort((a, b) => {
22746
22751
  if (a.time > b.time) {
22747
22752
  return 1;
@@ -22858,7 +22863,7 @@
22858
22863
  }
22859
22864
  }
22860
22865
  state.syncPointIndex = 0;
22861
- state.modifiedTempo = state.syncPoints.length > 0 ? state.syncPoints[0].syncBpm : state.currentTempo;
22866
+ state.syncPointTempo = state.syncPoints.length > 0 ? state.syncPoints[0].syncBpm : state.currentTempo;
22862
22867
  }
22863
22868
  currentTimePositionToTickPosition(timePosition) {
22864
22869
  const state = this._currentState;
@@ -22873,6 +22878,9 @@
22873
22878
  // we add 1 for possible rounding errors.(floating point issuses)
22874
22879
  return lastTempoChange.ticks + ticks + 1;
22875
22880
  }
22881
+ currentUpdateCurrentTempo(timePosition) {
22882
+ this.updateCurrentTempo(this._mainState, timePosition * this.playbackSpeed);
22883
+ }
22876
22884
  updateCurrentTempo(state, timePosition) {
22877
22885
  let tempoChangeIndex = state.tempoChangeIndex;
22878
22886
  if (timePosition < state.tempoChanges[tempoChangeIndex].time) {
@@ -22887,6 +22895,9 @@
22887
22895
  state.currentTempo = state.tempoChanges[state.tempoChangeIndex].bpm;
22888
22896
  }
22889
22897
  }
22898
+ currentUpdateSyncPoints(timePosition) {
22899
+ this.updateSyncPoints(this._mainState, timePosition);
22900
+ }
22890
22901
  updateSyncPoints(state, timePosition) {
22891
22902
  const syncPoints = state.syncPoints;
22892
22903
  if (syncPoints.length > 0) {
@@ -22899,11 +22910,11 @@
22899
22910
  }
22900
22911
  if (syncPointIndex !== state.syncPointIndex) {
22901
22912
  state.syncPointIndex = syncPointIndex;
22902
- state.modifiedTempo = syncPoints[syncPointIndex].syncBpm;
22913
+ state.syncPointTempo = syncPoints[syncPointIndex].syncBpm;
22903
22914
  }
22904
22915
  }
22905
22916
  else {
22906
- state.modifiedTempo = state.currentTempo;
22917
+ state.syncPointTempo = state.currentTempo;
22907
22918
  }
22908
22919
  }
22909
22920
  mainTimePositionFromBackingTrack(timePosition, backingTrackLength) {
@@ -23050,7 +23061,7 @@
23050
23061
  state.endTime = metronomeTime;
23051
23062
  state.endTick = metronomeTick;
23052
23063
  state.currentTempo = bpm;
23053
- state.modifiedTempo = bpm;
23064
+ state.syncPointTempo = bpm;
23054
23065
  this._countInState = state;
23055
23066
  }
23056
23067
  }
@@ -28379,6 +28390,82 @@
28379
28390
  }
28380
28391
  }
28381
28392
 
28393
+ /**
28394
+ * The options controlling how to export the audio.
28395
+ */
28396
+ class AudioExportOptions {
28397
+ constructor() {
28398
+ /**
28399
+ * The output sample rate.
28400
+ * @default `44100`
28401
+ */
28402
+ this.sampleRate = 44100;
28403
+ /**
28404
+ * Whether to respect sync point information during export.
28405
+ * @default `true`
28406
+ * @remarks
28407
+ * If the song contains sync point information for synchronization with an external media,
28408
+ * this option allows controlling whether the synthesized audio is aligned with these points.
28409
+ *
28410
+ * This is useful when mixing the exported audio together with external media, keeping the same timing.
28411
+ *
28412
+ * Disable this option if you want the original/exact timing as per music sheet in the exported audio.
28413
+ */
28414
+ this.useSyncPoints = false;
28415
+ /**
28416
+ * The current master volume as percentage. (range: 0.0-3.0, default 1.0)
28417
+ */
28418
+ this.masterVolume = 1;
28419
+ /**
28420
+ * The metronome volume. (range: 0.0-3.0, default 0.0)
28421
+ */
28422
+ this.metronomeVolume = 0;
28423
+ /**
28424
+ * The volume for individual tracks as percentage (range: 0.0-3.0).
28425
+ * @remarks
28426
+ * The key is the track index, and the value is the relative volume.
28427
+ * The configured volume (as per data model) still applies, this is an additional volume control.
28428
+ * If no custom value is set, 100% is used.
28429
+ * No values from the currently active synthesizer are applied.
28430
+ *
28431
+ * The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
28432
+ */
28433
+ this.trackVolume = new Map();
28434
+ /**
28435
+ * The additional semitone pitch transpose to apply for individual tracks.
28436
+ * @remarks
28437
+ * The key is the track index, and the value is the number of semitones to apply.
28438
+ * No values from the currently active synthesizer are applied.
28439
+ *
28440
+ * The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
28441
+ */
28442
+ this.trackTranspositionPitches = new Map();
28443
+ }
28444
+ }
28445
+ /**
28446
+ * Represents a single chunk of audio produced.
28447
+ */
28448
+ class AudioExportChunk {
28449
+ constructor() {
28450
+ /**
28451
+ * The current time position within the song in milliseconds.
28452
+ */
28453
+ this.currentTime = 0;
28454
+ /**
28455
+ * The total length of the song in milliseconds.
28456
+ */
28457
+ this.endTime = 0;
28458
+ /**
28459
+ * The current time position within the song in midi ticks.
28460
+ */
28461
+ this.currentTick = 0;
28462
+ /**
28463
+ * The total length of the song in midi ticks.
28464
+ */
28465
+ this.endTick = 0;
28466
+ }
28467
+ }
28468
+
28382
28469
  /**
28383
28470
  * This is the base class for synthesizer components which can be used to
28384
28471
  * play a {@link MidiFile} via a {@link ISynthOutput}.
@@ -28855,6 +28942,184 @@
28855
28942
  constructor(output, bufferTimeInMilliseconds) {
28856
28943
  super(output, new TinySoundFont(output.sampleRate), bufferTimeInMilliseconds);
28857
28944
  }
28945
+ /**
28946
+ * Creates a new audio exporter, initialized with the given data.
28947
+ * @param options The export options to use.
28948
+ * The track volume and transposition pitches must lists must be filled with midi channels.
28949
+ * @param midi The midi file to use.
28950
+ * @param syncPoints The sync points to use
28951
+ * @param transpositionPitches The initial transposition pitches to apply.
28952
+ * @param transpositionPitches The initial transposition pitches to apply.
28953
+ */
28954
+ exportAudio(options, midi, syncPoints, mainTranspositionPitches) {
28955
+ const exporter = new AlphaSynthAudioExporter(options);
28956
+ exporter.loadMidiFile(midi);
28957
+ if (options.useSyncPoints) {
28958
+ exporter.updateSyncPoints(syncPoints);
28959
+ }
28960
+ exporter.applyTranspositionPitches(mainTranspositionPitches);
28961
+ for (const [channel, semitones] of options.trackTranspositionPitches) {
28962
+ exporter.setChannelTranspositionPitch(channel, semitones);
28963
+ }
28964
+ for (const [channel, volume] of options.trackVolume) {
28965
+ exporter.channelSetMixVolume(channel, volume);
28966
+ }
28967
+ if (options.soundFonts) {
28968
+ for (const f of options.soundFonts) {
28969
+ exporter.loadSoundFont(f);
28970
+ }
28971
+ }
28972
+ else {
28973
+ exporter.loadPresets(this.synthesizer.presets);
28974
+ }
28975
+ if (options.playbackRange) {
28976
+ exporter.limitExport(options.playbackRange);
28977
+ }
28978
+ exporter.setup();
28979
+ return exporter;
28980
+ }
28981
+ }
28982
+ /**
28983
+ * A audio exporter allowing streaming synthesis of audio samples with a fixed configuration.
28984
+ */
28985
+ class AlphaSynthAudioExporter {
28986
+ constructor(options) {
28987
+ this._generatedAudioCurrentTime = 0;
28988
+ this._generatedAudioEndTime = 0;
28989
+ this._synth = new TinySoundFont(options.sampleRate);
28990
+ this._sequencer = new MidiFileSequencer(this._synth);
28991
+ this._synth.masterVolume = Math.max(options.masterVolume, SynthConstants.MinVolume);
28992
+ this._synth.metronomeVolume = Math.max(options.metronomeVolume, SynthConstants.MinVolume);
28993
+ }
28994
+ /**
28995
+ * Loads the specified sound font.
28996
+ * @param data The soundfont data.
28997
+ */
28998
+ loadSoundFont(data) {
28999
+ const input = ByteBuffer.fromBuffer(data);
29000
+ const soundFont = new Hydra();
29001
+ soundFont.load(input);
29002
+ const programs = this._sequencer.instrumentPrograms;
29003
+ const percussionKeys = this._sequencer.percussionKeys;
29004
+ this._synth.loadPresets(soundFont, programs, percussionKeys, true);
29005
+ }
29006
+ /**
29007
+ * Loads the specified presets.
29008
+ * @param presets The presets to use.
29009
+ */
29010
+ loadPresets(presets) {
29011
+ this._synth.presets = presets;
29012
+ }
29013
+ /**
29014
+ * Limits the time range for which the export is done.
29015
+ * @param range The time range
29016
+ */
29017
+ limitExport(range) {
29018
+ this._sequencer.mainPlaybackRange = range;
29019
+ this._sequencer.mainSeek(this._sequencer.mainTickPositionToTimePosition(range.startTick));
29020
+ }
29021
+ /**
29022
+ * Sets the transposition pitch of a given channel. This pitch is additionally applied beside the
29023
+ * ones applied already via {@link applyTranspositionPitches}.
29024
+ * @param channel The channel number
29025
+ * @param semitones The number of semitones to apply as pitch offset.
29026
+ */
29027
+ setChannelTranspositionPitch(channel, semitones) {
29028
+ this._synth.setChannelTranspositionPitch(channel, semitones);
29029
+ }
29030
+ /**
29031
+ * Applies the given transposition pitches used for general pitch changes that should be applied to the song.
29032
+ * Used for general transpositions applied to the file.
29033
+ * @param transpositionPitches A map defining for a given list of midi channels the number of semitones that should be adjusted.
29034
+ */
29035
+ applyTranspositionPitches(mainTranspositionPitches) {
29036
+ this._synth.applyTranspositionPitches(mainTranspositionPitches);
29037
+ }
29038
+ /**
29039
+ * Loads the given midi file for synthesis.
29040
+ * @param midi The midi file.
29041
+ */
29042
+ loadMidiFile(midi) {
29043
+ this._sequencer.loadMidi(midi);
29044
+ }
29045
+ /**
29046
+ * Updates the sync points used for time synchronization with a backing track.
29047
+ * @param syncPoints The sync points.
29048
+ */
29049
+ updateSyncPoints(syncPoints) {
29050
+ this._sequencer.mainUpdateSyncPoints(syncPoints);
29051
+ }
29052
+ /**
29053
+ * Sets the current and initial volume of the given channel.
29054
+ * @param channel The channel number.
29055
+ * @param volume The volume of of the channel (0.0-1.0)
29056
+ */
29057
+ channelSetMixVolume(channel, volume) {
29058
+ volume = Math.max(volume, SynthConstants.MinVolume);
29059
+ this._synth.channelSetMixVolume(channel, volume);
29060
+ }
29061
+ setup() {
29062
+ this._synth.setupMetronomeChannel(this._synth.metronomeVolume);
29063
+ const syncPoints = this._sequencer.currentSyncPoints;
29064
+ const alphaTabEndTime = this._sequencer.currentEndTime;
29065
+ if (syncPoints.length === 0) {
29066
+ this._generatedAudioEndTime = alphaTabEndTime;
29067
+ }
29068
+ else {
29069
+ const lastSyncPoint = syncPoints[syncPoints.length - 1];
29070
+ let endTime = lastSyncPoint.syncTime;
29071
+ const remainingTicks = this._sequencer.currentEndTick - lastSyncPoint.synthTick;
29072
+ if (remainingTicks > 0) {
29073
+ endTime += MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.syncBpm);
29074
+ }
29075
+ this._generatedAudioEndTime = endTime;
29076
+ }
29077
+ }
29078
+ render(milliseconds) {
29079
+ if (this._sequencer.isFinished) {
29080
+ return undefined;
29081
+ }
29082
+ const oneMicroBufferMillis = (SynthConstants.MicroBufferSize * 1000) / this._synth.outSampleRate;
29083
+ const microBufferCount = Math.ceil(milliseconds / oneMicroBufferMillis);
29084
+ let samples = new Float32Array(SynthConstants.MicroBufferSize * microBufferCount * SynthConstants.AudioChannels);
29085
+ const syncPoints = this._sequencer.currentSyncPoints;
29086
+ let bufferPos = 0;
29087
+ let subBufferTime = this._generatedAudioCurrentTime;
29088
+ let alphaTabGeneratedMillis = 0;
29089
+ for (let i = 0; i < microBufferCount; i++) {
29090
+ // if we're applying sync points, we calculate the needed tempo and set the playback speed
29091
+ if (syncPoints.length > 0) {
29092
+ this._sequencer.currentUpdateSyncPoints(subBufferTime);
29093
+ this._sequencer.currentUpdateCurrentTempo(this._sequencer.currentTime);
29094
+ const newSpeed = this._sequencer.syncPointTempo / this._sequencer.currentTempo;
29095
+ if (this._sequencer.playbackSpeed !== newSpeed) {
29096
+ this._sequencer.playbackSpeed = newSpeed;
29097
+ }
29098
+ }
29099
+ this._sequencer.fillMidiEventQueue();
29100
+ this._synth.synthesize(samples, bufferPos, SynthConstants.MicroBufferSize);
29101
+ bufferPos += SynthConstants.MicroBufferSize * SynthConstants.AudioChannels;
29102
+ subBufferTime += oneMicroBufferMillis;
29103
+ alphaTabGeneratedMillis += oneMicroBufferMillis * this._sequencer.playbackSpeed;
29104
+ if (this._sequencer.isFinished) {
29105
+ break;
29106
+ }
29107
+ }
29108
+ if (bufferPos < samples.length) {
29109
+ samples = samples.subarray(0, bufferPos);
29110
+ }
29111
+ const chunk = new AudioExportChunk();
29112
+ chunk.currentTime = this._generatedAudioCurrentTime;
29113
+ chunk.endTime = this._generatedAudioEndTime;
29114
+ chunk.currentTick = this._sequencer.currentTimePositionToTickPosition(this._sequencer.currentTime);
29115
+ chunk.endTick = this._sequencer.currentEndTick;
29116
+ this._generatedAudioCurrentTime += milliseconds;
29117
+ chunk.samples = samples;
29118
+ if (this._sequencer.isFinished) {
29119
+ this._synth.noteOffAll(true);
29120
+ }
29121
+ return chunk;
29122
+ }
28858
29123
  }
28859
29124
 
28860
29125
  /**
@@ -33288,6 +33553,7 @@
33288
33553
  */
33289
33554
  class AlphaSynthWebWorker {
33290
33555
  constructor(main, bufferTimeInMilliseconds) {
33556
+ this._exporter = new Map();
33291
33557
  this._main = main;
33292
33558
  this._main.addEventListener('message', this.handleMessage.bind(this));
33293
33559
  this._player = new AlphaSynth(new AlphaSynthWorkerSynthOutput(), bufferTimeInMilliseconds);
@@ -33403,6 +33669,53 @@
33403
33669
  this._player.applyTranspositionPitches(new Map(JSON.parse(data.transpositionPitches)));
33404
33670
  break;
33405
33671
  }
33672
+ if (cmd.startsWith('alphaSynth.exporter')) {
33673
+ this.handleExporterMessage(e);
33674
+ }
33675
+ }
33676
+ handleExporterMessage(e) {
33677
+ const data = e.data;
33678
+ const cmd = data.cmd;
33679
+ try {
33680
+ switch (cmd) {
33681
+ case 'alphaSynth.exporter.initialize':
33682
+ const exporter = this._player.exportAudio(data.options, JsonConverter.jsObjectToMidiFile(data.midi), data.syncPoints, data.transpositionPitches);
33683
+ this._exporter.set(data.exporterId, exporter);
33684
+ this._main.postMessage({
33685
+ cmd: 'alphaSynth.exporter.initialized',
33686
+ exporterId: data.exporterId
33687
+ });
33688
+ break;
33689
+ case 'alphaSynth.exporter.render':
33690
+ if (this._exporter.has(data.exporterId)) {
33691
+ const exporter = this._exporter.get(data.exporterId);
33692
+ const chunk = exporter.render(data.milliseconds);
33693
+ this._main.postMessage({
33694
+ cmd: 'alphaSynth.exporter.rendered',
33695
+ exporterId: data.exporterId,
33696
+ chunk
33697
+ });
33698
+ }
33699
+ else {
33700
+ this._main.postMessage({
33701
+ cmd: 'alphaSynth.exporter.error',
33702
+ exporterId: data.exporterId,
33703
+ error: new Error('Unknown exporter ID')
33704
+ });
33705
+ }
33706
+ break;
33707
+ case 'alphaSynth.exporter.destroy':
33708
+ this._exporter.delete(data.exporterId);
33709
+ break;
33710
+ }
33711
+ }
33712
+ catch (e) {
33713
+ this._main.postMessage({
33714
+ cmd: 'alphaSynth.exporter.error',
33715
+ exporterId: data.exporterId,
33716
+ error: e
33717
+ });
33718
+ }
33406
33719
  }
33407
33720
  onPositionChanged(e) {
33408
33721
  this._main.postMessage({
@@ -41709,8 +42022,7 @@
41709
42022
  if (this._isDestroyed) {
41710
42023
  return;
41711
42024
  }
41712
- if (this.hasCursor &&
41713
- this.settings.player.enableUserInteraction) {
42025
+ if (this.hasCursor && this.settings.player.enableUserInteraction) {
41714
42026
  this._selectionStart = new SelectionInfo(beat);
41715
42027
  this._selectionEnd = null;
41716
42028
  }
@@ -41750,8 +42062,7 @@
41750
42062
  if (this._isDestroyed) {
41751
42063
  return;
41752
42064
  }
41753
- if (this.hasCursor &&
41754
- this.settings.player.enableUserInteraction) {
42065
+ if (this.hasCursor && this.settings.player.enableUserInteraction) {
41755
42066
  if (this._selectionEnd) {
41756
42067
  const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ??
41757
42068
  this._selectionStart.beat.absolutePlaybackStart;
@@ -41881,9 +42192,7 @@
41881
42192
  }
41882
42193
  });
41883
42194
  this._renderer.postRenderFinished.on(() => {
41884
- if (!this._selectionStart ||
41885
- !this.hasCursor ||
41886
- !this.settings.player.enableUserInteraction) {
42195
+ if (!this._selectionStart || !this.hasCursor || !this.settings.player.enableUserInteraction) {
41887
42196
  return;
41888
42197
  }
41889
42198
  this.cursorSelectRange(this._selectionStart, this._selectionEnd);
@@ -42573,6 +42882,57 @@
42573
42882
  async getOutputDevice() {
42574
42883
  return await this._player.output.getOutputDevice();
42575
42884
  }
42885
+ /**
42886
+ * Starts the audio export for the currently loaded song.
42887
+ * @remarks
42888
+ * This will not export or use any backing track media but will always use the synthesizer to generate the output.
42889
+ * This method works with any PlayerMode active but changing the mode during export can lead to unexpected side effects.
42890
+ * @param options The export options.
42891
+ * @returns An exporter instance to export the audio in a streaming fashion.
42892
+ */
42893
+ async exportAudio(options) {
42894
+ if (!this.score) {
42895
+ throw new AlphaTabError(exports.AlphaTabErrorType.General, 'No song loaded');
42896
+ }
42897
+ let exporter;
42898
+ switch (this._actualPlayerMode) {
42899
+ case exports.PlayerMode.EnabledSynthesizer:
42900
+ exporter = this.uiFacade.createWorkerAudioExporter(this._player.instance);
42901
+ break;
42902
+ default:
42903
+ exporter = this.uiFacade.createWorkerAudioExporter(null);
42904
+ break;
42905
+ }
42906
+ const score = this.score;
42907
+ const midiFile = new MidiFile();
42908
+ const handler = new AlphaSynthMidiFileHandler(midiFile);
42909
+ const generator = new MidiFileGenerator(score, this.settings, handler);
42910
+ generator.applyTranspositionPitches = false;
42911
+ generator.generate();
42912
+ const optionsWithChannels = new AudioExportOptions();
42913
+ optionsWithChannels.soundFonts = options.soundFonts;
42914
+ optionsWithChannels.sampleRate = options.sampleRate;
42915
+ optionsWithChannels.useSyncPoints = options.useSyncPoints;
42916
+ optionsWithChannels.masterVolume = options.masterVolume;
42917
+ optionsWithChannels.metronomeVolume = options.metronomeVolume;
42918
+ optionsWithChannels.playbackRange = options.playbackRange;
42919
+ for (const [trackIndex, volume] of options.trackVolume) {
42920
+ if (trackIndex < this.score.tracks.length) {
42921
+ const track = this.score.tracks[trackIndex];
42922
+ optionsWithChannels.trackVolume.set(track.playbackInfo.primaryChannel, volume);
42923
+ optionsWithChannels.trackVolume.set(track.playbackInfo.secondaryChannel, volume);
42924
+ }
42925
+ }
42926
+ for (const [trackIndex, semitones] of options.trackTranspositionPitches) {
42927
+ if (trackIndex < this.score.tracks.length) {
42928
+ const track = this.score.tracks[trackIndex];
42929
+ optionsWithChannels.trackTranspositionPitches.set(track.playbackInfo.primaryChannel, semitones);
42930
+ optionsWithChannels.trackTranspositionPitches.set(track.playbackInfo.secondaryChannel, semitones);
42931
+ }
42932
+ }
42933
+ await exporter.initialize(optionsWithChannels, midiFile, generator.syncPoints, generator.transpositionPitches);
42934
+ return exporter;
42935
+ }
42576
42936
  }
42577
42937
 
42578
42938
  /**
@@ -43406,6 +43766,9 @@
43406
43766
  get logLevel() {
43407
43767
  return Logger.logLevel;
43408
43768
  }
43769
+ get worker() {
43770
+ return this._synth;
43771
+ }
43409
43772
  set logLevel(value) {
43410
43773
  Logger.logLevel = value;
43411
43774
  this._synth.postMessage({
@@ -44258,6 +44621,87 @@
44258
44621
  }
44259
44622
  }
44260
44623
 
44624
+ /**
44625
+ * @target web
44626
+ */
44627
+ class AlphaSynthAudioExporterWorkerApi {
44628
+ constructor(synthWorker, ownsWorker) {
44629
+ this._promise = null;
44630
+ this._exporterId = AlphaSynthAudioExporterWorkerApi._nextExporterId++;
44631
+ this._worker = synthWorker;
44632
+ this._ownsWorker = ownsWorker;
44633
+ }
44634
+ async initialize(options, midi, syncPoints, transpositionPitches) {
44635
+ const onmessage = this.handleWorkerMessage.bind(this);
44636
+ this._worker.worker.addEventListener('message', onmessage, false);
44637
+ this._unsubscribe = () => {
44638
+ this._worker.worker.removeEventListener('message', onmessage, false);
44639
+ };
44640
+ this._promise = Promise.withResolvers();
44641
+ this._worker.worker.postMessage({
44642
+ cmd: 'alphaSynth.exporter.initialize',
44643
+ exporterId: this._exporterId,
44644
+ options: Environment.prepareForPostMessage(options),
44645
+ midi: JsonConverter.midiFileToJsObject(Environment.prepareForPostMessage(midi)),
44646
+ syncPoints: Environment.prepareForPostMessage(syncPoints),
44647
+ transpositionPitches: Environment.prepareForPostMessage(transpositionPitches)
44648
+ });
44649
+ await this._promise.promise;
44650
+ }
44651
+ handleWorkerMessage(e) {
44652
+ const data = e.data;
44653
+ // for us?
44654
+ if (data.exporterId !== this._exporterId) {
44655
+ return;
44656
+ }
44657
+ const cmd = data.cmd;
44658
+ switch (cmd) {
44659
+ case 'alphaSynth.exporter.initialized':
44660
+ this._promise?.resolve(null);
44661
+ this._promise = null;
44662
+ break;
44663
+ case 'alphaSynth.exporter.error':
44664
+ this._promise?.reject(data.error);
44665
+ this._promise = null;
44666
+ break;
44667
+ case 'alphaSynth.exporter.rendered':
44668
+ this._promise?.resolve(data.chunk);
44669
+ this._promise = null;
44670
+ break;
44671
+ case 'alphaSynth.destroyed':
44672
+ this._promise?.reject(new AlphaTabError(exports.AlphaTabErrorType.General, 'Worker was destroyed'));
44673
+ this._promise = null;
44674
+ break;
44675
+ }
44676
+ }
44677
+ async render(milliseconds) {
44678
+ if (this._promise) {
44679
+ throw new AlphaTabError(exports.AlphaTabErrorType.General, 'There is already an ongoing operation, wait for initialize to complete before requesting render');
44680
+ }
44681
+ this._promise = Promise.withResolvers();
44682
+ this._worker.worker.postMessage({
44683
+ cmd: 'alphaSynth.exporter.render',
44684
+ exporterId: this._exporterId,
44685
+ milliseconds: milliseconds
44686
+ });
44687
+ return (await this._promise.promise);
44688
+ }
44689
+ destroy() {
44690
+ this._worker.worker.postMessage({
44691
+ cmd: 'alphaSynth.exporter.destroy',
44692
+ exporterId: this._exporterId
44693
+ });
44694
+ this._unsubscribe();
44695
+ if (this._ownsWorker) {
44696
+ this._worker.destroy();
44697
+ }
44698
+ }
44699
+ [Symbol.dispose]() {
44700
+ this.destroy();
44701
+ }
44702
+ }
44703
+ AlphaSynthAudioExporterWorkerApi._nextExporterId = 1;
44704
+
44261
44705
  /**
44262
44706
  * @target web
44263
44707
  */
@@ -44824,6 +45268,14 @@
44824
45268
  }
44825
45269
  return player;
44826
45270
  }
45271
+ createWorkerAudioExporter(synth) {
45272
+ const needNewWorker = synth === null || !(synth instanceof AlphaSynthWebWorkerApi);
45273
+ if (needNewWorker) {
45274
+ // nowadays we require browsers with workers
45275
+ synth = this.createWorkerPlayer();
45276
+ }
45277
+ return new AlphaSynthAudioExporterWorkerApi(synth, needNewWorker);
45278
+ }
44827
45279
  beginInvoke(action) {
44828
45280
  window.requestAnimationFrame(() => {
44829
45281
  action();
@@ -61281,9 +61733,9 @@
61281
61733
  print(`build date: ${VersionInfo.date}`);
61282
61734
  }
61283
61735
  }
61284
- VersionInfo.version = '1.6.0-alpha.1444';
61285
- VersionInfo.date = '2025-06-11T15:11:50.582Z';
61286
- VersionInfo.commit = '17557bdb7620020690fced1bc051d8a683a02c2b';
61736
+ VersionInfo.version = '1.6.0-alpha.1448';
61737
+ VersionInfo.date = '2025-06-14T22:07:33.833Z';
61738
+ VersionInfo.commit = 'bfeddfaced057b74c2fa71fa58aa407467dd7460';
61287
61739
 
61288
61740
  /**
61289
61741
  * A factory for custom layout engines.
@@ -61953,7 +62405,7 @@
61953
62405
  /**
61954
62406
  * Builds the default SMuFL font sources for the usage with alphaTab in cases
61955
62407
  * where no custom {@link smuflFontSources} are provided.
61956
- * @param fontDirectory The {@link fontDirectory} configured.
62408
+ * @param fontDirectory The {@link CoreSettings.fontDirectory} configured.
61957
62409
  * @target web
61958
62410
  */
61959
62411
  static buildDefaultSmuflFontSources(fontDirectory) {
@@ -65714,6 +66166,8 @@
65714
66166
  AlphaSynthScriptProcessorOutput,
65715
66167
  AlphaSynthWebAudioOutputBase,
65716
66168
  AlphaSynthWebWorkerApi,
66169
+ AudioExportChunk,
66170
+ AudioExportOptions,
65717
66171
  BackingTrackSyncPoint,
65718
66172
  CircularSampleBuffer,
65719
66173
  MidiEventsPlayedEventArgs,