diff --git a/src.compiler/csharp/CSharpAst.ts b/src.compiler/csharp/CSharpAst.ts index 268f4869d..dd7c531e1 100644 --- a/src.compiler/csharp/CSharpAst.ts +++ b/src.compiler/csharp/CSharpAst.ts @@ -140,13 +140,13 @@ export interface TypeParameterDeclaration extends NamedElement, Node { export interface NamedTypeDeclaration extends NamedElement, DocumentedElement, Node, AttributedElement { typeParameters?: TypeParameterDeclaration[]; visibility: Visibility; + partial: boolean; } export interface ClassDeclaration extends NamedTypeDeclaration { baseClass?: TypeNode; interfaces?: TypeNode[]; isAbstract: boolean; - partial: boolean; members: ClassMember[]; } diff --git a/src.compiler/csharp/CSharpAstPrinter.ts b/src.compiler/csharp/CSharpAstPrinter.ts index 27920ba5a..3259e61c5 100644 --- a/src.compiler/csharp/CSharpAstPrinter.ts +++ b/src.compiler/csharp/CSharpAstPrinter.ts @@ -143,6 +143,11 @@ export default class CSharpAstPrinter { private writeInterfaceDeclaration(d: cs.InterfaceDeclaration) { this.writeDocumentation(d); this.writeVisibility(d.visibility); + + if (d.partial) { + this.write('partial '); + } + this.write(`interface ${d.name}`); this.writeTypeParameters(d.typeParameters); diff --git a/src.compiler/csharp/CSharpAstTransformer.ts b/src.compiler/csharp/CSharpAstTransformer.ts index bae6f50bb..708e4dc58 100644 --- a/src.compiler/csharp/CSharpAstTransformer.ts +++ b/src.compiler/csharp/CSharpAstTransformer.ts @@ -329,6 +329,7 @@ export default class CSharpAstTransformer { parent: this._csharpFile.namespace, members: [], tsNode: node, + partial: false, skipEmit: this.shouldSkip(node, false), tsSymbol: this._context.getSymbolForDeclaration(node) }; @@ -385,6 +386,7 @@ export default class CSharpAstTransformer { members: [], tsNode: node, skipEmit: this.shouldSkip(node, false), + partial: !!ts.getJSDocTags(node).find(t => t.tagName.text === 'partial'), tsSymbol: this._context.getSymbolForDeclaration(node) }; diff --git a/src.csharp/AlphaTab.Windows/DelegatedEventEmitter.cs b/src.csharp/AlphaTab.Windows/DelegatedEventEmitter.cs index 3660cbbaa..9251dab85 100644 --- a/src.csharp/AlphaTab.Windows/DelegatedEventEmitter.cs +++ b/src.csharp/AlphaTab.Windows/DelegatedEventEmitter.cs @@ -35,6 +35,16 @@ public DelegatedEventEmitter(Action> on, Action> off) _off = off; } + public void On(Action value) + { + // not used internally + } + + public void Off(Action value) + { + // not used internally + } + public void On(Action value) { _on(value); diff --git a/src.csharp/AlphaTab/EventEmitter.cs b/src.csharp/AlphaTab/EventEmitter.cs new file mode 100644 index 000000000..4ff05bced --- /dev/null +++ b/src.csharp/AlphaTab/EventEmitter.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AlphaTab.Core.EcmaScript; +using AlphaTab.Platform.CSharp; + +namespace AlphaTab +{ + partial interface IEventEmitterOfT + { + void On(System.Action value); + void Off(System.Action value); + } + + partial class EventEmitterOfT + { + private System.Collections.Generic.IDictionary> _wrappers = + new System.Collections.Generic.Dictionary>(); + + [Obsolete("Use event registration overload with parameter.", false)] + public void On(System.Action value) + { + var wrapper = new Action(_=> + { + value(); + }); + _wrappers[value] = wrapper; + On(wrapper); + } + + [Obsolete("Use event unregistration with parameter.", false)] + public void Off(System.Action value) + { + if(_wrappers.TryGetValue(value, out var wrapper)) + { + Off(wrapper); + _wrappers.Remove(value); + } + } + } +} diff --git a/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs b/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs index 1315a4345..27b7956cb 100644 --- a/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs +++ b/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs @@ -172,7 +172,7 @@ public void SetChannelVolume(double channel, double volume) public IEventEmitter Finished { get; } = new EventEmitter(); public IEventEmitter SoundFontLoaded { get; } = new EventEmitter(); public IEventEmitterOfT SoundFontLoadFailed { get; } =new EventEmitterOfT(); - public IEventEmitter MidiLoaded { get; } = new EventEmitter(); + public IEventEmitterOfT MidiLoaded { get; } = new EventEmitterOfT(); public IEventEmitterOfT MidiLoadFailed { get; } = new EventEmitterOfT(); public IEventEmitterOfT StateChanged { get; } = new EventEmitterOfT(); public IEventEmitterOfT PositionChanged { get; } = new EventEmitterOfT(); @@ -202,9 +202,9 @@ protected virtual void OnSoundFontLoadFailed(Error e) DispatchOnUiThread(() => ((EventEmitterOfT)SoundFontLoadFailed).Trigger(e)); } - protected virtual void OnMidiLoaded() + protected virtual void OnMidiLoaded(PositionChangedEventArgs args) { - DispatchOnUiThread(() => ((EventEmitter)MidiLoaded).Trigger()); + DispatchOnUiThread(() => ((EventEmitterOfT)MidiLoaded).Trigger(args)); } protected virtual void OnMidiLoadFailed(Error e) diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts index 37da485d0..3e53fd0dc 100644 --- a/src/AlphaTabApiBase.ts +++ b/src/AlphaTabApiBase.ts @@ -1221,10 +1221,10 @@ export class AlphaTabApiBase { this.uiFacade.triggerEvent(this.container, 'soundFontLoaded', null); } - public midiLoaded: IEventEmitter = new EventEmitter(); - private onMidiLoaded(): void { - (this.midiLoaded as EventEmitter).trigger(); - this.uiFacade.triggerEvent(this.container, 'midiFileLoaded', null); + public midiLoaded: IEventEmitterOfT = new EventEmitterOfT(); + private onMidiLoaded(e:PositionChangedEventArgs): void { + (this.midiLoaded as EventEmitterOfT).trigger(e); + this.uiFacade.triggerEvent(this.container, 'midiFileLoaded', e); } public playerStateChanged: IEventEmitterOfT = new EventEmitterOfT< diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index 3300d9cb9..3efd6e73e 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -2,6 +2,10 @@ export interface IEventEmitter { on(value: () => void): void; off(value: () => void): void; } + +/** + * @partial + */ export interface IEventEmitterOfT { on(value: (arg: T) => void): void; off(value: (arg: T) => void): void; @@ -25,6 +29,9 @@ export class EventEmitter implements IEventEmitter { } } +/** + * @partial + */ export class EventEmitterOfT implements IEventEmitterOfT { private _listeners: ((arg: T) => void)[] = []; diff --git a/src/platform/javascript/AlphaSynthWebWorker.ts b/src/platform/javascript/AlphaSynthWebWorker.ts index 38afff512..59ec68876 100644 --- a/src/platform/javascript/AlphaSynthWebWorker.ts +++ b/src/platform/javascript/AlphaSynthWebWorker.ts @@ -126,7 +126,8 @@ export class AlphaSynthWebWorker { currentTime: e.currentTime, endTime: e.endTime, currentTick: e.currentTick, - endTick: e.endTick + endTick: e.endTick, + isSeek: e.isSeek }); } diff --git a/src/platform/javascript/AlphaSynthWebWorkerApi.ts b/src/platform/javascript/AlphaSynthWebWorkerApi.ts index 33fc6efd7..de9cd4a86 100644 --- a/src/platform/javascript/AlphaSynthWebWorkerApi.ts +++ b/src/platform/javascript/AlphaSynthWebWorkerApi.ts @@ -342,7 +342,7 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { this._timePosition = data.currentTime; this._tickPosition = data.currentTick; (this.positionChanged as EventEmitterOfT).trigger( - new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick) + new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek) ); break; case 'alphaSynth.playerStateChanged': @@ -362,7 +362,9 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { break; case 'alphaSynth.midiLoaded': this.checkReadyForPlayback(); - (this.midiLoaded as EventEmitter).trigger(); + (this.midiLoaded as EventEmitterOfT).trigger( + new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek) + ); break; case 'alphaSynth.midiLoadFailed': this.checkReadyForPlayback(); @@ -400,7 +402,9 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { readonly finished: IEventEmitter = new EventEmitter(); readonly soundFontLoaded: IEventEmitter = new EventEmitter(); readonly soundFontLoadFailed: IEventEmitterOfT = new EventEmitterOfT(); - readonly midiLoaded: IEventEmitter = new EventEmitter(); + readonly midiLoaded: IEventEmitterOfT = new EventEmitterOfT< + PositionChangedEventArgs + >(); readonly midiLoadFailed: IEventEmitterOfT = new EventEmitterOfT(); readonly stateChanged: IEventEmitterOfT = new EventEmitterOfT< PlayerStateChangedEventArgs diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 2c6f85b31..d22aafe0d 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -86,7 +86,7 @@ export class AlphaSynth implements IAlphaSynth { value = SynthHelper.clamp(value, SynthConstants.MinPlaybackSpeed, SynthConstants.MaxPlaybackSpeed); let oldSpeed: number = this._sequencer.playbackSpeed; this._sequencer.playbackSpeed = value; - this.updateTimePosition(this._timePosition * (oldSpeed / value)); + this.updateTimePosition(this._timePosition * (oldSpeed / value), true); } public get tickPosition(): number { @@ -108,7 +108,7 @@ export class AlphaSynth implements IAlphaSynth { this._sequencer.seek(value); // update the internal position - this.updateTimePosition(value); + this.updateTimePosition(value, true); // tell the output to reset the already synthesized buffers and request data again this.output.resetSamples(); @@ -309,8 +309,9 @@ export class AlphaSynth implements IAlphaSynth { Logger.debug('AlphaSynth', 'Loading midi from model'); this._sequencer.loadMidi(midiFile); this._isMidiLoaded = true; - (this.midiLoaded as EventEmitter).trigger(); - + (this.midiLoaded as EventEmitterOfT).trigger( + new PositionChangedEventArgs(0, this._sequencer.endTime, 0, this._sequencer.endTick, false) + ); Logger.debug('AlphaSynth', 'Midi successfully loaded'); this.checkReadyForPlayback(); this.tickPosition = 0; @@ -339,7 +340,7 @@ export class AlphaSynth implements IAlphaSynth { private onSamplesPlayed(sampleCount: number): void { let playedMillis: number = (sampleCount / this._synthesizer.outSampleRate) * 1000; - this.updateTimePosition(this._timePosition + playedMillis); + this.updateTimePosition(this._timePosition + playedMillis, false); this.checkForFinish(); } @@ -376,7 +377,7 @@ export class AlphaSynth implements IAlphaSynth { } } - private updateTimePosition(timePosition: number): void { + private updateTimePosition(timePosition: number, isSeek: boolean): void { // update the real positions const currentTime: number = (this._timePosition = timePosition); const currentTick: number = (this._tickPosition = this._sequencer.timePositionToTickPosition(currentTime)); @@ -389,7 +390,7 @@ export class AlphaSynth implements IAlphaSynth { `Position changed: (time: ${currentTime}/${endTime}, tick: ${currentTick}/${endTick}, Active Voices: ${this._synthesizer.activeVoiceCount}` ); (this.positionChanged as EventEmitterOfT).trigger( - new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick) + new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek) ); } } @@ -399,7 +400,7 @@ export class AlphaSynth implements IAlphaSynth { readonly finished: IEventEmitter = new EventEmitter(); readonly soundFontLoaded: IEventEmitter = new EventEmitter(); readonly soundFontLoadFailed: IEventEmitterOfT = new EventEmitterOfT(); - readonly midiLoaded: IEventEmitter = new EventEmitter(); + readonly midiLoaded: IEventEmitterOfT = new EventEmitterOfT(); readonly midiLoadFailed: IEventEmitterOfT = new EventEmitterOfT(); readonly stateChanged: IEventEmitterOfT = new EventEmitterOfT< PlayerStateChangedEventArgs diff --git a/src/synth/IAlphaSynth.ts b/src/synth/IAlphaSynth.ts index 5f0f68a23..bd3f9f515 100644 --- a/src/synth/IAlphaSynth.ts +++ b/src/synth/IAlphaSynth.ts @@ -175,7 +175,7 @@ export interface IAlphaSynth { /** * This event is fired when the Midi file needed for playback was loaded. */ - readonly midiLoaded: IEventEmitter; + readonly midiLoaded: IEventEmitterOfT; /** * This event is fired when the loading of the Midi file failed. diff --git a/src/synth/PositionChangedEventArgs.ts b/src/synth/PositionChangedEventArgs.ts index 14e9dff30..13cd6ac77 100644 --- a/src/synth/PositionChangedEventArgs.ts +++ b/src/synth/PositionChangedEventArgs.ts @@ -2,6 +2,11 @@ * Represents the info when the time in the synthesizer changes. */ export class PositionChangedEventArgs { + /** + * Gets a value indicating whether the position changed because of time seeking. + */ + public isSeek: boolean; + /** * Gets the current time in milliseconds. */ @@ -28,11 +33,13 @@ export class PositionChangedEventArgs { * @param endTime The end time. * @param currentTick The current tick. * @param endTick The end tick. + * @param isSeek Whether the time was seeked. */ - public constructor(currentTime: number, endTime: number, currentTick: number, endTick: number) { + public constructor(currentTime: number, endTime: number, currentTick: number, endTick: number, isSeek:boolean) { this.currentTime = currentTime; this.endTime = endTime; this.currentTick = currentTick; this.endTick = endTick; + this.isSeek = isSeek; } }