@coderline/alphatab 1.4.0 → 1.5.0-alpha.1331

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.4.0 (, build 12)
2
+ * alphaTab v1.5.0-alpha.1331 (develop, build 1331)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -27073,6 +27073,9 @@ class PlaybackRangeChangedEventArgs {
27073
27073
  * play a {@link MidiFile} via a {@link ISynthOutput}.
27074
27074
  */
27075
27075
  class AlphaSynth {
27076
+ get output() {
27077
+ return this._output;
27078
+ }
27076
27079
  get isReadyForPlayback() {
27077
27080
  return this.isReady && this._isSoundFontLoaded && this._isMidiLoaded;
27078
27081
  }
@@ -27193,7 +27196,7 @@ class AlphaSynth {
27193
27196
  Logger.debug('AlphaSynth', 'Initializing player');
27194
27197
  this.state = PlayerState.Paused;
27195
27198
  Logger.debug('AlphaSynth', 'Creating output');
27196
- this.output = output;
27199
+ this._output = output;
27197
27200
  Logger.debug('AlphaSynth', 'Creating synthesizer');
27198
27201
  this._synthesizer = new TinySoundFont(this.output.sampleRate);
27199
27202
  this._sequencer = new MidiFileSequencer(this._synthesizer);
@@ -27566,6 +27569,13 @@ class AlphaSynthWorkerSynthOutput {
27566
27569
  activate() {
27567
27570
  // nothing to do
27568
27571
  }
27572
+ async enumerateOutputDevices() {
27573
+ return [];
27574
+ }
27575
+ async setOutputDevice(device) { }
27576
+ async getOutputDevice() {
27577
+ return null;
27578
+ }
27569
27579
  }
27570
27580
  AlphaSynthWorkerSynthOutput.CmdOutputPrefix = 'alphaSynth.output.';
27571
27581
  AlphaSynthWorkerSynthOutput.CmdOutputAddSamples = AlphaSynthWorkerSynthOutput.CmdOutputPrefix + 'addSamples';
@@ -33383,6 +33393,37 @@ class AlphaTabApiBase {
33383
33393
  this.settingsUpdated.trigger();
33384
33394
  this.uiFacade.triggerEvent(this.container, 'settingsUpdated', null);
33385
33395
  }
33396
+ /**
33397
+ * Loads and lists the available output devices which can be used by the player. Will request permissions if needed.
33398
+ * @returns the list of available devices or an empty list if there are no permissions, or the player is not enabled.
33399
+ */
33400
+ async enumerateOutputDevices() {
33401
+ if (this.player) {
33402
+ return await this.player.output.enumerateOutputDevices();
33403
+ }
33404
+ return [];
33405
+ }
33406
+ /**
33407
+ * Changes the output device which should be used for playing the audio (player must be enabled).
33408
+ * @param device The output device to use, or null to switch to the default device.
33409
+ */
33410
+ async setOutputDevice(device) {
33411
+ if (this.player) {
33412
+ await this.player.output.setOutputDevice(device);
33413
+ }
33414
+ }
33415
+ /**
33416
+ * The currently configured output device if changed via {@link setOutputDevice}.
33417
+ * @returns The custom configured output device which was set via {@link setOutputDevice} or `null`
33418
+ * if the default outputDevice is used.
33419
+ * The output device might change dynamically if devices are connected/disconnected (e.g. bluetooth headset).
33420
+ */
33421
+ async getOutputDevice() {
33422
+ if (this.player) {
33423
+ return await this.player.output.getOutputDevice();
33424
+ }
33425
+ return null;
33426
+ }
33386
33427
  }
33387
33428
 
33388
33429
  /**
@@ -33845,6 +33886,21 @@ class CircularSampleBuffer {
33845
33886
  }
33846
33887
  }
33847
33888
 
33889
+ /**
33890
+ * @target web
33891
+ */
33892
+ class AlphaSynthWebAudioSynthOutputDevice {
33893
+ constructor(device) {
33894
+ this.isDefault = false;
33895
+ this.device = device;
33896
+ }
33897
+ get deviceId() {
33898
+ return this.device.deviceId;
33899
+ }
33900
+ get label() {
33901
+ return this.device.label;
33902
+ }
33903
+ }
33848
33904
  /**
33849
33905
  * @target web
33850
33906
  */
@@ -33856,6 +33912,7 @@ class AlphaSynthWebAudioOutputBase {
33856
33912
  this.ready = new EventEmitter();
33857
33913
  this.samplesPlayed = new EventEmitterOfT();
33858
33914
  this.sampleRequest = new EventEmitter();
33915
+ this._knownDevices = [];
33859
33916
  }
33860
33917
  get sampleRate() {
33861
33918
  return this._context ? this._context.sampleRate : AlphaSynthWebAudioOutputBase.PreferredSampleRate;
@@ -33954,6 +34011,101 @@ class AlphaSynthWebAudioOutputBase {
33954
34011
  onReady() {
33955
34012
  this.ready.trigger();
33956
34013
  }
34014
+ async checkSinkIdSupport() {
34015
+ // https://caniuse.com/mdn-api_audiocontext_sinkid
34016
+ const context = this._context ?? this.createAudioContext();
34017
+ if (!('setSinkId' in context)) {
34018
+ Logger.warning('WebAudio', 'Browser does not support changing the output device');
34019
+ return false;
34020
+ }
34021
+ return true;
34022
+ }
34023
+ async enumerateOutputDevices() {
34024
+ try {
34025
+ if (!(await this.checkSinkIdSupport())) {
34026
+ return [];
34027
+ }
34028
+ // Request permissions
34029
+ try {
34030
+ await navigator.mediaDevices.getUserMedia({ audio: true });
34031
+ }
34032
+ catch (e) {
34033
+ // sometimes we get an error but can still enumerate, e.g. if microphone access is denied,
34034
+ // we can still load the output devices in some cases.
34035
+ Logger.warning('WebAudio', 'Output device permission rejected', e);
34036
+ }
34037
+ // load devices
34038
+ const devices = await navigator.mediaDevices.enumerateDevices();
34039
+ // default device candidates
34040
+ let defaultDeviceGroupId = '';
34041
+ let defaultDeviceId = '';
34042
+ const realDevices = new Map();
34043
+ for (const device of devices) {
34044
+ if (device.kind === 'audiooutput') {
34045
+ realDevices.set(device.groupId, new AlphaSynthWebAudioSynthOutputDevice(device));
34046
+ // chromium has the default device as deviceID: 'default'
34047
+ // the standard defines empty-string as default
34048
+ if (device.deviceId === 'default' || device.deviceId === '') {
34049
+ defaultDeviceGroupId = device.groupId;
34050
+ defaultDeviceId = device.deviceId;
34051
+ }
34052
+ }
34053
+ }
34054
+ const final = Array.from(realDevices.values());
34055
+ // flag default device
34056
+ let defaultDevice = final.find(d => d.deviceId === defaultDeviceId);
34057
+ if (!defaultDevice) {
34058
+ defaultDevice = final.find(d => d.device.groupId === defaultDeviceGroupId);
34059
+ }
34060
+ if (!defaultDevice && final.length > 0) {
34061
+ defaultDevice = final[0];
34062
+ }
34063
+ if (defaultDevice) {
34064
+ defaultDevice.isDefault = true;
34065
+ }
34066
+ this._knownDevices = final;
34067
+ return final;
34068
+ }
34069
+ catch (e) {
34070
+ Logger.error('WebAudio', 'Failed to enumerate output devices', e);
34071
+ return [];
34072
+ }
34073
+ }
34074
+ async setOutputDevice(device) {
34075
+ if (!(await this.checkSinkIdSupport())) {
34076
+ return;
34077
+ }
34078
+ // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId
34079
+ if (!device) {
34080
+ await this._context.setSinkId('');
34081
+ }
34082
+ else {
34083
+ await this._context.setSinkId(device.deviceId);
34084
+ }
34085
+ }
34086
+ async getOutputDevice() {
34087
+ if (!(await this.checkSinkIdSupport())) {
34088
+ return null;
34089
+ }
34090
+ // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/sinkId
34091
+ const sinkId = this._context.sinkId;
34092
+ if (typeof sinkId !== 'string' || sinkId === '' || sinkId === 'default') {
34093
+ return null;
34094
+ }
34095
+ // fast path -> cached devices list
34096
+ let device = this._knownDevices.find(d => d.deviceId === sinkId);
34097
+ if (device) {
34098
+ return device;
34099
+ }
34100
+ // slow path -> enumerate devices
34101
+ const allDevices = await this.enumerateOutputDevices();
34102
+ device = allDevices.find(d => d.deviceId === sinkId);
34103
+ if (device) {
34104
+ return device;
34105
+ }
34106
+ Logger.warning('WebAudio', 'Could not find output device in device list', sinkId, allDevices);
34107
+ return null;
34108
+ }
33957
34109
  }
33958
34110
  AlphaSynthWebAudioOutputBase.BufferSize = 4096;
33959
34111
  AlphaSynthWebAudioOutputBase.PreferredSampleRate = 44100;
@@ -34070,6 +34222,9 @@ class ProgressEventArgs {
34070
34222
  * @target web
34071
34223
  */
34072
34224
  class AlphaSynthWebWorkerApi {
34225
+ get output() {
34226
+ return this._output;
34227
+ }
34073
34228
  get isReady() {
34074
34229
  return this._workerIsReady && this._outputIsReady;
34075
34230
  }
@@ -52127,8 +52282,8 @@ class CoreSettings {
52127
52282
  // </auto-generated>
52128
52283
  class VersionInfo {
52129
52284
  }
52130
- VersionInfo.version = '1.4.0';
52131
- VersionInfo.date = '2025-03-02T15:59:05.431Z';
52285
+ VersionInfo.version = '1.5.0-alpha.1331';
52286
+ VersionInfo.date = '2025-03-03T01:56:53.590Z';
52132
52287
 
52133
52288
  var index$4 = /*#__PURE__*/Object.freeze({
52134
52289
  __proto__: null,
@@ -7299,6 +7299,95 @@ declare class MidiEventsPlayedEventArgs {
7299
7299
  constructor(events: MidiEvent[]);
7300
7300
  }
7301
7301
 
7302
+ /**
7303
+ * Represents a output device on which the synth can send the audio to.
7304
+ */
7305
+ interface ISynthOutputDevice {
7306
+ /**
7307
+ * The ID to uniquely identify the device.
7308
+ */
7309
+ readonly deviceId: string;
7310
+ /**
7311
+ * A string describing the device.
7312
+ */
7313
+ readonly label: string;
7314
+ /**
7315
+ * Gets a value indicating whether the device is the default output device.
7316
+ */
7317
+ readonly isDefault: boolean;
7318
+ }
7319
+ /**
7320
+ * This is the base interface for output devices which can
7321
+ * request and playback audio samples.
7322
+ * @csharp_public
7323
+ */
7324
+ interface ISynthOutput {
7325
+ /**
7326
+ * Gets the sample rate required by the output.
7327
+ */
7328
+ readonly sampleRate: number;
7329
+ /**
7330
+ * Called when the output should be opened.
7331
+ */
7332
+ open(bufferTimeInMilliseconds: number): void;
7333
+ /**
7334
+ * Called when the output should start the playback.
7335
+ */
7336
+ play(): void;
7337
+ /**
7338
+ * Requests the output to destroy itself.
7339
+ */
7340
+ destroy(): void;
7341
+ /**
7342
+ * Called when the output should stop the playback.
7343
+ */
7344
+ pause(): void;
7345
+ /**
7346
+ * Called when samples have been synthesized and should be added to the playback buffer.
7347
+ * @param samples
7348
+ */
7349
+ addSamples(samples: Float32Array): void;
7350
+ /**
7351
+ * Called when the samples in the output buffer should be reset. This is neeed for instance when seeking to another position.
7352
+ */
7353
+ resetSamples(): void;
7354
+ /**
7355
+ * Activates the output component.
7356
+ */
7357
+ activate(): void;
7358
+ /**
7359
+ * Fired when the output has been successfully opened and is ready to play samples.
7360
+ */
7361
+ readonly ready: IEventEmitter;
7362
+ /**
7363
+ * Fired when a certain number of samples have been played.
7364
+ */
7365
+ readonly samplesPlayed: IEventEmitterOfT<number>;
7366
+ /**
7367
+ * Fired when the output needs more samples to be played.
7368
+ */
7369
+ readonly sampleRequest: IEventEmitter;
7370
+ /**
7371
+ * Loads and lists the available output devices. Will request permissions if needed.
7372
+ * @async
7373
+ */
7374
+ enumerateOutputDevices(): Promise<ISynthOutputDevice[]>;
7375
+ /**
7376
+ * Changes the output device which should be used for playing the audio.
7377
+ * @async
7378
+ * @param device The output device to use, or null to switch to the default device.
7379
+ */
7380
+ setOutputDevice(device: ISynthOutputDevice | null): Promise<void>;
7381
+ /**
7382
+ * The currently configured output device if changed via {@link setOutputDevice}.
7383
+ * @async
7384
+ * @returns The custom configured output device which was set via {@link setOutputDevice} or `null`
7385
+ * if the default outputDevice is used.
7386
+ * The output device might change dynamically if devices are connected/disconnected (e.g. bluetooth headset).
7387
+ */
7388
+ getOutputDevice(): Promise<ISynthOutputDevice | null>;
7389
+ }
7390
+
7302
7391
  /**
7303
7392
  * The public API interface for interacting with the synthesizer.
7304
7393
  */
@@ -7356,6 +7445,10 @@ interface IAlphaSynth {
7356
7445
  * Gets or sets the midi events which will trigger the `midiEventsPlayed` event.
7357
7446
  */
7358
7447
  midiEventsPlayedFilter: MidiEventType[];
7448
+ /**
7449
+ * Gets the output used by alphaSynth.
7450
+ */
7451
+ readonly output: ISynthOutput;
7359
7452
  /**
7360
7453
  * Destroys the synthesizer and all related components
7361
7454
  */
@@ -8039,6 +8132,23 @@ declare class AlphaTabApiBase<TSettings> {
8039
8132
  */
8040
8133
  settingsUpdated: IEventEmitter;
8041
8134
  private onSettingsUpdated;
8135
+ /**
8136
+ * Loads and lists the available output devices which can be used by the player. Will request permissions if needed.
8137
+ * @returns the list of available devices or an empty list if there are no permissions, or the player is not enabled.
8138
+ */
8139
+ enumerateOutputDevices(): Promise<ISynthOutputDevice[]>;
8140
+ /**
8141
+ * Changes the output device which should be used for playing the audio (player must be enabled).
8142
+ * @param device The output device to use, or null to switch to the default device.
8143
+ */
8144
+ setOutputDevice(device: ISynthOutputDevice | null): Promise<void>;
8145
+ /**
8146
+ * The currently configured output device if changed via {@link setOutputDevice}.
8147
+ * @returns The custom configured output device which was set via {@link setOutputDevice} or `null`
8148
+ * if the default outputDevice is used.
8149
+ * The output device might change dynamically if devices are connected/disconnected (e.g. bluetooth headset).
8150
+ */
8151
+ getOutputDevice(): Promise<ISynthOutputDevice | null>;
8042
8152
  }
8043
8153
 
8044
8154
  /**
@@ -8542,59 +8652,6 @@ declare namespace index_d$1 {
8542
8652
  export { index_d$1_CssFontSvgCanvas as CssFontSvgCanvas, index_d$1_Cursors as Cursors, index_d$1_FontSizes as FontSizes, type index_d$1_ICanvas as ICanvas, type index_d$1_IContainer as IContainer, type index_d$1_IMouseEventArgs as IMouseEventArgs, type index_d$1_IUiFacade as IUiFacade, index_d$1_SvgCanvas as SvgCanvas, index_d$1_TextAlign as TextAlign, index_d$1_TextBaseline as TextBaseline };
8543
8653
  }
8544
8654
 
8545
- /**
8546
- * This is the base interface for output devices which can
8547
- * request and playback audio samples.
8548
- * @csharp_public
8549
- */
8550
- interface ISynthOutput {
8551
- /**
8552
- * Gets the sample rate required by the output.
8553
- */
8554
- readonly sampleRate: number;
8555
- /**
8556
- * Called when the output should be opened.
8557
- */
8558
- open(bufferTimeInMilliseconds: number): void;
8559
- /**
8560
- * Called when the output should start the playback.
8561
- */
8562
- play(): void;
8563
- /**
8564
- * Requests the output to destroy itself.
8565
- */
8566
- destroy(): void;
8567
- /**
8568
- * Called when the output should stop the playback.
8569
- */
8570
- pause(): void;
8571
- /**
8572
- * Called when samples have been synthesized and should be added to the playback buffer.
8573
- * @param samples
8574
- */
8575
- addSamples(samples: Float32Array): void;
8576
- /**
8577
- * Called when the samples in the output buffer should be reset. This is neeed for instance when seeking to another position.
8578
- */
8579
- resetSamples(): void;
8580
- /**
8581
- * Activates the output component.
8582
- */
8583
- activate(): void;
8584
- /**
8585
- * Fired when the output has been successfully opened and is ready to play samples.
8586
- */
8587
- readonly ready: IEventEmitter;
8588
- /**
8589
- * Fired when a certain number of samples have been played.
8590
- */
8591
- readonly samplesPlayed: IEventEmitterOfT<number>;
8592
- /**
8593
- * Fired when the output needs more samples to be played.
8594
- */
8595
- readonly sampleRequest: IEventEmitter;
8596
- }
8597
-
8598
8655
  /**
8599
8656
  * This is the main synthesizer component which can be used to
8600
8657
  * play a {@link MidiFile} via a {@link ISynthOutput}.
@@ -8612,10 +8669,8 @@ declare class AlphaSynth implements IAlphaSynth {
8612
8669
  private _midiEventsPlayedFilter;
8613
8670
  private _notPlayedSamples;
8614
8671
  private _synthStopping;
8615
- /**
8616
- * Gets the {@link ISynthOutput} used for playing the generated samples.
8617
- */
8618
- readonly output: ISynthOutput;
8672
+ private _output;
8673
+ get output(): ISynthOutput;
8619
8674
  isReady: boolean;
8620
8675
  get isReadyForPlayback(): boolean;
8621
8676
  state: PlayerState;
@@ -8750,6 +8805,7 @@ declare class AlphaSynthWebWorkerApi implements IAlphaSynth {
8750
8805
  private _isLooping;
8751
8806
  private _playbackRange;
8752
8807
  private _midiEventsPlayedFilter;
8808
+ get output(): ISynthOutput;
8753
8809
  get isReady(): boolean;
8754
8810
  get isReadyForPlayback(): boolean;
8755
8811
  get state(): PlayerState;
@@ -8837,6 +8893,11 @@ declare abstract class AlphaSynthWebAudioOutputBase implements ISynthOutput {
8837
8893
  protected onSamplesPlayed(numberOfSamples: number): void;
8838
8894
  protected onSampleRequest(): void;
8839
8895
  protected onReady(): void;
8896
+ private checkSinkIdSupport;
8897
+ private _knownDevices;
8898
+ enumerateOutputDevices(): Promise<ISynthOutputDevice[]>;
8899
+ setOutputDevice(device: ISynthOutputDevice | null): Promise<void>;
8900
+ getOutputDevice(): Promise<ISynthOutputDevice | null>;
8840
8901
  }
8841
8902
 
8842
8903
  /**
@@ -8893,6 +8954,7 @@ type index_d_CircularSampleBuffer = CircularSampleBuffer;
8893
8954
  declare const index_d_CircularSampleBuffer: typeof CircularSampleBuffer;
8894
8955
  type index_d_IAlphaSynth = IAlphaSynth;
8895
8956
  type index_d_ISynthOutput = ISynthOutput;
8957
+ type index_d_ISynthOutputDevice = ISynthOutputDevice;
8896
8958
  type index_d_MidiEventsPlayedEventArgs = MidiEventsPlayedEventArgs;
8897
8959
  declare const index_d_MidiEventsPlayedEventArgs: typeof MidiEventsPlayedEventArgs;
8898
8960
  type index_d_PlaybackRange = PlaybackRange;
@@ -8906,7 +8968,7 @@ declare const index_d_PlayerStateChangedEventArgs: typeof PlayerStateChangedEven
8906
8968
  type index_d_PositionChangedEventArgs = PositionChangedEventArgs;
8907
8969
  declare const index_d_PositionChangedEventArgs: typeof PositionChangedEventArgs;
8908
8970
  declare namespace index_d {
8909
- export { index_d_ActiveBeatsChangedEventArgs as ActiveBeatsChangedEventArgs, index_d_AlphaSynth as AlphaSynth, index_d_AlphaSynthAudioWorkletOutput as AlphaSynthAudioWorkletOutput, index_d_AlphaSynthScriptProcessorOutput as AlphaSynthScriptProcessorOutput, index_d_AlphaSynthWebAudioOutputBase as AlphaSynthWebAudioOutputBase, index_d_AlphaSynthWebWorkerApi as AlphaSynthWebWorkerApi, index_d_CircularSampleBuffer as CircularSampleBuffer, type index_d_IAlphaSynth as IAlphaSynth, type index_d_ISynthOutput as ISynthOutput, index_d_MidiEventsPlayedEventArgs as MidiEventsPlayedEventArgs, index_d_PlaybackRange as PlaybackRange, index_d_PlaybackRangeChangedEventArgs as PlaybackRangeChangedEventArgs, index_d_PlayerState as PlayerState, index_d_PlayerStateChangedEventArgs as PlayerStateChangedEventArgs, index_d_PositionChangedEventArgs as PositionChangedEventArgs };
8971
+ export { index_d_ActiveBeatsChangedEventArgs as ActiveBeatsChangedEventArgs, index_d_AlphaSynth as AlphaSynth, index_d_AlphaSynthAudioWorkletOutput as AlphaSynthAudioWorkletOutput, index_d_AlphaSynthScriptProcessorOutput as AlphaSynthScriptProcessorOutput, index_d_AlphaSynthWebAudioOutputBase as AlphaSynthWebAudioOutputBase, index_d_AlphaSynthWebWorkerApi as AlphaSynthWebWorkerApi, index_d_CircularSampleBuffer as CircularSampleBuffer, type index_d_IAlphaSynth as IAlphaSynth, type index_d_ISynthOutput as ISynthOutput, type index_d_ISynthOutputDevice as ISynthOutputDevice, index_d_MidiEventsPlayedEventArgs as MidiEventsPlayedEventArgs, index_d_PlaybackRange as PlaybackRange, index_d_PlaybackRangeChangedEventArgs as PlaybackRangeChangedEventArgs, index_d_PlayerState as PlayerState, index_d_PlayerStateChangedEventArgs as PlayerStateChangedEventArgs, index_d_PositionChangedEventArgs as PositionChangedEventArgs };
8910
8972
  }
8911
8973
 
8912
8974
  type json_d_CoreSettingsJson = CoreSettingsJson;
package/dist/alphaTab.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * alphaTab v1.4.0 (, build 12)
2
+ * alphaTab v1.5.0-alpha.1331 (develop, build 1331)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -27079,6 +27079,9 @@
27079
27079
  * play a {@link MidiFile} via a {@link ISynthOutput}.
27080
27080
  */
27081
27081
  class AlphaSynth {
27082
+ get output() {
27083
+ return this._output;
27084
+ }
27082
27085
  get isReadyForPlayback() {
27083
27086
  return this.isReady && this._isSoundFontLoaded && this._isMidiLoaded;
27084
27087
  }
@@ -27199,7 +27202,7 @@
27199
27202
  Logger.debug('AlphaSynth', 'Initializing player');
27200
27203
  this.state = PlayerState.Paused;
27201
27204
  Logger.debug('AlphaSynth', 'Creating output');
27202
- this.output = output;
27205
+ this._output = output;
27203
27206
  Logger.debug('AlphaSynth', 'Creating synthesizer');
27204
27207
  this._synthesizer = new TinySoundFont(this.output.sampleRate);
27205
27208
  this._sequencer = new MidiFileSequencer(this._synthesizer);
@@ -27572,6 +27575,13 @@
27572
27575
  activate() {
27573
27576
  // nothing to do
27574
27577
  }
27578
+ async enumerateOutputDevices() {
27579
+ return [];
27580
+ }
27581
+ async setOutputDevice(device) { }
27582
+ async getOutputDevice() {
27583
+ return null;
27584
+ }
27575
27585
  }
27576
27586
  AlphaSynthWorkerSynthOutput.CmdOutputPrefix = 'alphaSynth.output.';
27577
27587
  AlphaSynthWorkerSynthOutput.CmdOutputAddSamples = AlphaSynthWorkerSynthOutput.CmdOutputPrefix + 'addSamples';
@@ -33389,6 +33399,37 @@
33389
33399
  this.settingsUpdated.trigger();
33390
33400
  this.uiFacade.triggerEvent(this.container, 'settingsUpdated', null);
33391
33401
  }
33402
+ /**
33403
+ * Loads and lists the available output devices which can be used by the player. Will request permissions if needed.
33404
+ * @returns the list of available devices or an empty list if there are no permissions, or the player is not enabled.
33405
+ */
33406
+ async enumerateOutputDevices() {
33407
+ if (this.player) {
33408
+ return await this.player.output.enumerateOutputDevices();
33409
+ }
33410
+ return [];
33411
+ }
33412
+ /**
33413
+ * Changes the output device which should be used for playing the audio (player must be enabled).
33414
+ * @param device The output device to use, or null to switch to the default device.
33415
+ */
33416
+ async setOutputDevice(device) {
33417
+ if (this.player) {
33418
+ await this.player.output.setOutputDevice(device);
33419
+ }
33420
+ }
33421
+ /**
33422
+ * The currently configured output device if changed via {@link setOutputDevice}.
33423
+ * @returns The custom configured output device which was set via {@link setOutputDevice} or `null`
33424
+ * if the default outputDevice is used.
33425
+ * The output device might change dynamically if devices are connected/disconnected (e.g. bluetooth headset).
33426
+ */
33427
+ async getOutputDevice() {
33428
+ if (this.player) {
33429
+ return await this.player.output.getOutputDevice();
33430
+ }
33431
+ return null;
33432
+ }
33392
33433
  }
33393
33434
 
33394
33435
  /**
@@ -33851,6 +33892,21 @@
33851
33892
  }
33852
33893
  }
33853
33894
 
33895
+ /**
33896
+ * @target web
33897
+ */
33898
+ class AlphaSynthWebAudioSynthOutputDevice {
33899
+ constructor(device) {
33900
+ this.isDefault = false;
33901
+ this.device = device;
33902
+ }
33903
+ get deviceId() {
33904
+ return this.device.deviceId;
33905
+ }
33906
+ get label() {
33907
+ return this.device.label;
33908
+ }
33909
+ }
33854
33910
  /**
33855
33911
  * @target web
33856
33912
  */
@@ -33862,6 +33918,7 @@
33862
33918
  this.ready = new EventEmitter();
33863
33919
  this.samplesPlayed = new EventEmitterOfT();
33864
33920
  this.sampleRequest = new EventEmitter();
33921
+ this._knownDevices = [];
33865
33922
  }
33866
33923
  get sampleRate() {
33867
33924
  return this._context ? this._context.sampleRate : AlphaSynthWebAudioOutputBase.PreferredSampleRate;
@@ -33960,6 +34017,101 @@
33960
34017
  onReady() {
33961
34018
  this.ready.trigger();
33962
34019
  }
34020
+ async checkSinkIdSupport() {
34021
+ // https://caniuse.com/mdn-api_audiocontext_sinkid
34022
+ const context = this._context ?? this.createAudioContext();
34023
+ if (!('setSinkId' in context)) {
34024
+ Logger.warning('WebAudio', 'Browser does not support changing the output device');
34025
+ return false;
34026
+ }
34027
+ return true;
34028
+ }
34029
+ async enumerateOutputDevices() {
34030
+ try {
34031
+ if (!(await this.checkSinkIdSupport())) {
34032
+ return [];
34033
+ }
34034
+ // Request permissions
34035
+ try {
34036
+ await navigator.mediaDevices.getUserMedia({ audio: true });
34037
+ }
34038
+ catch (e) {
34039
+ // sometimes we get an error but can still enumerate, e.g. if microphone access is denied,
34040
+ // we can still load the output devices in some cases.
34041
+ Logger.warning('WebAudio', 'Output device permission rejected', e);
34042
+ }
34043
+ // load devices
34044
+ const devices = await navigator.mediaDevices.enumerateDevices();
34045
+ // default device candidates
34046
+ let defaultDeviceGroupId = '';
34047
+ let defaultDeviceId = '';
34048
+ const realDevices = new Map();
34049
+ for (const device of devices) {
34050
+ if (device.kind === 'audiooutput') {
34051
+ realDevices.set(device.groupId, new AlphaSynthWebAudioSynthOutputDevice(device));
34052
+ // chromium has the default device as deviceID: 'default'
34053
+ // the standard defines empty-string as default
34054
+ if (device.deviceId === 'default' || device.deviceId === '') {
34055
+ defaultDeviceGroupId = device.groupId;
34056
+ defaultDeviceId = device.deviceId;
34057
+ }
34058
+ }
34059
+ }
34060
+ const final = Array.from(realDevices.values());
34061
+ // flag default device
34062
+ let defaultDevice = final.find(d => d.deviceId === defaultDeviceId);
34063
+ if (!defaultDevice) {
34064
+ defaultDevice = final.find(d => d.device.groupId === defaultDeviceGroupId);
34065
+ }
34066
+ if (!defaultDevice && final.length > 0) {
34067
+ defaultDevice = final[0];
34068
+ }
34069
+ if (defaultDevice) {
34070
+ defaultDevice.isDefault = true;
34071
+ }
34072
+ this._knownDevices = final;
34073
+ return final;
34074
+ }
34075
+ catch (e) {
34076
+ Logger.error('WebAudio', 'Failed to enumerate output devices', e);
34077
+ return [];
34078
+ }
34079
+ }
34080
+ async setOutputDevice(device) {
34081
+ if (!(await this.checkSinkIdSupport())) {
34082
+ return;
34083
+ }
34084
+ // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId
34085
+ if (!device) {
34086
+ await this._context.setSinkId('');
34087
+ }
34088
+ else {
34089
+ await this._context.setSinkId(device.deviceId);
34090
+ }
34091
+ }
34092
+ async getOutputDevice() {
34093
+ if (!(await this.checkSinkIdSupport())) {
34094
+ return null;
34095
+ }
34096
+ // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/sinkId
34097
+ const sinkId = this._context.sinkId;
34098
+ if (typeof sinkId !== 'string' || sinkId === '' || sinkId === 'default') {
34099
+ return null;
34100
+ }
34101
+ // fast path -> cached devices list
34102
+ let device = this._knownDevices.find(d => d.deviceId === sinkId);
34103
+ if (device) {
34104
+ return device;
34105
+ }
34106
+ // slow path -> enumerate devices
34107
+ const allDevices = await this.enumerateOutputDevices();
34108
+ device = allDevices.find(d => d.deviceId === sinkId);
34109
+ if (device) {
34110
+ return device;
34111
+ }
34112
+ Logger.warning('WebAudio', 'Could not find output device in device list', sinkId, allDevices);
34113
+ return null;
34114
+ }
33963
34115
  }
33964
34116
  AlphaSynthWebAudioOutputBase.BufferSize = 4096;
33965
34117
  AlphaSynthWebAudioOutputBase.PreferredSampleRate = 44100;
@@ -34076,6 +34228,9 @@
34076
34228
  * @target web
34077
34229
  */
34078
34230
  class AlphaSynthWebWorkerApi {
34231
+ get output() {
34232
+ return this._output;
34233
+ }
34079
34234
  get isReady() {
34080
34235
  return this._workerIsReady && this._outputIsReady;
34081
34236
  }
@@ -52133,8 +52288,8 @@
52133
52288
  // </auto-generated>
52134
52289
  class VersionInfo {
52135
52290
  }
52136
- VersionInfo.version = '1.4.0';
52137
- VersionInfo.date = '2025-03-02T15:59:05.431Z';
52291
+ VersionInfo.version = '1.5.0-alpha.1331';
52292
+ VersionInfo.date = '2025-03-03T01:56:53.590Z';
52138
52293
 
52139
52294
  var index$4 = /*#__PURE__*/Object.freeze({
52140
52295
  __proto__: null,