From 636fe8513682001772b1e4b3ed195565584e648d Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Mon, 7 Mar 2022 22:47:29 +0100 Subject: [PATCH 1/5] WIP rework playBeat and playNote handling --- src/AlphaTabApiBase.ts | 62 +++++++++++++++++- src/synth/AlphaSynth.ts | 50 +++++++++------ src/synth/MidiFileSequencer.ts | 112 +++++++++++++++++++-------------- 3 files changed, 155 insertions(+), 69 deletions(-) diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts index b47590e1c..42e1d5ecf 100644 --- a/src/AlphaTabApiBase.ts +++ b/src/AlphaTabApiBase.ts @@ -995,7 +995,7 @@ export class AlphaTabApiBase { if ( nextBeatBoundings && nextBeatBoundings.barBounds.masterBarBounds.staveGroupBounds === - barBoundings.staveGroupBounds + barBoundings.staveGroupBounds ) { nextBeatX = nextBeatBoundings.visualBounds.x; } @@ -1033,6 +1033,7 @@ export class AlphaTabApiBase { } private _beatMouseDown: boolean = false; + private _noteMouseDown: boolean = false; private _selectionStart: SelectionInfo | null = null; private _selectionEnd: SelectionInfo | null = null; @@ -1040,6 +1041,10 @@ export class AlphaTabApiBase { public beatMouseMove: IEventEmitterOfT = new EventEmitterOfT(); public beatMouseUp: IEventEmitterOfT = new EventEmitterOfT(); + public noteMouseDown: IEventEmitterOfT = new EventEmitterOfT(); + public noteMouseMove: IEventEmitterOfT = new EventEmitterOfT(); + public noteMouseUp: IEventEmitterOfT = new EventEmitterOfT(); + private onBeatMouseDown(originalEvent: IMouseEventArgs, beat: Beat): void { if (this._isDestroyed) { return; @@ -1058,6 +1063,16 @@ export class AlphaTabApiBase { this.uiFacade.triggerEvent(this.container, 'beatMouseDown', beat, originalEvent); } + private onNoteMouseDown(originalEvent: IMouseEventArgs, note: Note): void { + if (this._isDestroyed) { + return; + } + + this._noteMouseDown = true; + (this.noteMouseDown as EventEmitterOfT).trigger(note); + this.uiFacade.triggerEvent(this.container, 'noteMouseDown', note, originalEvent); + } + private onBeatMouseMove(originalEvent: IMouseEventArgs, beat: Beat): void { if (this._isDestroyed) { return; @@ -1073,6 +1088,15 @@ export class AlphaTabApiBase { this.uiFacade.triggerEvent(this.container, 'beatMouseMove', beat, originalEvent); } + private onNoteMouseMove(originalEvent: IMouseEventArgs, note: Note): void { + if (this._isDestroyed) { + return; + } + + (this.noteMouseMove as EventEmitterOfT).trigger(note); + this.uiFacade.triggerEvent(this.container, 'noteMouseMove', note, originalEvent); + } + private onBeatMouseUp(originalEvent: IMouseEventArgs, beat: Beat | null): void { if (this._isDestroyed) { return; @@ -1128,6 +1152,17 @@ export class AlphaTabApiBase { this._beatMouseDown = false; } + + private onNoteMouseUp(originalEvent: IMouseEventArgs, note: Note | null): void { + if (this._isDestroyed) { + return; + } + + (this.noteMouseUp as EventEmitterOfT).trigger(note); + this.uiFacade.triggerEvent(this.container, 'noteMouseUp', note, originalEvent); + this._noteMouseDown = false; + } + private updateSelectionCursor(range: PlaybackRange | null) { if (!this._tickCache) { return; @@ -1158,6 +1193,14 @@ export class AlphaTabApiBase { let beat: Beat | null = this.renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null; if (beat) { this.onBeatMouseDown(e, beat); + + if (this.settings.core.includeNoteBounds) { + const note = this.renderer.boundsLookup?.getNoteAtPos(beat, relX, relY); + if (note) { + this.onNoteMouseDown(e, note); + } + } + } }); this.canvasElement.mouseMove.on(e => { @@ -1169,6 +1212,13 @@ export class AlphaTabApiBase { let beat: Beat | null = this.renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null; if (beat) { this.onBeatMouseMove(e, beat); + + if (this._noteMouseDown) { + const note = this.renderer.boundsLookup?.getNoteAtPos(beat, relX, relY); + if (note) { + this.onNoteMouseMove(e, note); + } + } } }); this.canvasElement.mouseUp.on(e => { @@ -1182,6 +1232,16 @@ export class AlphaTabApiBase { let relY: number = e.getY(this.canvasElement); let beat: Beat | null = this.renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null; this.onBeatMouseUp(e, beat); + + if (this._noteMouseDown) { + if (beat) { + const note = this.renderer.boundsLookup?.getNoteAtPos(beat, relX, relY) ?? null; + this.onNoteMouseUp(e, note); + } + else { + this.onNoteMouseUp(e, null); + } + } }); this.renderer.postRenderFinished.on(() => { if ( diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 5769b95d5..5026c4ff3 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -111,7 +111,7 @@ export class AlphaSynth implements IAlphaSynth { } public set tickPosition(value: number) { - this.timePosition = this._sequencer.tickPositionToTimePosition(value); + this.timePosition = this._sequencer.mainTickPositionToTimePosition(value); } public get timePosition(): number { @@ -119,10 +119,10 @@ export class AlphaSynth implements IAlphaSynth { } public set timePosition(value: number) { - Logger.debug('AlphaSynth', `Seeking to position ${value}ms`); + Logger.debug('AlphaSynth', `Seeking to position ${value}ms (main)`); // tell the sequencer to jump to the given position - this._sequencer.seek(value); + this._sequencer.mainSeek(value); // update the internal position this.updateTimePosition(value, true); @@ -132,11 +132,11 @@ export class AlphaSynth implements IAlphaSynth { } public get playbackRange(): PlaybackRange | null { - return this._sequencer.playbackRange; + return this._sequencer.mainPlaybackRange; } public set playbackRange(value: PlaybackRange | null) { - this._sequencer.playbackRange = value; + this._sequencer.mainPlaybackRange = value; if (value) { this.tickPosition = value.startTick; } @@ -276,7 +276,7 @@ export class AlphaSynth implements IAlphaSynth { this.output.pause(); this._sequencer.stop(); this._synthesizer.noteOffAll(true); - this.tickPosition = this._sequencer.playbackRange ? this._sequencer.playbackRange.startTick : 0; + this.tickPosition = this._sequencer.mainPlaybackRange ? this._sequencer.mainPlaybackRange.startTick : 0; (this.stateChanged as EventEmitterOfT).trigger( new PlayerStateChangedEventArgs(this.state, true) ); @@ -287,10 +287,13 @@ export class AlphaSynth implements IAlphaSynth { this.pause(); this._sequencer.loadOneTimeMidi(midi); - - this._sequencer.stop(); this._synthesizer.noteOffAll(true); - this.tickPosition = 0; + + // update the internal position + this.updateTimePosition(0, true); + + // tell the output to reset the already synthesized buffers and request data again + this.output.resetSamples(); this.output.play(); } @@ -341,7 +344,7 @@ export class AlphaSynth implements IAlphaSynth { this._sequencer.loadMidi(midi); this._isMidiLoaded = true; (this.midiLoaded as EventEmitterOfT).trigger( - new PositionChangedEventArgs(0, this._sequencer.endTime, 0, this._sequencer.endTick, false) + new PositionChangedEventArgs(0, this._sequencer.currentEndTime, 0, this._sequencer.currentEndTick, false) ); Logger.debug('AlphaSynth', 'Midi successfully loaded'); this.checkReadyForPlayback(); @@ -391,21 +394,23 @@ export class AlphaSynth implements IAlphaSynth { startTick = this.playbackRange.startTick; endTick = this.playbackRange.endTick; } else { - endTick = this._sequencer.endTick; + endTick = this._sequencer.currentEndTick; } if (this._tickPosition >= endTick) { - Logger.debug('AlphaSynth', 'Finished playback'); if (this._sequencer.isPlayingCountIn) { + Logger.debug('AlphaSynth', 'Finished playback (count-in)'); this._sequencer.resetCountIn(); this.timePosition = this._sequencer.currentTime; this.playInternal(); } else if (this._sequencer.isPlayingOneTimeMidi) { + Logger.debug('AlphaSynth', 'Finished playback (one time)'); this._sequencer.resetOneTimeMidi(); this.state = PlayerState.Paused; this.output.pause(); this._synthesizer.noteOffAll(false); } else { + Logger.debug('AlphaSynth', 'Finished playback (main)'); (this.finished as EventEmitter).trigger(); if (this.isLooping) { @@ -421,16 +426,21 @@ export class AlphaSynth implements IAlphaSynth { // update the real positions const currentTime: number = timePosition; this._timePosition = currentTime; - const currentTick: number = this._sequencer.timePositionToTickPosition(currentTime); + const currentTick: number = this._sequencer.currentTimePositionToTickPosition(currentTime); this._tickPosition = currentTick; - const endTime: number = this._sequencer.endTime; - const endTick: number = this._sequencer.endTick; - if (!this._sequencer.isPlayingOneTimeMidi && !this._sequencer.isPlayingCountIn) { - Logger.debug( - 'AlphaSynth', - `Position changed: (time: ${currentTime}/${endTime}, tick: ${currentTick}/${endTick}, Active Voices: ${this._synthesizer.activeVoiceCount}` - ); + const endTime: number = this._sequencer.currentEndTime; + const endTick: number = this._sequencer.currentEndTick; + + const mode = this._sequencer.isPlayingMain ? 'main' : this._sequencer.isPlayingCountIn ? 'count-in' : 'one-time'; + + Logger.debug( + 'AlphaSynth', + `Position changed: (time: ${currentTime}/${endTime}, tick: ${currentTick}/${endTick}, Active Voices: ${this._synthesizer.activeVoiceCount} (${mode})` + ); + + if (this._sequencer.isPlayingMain) { + (this.positionChanged as EventEmitterOfT).trigger( new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek) ); diff --git a/src/synth/MidiFileSequencer.ts b/src/synth/MidiFileSequencer.ts index c70d91fb0..6250162f8 100644 --- a/src/synth/MidiFileSequencer.ts +++ b/src/synth/MidiFileSequencer.ts @@ -49,6 +49,10 @@ export class MidiFileSequencer { private _oneTimeState: MidiSequencerState | null = null; private _countInState: MidiSequencerState | null = null; + public get isPlayingMain(): boolean { + return this._currentState == this._mainState; + } + public get isPlayingOneTimeMidi(): boolean { return this._currentState == this._oneTimeState; } @@ -63,15 +67,15 @@ export class MidiFileSequencer { this._currentState = this._mainState; } - public get playbackRange(): PlaybackRange | null { - return this._currentState.playbackRange; + public get mainPlaybackRange(): PlaybackRange | null { + return this._mainState.playbackRange; } - public set playbackRange(value: PlaybackRange | null) { - this._currentState.playbackRange = value; + public set mainPlaybackRange(value: PlaybackRange | null) { + this._mainState.playbackRange = value; if (value) { - this._currentState.playbackRangeStartTime = this.tickPositionToTimePositionWithSpeed(value.startTick, 1); - this._currentState.playbackRangeEndTime = this.tickPositionToTimePositionWithSpeed(value.endTick, 1); + this._mainState.playbackRangeStartTime = this.tickPositionToTimePositionWithSpeed(this._mainState, value.startTick, 1); + this._mainState.playbackRangeEndTime = this.tickPositionToTimePositionWithSpeed(this._mainState, value.endTick, 1); } } @@ -84,11 +88,11 @@ export class MidiFileSequencer { /** * Gets the duration of the song in ticks. */ - public get endTick() { + public get currentEndTick() { return this._currentState.endTick; } - public get endTime(): number { + public get currentEndTime(): number { return this._currentState.endTime / this.playbackSpeed; } @@ -97,51 +101,55 @@ export class MidiFileSequencer { */ public playbackSpeed: number = 1; - public seek(timePosition: number): void { + public mainSeek(timePosition: number): void { // map to speed=1 timePosition *= this.playbackSpeed; // ensure playback range - if (this.playbackRange) { - if (timePosition < this._currentState.playbackRangeStartTime) { - timePosition = this._currentState.playbackRangeStartTime; - } else if (timePosition > this._currentState.playbackRangeEndTime) { - timePosition = this._currentState.playbackRangeEndTime; + if (this.mainPlaybackRange) { + if (timePosition < this._mainState.playbackRangeStartTime) { + timePosition = this._mainState.playbackRangeStartTime; + } else if (timePosition > this._mainState.playbackRangeEndTime) { + timePosition = this._mainState.playbackRangeEndTime; } } - if (timePosition > this._currentState.currentTime) { - this.silentProcess(timePosition - this._currentState.currentTime); - } else if (timePosition < this._currentState.currentTime) { + if (timePosition > this._mainState.currentTime) { + this.mainSilentProcess(timePosition - this._mainState.currentTime); + } else if (timePosition < this._mainState.currentTime) { // we have to restart the midi to make sure we get the right state: instruments, volume, pan, etc - this._currentState.currentTime = 0; - this._currentState.eventIndex = 0; - let metronomeVolume: number = this._synthesizer.metronomeVolume; - this._synthesizer.noteOffAll(true); - this._synthesizer.resetSoft(); - this._synthesizer.setupMetronomeChannel(metronomeVolume); - this.silentProcess(timePosition); + this._mainState.currentTime = 0; + this._mainState.eventIndex = 0; + if (this.isPlayingMain) { + let metronomeVolume: number = this._synthesizer.metronomeVolume; + this._synthesizer.noteOffAll(true); + this._synthesizer.resetSoft(); + this._synthesizer.setupMetronomeChannel(metronomeVolume); + } + this.mainSilentProcess(timePosition); } } - private silentProcess(milliseconds: number): void { + private mainSilentProcess(milliseconds: number): void { if (milliseconds <= 0) { return; } let start: number = Date.now(); - let finalTime: number = this._currentState.currentTime + milliseconds; + let finalTime: number = this._mainState.currentTime + milliseconds; - while (this._currentState.currentTime < finalTime) { - if (this.fillMidiEventQueueLimited(finalTime - this._currentState.currentTime)) { - this._synthesizer.synthesizeSilent(SynthConstants.MicroBufferSize); + if(this.isPlayingMain) { + while (this._mainState.currentTime < finalTime) { + if (this.fillMidiEventQueueLimited(finalTime - this._mainState.currentTime)) { + this._synthesizer.synthesizeSilent(SynthConstants.MicroBufferSize); + } } } - - this._currentState.currentTime = finalTime; + + this._mainState.currentTime = finalTime; let duration: number = Date.now() - start; - Logger.debug('Sequencer', 'Silent seek finished in ' + duration + 'ms'); + Logger.debug('Sequencer', 'Silent seek finished in ' + duration + 'ms (main)'); } public loadOneTimeMidi(midiFile: MidiFile): void { @@ -272,21 +280,25 @@ export class MidiFileSequencer { return anyEventsDispatched; } - public tickPositionToTimePosition(tickPosition: number): number { - return this.tickPositionToTimePositionWithSpeed(tickPosition, this.playbackSpeed); + public mainTickPositionToTimePosition(tickPosition: number): number { + return this.tickPositionToTimePositionWithSpeed(this._mainState, tickPosition, this.playbackSpeed); } - public timePositionToTickPosition(timePosition: number): number { - return this.timePositionToTickPositionWithSpeed(timePosition, this.playbackSpeed); + public mainTimePositionToTickPosition(timePosition: number): number { + return this.timePositionToTickPositionWithSpeed(this._mainState, timePosition, this.playbackSpeed); + } + + public currentTimePositionToTickPosition(timePosition: number): number { + return this.timePositionToTickPositionWithSpeed(this._currentState, timePosition, this.playbackSpeed); } - private tickPositionToTimePositionWithSpeed(tickPosition: number, playbackSpeed: number): number { + private tickPositionToTimePositionWithSpeed(state: MidiSequencerState, tickPosition: number, playbackSpeed: number): number { let timePosition: number = 0.0; let bpm: number = 120.0; let lastChange: number = 0; // find start and bpm of last tempo change before time - for (const c of this._currentState.tempoChanges) { + for (const c of state.tempoChanges) { if (tickPosition < c.ticks) { break; } @@ -298,12 +310,12 @@ export class MidiFileSequencer { // add the missing millis tickPosition -= lastChange; - timePosition += tickPosition * (60000.0 / (bpm * this._currentState.division)); + timePosition += tickPosition * (60000.0 / (bpm * state.division)); return timePosition / playbackSpeed; } - private timePositionToTickPositionWithSpeed(timePosition: number, playbackSpeed: number): number { + private timePositionToTickPositionWithSpeed(state: MidiSequencerState, timePosition: number, playbackSpeed: number): number { timePosition *= playbackSpeed; let ticks: number = 0; @@ -311,7 +323,7 @@ export class MidiFileSequencer { let lastChange: number = 0; // find start and bpm of last tempo change before time - for (const c of this._currentState.tempoChanges) { + for (const c of state.tempoChanges) { if (timePosition < c.time) { break; } @@ -322,13 +334,17 @@ export class MidiFileSequencer { // add the missing ticks timePosition -= lastChange; - ticks += (timePosition / (60000.0 / (bpm * this._currentState.division))) | 0; + ticks += (timePosition / (60000.0 / (bpm * state.division))) | 0; // we add 1 for possible rounding errors.(floating point issuses) return ticks + 1; } private get internalEndTime(): number { - return !this.playbackRange ? this._currentState.endTime : this._currentState.playbackRangeEndTime; + if (this.isPlayingMain) { + return !this.mainPlaybackRange ? this._currentState.endTime : this._currentState.playbackRangeEndTime; + } else { + return this._currentState.endTime; + } } public get isFinished(): boolean { @@ -336,13 +352,13 @@ export class MidiFileSequencer { } public stop(): void { - if (!this.playbackRange) { + if (this.isPlayingMain && this.mainPlaybackRange) { + this._currentState.currentTime = this.mainPlaybackRange.startTick; + } else { this._currentState.currentTime = 0; - this._currentState.eventIndex = 0; - } else if (this.playbackRange) { - this._currentState.currentTime = this.playbackRange.startTick; - this._currentState.eventIndex = 0; } + + this._currentState.eventIndex = 0; } public resetOneTimeMidi() { From 74c5efa05573dde0ce4c3777b8d9a59f7b893533 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 12 Mar 2022 21:40:43 +0100 Subject: [PATCH 2/5] Fixed synth issues on wrong sample counting and --- .../platform/android/AndroidAudioWorker.kt | 4 +- src/AlphaTabApiBase.ts | 1 - .../AlphaSynthAudioWorkletOutput.ts | 7 +- .../AlphaSynthScriptProcessorOutput.ts | 5 +- src/synth/AlphaSynth.ts | 135 ++++++++++++------ 5 files changed, 100 insertions(+), 52 deletions(-) diff --git a/src.kotlin/alphaTab/alphaTab/src/androidMain/kotlin/alphaTab/platform/android/AndroidAudioWorker.kt b/src.kotlin/alphaTab/alphaTab/src/androidMain/kotlin/alphaTab/platform/android/AndroidAudioWorker.kt index bec7fd95f..c0220c6a4 100644 --- a/src.kotlin/alphaTab/alphaTab/src/androidMain/kotlin/alphaTab/platform/android/AndroidAudioWorker.kt +++ b/src.kotlin/alphaTab/alphaTab/src/androidMain/kotlin/alphaTab/platform/android/AndroidAudioWorker.kt @@ -48,12 +48,12 @@ internal class AndroidAudioWorker( private fun writeSamples() { while (!_stopped) { if (_track.playState == AudioTrack.PLAYSTATE_PLAYING) { - _output.read(_buffer, 0, _buffer.size) + val samplesFromBuffer = _output.read(_buffer, 0, _buffer.size) if (_previousPosition == -1) { _previousPosition = _track.playbackHeadPosition _track.getTimestamp(_timestamp) } - _track.write(_buffer, 0, _buffer.size, AudioTrack.WRITE_BLOCKING) + _track.write(_buffer, 0, samplesFromBuffer, AudioTrack.WRITE_BLOCKING) } else { _playingSemaphore.acquire() // wait for playing to start _playingSemaphore.release() // release semaphore for others diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts index 42e1d5ecf..883df12a2 100644 --- a/src/AlphaTabApiBase.ts +++ b/src/AlphaTabApiBase.ts @@ -777,7 +777,6 @@ export class AlphaTabApiBase { this._playerState = PlayerState.Paused; // we need to update our position caches if we render a tablature this.renderer.postRenderFinished.on(() => { - debugger; this.cursorUpdateTick(this._previousTick, false, this._previousTick > 10); }); if (this.player) { diff --git a/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts b/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts index 949605229..be1e0e96e 100644 --- a/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts +++ b/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts @@ -3,6 +3,7 @@ import { Environment } from '@src/Environment'; import { Logger } from '@src/Logger'; import { AlphaSynthWorkerSynthOutput } from '@src/platform/javascript/AlphaSynthWorkerSynthOutput'; import { AlphaSynthWebAudioOutputBase } from '@src/platform/javascript/AlphaSynthWebAudioOutputBase'; +import { SynthConstants } from '@src/synth/SynthConstants'; /** * @target web @@ -58,7 +59,7 @@ export class AlphaSynthWebWorklet { constructor(...args: any[]) { super(...args); - Logger.info('WebAudio', 'creating processor'); + Logger.debug('WebAudio', 'creating processor'); this._bufferCount = Math.floor( (AlphaSynthWebWorkletProcessor.TotalBufferTimeInMilliseconds * @@ -110,7 +111,7 @@ export class AlphaSynthWebWorklet { buffer = new Float32Array(samples); this._outputBuffer = buffer; } - this._circularBuffer.read(buffer, 0, Math.min(buffer.length, this._circularBuffer.count)); + const samplesFromBuffer = this._circularBuffer.read(buffer, 0, Math.min(buffer.length, this._circularBuffer.count)); let s: number = 0; for (let i: number = 0; i < left.length; i++) { left[i] = buffer[s++]; @@ -118,7 +119,7 @@ export class AlphaSynthWebWorklet { } this.port.postMessage({ cmd: AlphaSynthWorkerSynthOutput.CmdOutputSamplesPlayed, - samples: left.length + samples: samplesFromBuffer / SynthConstants.AudioChannels }); this.requestBuffers(); diff --git a/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts b/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts index 1888909e5..2a8472c32 100644 --- a/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts +++ b/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts @@ -1,5 +1,6 @@ import { CircularSampleBuffer } from '@src/synth/ds/CircularSampleBuffer'; import { AlphaSynthWebAudioOutputBase } from '@src/platform/javascript/AlphaSynthWebAudioOutputBase'; +import { SynthConstants } from '@src/synth/SynthConstants'; // tslint:disable: deprecation @@ -86,13 +87,13 @@ export class AlphaSynthScriptProcessorOutput extends AlphaSynthWebAudioOutputBas buffer = new Float32Array(samples); this._outputBuffer = buffer; } - this._circularBuffer.read(buffer, 0, Math.min(buffer.length, this._circularBuffer.count)); + const samplesFromBuffer = this._circularBuffer.read(buffer, 0, Math.min(buffer.length, this._circularBuffer.count)); let s: number = 0; for (let i: number = 0; i < left.length; i++) { left[i] = buffer[s++]; right[i] = buffer[s++]; } - this.onSamplesPlayed(left.length); + this.onSamplesPlayed(samplesFromBuffer / SynthConstants.AudioChannels); this.requestBuffers(); } } diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 5026c4ff3..70fb7bcbd 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -35,6 +35,7 @@ export class AlphaSynth implements IAlphaSynth { private _countInVolume: number = 0; private _playedEventsQueue: Queue = new Queue(); private _midiEventsPlayedFilter: Set = new Set(); + private _notPlayedSamples: number = 0; /** * Gets the {@link ISynthOutput} used for playing the generated samples. @@ -128,7 +129,10 @@ export class AlphaSynth implements IAlphaSynth { this.updateTimePosition(value, true); // tell the output to reset the already synthesized buffers and request data again - this.output.resetSamples(); + if(this._sequencer.isPlayingMain) { + this._notPlayedSamples = 0; + this.output.resetSamples(); + } } public get playbackRange(): PlaybackRange | null { @@ -181,38 +185,42 @@ export class AlphaSynth implements IAlphaSynth { this.checkReadyForPlayback(); }); this.output.sampleRequest.on(() => { - let samples: Float32Array = new Float32Array( - SynthConstants.MicroBufferSize * SynthConstants.MicroBufferCount * SynthConstants.AudioChannels - ); - let bufferPos: number = 0; - - for (let i = 0; i < SynthConstants.MicroBufferCount; i++) { - // synthesize buffer - this._sequencer.fillMidiEventQueue(); - const synthesizedEvents = this._synthesizer.synthesize( - samples, - bufferPos, - SynthConstants.MicroBufferSize + if (!this._sequencer.isFinished) { + let samples: Float32Array = new Float32Array( + SynthConstants.MicroBufferSize * SynthConstants.MicroBufferCount * SynthConstants.AudioChannels ); - bufferPos += SynthConstants.MicroBufferSize * SynthConstants.AudioChannels; - // push all processed events into the queue - // for informing users about played events - for (const e of synthesizedEvents) { - if (this._midiEventsPlayedFilter.has(e.event.command)) { - this._playedEventsQueue.enqueue(e); + let bufferPos: number = 0; + + for (let i = 0; i < SynthConstants.MicroBufferCount; i++) { + // synthesize buffer + this._sequencer.fillMidiEventQueue(); + const synthesizedEvents = this._synthesizer.synthesize( + samples, + bufferPos, + SynthConstants.MicroBufferSize + ); + bufferPos += SynthConstants.MicroBufferSize * SynthConstants.AudioChannels; + // push all processed events into the queue + // for informing users about played events + for (const e of synthesizedEvents) { + if (this._midiEventsPlayedFilter.has(e.event.command)) { + this._playedEventsQueue.enqueue(e); + } + } + // tell sequencer to check whether its work is done + if (this._sequencer.isFinished) { + break; } } - // tell sequencer to check whether its work is done - if (this._sequencer.isFinished) { - break; - } - } - // send it to output - if (bufferPos < samples.length) { - samples = samples.subarray(0, bufferPos); + // send it to output + if (bufferPos < samples.length) { + samples = samples.subarray(0, bufferPos); + } + this._notPlayedSamples += samples.length; + Logger.debug('AlphaSynth', `NotPlayedSamples ${this._notPlayedSamples} (+${samples.length})`); + this.output.addSamples(samples); } - this.output.addSamples(samples); }); this.output.samplesPlayed.on(this.onSamplesPlayed.bind(this)); this.output.open(); @@ -238,6 +246,11 @@ export class AlphaSynth implements IAlphaSynth { } private playInternal() { + if (this._sequencer.isPlayingOneTimeMidi) { + Logger.debug('AlphaSynth', 'Cancelling one time midi'); + this.stopOneTimeMidi(); + } + Logger.debug('AlphaSynth', 'Starting playback'); this._synthesizer.setupMetronomeChannel(this.metronomeVolume); this.state = PlayerState.Playing; @@ -274,6 +287,7 @@ export class AlphaSynth implements IAlphaSynth { Logger.debug('AlphaSynth', 'Stopping playback'); this.state = PlayerState.Paused; this.output.pause(); + this._notPlayedSamples = 0; this._sequencer.stop(); this._synthesizer.noteOffAll(true); this.tickPosition = this._sequencer.mainPlaybackRange ? this._sequencer.mainPlaybackRange.startTick : 0; @@ -283,8 +297,12 @@ export class AlphaSynth implements IAlphaSynth { } public playOneTimeMidiFile(midi: MidiFile): void { - // pause current playback. - this.pause(); + if (this._sequencer.isPlayingOneTimeMidi) { + this.stopOneTimeMidi(); + } else { + // pause current playback. + this.pause(); + } this._sequencer.loadOneTimeMidi(midi); this._synthesizer.noteOffAll(true); @@ -293,11 +311,12 @@ export class AlphaSynth implements IAlphaSynth { this.updateTimePosition(0, true); // tell the output to reset the already synthesized buffers and request data again + this._notPlayedSamples = 0; this.output.resetSamples(); this.output.play(); } - + public resetSoundFonts(): void { this.stop(); this._synthesizer.resetPresets(); @@ -344,7 +363,13 @@ export class AlphaSynth implements IAlphaSynth { this._sequencer.loadMidi(midi); this._isMidiLoaded = true; (this.midiLoaded as EventEmitterOfT).trigger( - new PositionChangedEventArgs(0, this._sequencer.currentEndTime, 0, this._sequencer.currentEndTick, false) + new PositionChangedEventArgs( + 0, + this._sequencer.currentEndTime, + 0, + this._sequencer.currentEndTick, + false + ) ); Logger.debug('AlphaSynth', 'Midi successfully loaded'); this.checkReadyForPlayback(); @@ -376,13 +401,21 @@ export class AlphaSynth implements IAlphaSynth { } private onAudioSettingsUpdate() { // seeking to the currently known position, will ensure we - // clear all audio buffers and re-generate the audio - // which was not actually played yet. + // clear all audio buffers and re-generate the audio + // which was not actually played yet. this.timePosition = this.timePosition; } private onSamplesPlayed(sampleCount: number): void { + if (sampleCount === 0) { + return; + } let playedMillis: number = (sampleCount / this._synthesizer.outSampleRate) * 1000; + this._notPlayedSamples -= sampleCount * SynthConstants.AudioChannels; + Logger.debug( + 'AlphaSynth', + `NotPlayedSamples ${this._notPlayedSamples} (-${sampleCount * SynthConstants.AudioChannels})` + ); this.updateTimePosition(this._timePosition + playedMillis, false); this.checkForFinish(); } @@ -397,7 +430,8 @@ export class AlphaSynth implements IAlphaSynth { endTick = this._sequencer.currentEndTick; } - if (this._tickPosition >= endTick) { + if (this._tickPosition >= endTick && this._notPlayedSamples <= 0) { + this._notPlayedSamples = 0; if (this._sequencer.isPlayingCountIn) { Logger.debug('AlphaSynth', 'Finished playback (count-in)'); this._sequencer.resetCountIn(); @@ -405,10 +439,9 @@ export class AlphaSynth implements IAlphaSynth { this.playInternal(); } else if (this._sequencer.isPlayingOneTimeMidi) { Logger.debug('AlphaSynth', 'Finished playback (one time)'); - this._sequencer.resetOneTimeMidi(); + this.output.resetSamples(); this.state = PlayerState.Paused; - this.output.pause(); - this._synthesizer.noteOffAll(false); + this.stopOneTimeMidi(); } else { Logger.debug('AlphaSynth', 'Finished playback (main)'); (this.finished as EventEmitter).trigger(); @@ -422,6 +455,13 @@ export class AlphaSynth implements IAlphaSynth { } } + private stopOneTimeMidi() { + this.output.pause(); + this._synthesizer.noteOffAll(true); + this._sequencer.resetOneTimeMidi(); + this.timePosition = this._sequencer.currentTime; + } + private updateTimePosition(timePosition: number, isSeek: boolean): void { // update the real positions const currentTime: number = timePosition; @@ -432,7 +472,11 @@ export class AlphaSynth implements IAlphaSynth { const endTime: number = this._sequencer.currentEndTime; const endTick: number = this._sequencer.currentEndTick; - const mode = this._sequencer.isPlayingMain ? 'main' : this._sequencer.isPlayingCountIn ? 'count-in' : 'one-time'; + const mode = this._sequencer.isPlayingMain + ? 'main' + : this._sequencer.isPlayingCountIn + ? 'count-in' + : 'one-time'; Logger.debug( 'AlphaSynth', @@ -440,7 +484,6 @@ export class AlphaSynth implements IAlphaSynth { ); if (this._sequencer.isPlayingMain) { - (this.positionChanged as EventEmitterOfT).trigger( new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick, isSeek) ); @@ -470,8 +513,12 @@ export class AlphaSynth implements IAlphaSynth { readonly soundFontLoadFailed: IEventEmitterOfT = new EventEmitterOfT(); readonly midiLoaded: IEventEmitterOfT = new EventEmitterOfT(); readonly midiLoadFailed: IEventEmitterOfT = new EventEmitterOfT(); - readonly stateChanged: IEventEmitterOfT = new EventEmitterOfT(); - readonly positionChanged: IEventEmitterOfT = new EventEmitterOfT(); - readonly midiEventsPlayed: IEventEmitterOfT = new EventEmitterOfT(); - readonly playbackRangeChanged: IEventEmitterOfT = new EventEmitterOfT(); + readonly stateChanged: IEventEmitterOfT = + new EventEmitterOfT(); + readonly positionChanged: IEventEmitterOfT = + new EventEmitterOfT(); + readonly midiEventsPlayed: IEventEmitterOfT = + new EventEmitterOfT(); + readonly playbackRangeChanged: IEventEmitterOfT = + new EventEmitterOfT(); } From 8f69dd136b3037381f85274cb07425f7e2b43c22 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 12 Mar 2022 21:41:20 +0100 Subject: [PATCH 3/5] Fixed wrong bounds lookup handling for notes --- src/rendering/glyphs/NoteNumberGlyph.ts | 2 +- src/rendering/utils/BoundsLookup.ts | 34 ++++++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/rendering/glyphs/NoteNumberGlyph.ts b/src/rendering/glyphs/NoteNumberGlyph.ts index 80f98c040..d49484d97 100644 --- a/src/rendering/glyphs/NoteNumberGlyph.ts +++ b/src/rendering/glyphs/NoteNumberGlyph.ts @@ -112,6 +112,6 @@ export class NoteNumberGlyph extends Glyph { noteBounds.noteHeadBounds.y = cy + this.y - this.height/2; noteBounds.noteHeadBounds.w = this.width; noteBounds.noteHeadBounds.h = this.height; - this.renderer.scoreRenderer.boundsLookup!.addNote(noteBounds); + beatBounds.addNote(noteBounds); } } diff --git a/src/rendering/utils/BoundsLookup.ts b/src/rendering/utils/BoundsLookup.ts index b8bf1161e..a50738ea3 100644 --- a/src/rendering/utils/BoundsLookup.ts +++ b/src/rendering/utils/BoundsLookup.ts @@ -128,7 +128,7 @@ export class BoundsLookup { return json; } - private _beatLookup: Map = new Map(); + private _beatLookup: Map = new Map(); private _masterBarLookup: Map = new Map(); private _currentStaveGroup: StaveGroupBounds | null = null; /** @@ -151,15 +151,6 @@ export class BoundsLookup { this.isFinished = true; } - /** - * Adds a new note to the lookup. - * @param bounds The note bounds to add. - */ - public addNote(bounds: NoteBounds): void { - let beat = this.findBeat(bounds.note.beat); - beat!.addNote(bounds); - } - /** * Adds a new stave group to the lookup. * @param bounds The stave group bounds to add. @@ -190,7 +181,10 @@ export class BoundsLookup { * @param bounds The beat bounds to add. */ public addBeat(bounds: BeatBounds): void { - this._beatLookup.set(bounds.beat.id, bounds); + if (!this._beatLookup.has(bounds.beat.id)) { + this._beatLookup.set(bounds.beat.id, []); + } + this._beatLookup.get(bounds.beat.id)?.push(bounds); } /** @@ -224,6 +218,11 @@ export class BoundsLookup { * @returns The beat bounds if it was rendered, or null if no boundary information is available. */ public findBeat(beat: Beat): BeatBounds | null { + const all = this.findBeats(beat); + return all ? all[0] : null; + } + + public findBeats(beat: Beat): BeatBounds[] | null { let id: number = beat.id; if (this._beatLookup.has(id)) { return this._beatLookup.get(id)!; @@ -281,10 +280,15 @@ export class BoundsLookup { * @returns The note at the given position within the beat. */ public getNoteAtPos(beat: Beat, x: number, y: number): Note | null { - let beatBounds: BeatBounds | null = this.findBeat(beat); - if (!beatBounds) { - return null; + const beatBounds = this.findBeats(beat); + if (beatBounds) { + for (const b of beatBounds) { + const note = b.findNoteAtPos(x, y); + if (note) { + return note; + } + } } - return beatBounds.findNoteAtPos(x, y); + return null; } } From f7ffab2bc32e4e0bb286cb7aaa27e489d305d649 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 12 Mar 2022 21:41:37 +0100 Subject: [PATCH 4/5] Fixed some issues on audio generation --- src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs | 5 +++-- src/midi/MidiFileGenerator.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs index ee6e17d74..fac192b3a 100644 --- a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs +++ b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs @@ -128,13 +128,14 @@ private void RequestBuffers() public override int Read(float[] buffer, int offset, int count) { var read = new Float32Array(count); - _circularBuffer.Read(read, 0, System.Math.Min(read.Length, _circularBuffer.Count)); + + var samplesFromBuffer = _circularBuffer.Read(read, 0, System.Math.Min(read.Length, _circularBuffer.Count)); Buffer.BlockCopy(read.Data, 0, buffer, offset * sizeof(float), count * sizeof(float)); var samples = count / 2; - ((EventEmitterOfT) SamplesPlayed).Trigger(samples); + ((EventEmitterOfT) SamplesPlayed).Trigger(samples / SynthConstants.AudioChannels); RequestBuffers(); diff --git a/src/midi/MidiFileGenerator.ts b/src/midi/MidiFileGenerator.ts index 0060fc60e..d2b9c6ddd 100644 --- a/src/midi/MidiFileGenerator.ts +++ b/src/midi/MidiFileGenerator.ts @@ -1362,7 +1362,7 @@ export class MidiFileGenerator { this.prepareSingleBeat(note.beat); this.generateNote( note, - -note.beat.playbackStart, + 0, note.beat.playbackDuration, new Int32Array(note.beat.voice.bar.staff.tuning.length) ); From 455a76412f2e16578f5917b199bbdcc63b2827be Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 12 Mar 2022 21:53:33 +0100 Subject: [PATCH 5/5] Fixed some highlighting issues. --- src/platform/javascript/BrowserUiFacade.ts | 4 +++- src/rendering/RenderFinishedEventArgs.ts | 4 ++-- src/rendering/layout/PageViewLayout.ts | 6 ------ src/rendering/utils/BoundsLookup.ts | 5 +++++ src/synth/AlphaSynth.ts | 5 ----- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/platform/javascript/BrowserUiFacade.ts b/src/platform/javascript/BrowserUiFacade.ts index 3977abac4..b7179f6d0 100644 --- a/src/platform/javascript/BrowserUiFacade.ts +++ b/src/platform/javascript/BrowserUiFacade.ts @@ -472,7 +472,9 @@ export class BrowserUiFacade implements IUiFacade { // remember which bar is contained in which node for faster lookup // on highlight/unhighlight for (let i = renderResult.firstMasterBarIndex; i <= renderResult.lastMasterBarIndex; i++) { - this._barToElementLookup.set(i, placeholder); + if(i >= 0) { + this._barToElementLookup.set(i, placeholder); + } } if (this._api.settings.core.enableLazyLoading) { diff --git a/src/rendering/RenderFinishedEventArgs.ts b/src/rendering/RenderFinishedEventArgs.ts index cf4f65189..6db796581 100644 --- a/src/rendering/RenderFinishedEventArgs.ts +++ b/src/rendering/RenderFinishedEventArgs.ts @@ -42,12 +42,12 @@ export class RenderFinishedEventArgs { /** * Gets or sets the index of the first masterbar that was rendered in this result. */ - public firstMasterBarIndex: number = 0; + public firstMasterBarIndex: number = -1; /** * Gets or sets the index of the last masterbar that was rendered in this result. */ - public lastMasterBarIndex: number = 0; + public lastMasterBarIndex: number = -1; /** * Gets or sets the render engine specific result object which contains the rendered music sheet. diff --git a/src/rendering/layout/PageViewLayout.ts b/src/rendering/layout/PageViewLayout.ts index c5ff89bde..1af47e076 100644 --- a/src/rendering/layout/PageViewLayout.ts +++ b/src/rendering/layout/PageViewLayout.ts @@ -122,8 +122,6 @@ export class PageViewLayout extends ScoreLayout { e.height = tuningHeight; e.totalWidth = this.width; e.totalHeight = totalHeight < 0 ? y + e.height : totalHeight; - e.firstMasterBarIndex = -1; - e.lastMasterBarIndex = -1; this.registerPartial(e, (canvas: ICanvas) => { canvas.color = res.scoreInfoColor; @@ -151,8 +149,6 @@ export class PageViewLayout extends ScoreLayout { e.height = diagramHeight; e.totalWidth = this.width; e.totalHeight = totalHeight < 0 ? y + diagramHeight : totalHeight; - e.firstMasterBarIndex = -1; - e.lastMasterBarIndex = -1; this.registerPartial(e, (canvas: ICanvas) => { canvas.color = res.scoreInfoColor; @@ -218,8 +214,6 @@ export class PageViewLayout extends ScoreLayout { e.height = infoHeight; e.totalWidth = this.width; e.totalHeight = totalHeight < 0 ? y + e.height : totalHeight; - e.firstMasterBarIndex = -1; - e.lastMasterBarIndex = -1; this.registerPartial(e, (canvas: ICanvas) => { canvas.color = res.scoreInfoColor; canvas.textAlign = TextAlign.Center; diff --git a/src/rendering/utils/BoundsLookup.ts b/src/rendering/utils/BoundsLookup.ts index a50738ea3..4840bb5fc 100644 --- a/src/rendering/utils/BoundsLookup.ts +++ b/src/rendering/utils/BoundsLookup.ts @@ -222,6 +222,11 @@ export class BoundsLookup { return all ? all[0] : null; } + /** + * Tries to find the bounds of a given beat. + * @param beat The beat to find. + * @returns The beat bounds if it was rendered, or null if no boundary information is available. + */ public findBeats(beat: Beat): BeatBounds[] | null { let id: number = beat.id; if (this._beatLookup.has(id)) { diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 70fb7bcbd..eb1bbff05 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -218,7 +218,6 @@ export class AlphaSynth implements IAlphaSynth { samples = samples.subarray(0, bufferPos); } this._notPlayedSamples += samples.length; - Logger.debug('AlphaSynth', `NotPlayedSamples ${this._notPlayedSamples} (+${samples.length})`); this.output.addSamples(samples); } }); @@ -412,10 +411,6 @@ export class AlphaSynth implements IAlphaSynth { } let playedMillis: number = (sampleCount / this._synthesizer.outSampleRate) * 1000; this._notPlayedSamples -= sampleCount * SynthConstants.AudioChannels; - Logger.debug( - 'AlphaSynth', - `NotPlayedSamples ${this._notPlayedSamples} (-${sampleCount * SynthConstants.AudioChannels})` - ); this.updateTimePosition(this._timePosition + playedMillis, false); this.checkForFinish(); }