diff --git a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
index 50e646d58..7c9fae5cc 100644
--- a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
+++ b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
@@ -49,6 +49,12 @@ public void Open()
((EventEmitter) Ready).Trigger();
}
+ ///
+ public void Destroy()
+ {
+ Dispose();
+ }
+
///
public void Dispose()
{
diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts
index f4091ba58..a9e7223a0 100644
--- a/src/AlphaTabApiBase.ts
+++ b/src/AlphaTabApiBase.ts
@@ -61,6 +61,7 @@ class SelectionInfo {
export class AlphaTabApiBase {
private _startTime: number = 0;
private _trackIndexes: number[] | null = null;
+ private _isDestroyed: boolean = false;
/**
* Gets the UI facade to use for interacting with the user interface.
*/
@@ -122,6 +123,9 @@ export class AlphaTabApiBase {
this.container.resize.on(
Environment.throttle(() => {
+ if (this._isDestroyed) {
+ return;
+ }
if (this.container.width !== this.renderer.width) {
this.triggerResize();
}
@@ -164,6 +168,7 @@ export class AlphaTabApiBase {
* Destroys the alphaTab control and restores the initial state of the UI.
*/
public destroy(): void {
+ this._isDestroyed = true;
if (this.player) {
this.player.destroy();
}
@@ -678,7 +683,11 @@ export class AlphaTabApiBase {
// we generate a new midi file containing only the beat
let midiFile: MidiFile = new MidiFile();
let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile);
- let generator: MidiFileGenerator = new MidiFileGenerator(beat.voice.bar.staff.track.score, this.settings, handler);
+ let generator: MidiFileGenerator = new MidiFileGenerator(
+ beat.voice.bar.staff.track.score,
+ this.settings,
+ handler
+ );
generator.generateSingleBeat(beat);
this.player.playOneTimeMidiFile(midiFile);
@@ -696,13 +705,16 @@ export class AlphaTabApiBase {
// we generate a new midi file containing only the beat
let midiFile: MidiFile = new MidiFile();
let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile);
- let generator: MidiFileGenerator = new MidiFileGenerator(note.beat.voice.bar.staff.track.score, this.settings, handler);
+ let generator: MidiFileGenerator = new MidiFileGenerator(
+ note.beat.voice.bar.staff.track.score,
+ this.settings,
+ handler
+ );
generator.generateSingleNote(note);
this.player.playOneTimeMidiFile(midiFile);
}
-
private _cursorWrapper: IContainer | null = null;
private _barCursor: IContainer | null = null;
private _beatCursor: IContainer | null = null;
@@ -867,7 +879,7 @@ export class AlphaTabApiBase {
if (
nextBeatBoundings &&
nextBeatBoundings.barBounds.masterBarBounds.staveGroupBounds ===
- barBoundings.staveGroupBounds
+ barBoundings.staveGroupBounds
) {
nextBeatX = nextBeatBoundings.visualBounds.x;
}
@@ -943,6 +955,9 @@ export class AlphaTabApiBase {
public playedBeatChanged: IEventEmitterOfT = new EventEmitterOfT();
private onPlayedBeatChanged(beat: Beat): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.playedBeatChanged as EventEmitterOfT).trigger(beat);
this.uiFacade.triggerEvent(this.container, 'playedBeatChanged', beat);
}
@@ -956,6 +971,10 @@ export class AlphaTabApiBase {
public beatMouseUp: IEventEmitterOfT = new EventEmitterOfT();
private onBeatMouseDown(originalEvent: IMouseEventArgs, beat: Beat): void {
+ if (this._isDestroyed) {
+ return;
+ }
+
if (
this.settings.player.enablePlayer &&
this.settings.player.enableCursor &&
@@ -970,6 +989,10 @@ export class AlphaTabApiBase {
}
private onBeatMouseMove(originalEvent: IMouseEventArgs, beat: Beat): void {
+ if (this._isDestroyed) {
+ return;
+ }
+
if (this.settings.player.enableUserInteraction) {
if (!this._selectionEnd || this._selectionEnd.beat !== beat) {
this._selectionEnd = new SelectionInfo(beat);
@@ -981,6 +1004,10 @@ export class AlphaTabApiBase {
}
private onBeatMouseUp(originalEvent: IMouseEventArgs, beat: Beat | null): void {
+ if (this._isDestroyed) {
+ return;
+ }
+
if (this.settings.player.enableUserInteraction) {
// for the selection ensure start < end
if (this._selectionEnd) {
@@ -1046,7 +1073,6 @@ export class AlphaTabApiBase {
} else {
this.cursorSelectRange(null, null);
}
-
}
private setupClickHandling(): void {
@@ -1183,36 +1209,54 @@ export class AlphaTabApiBase {
public scoreLoaded: IEventEmitterOfT = new EventEmitterOfT();
private onScoreLoaded(score: Score): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.scoreLoaded as EventEmitterOfT).trigger(score);
this.uiFacade.triggerEvent(this.container, 'scoreLoaded', score);
}
public resize: IEventEmitterOfT = new EventEmitterOfT();
private onResize(e: ResizeEventArgs): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.resize as EventEmitterOfT).trigger(e);
this.uiFacade.triggerEvent(this.container, 'resize', e);
}
public renderStarted: IEventEmitterOfT = new EventEmitterOfT();
private onRenderStarted(resize: boolean): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.renderStarted as EventEmitterOfT).trigger(resize);
this.uiFacade.triggerEvent(this.container, 'renderStarted', resize);
}
public renderFinished: IEventEmitterOfT = new EventEmitterOfT();
private onRenderFinished(renderingResult: RenderFinishedEventArgs): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.renderFinished as EventEmitterOfT).trigger(renderingResult);
this.uiFacade.triggerEvent(this.container, 'renderFinished', renderingResult);
}
public postRenderFinished: IEventEmitter = new EventEmitter();
private onPostRenderFinished(): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.postRenderFinished as EventEmitter).trigger();
this.uiFacade.triggerEvent(this.container, 'postRenderFinished', null);
}
public error: IEventEmitterOfT = new EventEmitterOfT();
public onError(error: Error): void {
+ if (this._isDestroyed) {
+ return;
+ }
Logger.error('API', 'An unexpected error occurred', error);
(this.error as EventEmitterOfT).trigger(error);
this.uiFacade.triggerEvent(this.container, 'error', error);
@@ -1220,54 +1264,72 @@ export class AlphaTabApiBase {
public playerReady: IEventEmitter = new EventEmitter();
private onPlayerReady(): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.playerReady as EventEmitter).trigger();
this.uiFacade.triggerEvent(this.container, 'playerReady', null);
}
public playerFinished: IEventEmitter = new EventEmitter();
private onPlayerFinished(): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.playerFinished as EventEmitter).trigger();
this.uiFacade.triggerEvent(this.container, 'playerFinished', null);
}
public soundFontLoaded: IEventEmitter = new EventEmitter();
private onSoundFontLoaded(): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.soundFontLoaded as EventEmitter).trigger();
this.uiFacade.triggerEvent(this.container, 'soundFontLoaded', null);
}
public midiLoad: IEventEmitterOfT = new EventEmitterOfT();
- private onMidiLoad(e:MidiFile): void {
+ private onMidiLoad(e: MidiFile): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.midiLoad as EventEmitterOfT).trigger(e);
this.uiFacade.triggerEvent(this.container, 'midiLoad', e);
}
public midiLoaded: IEventEmitterOfT = new EventEmitterOfT();
- private onMidiLoaded(e:PositionChangedEventArgs): void {
+ private onMidiLoaded(e: PositionChangedEventArgs): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.midiLoaded as EventEmitterOfT).trigger(e);
this.uiFacade.triggerEvent(this.container, 'midiFileLoaded', e);
}
- public playerStateChanged: IEventEmitterOfT = new EventEmitterOfT<
- PlayerStateChangedEventArgs
- >();
+ public playerStateChanged: IEventEmitterOfT = new EventEmitterOfT();
private onPlayerStateChanged(e: PlayerStateChangedEventArgs): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.playerStateChanged as EventEmitterOfT).trigger(e);
this.uiFacade.triggerEvent(this.container, 'playerStateChanged', e);
}
- public playerPositionChanged: IEventEmitterOfT = new EventEmitterOfT<
- PositionChangedEventArgs
- >();
+ public playerPositionChanged: IEventEmitterOfT = new EventEmitterOfT();
private onPlayerPositionChanged(e: PositionChangedEventArgs): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.playerPositionChanged as EventEmitterOfT).trigger(e);
this.uiFacade.triggerEvent(this.container, 'playerPositionChanged', e);
}
- public midiEventsPlayed: IEventEmitterOfT = new EventEmitterOfT<
- MidiEventsPlayedEventArgs
- >();
+ public midiEventsPlayed: IEventEmitterOfT = new EventEmitterOfT();
private onMidiEventsPlayed(e: MidiEventsPlayedEventArgs): void {
+ if (this._isDestroyed) {
+ return;
+ }
(this.midiEventsPlayed as EventEmitterOfT).trigger(e);
this.uiFacade.triggerEvent(this.container, 'midiEventsPlayed', e);
}
diff --git a/src/platform/javascript/AlphaSynthWebAudioOutput.ts b/src/platform/javascript/AlphaSynthWebAudioOutput.ts
index e8435b70d..342f41e4f 100644
--- a/src/platform/javascript/AlphaSynthWebAudioOutput.ts
+++ b/src/platform/javascript/AlphaSynthWebAudioOutput.ts
@@ -119,6 +119,11 @@ export class AlphaSynthWebAudioOutput implements ISynthOutput {
this._audioNode = null;
}
+ public destroy(): void {
+ this.pause();
+ this._context?.close();
+ }
+
public addSamples(f: Float32Array): void {
this._circularBuffer.write(f, 0, f.length);
}
diff --git a/src/platform/javascript/AlphaSynthWebWorker.ts b/src/platform/javascript/AlphaSynthWebWorker.ts
index 858c037b5..14de21e1a 100644
--- a/src/platform/javascript/AlphaSynthWebWorker.ts
+++ b/src/platform/javascript/AlphaSynthWebWorker.ts
@@ -122,6 +122,12 @@ export class AlphaSynthWebWorker {
case 'alphaSynth.resetChannelStates':
this._player.resetChannelStates();
break;
+ case 'alphaSynth.destroy':
+ this._player.destroy();
+ this._main.postMessage({
+ cmd: 'alphaSynth.destroyed'
+ });
+ break;
}
}
diff --git a/src/platform/javascript/AlphaSynthWebWorkerApi.ts b/src/platform/javascript/AlphaSynthWebWorkerApi.ts
index fed61cdc7..61ad07d74 100644
--- a/src/platform/javascript/AlphaSynthWebWorkerApi.ts
+++ b/src/platform/javascript/AlphaSynthWebWorkerApi.ts
@@ -228,7 +228,9 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth {
}
public destroy(): void {
- this._synth.terminate();
+ this._synth.postMessage({
+ cmd: 'alphaSynth.destroy'
+ });
}
//
@@ -349,6 +351,9 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth {
this._workerIsReady = true;
this.checkReady();
break;
+ case 'alphaSynth.destroyed':
+ this._synth.terminate();
+ break;
case 'alphaSynth.readyForPlayback':
this._workerIsReadyForPlayback = true;
this.checkReadyForPlayback();
@@ -399,6 +404,9 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth {
case 'alphaSynth.output.pause':
this._output.pause();
break;
+ case 'alphaSynth.output.destroy':
+ this._output.destroy();
+ break;
case 'alphaSynth.output.resetSamples':
this._output.resetSamples();
break;
diff --git a/src/platform/javascript/AlphaSynthWorkerSynthOutput.ts b/src/platform/javascript/AlphaSynthWorkerSynthOutput.ts
index 19e08b877..08b5c3015 100644
--- a/src/platform/javascript/AlphaSynthWorkerSynthOutput.ts
+++ b/src/platform/javascript/AlphaSynthWorkerSynthOutput.ts
@@ -35,6 +35,12 @@ export class AlphaSynthWorkerSynthOutput implements ISynthOutput {
(this.ready as EventEmitter).trigger();
}
+ public destroy(): void {
+ this._worker.postMessage({
+ cmd: 'alphaSynth.output.destroy'
+ });
+ }
+
private handleMessage(e: MessageEvent): void {
let data: any = e.data;
let cmd: any = data.cmd;
diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts
index 36a846aa7..f2ddd3168 100644
--- a/src/synth/AlphaSynth.ts
+++ b/src/synth/AlphaSynth.ts
@@ -150,6 +150,7 @@ export class AlphaSynth implements IAlphaSynth {
public destroy(): void {
Logger.debug('AlphaSynth', 'Destroying player');
this.stop();
+ this.output.destroy();
}
/**
diff --git a/src/synth/ISynthOutput.ts b/src/synth/ISynthOutput.ts
index 924884c38..ba87f0db9 100644
--- a/src/synth/ISynthOutput.ts
+++ b/src/synth/ISynthOutput.ts
@@ -21,6 +21,11 @@ export interface ISynthOutput {
*/
play(): void;
+ /**
+ * Requests the output to destroy itself.
+ */
+ destroy(): void;
+
/**
* Called when the output should stop the playback.
*/
diff --git a/test/audio/TestOutput.ts b/test/audio/TestOutput.ts
index c48d9830a..6647c23e0 100644
--- a/test/audio/TestOutput.ts
+++ b/test/audio/TestOutput.ts
@@ -17,6 +17,10 @@ export class TestOutput implements ISynthOutput {
// nothing to do
}
+ public destroy(): void {
+ // nothing to do
+ }
+
public next(): void {
(this.sampleRequest as EventEmitter).trigger();
}