From 306ea58c19d19e293ecafbe65c52d957e84b4993 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 8 May 2021 11:19:19 +0200 Subject: [PATCH 1/3] Fully destroy alphaTab player --- .../AlphaTab.Windows/NAudioSynthOutput.cs | 6 ++ src/AlphaTabApiBase.ts | 94 +++++++++++++++---- .../javascript/AlphaSynthWebAudioOutput.ts | 5 + .../javascript/AlphaSynthWebWorker.ts | 3 + .../javascript/AlphaSynthWebWorkerApi.ts | 10 +- .../javascript/AlphaSynthWorkerSynthOutput.ts | 6 ++ src/synth/AlphaSynth.ts | 13 ++- src/synth/ISynthOutput.ts | 5 + 8 files changed, 120 insertions(+), 22 deletions(-) diff --git a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs index 50e646d58..8914237eb 100644 --- a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs +++ b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs @@ -48,6 +48,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..0e6eb4362 100644 --- a/src/platform/javascript/AlphaSynthWebWorker.ts +++ b/src/platform/javascript/AlphaSynthWebWorker.ts @@ -122,6 +122,9 @@ export class AlphaSynthWebWorker { case 'alphaSynth.resetChannelStates': this._player.resetChannelStates(); break; + case 'alphaSynth.destroy': + this._player.destroy(); + 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..f97bb3499 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -149,7 +149,8 @@ export class AlphaSynth implements IAlphaSynth { public destroy(): void { Logger.debug('AlphaSynth', 'Destroying player'); - this.stop(); + this.stop(true); + this.output.destroy(); } /** @@ -260,7 +261,7 @@ export class AlphaSynth implements IAlphaSynth { } } - public stop(): void { + public stop(destroying: boolean = false): void { if (!this._isMidiLoaded) { return; } @@ -270,9 +271,11 @@ export class AlphaSynth implements IAlphaSynth { this._sequencer.stop(); this._synthesizer.noteOffAll(true); this.tickPosition = this._sequencer.playbackRange ? this._sequencer.playbackRange.startTick : 0; - (this.stateChanged as EventEmitterOfT).trigger( - new PlayerStateChangedEventArgs(this.state, true) - ); + if (!destroying) { + (this.stateChanged as EventEmitterOfT).trigger( + new PlayerStateChangedEventArgs(this.state, true) + ); + } } public playOneTimeMidiFile(midi: MidiFile): void { 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. */ From 717a912ccc7cc4d0f2ee0c6b79623ec86c9154f4 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 8 May 2021 11:34:56 +0200 Subject: [PATCH 2/3] Update test output --- src/synth/AlphaSynth.ts | 8 ++++++-- test/audio/TestOutput.ts | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index f97bb3499..e0a4e22c8 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -149,7 +149,7 @@ export class AlphaSynth implements IAlphaSynth { public destroy(): void { Logger.debug('AlphaSynth', 'Destroying player'); - this.stop(true); + this.internalStop(true); this.output.destroy(); } @@ -261,7 +261,11 @@ export class AlphaSynth implements IAlphaSynth { } } - public stop(destroying: boolean = false): void { + public stop(): void { + this.internalStop(false); + } + + public internalStop(destroying: boolean = false): void { if (!this._isMidiLoaded) { return; } 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(); } From 4390325f8e6dfad9c646ab733b58dbf9024e4ba5 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 8 May 2021 12:44:22 +0200 Subject: [PATCH 3/3] Slight cleanup --- src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs | 10 +++++----- src/platform/javascript/AlphaSynthWebWorker.ts | 3 +++ src/synth/AlphaSynth.ts | 14 ++++---------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs index 8914237eb..7c9fae5cc 100644 --- a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs +++ b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs @@ -48,12 +48,12 @@ public void Open() ((EventEmitter) Ready).Trigger(); } - + /// - public void Destroy() - { - Dispose(); - } + public void Destroy() + { + Dispose(); + } /// public void Dispose() diff --git a/src/platform/javascript/AlphaSynthWebWorker.ts b/src/platform/javascript/AlphaSynthWebWorker.ts index 0e6eb4362..14de21e1a 100644 --- a/src/platform/javascript/AlphaSynthWebWorker.ts +++ b/src/platform/javascript/AlphaSynthWebWorker.ts @@ -124,6 +124,9 @@ export class AlphaSynthWebWorker { break; case 'alphaSynth.destroy': this._player.destroy(); + this._main.postMessage({ + cmd: 'alphaSynth.destroyed' + }); break; } } diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index e0a4e22c8..f2ddd3168 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -149,7 +149,7 @@ export class AlphaSynth implements IAlphaSynth { public destroy(): void { Logger.debug('AlphaSynth', 'Destroying player'); - this.internalStop(true); + this.stop(); this.output.destroy(); } @@ -262,10 +262,6 @@ export class AlphaSynth implements IAlphaSynth { } public stop(): void { - this.internalStop(false); - } - - public internalStop(destroying: boolean = false): void { if (!this._isMidiLoaded) { return; } @@ -275,11 +271,9 @@ export class AlphaSynth implements IAlphaSynth { this._sequencer.stop(); this._synthesizer.noteOffAll(true); this.tickPosition = this._sequencer.playbackRange ? this._sequencer.playbackRange.startTick : 0; - if (!destroying) { - (this.stateChanged as EventEmitterOfT).trigger( - new PlayerStateChangedEventArgs(this.state, true) - ); - } + (this.stateChanged as EventEmitterOfT).trigger( + new PlayerStateChangedEventArgs(this.state, true) + ); } public playOneTimeMidiFile(midi: MidiFile): void {