@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.core.min.mjs +2 -2
- package/dist/alphaTab.core.mjs +475 -21
- package/dist/alphaTab.d.ts +184 -4
- package/dist/alphaTab.js +475 -21
- package/dist/alphaTab.min.js +2 -2
- package/dist/alphaTab.min.mjs +1 -1
- package/dist/alphaTab.mjs +1 -1
- package/dist/alphaTab.vite.js +1 -1
- package/dist/alphaTab.vite.mjs +1 -1
- package/dist/alphaTab.webpack.js +1 -1
- package/dist/alphaTab.webpack.mjs +1 -1
- package/dist/alphaTab.worker.min.mjs +1 -1
- package/dist/alphaTab.worker.mjs +1 -1
- package/dist/alphaTab.worklet.min.mjs +1 -1
- package/dist/alphaTab.worklet.mjs +1 -1
- package/package.json +3 -2
package/dist/alphaTab.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.6.0-alpha.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
22913
|
+
state.syncPointTempo = syncPoints[syncPointIndex].syncBpm;
|
|
22903
22914
|
}
|
|
22904
22915
|
}
|
|
22905
22916
|
else {
|
|
22906
|
-
state.
|
|
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.
|
|
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.
|
|
61285
|
-
VersionInfo.date = '2025-06-
|
|
61286
|
-
VersionInfo.commit = '
|
|
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,
|