From 225d297e7f2212fd28e79b0c0df13008c10b156c Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 16 Jan 2021 17:04:50 +0100 Subject: [PATCH 1/7] WIP --- src/midi/AlphaSynthMidiFileHandler.ts | 6 +- src/midi/MidiEvent.ts | 12 ++- src/midi/SystemExclusiveEvent.ts | 17 +++ src/model/JsonConverter.ts | 101 ++++++++++-------- .../javascript/AlphaSynthWebWorker.ts | 21 +++- .../javascript/AlphaSynthWebWorkerApi.ts | 22 ++++ src/synth/AlphaSynth.ts | 37 ++++++- src/synth/IAlphaSynth.ts | 16 +++ src/synth/MidiEventsPlayedEventArgs.ts | 19 ++++ src/synth/MidiFileSequencer.ts | 8 +- src/synth/ds/Queue.ts | 32 ++++++ src/synth/synthesis/SynthEvent.ts | 18 ++-- src/synth/synthesis/TinySoundFont.ts | 46 ++++---- 13 files changed, 268 insertions(+), 87 deletions(-) create mode 100644 src/synth/MidiEventsPlayedEventArgs.ts create mode 100644 src/synth/ds/Queue.ts diff --git a/src/midi/AlphaSynthMidiFileHandler.ts b/src/midi/AlphaSynthMidiFileHandler.ts index 02619d1c2..edf7a0c4c 100644 --- a/src/midi/AlphaSynthMidiFileHandler.ts +++ b/src/midi/AlphaSynthMidiFileHandler.ts @@ -3,7 +3,7 @@ import { MetaEventType } from '@src/midi/MetaEvent'; import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; import { SystemCommonType } from '@src/midi/SystemCommonEvent'; -import { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; +import { AlphaTabSystemExclusiveEvents, SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; import { IMidiFileHandler } from '@src/midi/IMidiFileHandler'; import { MidiFile } from '@src/midi/MidiFile'; import { MidiUtils } from '@src/midi/MidiUtils'; @@ -45,8 +45,8 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { const message: SystemExclusiveEvent = new SystemExclusiveEvent( tick, SystemCommonType.SystemExclusive, - 0, - new Uint8Array([0xff]) + SystemExclusiveEvent.AlphaTabManufacturerId, + new Uint8Array([AlphaTabSystemExclusiveEvents.Rest]) ); this._midiFile.addEvent(message); } diff --git a/src/midi/MidiEvent.ts b/src/midi/MidiEvent.ts index b6c2bd0fc..322dd6102 100644 --- a/src/midi/MidiEvent.ts +++ b/src/midi/MidiEvent.ts @@ -46,7 +46,17 @@ export enum MidiEventType { PitchBend = 0xE0, /** - * A meta event. See for details. + * A System Exclusive event. + */ + SystemExclusive = 0xF0, + + /** + * A System Exclusive event. + */ + SystemExclusive2 = 0xF7, + + /** + * A meta event. See `MetaEventType` for details. */ Meta = 0xFF } diff --git a/src/midi/SystemExclusiveEvent.ts b/src/midi/SystemExclusiveEvent.ts index b5ea58d2d..1732b9075 100644 --- a/src/midi/SystemExclusiveEvent.ts +++ b/src/midi/SystemExclusiveEvent.ts @@ -1,9 +1,26 @@ import { SystemCommonEvent } from '@src/midi/SystemCommonEvent'; import { IWriteable } from '@src/io/IWriteable'; +export enum AlphaTabSystemExclusiveEvents { + MetronomeTick = 0, + Rest = 1 +} + export class SystemExclusiveEvent extends SystemCommonEvent { + public static readonly AlphaTabManufacturerId = 0x7D; + public data: Uint8Array; + public get isMetronome():boolean { + return this.manufacturerId == SystemExclusiveEvent.AlphaTabManufacturerId && + this.data[0] == AlphaTabSystemExclusiveEvents.MetronomeTick; + } + + public get isRest():boolean { + return this.manufacturerId == SystemExclusiveEvent.AlphaTabManufacturerId && + this.data[0] == AlphaTabSystemExclusiveEvents.Rest; + } + public get manufacturerId(): number { return this.message >> 8; } diff --git a/src/model/JsonConverter.ts b/src/model/JsonConverter.ts index 1d29d0a6a..503ca33c9 100644 --- a/src/model/JsonConverter.ts +++ b/src/model/JsonConverter.ts @@ -127,36 +127,44 @@ export class JsonConverter { midi2.division = midi.division; let midiEvents: any[] = midi.events; for (let midiEvent of midiEvents) { - let tick: number = midiEvent.tick; - let message: number = midiEvent.message; - let midiEvent2: MidiEvent; - switch (midiEvent.type) { - case 'SystemExclusiveEvent': - midiEvent2 = new SystemExclusiveEvent(tick, 0, 0, midiEvent.data); - midiEvent2.message = message; - break; - case 'MetaDataEvent': - midiEvent2 = new MetaDataEvent(tick, 0, 0, midiEvent.data); - midiEvent2.message = message; - break; - case 'MetaNumberEvent': - midiEvent2 = new MetaNumberEvent(tick, 0, 0, midiEvent.value); - midiEvent2.message = message; - break; - case 'Midi20PerNotePitchBendEvent': - midiEvent2 = new Midi20PerNotePitchBendEvent(tick, 0, midiEvent.noteKey, midiEvent.pitch); - midiEvent2.message = message; - break; - default: - midiEvent2 = new MidiEvent(tick, 0, 0, 0); - midiEvent2.message = message; - break; - } + let midiEvent2: MidiEvent = JsonConverter.jsObjectToMidiEvent(midiEvent); midi2.events.push(midiEvent2); } return midi2; } + /** + * @target web + */ + public static jsObjectToMidiEvent(midiEvent: any): MidiEvent { + let tick: number = midiEvent.tick; + let message: number = midiEvent.message; + let midiEvent2: MidiEvent; + switch (midiEvent.type) { + case 'SystemExclusiveEvent': + midiEvent2 = new SystemExclusiveEvent(tick, 0, 0, midiEvent.data); + midiEvent2.message = message; + break; + case 'MetaDataEvent': + midiEvent2 = new MetaDataEvent(tick, 0, 0, midiEvent.data); + midiEvent2.message = message; + break; + case 'MetaNumberEvent': + midiEvent2 = new MetaNumberEvent(tick, 0, 0, midiEvent.value); + midiEvent2.message = message; + break; + case 'Midi20PerNotePitchBendEvent': + midiEvent2 = new Midi20PerNotePitchBendEvent(tick, 0, midiEvent.noteKey, midiEvent.pitch); + midiEvent2.message = message; + break; + default: + midiEvent2 = new MidiEvent(tick, 0, 0, 0); + midiEvent2.message = message; + break; + } + return midiEvent2; + } + /** * @target web */ @@ -166,25 +174,32 @@ export class JsonConverter { let midiEvents: unknown[] = []; midi2.events = midiEvents; for (let midiEvent of midi.events) { - let midiEvent2: any = {} as any; - midiEvents.push(midiEvent2); - midiEvent2.tick = midiEvent.tick; - midiEvent2.message = midiEvent.message; - if (midiEvent instanceof SystemExclusiveEvent) { - midiEvent2.type = 'SystemExclusiveEvent'; - midiEvent2.data = midiEvent.data; - } else if (midiEvent instanceof MetaDataEvent) { - midiEvent2.type = 'MetaDataEvent'; - midiEvent2.data = midiEvent.data; - } else if (midiEvent instanceof MetaNumberEvent) { - midiEvent2.type = 'MetaNumberEvent'; - midiEvent2.value = midiEvent.value; - } else if (midiEvent instanceof Midi20PerNotePitchBendEvent) { - midiEvent2.type = 'Midi20PerNotePitchBendEvent'; - midiEvent2.noteKey = midiEvent.noteKey; - midiEvent2.pitch = midiEvent.pitch; - } + midiEvents.push(JsonConverter.midiEventToJsObject(midiEvent)); } return midi2; } + + /** + * @target web + */ + public static midiEventToJsObject(midiEvent: MidiEvent): unknown { + let midiEvent2: any = {} as any; + midiEvent2.tick = midiEvent.tick; + midiEvent2.message = midiEvent.message; + if (midiEvent instanceof SystemExclusiveEvent) { + midiEvent2.type = 'SystemExclusiveEvent'; + midiEvent2.data = midiEvent.data; + } else if (midiEvent instanceof MetaDataEvent) { + midiEvent2.type = 'MetaDataEvent'; + midiEvent2.data = midiEvent.data; + } else if (midiEvent instanceof MetaNumberEvent) { + midiEvent2.type = 'MetaNumberEvent'; + midiEvent2.value = midiEvent.value; + } else if (midiEvent instanceof Midi20PerNotePitchBendEvent) { + midiEvent2.type = 'Midi20PerNotePitchBendEvent'; + midiEvent2.noteKey = midiEvent.noteKey; + midiEvent2.pitch = midiEvent.pitch; + } + return midiEvent2; + } } diff --git a/src/platform/javascript/AlphaSynthWebWorker.ts b/src/platform/javascript/AlphaSynthWebWorker.ts index 38afff512..15af0310c 100644 --- a/src/platform/javascript/AlphaSynthWebWorker.ts +++ b/src/platform/javascript/AlphaSynthWebWorker.ts @@ -6,6 +6,7 @@ import { AlphaSynthWorkerSynthOutput } from '@src/platform/javascript/AlphaSynth import { IWorkerScope } from '@src/platform/javascript/IWorkerScope'; import { Logger } from '@src/Logger'; import { Environment } from '@src/Environment'; +import { MidiEventsPlayedEventArgs } from '@src/synth/MidiEventsPlayedEventArgs'; /** * This class implements a HTML5 WebWorker based version of alphaSynth @@ -30,6 +31,7 @@ export class AlphaSynthWebWorker { this._player.midiLoaded.on(this.onMidiLoaded.bind(this)); this._player.midiLoadFailed.on(this.onMidiLoadFailed.bind(this)); this._player.readyForPlayback.on(this.onReadyForPlayback.bind(this)); + this._player.midiEventsPlayed.on(this.onMidiEventsPlayed.bind(this)); this._main.postMessage({ cmd: 'alphaSynth.ready' }); @@ -80,6 +82,9 @@ export class AlphaSynthWebWorker { break; case 'alphaSynth.setCountInVolume': this._player.countInVolume = data.value; + break; + case 'alphaSynth.setMidiEventPlayedFilter': + this._player.midiEventPlayedFilter = data.value; break; case 'alphaSynth.play': this._player.play(); @@ -171,9 +176,14 @@ export class AlphaSynthWebWorker { return error; } - public onMidiLoaded(): void { + public onMidiLoaded(e: PositionChangedEventArgs): void { this._main.postMessage({ - cmd: 'alphaSynth.midiLoaded' + cmd: 'alphaSynth.midiLoaded', + currentTime: e.currentTime, + endTime: e.endTime, + currentTick: e.currentTick, + endTick: e.endTick, + isSeek: e.isSeek }); } @@ -189,4 +199,11 @@ export class AlphaSynthWebWorker { cmd: 'alphaSynth.readyForPlayback' }); } + + public onMidiEventsPlayed(args: MidiEventsPlayedEventArgs): void { + this._main.postMessage({ + cmd: 'alphaSynth.midiEventsPlayed', + events: args.events.map(JsonConverter.midiEventToJsObject) + }); + } } diff --git a/src/platform/javascript/AlphaSynthWebWorkerApi.ts b/src/platform/javascript/AlphaSynthWebWorkerApi.ts index 33fc6efd7..bffcb4a92 100644 --- a/src/platform/javascript/AlphaSynthWebWorkerApi.ts +++ b/src/platform/javascript/AlphaSynthWebWorkerApi.ts @@ -13,6 +13,8 @@ import { LogLevel } from '@src/LogLevel'; import { SynthConstants } from '@src/synth/SynthConstants'; import { ProgressEventArgs } from '@src/alphatab'; import { FileLoadError } from '@src/FileLoadError'; +import { MidiEventsPlayedEventArgs } from '@src/synth/MidiEventsPlayedEventArgs'; +import { MidiEventType } from '@src/midi/MidiEvent'; /** * a WebWorker based alphaSynth which uses the given player as output. @@ -33,6 +35,7 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { private _timePosition: number = 0; private _isLooping: boolean = false; private _playbackRange: PlaybackRange | null = null; + private _midiEventPlayedFilter: MidiEventType[] = []; public get isReady(): boolean { return this._workerIsReady && this._outputIsReady; @@ -96,6 +99,18 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { }); } + public get midiEventPlayedFilter(): MidiEventType[] { + return this._midiEventPlayedFilter; + } + + public set midiEventPlayedFilter(value: MidiEventType[]) { + this._midiEventPlayedFilter = value; + this._synth.postMessage({ + cmd: 'alphaSynth.setMidiEventPlayedFilter', + value: value + }) + } + public get playbackSpeed(): number { return this._playbackSpeed; } @@ -343,6 +358,10 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { this._tickPosition = data.currentTick; (this.positionChanged as EventEmitterOfT).trigger( new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick) + break; + case 'alphaSynth.midiEventsPlayed': + (this.midiEventsPlayed as EventEmitterOfT).trigger( + new MidiEventsPlayedEventArgs((data.events as unknown[]).map(JsonConverter.jsObjectToMidiEvent)) ); break; case 'alphaSynth.playerStateChanged': @@ -407,6 +426,9 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { >(); readonly positionChanged: IEventEmitterOfT = new EventEmitterOfT< PositionChangedEventArgs + >(); + readonly midiEventsPlayed: IEventEmitterOfT = new EventEmitterOfT< + MidiEventsPlayedEventArgs >(); // diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 2c6f85b31..828f137b8 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -14,6 +14,10 @@ import { ByteBuffer } from '@src/io/ByteBuffer'; import { Logger } from '@src/Logger'; import { LogLevel } from '@src/LogLevel'; import { SynthConstants } from '@src/synth/SynthConstants'; +import { SynthEvent } from './synthesis/SynthEvent'; +import { Queue } from './ds/Queue'; +import { MidiEventsPlayedEventArgs } from './MidiEventsPlayedEventArgs'; +import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; /** * This is the main synthesizer component which can be used to @@ -28,6 +32,8 @@ export class AlphaSynth implements IAlphaSynth { private _timePosition: number = 0; private _metronomeVolume: number = 0; private _countInVolume: number = 0; + private _playedEventsQueue: Queue = new Queue(); + private _midiEventPlayedFilter: Set = new Set(); /** * Gets the {@link ISynthOutput} used for playing the generated samples. @@ -78,6 +84,14 @@ export class AlphaSynth implements IAlphaSynth { this._countInVolume = value; } + public get midiEventPlayedFilter(): MidiEventType[] { + return Array.from(this._midiEventPlayedFilter); + } + + public set midiEventPlayedFilter(value: MidiEventType[]) { + this._midiEventPlayedFilter = new Set(value); + } + public get playbackSpeed(): number { return this._sequencer.playbackSpeed; } @@ -162,9 +176,15 @@ export class AlphaSynth implements IAlphaSynth { for (let i = 0; i < SynthConstants.MicroBufferCount; i++) { // synthesize buffer this._sequencer.fillMidiEventQueue(); - this._synthesizer.synthesize(samples, bufferPos, SynthConstants.MicroBufferSize); + 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._midiEventPlayedFilter.has(e.event.command)) { + this._playedEventsQueue.enqueue(e); + } + } // tell sequencer to check whether its work is done if (this._sequencer.isFinished) { break; @@ -192,7 +212,7 @@ export class AlphaSynth implements IAlphaSynth { return false; } this.output.activate(); - + this.playInternal(); if (this._countInVolume > 0) { @@ -392,6 +412,16 @@ export class AlphaSynth implements IAlphaSynth { new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick) ); } + + // build events which were actually played + const playedEvents = new Queue(); + while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < currentTime) { + const synthEvent = this._playedEventsQueue.dequeue(); + playedEvents.enqueue(synthEvent.event); + } + if (!playedEvents.isEmpty) { + (this.midiEventsPlayed as EventEmitterOfT).trigger(new MidiEventsPlayedEventArgs(playedEvents.toArray())) + } } readonly ready: IEventEmitter = new EventEmitter(); @@ -407,4 +437,5 @@ export class AlphaSynth implements IAlphaSynth { readonly positionChanged: IEventEmitterOfT = new EventEmitterOfT< PositionChangedEventArgs >(); + readonly midiEventsPlayed: IEventEmitterOfT = new EventEmitterOfT(); } diff --git a/src/synth/IAlphaSynth.ts b/src/synth/IAlphaSynth.ts index 5f0f68a23..9a8eb17e6 100644 --- a/src/synth/IAlphaSynth.ts +++ b/src/synth/IAlphaSynth.ts @@ -5,6 +5,8 @@ import { PlayerStateChangedEventArgs } from '@src/synth/PlayerStateChangedEventA import { PositionChangedEventArgs } from '@src/synth/PositionChangedEventArgs'; import { IEventEmitter, IEventEmitterOfT } from '@src/EventEmitter'; import { LogLevel } from '@src/LogLevel'; +import { MidiEventsPlayedEventArgs } from './MidiEventsPlayedEventArgs'; +import { MidiEventType } from '@src/midi/MidiEvent'; /** * The public API interface for interacting with the synthesizer. @@ -71,6 +73,15 @@ export interface IAlphaSynth { */ countInVolume: number; + /** + * Gets or sets the type fo midi events which will trigger the `midiEventsPlayed` event for + * midi events which have been played. + * + * Metronome events are signaled as `SystemExclusiveEvent` + * + */ + midiEventPlayedFilter: MidiEventType[]; + /** * Destroys the synthesizer and all related components */ @@ -191,4 +202,9 @@ export interface IAlphaSynth { * This event is fired when the current playback position of/ the song changed. */ readonly positionChanged: IEventEmitterOfT; + + /** + * The event is fired when certain midi events were sent to the audio output device for playback. + */ + readonly midiEventsPlayed: IEventEmitterOfT; } diff --git a/src/synth/MidiEventsPlayedEventArgs.ts b/src/synth/MidiEventsPlayedEventArgs.ts new file mode 100644 index 000000000..601f55dce --- /dev/null +++ b/src/synth/MidiEventsPlayedEventArgs.ts @@ -0,0 +1,19 @@ +import { MidiEvent } from "@src/midi/MidiEvent"; + +/** + * Represents the info when the synthesizer played certain midi events. + */ +export class MidiEventsPlayedEventArgs { + /** + * Gets the events which were played. + */ + public readonly events: MidiEvent[]; + + /** + * Initializes a new instance of the {@link MidiEventsPlayedEventArgs} class. + * @param events The events which were played. + */ + public constructor(events: MidiEvent[]) { + this.events = events; + } +} \ No newline at end of file diff --git a/src/synth/MidiFileSequencer.ts b/src/synth/MidiFileSequencer.ts index b666b5c3d..fd2b1f60a 100644 --- a/src/synth/MidiFileSequencer.ts +++ b/src/synth/MidiFileSequencer.ts @@ -187,7 +187,7 @@ export class MidiFileSequencer { if (metronomeLength > 0) { while (metronomeTick < absTick) { - let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length); + let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, metronomeTick % metronomeLength); state.synthData.push(metronome); metronome.time = metronomeTime; metronomeTick += metronomeLength; @@ -356,10 +356,10 @@ export class MidiFileSequencer { const state = new MidiSequencerState(); state.division = this._mainState.division; - let bpm :number = 120; + let bpm: number = 120; let timeSignatureNumerator = 4; let timeSignatureDenominator = 4; - if(this._mainState.eventIndex === 0) { + if (this._mainState.eventIndex === 0) { bpm = this._mainState.tempoChanges[0].bpm; timeSignatureNumerator = this._mainState.firstTimeSignatureNumerator; timeSignatureDenominator = this._mainState.firstTimeSignatureDenominator; @@ -376,7 +376,7 @@ export class MidiFileSequencer { let metronomeTime: number = 0.0; for (let i = 0; i < timeSignatureNumerator; i++) { - let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length); + let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, i); state.synthData.push(metronome); metronome.time = metronomeTime; metronomeTick += metronomeLength; diff --git a/src/synth/ds/Queue.ts b/src/synth/ds/Queue.ts new file mode 100644 index 000000000..e6eacda56 --- /dev/null +++ b/src/synth/ds/Queue.ts @@ -0,0 +1,32 @@ +export class Queue { + private _items: T[] = []; + private _position: number = 0; + + public isEmpty: boolean = true; + + public enqueue(item: T) { + this.isEmpty = false; + this._items.push(item); + } + + public peek(): T { + return this._items[this._position]; + } + + public dequeue(): T { + const item = this._items[this._position]; + this._position++; + if (this._position >= this._items.length / 2) { + this._items = this._items.slice(this._position); + this._position = 0; + } + this.isEmpty = this._items.length == 0; + return item; + } + + public toArray(): T[] { + const items = this._items.slice(this._position); + items.reverse(); + return items; + } +} diff --git a/src/synth/synthesis/SynthEvent.ts b/src/synth/synthesis/SynthEvent.ts index dd83650c3..9878964e2 100644 --- a/src/synth/synthesis/SynthEvent.ts +++ b/src/synth/synthesis/SynthEvent.ts @@ -2,22 +2,26 @@ // developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont) // TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny // Licensed under: MPL-2.0 -import { MidiEvent } from '@src/midi/MidiEvent'; +import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; +import { AlphaTabSystemExclusiveEvents, SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; export class SynthEvent { public eventIndex: number; - public event: MidiEvent | null; - public isMetronome: boolean = false; + public event: MidiEvent; + public readonly isMetronome: boolean; public time: number = 0; - public constructor(eventIndex: number, e: MidiEvent | null) { + public constructor(eventIndex: number, e: MidiEvent) { this.eventIndex = eventIndex; this.event = e; + this.isMetronome = this.event instanceof SystemExclusiveEvent && (this.event as SystemExclusiveEvent).isMetronome; } - public static newMetronomeEvent(eventIndex: number): SynthEvent { - const x: SynthEvent = new SynthEvent(eventIndex, null); - x.isMetronome = true; + + private static readonly MetronomeTickData: Uint8Array = new Uint8Array([AlphaTabSystemExclusiveEvents.MetronomeTick]); + public static newMetronomeEvent(eventIndex: number, counter:number): SynthEvent { + const evt = new SystemExclusiveEvent(counter, MidiEventType.SystemExclusive2, SystemExclusiveEvent.AlphaTabManufacturerId, SynthEvent.MetronomeTickData); + const x: SynthEvent = new SynthEvent(eventIndex, evt); return x; } } diff --git a/src/synth/synthesis/TinySoundFont.ts b/src/synth/synthesis/TinySoundFont.ts index 5916c4f2f..8eb75778f 100644 --- a/src/synth/synthesis/TinySoundFont.ts +++ b/src/synth/synthesis/TinySoundFont.ts @@ -30,6 +30,7 @@ import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20ChannelVoiceEvent'; import { MetaEventType } from '@src/midi/MetaEvent'; import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; import { MetaDataEvent } from '@src/midi/MetaDataEvent'; +import { Queue } from '../ds/Queue'; /** * This is a tiny soundfont based synthesizer. @@ -39,22 +40,21 @@ import { MetaDataEvent } from '@src/midi/MetaDataEvent'; * - Support for modulators */ export class TinySoundFont { - private _midiEventQueue: SynthEvent[] = []; - private _midiEventCount: number = 0; + private _midiEventQueue: Queue = new Queue(); private _mutedChannels: Map = new Map(); private _soloChannels: Map = new Map(); private _isAnySolo: boolean = false; - public currentTempo:number = 0; - public timeSignatureNumerator:number = 0; - public timeSignatureDenominator:number = 0; + public currentTempo: number = 0; + public timeSignatureNumerator: number = 0; + public timeSignatureDenominator: number = 0; public constructor(sampleRate: number) { this.outSampleRate = sampleRate; } - public synthesize(buffer: Float32Array, bufferPos: number, sampleCount: number) { - this.fillWorkingBuffer(buffer, bufferPos, sampleCount); + public synthesize(buffer: Float32Array, bufferPos: number, sampleCount: number): SynthEvent[] { + return this.fillWorkingBuffer(buffer, bufferPos, sampleCount); } public synthesizeSilent(sampleCount: number): void { @@ -101,29 +101,27 @@ export class TinySoundFont { } public dispatchEvent(synthEvent: SynthEvent): void { - this._midiEventQueue.unshift(synthEvent); - this._midiEventCount++; + this._midiEventQueue.enqueue(synthEvent); } - private fillWorkingBuffer(buffer: Float32Array | null, bufferPos: number, sampleCount: number) { + private fillWorkingBuffer(buffer: Float32Array | null, bufferPos: number, sampleCount: number): SynthEvent[] { // Break the process loop into sections representing the smallest timeframe before the midi controls need to be updated // the bigger the timeframe the more efficent the process is, but playback quality will be reduced. const anySolo: boolean = this._isAnySolo; + const processedEvents: SynthEvent[] = []; + // process in micro-buffers // process events for first microbuffer - if (this._midiEventQueue.length > 0) { - for (let i: number = 0; i < this._midiEventCount; i++) { - let m: SynthEvent | undefined = this._midiEventQueue.pop(); - if (m) { - if (m.isMetronome && this.metronomeVolume > 0) { - this.channelNoteOff(SynthConstants.MetronomeChannel, 33); - this.channelNoteOn(SynthConstants.MetronomeChannel, 33, 95 / 127); - } else if (m.event) { - this.processMidiMessage(m.event); - } - } + while (!this._midiEventQueue.isEmpty) { + let m: SynthEvent = this._midiEventQueue.dequeue(); + if (m.isMetronome && this.metronomeVolume > 0) { + this.channelNoteOff(SynthConstants.MetronomeChannel, 33); + this.channelNoteOn(SynthConstants.MetronomeChannel, 33, 95 / 127); + } else if (m.event) { + this.processMidiMessage(m.event); } + processedEvents.push(m); } // voice processing loop @@ -143,7 +141,7 @@ export class TinySoundFont { } } - this._midiEventCount = 0; + return processedEvents; } private processMidiMessage(e: MidiEvent): void { @@ -185,8 +183,8 @@ export class TinySoundFont { this.currentTempo = 60000000 / (e as MetaNumberEvent).value; break; case MetaEventType.TimeSignature: - this.timeSignatureNumerator = (e as MetaDataEvent).data[0]; - this.timeSignatureDenominator = Math.pow(2, (e as MetaDataEvent).data[1]); + this.timeSignatureNumerator = (e as MetaDataEvent).data[0]; + this.timeSignatureDenominator = Math.pow(2, (e as MetaDataEvent).data[1]); break; } break; From 78021dbf561e3a039cc2cebe79e7f51b0e5e290f Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 16 Jan 2021 18:55:56 +0100 Subject: [PATCH 2/7] Include track in events --- src/midi/AlphaSynthMidiFileHandler.ts | 14 +++++++++++--- src/midi/MetaDataEvent.ts | 4 ++-- src/midi/MetaEvent.ts | 4 ++-- src/midi/MetaNumberEvent.ts | 4 ++-- src/midi/Midi20ChannelVoiceEvent.ts | 4 ++-- src/midi/MidiEvent.ts | 11 +++++++++-- src/midi/SystemCommonEvent.ts | 4 ++-- src/midi/SystemExclusiveEvent.ts | 4 ++-- src/model/JsonConverter.ts | 12 +++++++----- src/synth/MidiFileSequencer.ts | 4 +++- src/synth/synthesis/SynthEvent.ts | 2 +- 11 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/midi/AlphaSynthMidiFileHandler.ts b/src/midi/AlphaSynthMidiFileHandler.ts index edf7a0c4c..5b305e5e0 100644 --- a/src/midi/AlphaSynthMidiFileHandler.ts +++ b/src/midi/AlphaSynthMidiFileHandler.ts @@ -33,6 +33,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { denominatorIndex++; } const message: MetaDataEvent = new MetaDataEvent( + 0, tick, 0xff, MetaEventType.TimeSignature, @@ -43,6 +44,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { public addRest(track: number, tick: number, channel: number): void { const message: SystemExclusiveEvent = new SystemExclusiveEvent( + track, tick, SystemCommonType.SystemExclusive, SystemExclusiveEvent.AlphaTabManufacturerId, @@ -61,6 +63,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { ): void { const velocity: number = MidiUtils.dynamicToVelocity(dynamicValue); const noteOn: MidiEvent = new MidiEvent( + track, start, this.makeCommand(MidiEventType.NoteOn, channel), AlphaSynthMidiFileHandler.fixValue(key), @@ -68,6 +71,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { ); this._midiFile.addEvent(noteOn); const noteOff: MidiEvent = new MidiEvent( + track, start + length, this.makeCommand(MidiEventType.NoteOff, channel), AlphaSynthMidiFileHandler.fixValue(key), @@ -92,6 +96,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { public addControlChange(track: number, tick: number, channel: number, controller: number, value: number): void { const message: MidiEvent = new MidiEvent( + track, tick, this.makeCommand(MidiEventType.Controller, channel), AlphaSynthMidiFileHandler.fixValue(controller), @@ -102,6 +107,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { public addProgramChange(track: number, tick: number, channel: number, program: number): void { const message: MidiEvent = new MidiEvent( + track, tick, this.makeCommand(MidiEventType.ProgramChange, channel), AlphaSynthMidiFileHandler.fixValue(program), @@ -113,7 +119,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { public addTempo(tick: number, tempo: number): void { // bpm -> microsecond per quarter note const tempoInUsq: number = (60000000 / tempo) | 0; - const message: MetaNumberEvent = new MetaNumberEvent(tick, 0xff, MetaEventType.Tempo, tempoInUsq); + const message: MetaNumberEvent = new MetaNumberEvent(0, tick, 0xff, MetaEventType.Tempo, tempoInUsq); this._midiFile.addEvent(message); } @@ -125,9 +131,10 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { } const message: MidiEvent = new MidiEvent( + track, tick, this.makeCommand(MidiEventType.PitchBend, channel), - value & 0x7F, + value & 0x7F, (value >> 7) & 0x7F ); this._midiFile.addEvent(message); @@ -145,6 +152,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { value = value * SynthConstants.MaxPitchWheel20 / SynthConstants.MaxPitchWheel const message = new Midi20PerNotePitchBendEvent( + track, tick, this.makeCommand(MidiEventType.PerNotePitchBend, channel), key, @@ -154,7 +162,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { } public finishTrack(track: number, tick: number): void { - const message: MetaDataEvent = new MetaDataEvent(tick, 0xff, MetaEventType.EndOfTrack, new Uint8Array(0)); + const message: MetaDataEvent = new MetaDataEvent(track, tick, 0xff, MetaEventType.EndOfTrack, new Uint8Array(0)); this._midiFile.addEvent(message); } } diff --git a/src/midi/MetaDataEvent.ts b/src/midi/MetaDataEvent.ts index cb21c86ff..abd1636aa 100644 --- a/src/midi/MetaDataEvent.ts +++ b/src/midi/MetaDataEvent.ts @@ -5,8 +5,8 @@ import { IWriteable } from '@src/io/IWriteable'; export class MetaDataEvent extends MetaEvent { public data: Uint8Array; - public constructor(delta: number, status: number, metaId: number, data: Uint8Array) { - super(delta, status, metaId, 0); + public constructor(track:number, delta: number, status: number, metaId: number, data: Uint8Array) { + super(track, delta, status, metaId, 0); this.data = data; } diff --git a/src/midi/MetaEvent.ts b/src/midi/MetaEvent.ts index 1017bca8d..760db25b3 100644 --- a/src/midi/MetaEvent.ts +++ b/src/midi/MetaEvent.ts @@ -34,7 +34,7 @@ export class MetaEvent extends MidiEvent { return this.data1; } - protected constructor(delta: number, status: number, data1: number, data2: number) { - super(delta, status, data1, data2); + protected constructor(track: number, delta: number, status: number, data1: number, data2: number) { + super(track, delta, status, data1, data2); } } diff --git a/src/midi/MetaNumberEvent.ts b/src/midi/MetaNumberEvent.ts index e757d7d35..5e0b8f3f5 100644 --- a/src/midi/MetaNumberEvent.ts +++ b/src/midi/MetaNumberEvent.ts @@ -5,8 +5,8 @@ import { IWriteable } from '@src/io/IWriteable'; export class MetaNumberEvent extends MetaEvent { public value: number; - public constructor(delta: number, status: number, metaId: number, value: number) { - super(delta, status, metaId, 0); + public constructor(track:number, delta: number, status: number, metaId: number, value: number) { + super(track, delta, status, metaId, 0); this.value = value; } diff --git a/src/midi/Midi20ChannelVoiceEvent.ts b/src/midi/Midi20ChannelVoiceEvent.ts index 35c85b366..bebf7bb53 100644 --- a/src/midi/Midi20ChannelVoiceEvent.ts +++ b/src/midi/Midi20ChannelVoiceEvent.ts @@ -9,8 +9,8 @@ export class Midi20PerNotePitchBendEvent extends MidiEvent { public noteKey: number; public pitch: number; - public constructor(tick: number, status: number, noteKey: number, pitch: number) { - super(tick, status, 0, 0); + public constructor(track:number, tick: number, status: number, noteKey: number, pitch: number) { + super(track, tick, status, 0, 0); this.noteKey = noteKey; this.pitch = pitch; } diff --git a/src/midi/MidiEvent.ts b/src/midi/MidiEvent.ts index 322dd6102..bf6a8df7e 100644 --- a/src/midi/MidiEvent.ts +++ b/src/midi/MidiEvent.ts @@ -65,6 +65,11 @@ export enum MidiEventType { * Represents a midi event. */ export class MidiEvent { + /** + * Gets or sets the track to which the midi event belongs. + */ + public track:number; + /** * Gets or sets the raw midi message. */ @@ -103,12 +108,14 @@ export class MidiEvent { /** * Initializes a new instance of the {@link MidiEvent} class. - * @param tick The absolute midi ticks of this event.. + * @param track The track this event belongs to. + * @param tick The absolute midi ticks of this event. * @param status The status information of this event. * @param data1 The first data component of this midi event. * @param data2 The second data component of this midi event. */ - public constructor(tick: number, status: number, data1: number, data2: number) { + public constructor(track:number, tick: number, status: number, data1: number, data2: number) { + this.track = track; this.tick = tick; this.message = status | (data1 << 8) | (data2 << 16); } diff --git a/src/midi/SystemCommonEvent.ts b/src/midi/SystemCommonEvent.ts index fc14f51fc..d6ab24999 100644 --- a/src/midi/SystemCommonEvent.ts +++ b/src/midi/SystemCommonEvent.ts @@ -18,7 +18,7 @@ export class SystemCommonEvent extends MidiEvent { return (this.message & 0x00000ff) as MidiEventType; } - protected constructor(delta: number, status: number, data1: number, data2: number) { - super(delta, status, data1, data2); + protected constructor(track:number, delta: number, status: number, data1: number, data2: number) { + super(track, delta, status, data1, data2); } } diff --git a/src/midi/SystemExclusiveEvent.ts b/src/midi/SystemExclusiveEvent.ts index 1732b9075..3a5bd3a50 100644 --- a/src/midi/SystemExclusiveEvent.ts +++ b/src/midi/SystemExclusiveEvent.ts @@ -25,8 +25,8 @@ export class SystemExclusiveEvent extends SystemCommonEvent { return this.message >> 8; } - public constructor(delta: number, status: number, id: number, data: Uint8Array) { - super(delta, status, id & 0x00ff, (id >> 8) & 0xff); + public constructor(track:number, delta: number, status: number, id: number, data: Uint8Array) { + super(track, delta, status, id & 0x00ff, (id >> 8) & 0xff); this.data = data; } diff --git a/src/model/JsonConverter.ts b/src/model/JsonConverter.ts index 503ca33c9..3825ab4e8 100644 --- a/src/model/JsonConverter.ts +++ b/src/model/JsonConverter.ts @@ -137,28 +137,29 @@ export class JsonConverter { * @target web */ public static jsObjectToMidiEvent(midiEvent: any): MidiEvent { + let track: number = midiEvent.track; let tick: number = midiEvent.tick; let message: number = midiEvent.message; let midiEvent2: MidiEvent; switch (midiEvent.type) { case 'SystemExclusiveEvent': - midiEvent2 = new SystemExclusiveEvent(tick, 0, 0, midiEvent.data); + midiEvent2 = new SystemExclusiveEvent(track, tick, 0, 0, midiEvent.data); midiEvent2.message = message; break; case 'MetaDataEvent': - midiEvent2 = new MetaDataEvent(tick, 0, 0, midiEvent.data); + midiEvent2 = new MetaDataEvent(track, tick, 0, 0, midiEvent.data); midiEvent2.message = message; break; case 'MetaNumberEvent': - midiEvent2 = new MetaNumberEvent(tick, 0, 0, midiEvent.value); + midiEvent2 = new MetaNumberEvent(track, tick, 0, 0, midiEvent.value); midiEvent2.message = message; break; case 'Midi20PerNotePitchBendEvent': - midiEvent2 = new Midi20PerNotePitchBendEvent(tick, 0, midiEvent.noteKey, midiEvent.pitch); + midiEvent2 = new Midi20PerNotePitchBendEvent(track, tick, 0, midiEvent.noteKey, midiEvent.pitch); midiEvent2.message = message; break; default: - midiEvent2 = new MidiEvent(tick, 0, 0, 0); + midiEvent2 = new MidiEvent(track, tick, 0, 0, 0); midiEvent2.message = message; break; } @@ -184,6 +185,7 @@ export class JsonConverter { */ public static midiEventToJsObject(midiEvent: MidiEvent): unknown { let midiEvent2: any = {} as any; + midiEvent2.track = midiEvent.track; midiEvent2.tick = midiEvent.tick; midiEvent2.message = midiEvent.message; if (midiEvent instanceof SystemExclusiveEvent) { diff --git a/src/synth/MidiFileSequencer.ts b/src/synth/MidiFileSequencer.ts index fd2b1f60a..531dae7b0 100644 --- a/src/synth/MidiFileSequencer.ts +++ b/src/synth/MidiFileSequencer.ts @@ -170,6 +170,7 @@ export class MidiFileSequencer { let absTick: number = 0; let absTime: number = 0.0; + let metronomeCount: number = 0; let metronomeLength: number = 0; let metronomeTick: number = 0; let metronomeTime: number = 0.0; @@ -187,7 +188,7 @@ export class MidiFileSequencer { if (metronomeLength > 0) { while (metronomeTick < absTick) { - let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, metronomeTick % metronomeLength); + let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, Math.floor(metronomeTick / metronomeLength) % metronomeCount); state.synthData.push(metronome); metronome.time = metronomeTime; metronomeTick += metronomeLength; @@ -202,6 +203,7 @@ export class MidiFileSequencer { } else if (mEvent.command === MidiEventType.Meta && mEvent.data1 === MetaEventType.TimeSignature) { let meta: MetaDataEvent = mEvent as MetaDataEvent; let timeSignatureDenominator: number = Math.pow(2, meta.data[1]); + metronomeCount = meta.data[0]; metronomeLength = (state.division * (4.0 / timeSignatureDenominator)) | 0; if (state.firstTimeSignatureDenominator === 0) { state.firstTimeSignatureNumerator = meta.data[0]; diff --git a/src/synth/synthesis/SynthEvent.ts b/src/synth/synthesis/SynthEvent.ts index 9878964e2..7b6e1bd0c 100644 --- a/src/synth/synthesis/SynthEvent.ts +++ b/src/synth/synthesis/SynthEvent.ts @@ -20,7 +20,7 @@ export class SynthEvent { private static readonly MetronomeTickData: Uint8Array = new Uint8Array([AlphaTabSystemExclusiveEvents.MetronomeTick]); public static newMetronomeEvent(eventIndex: number, counter:number): SynthEvent { - const evt = new SystemExclusiveEvent(counter, MidiEventType.SystemExclusive2, SystemExclusiveEvent.AlphaTabManufacturerId, SynthEvent.MetronomeTickData); + const evt = new SystemExclusiveEvent(0, counter, MidiEventType.SystemExclusive2, SystemExclusiveEvent.AlphaTabManufacturerId, SynthEvent.MetronomeTickData); const x: SynthEvent = new SynthEvent(eventIndex, evt); return x; } From 6f1fa52985f6f293d871f1cb8db86a3eceb238ff Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 17 Jan 2021 10:39:39 +0100 Subject: [PATCH 3/7] Cleanup and fixes --- src/AlphaTabApiBase.ts | 24 +++++++++++++++++++ src/midi/AlphaSynthMidiFileHandler.ts | 2 +- src/midi/MetaDataEvent.ts | 2 +- src/midi/MetaEvent.ts | 2 +- src/midi/MetaNumberEvent.ts | 2 +- ...vent.ts => Midi20PerNotePitchBendEvent.ts} | 0 src/model/JsonConverter.ts | 2 +- .../javascript/AlphaSynthWebWorker.ts | 4 ++-- .../javascript/AlphaSynthWebWorkerApi.ts | 13 +++++----- src/platform/javascript/JQueryAlphaTab.ts | 8 +++++++ src/synth/AlphaSynth.ts | 12 +++++----- src/synth/IAlphaSynth.ts | 9 +++---- src/synth/synthesis/TinySoundFont.ts | 2 +- test/audio/MidiFileGenerator.test.ts | 10 ++++---- 14 files changed, 61 insertions(+), 31 deletions(-) rename src/midi/{Midi20ChannelVoiceEvent.ts => Midi20PerNotePitchBendEvent.ts} (100%) diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts index 3e53fd0dc..957634cec 100644 --- a/src/AlphaTabApiBase.ts +++ b/src/AlphaTabApiBase.ts @@ -41,6 +41,8 @@ import { Logger } from '@src/Logger'; import { ModelUtils } from '@src/model/ModelUtils'; import { AlphaTabError, AlphaTabErrorType } from '@src/AlphaTabError'; import { Note } from './model/Note'; +import { MidiEventType } from './midi/MidiEvent'; +import { MidiEventsPlayedEventArgs } from './synth/MidiEventsPlayedEventArgs'; class SelectionInfo { public beat: Beat; @@ -435,6 +437,19 @@ export class AlphaTabApiBase { } } + public get midiEventsPlayedFilter(): MidiEventType[] { + if (!this.player) { + return []; + } + return this.player.midiEventsPlayedFilter; + } + + public set midiEventsPlayedFilter(value: MidiEventType[]) { + if (this.player) { + this.player.midiEventsPlayedFilter = value; + } + } + public get tickPosition(): number { if (!this.player) { return 0; @@ -543,6 +558,7 @@ export class AlphaTabApiBase { }); this.player.stateChanged.on(this.onPlayerStateChanged.bind(this)); this.player.positionChanged.on(this.onPlayerPositionChanged.bind(this)); + this.player.midiEventsPlayed.on(this.onMidiEventsPlayed.bind(this)); this.player.finished.on(this.onPlayerFinished.bind(this)); if (this.settings.player.enableCursor) { this.setupCursors(); @@ -1242,4 +1258,12 @@ export class AlphaTabApiBase { (this.playerPositionChanged as EventEmitterOfT).trigger(e); this.uiFacade.triggerEvent(this.container, 'playerPositionChanged', e); } + + public midiEventsPlayed: IEventEmitterOfT = new EventEmitterOfT< + MidiEventsPlayedEventArgs + >(); + private onMidiEventsPlayed(e: MidiEventsPlayedEventArgs): void { + (this.midiEventsPlayed as EventEmitterOfT).trigger(e); + this.uiFacade.triggerEvent(this.container, 'midiEventsPlayed', e); + } } diff --git a/src/midi/AlphaSynthMidiFileHandler.ts b/src/midi/AlphaSynthMidiFileHandler.ts index 5b305e5e0..ac60e1a60 100644 --- a/src/midi/AlphaSynthMidiFileHandler.ts +++ b/src/midi/AlphaSynthMidiFileHandler.ts @@ -9,7 +9,7 @@ import { MidiFile } from '@src/midi/MidiFile'; import { MidiUtils } from '@src/midi/MidiUtils'; import { DynamicValue } from '@src/model/DynamicValue'; import { SynthConstants } from '@src/synth/SynthConstants'; -import { Midi20PerNotePitchBendEvent } from './Midi20ChannelVoiceEvent'; +import { Midi20PerNotePitchBendEvent } from './Midi20PerNotePitchBendEvent'; /** * This implementation of the {@link IMidiFileHandler} diff --git a/src/midi/MetaDataEvent.ts b/src/midi/MetaDataEvent.ts index abd1636aa..0f3ffec50 100644 --- a/src/midi/MetaDataEvent.ts +++ b/src/midi/MetaDataEvent.ts @@ -12,7 +12,7 @@ export class MetaDataEvent extends MetaEvent { public writeTo(s: IWriteable): void { s.writeByte(0xff); - s.writeByte(this.metaStatus); + s.writeByte(this.metaStatus as number); let l: number = this.data.length; MidiFile.writeVariableInt(s, l); s.write(this.data, 0, this.data.length); diff --git a/src/midi/MetaEvent.ts b/src/midi/MetaEvent.ts index 760db25b3..66955648d 100644 --- a/src/midi/MetaEvent.ts +++ b/src/midi/MetaEvent.ts @@ -30,7 +30,7 @@ export class MetaEvent extends MidiEvent { return (this.message & 0x00000ff) as MidiEventType; } - public get metaStatus(): number { + public get metaStatus(): MetaEventType { return this.data1; } diff --git a/src/midi/MetaNumberEvent.ts b/src/midi/MetaNumberEvent.ts index 5e0b8f3f5..ebe1af152 100644 --- a/src/midi/MetaNumberEvent.ts +++ b/src/midi/MetaNumberEvent.ts @@ -12,7 +12,7 @@ export class MetaNumberEvent extends MetaEvent { public writeTo(s: IWriteable): void { s.writeByte(0xff); - s.writeByte(this.metaStatus); + s.writeByte(this.metaStatus as number); MidiFile.writeVariableInt(s, 3); let b: Uint8Array = new Uint8Array([(this.value >> 16) & 0xff, (this.value >> 8) & 0xff, this.value & 0xff]); s.write(b, 0, b.length); diff --git a/src/midi/Midi20ChannelVoiceEvent.ts b/src/midi/Midi20PerNotePitchBendEvent.ts similarity index 100% rename from src/midi/Midi20ChannelVoiceEvent.ts rename to src/midi/Midi20PerNotePitchBendEvent.ts diff --git a/src/model/JsonConverter.ts b/src/model/JsonConverter.ts index 3825ab4e8..3dcd50e72 100644 --- a/src/model/JsonConverter.ts +++ b/src/model/JsonConverter.ts @@ -5,7 +5,7 @@ import { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; import { MidiFile } from '@src/midi/MidiFile'; import { Score } from '@src/model/Score'; import { Settings } from '@src/Settings'; -import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20ChannelVoiceEvent'; +import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; import { ScoreSerializer } from '@src/generated/model/ScoreSerializer'; import { SettingsSerializer } from '@src/generated/SettingsSerializer'; diff --git a/src/platform/javascript/AlphaSynthWebWorker.ts b/src/platform/javascript/AlphaSynthWebWorker.ts index 446381543..858c037b5 100644 --- a/src/platform/javascript/AlphaSynthWebWorker.ts +++ b/src/platform/javascript/AlphaSynthWebWorker.ts @@ -83,8 +83,8 @@ export class AlphaSynthWebWorker { case 'alphaSynth.setCountInVolume': this._player.countInVolume = data.value; break; - case 'alphaSynth.setMidiEventPlayedFilter': - this._player.midiEventPlayedFilter = data.value; + case 'alphaSynth.setMidiEventsPlayedFilter': + this._player.midiEventsPlayedFilter = data.value; break; case 'alphaSynth.play': this._player.play(); diff --git a/src/platform/javascript/AlphaSynthWebWorkerApi.ts b/src/platform/javascript/AlphaSynthWebWorkerApi.ts index 04cbf9457..fed61cdc7 100644 --- a/src/platform/javascript/AlphaSynthWebWorkerApi.ts +++ b/src/platform/javascript/AlphaSynthWebWorkerApi.ts @@ -35,7 +35,7 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { private _timePosition: number = 0; private _isLooping: boolean = false; private _playbackRange: PlaybackRange | null = null; - private _midiEventPlayedFilter: MidiEventType[] = []; + private _midiEventsPlayedFilter: MidiEventType[] = []; public get isReady(): boolean { return this._workerIsReady && this._outputIsReady; @@ -99,14 +99,14 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { }); } - public get midiEventPlayedFilter(): MidiEventType[] { - return this._midiEventPlayedFilter; + public get midiEventsPlayedFilter(): MidiEventType[] { + return this._midiEventsPlayedFilter; } - public set midiEventPlayedFilter(value: MidiEventType[]) { - this._midiEventPlayedFilter = value; + public set midiEventsPlayedFilter(value: MidiEventType[]) { + this._midiEventsPlayedFilter = value; this._synth.postMessage({ - cmd: 'alphaSynth.setMidiEventPlayedFilter', + cmd: 'alphaSynth.setMidiEventsPlayedFilter', value: value }) } @@ -358,6 +358,7 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth { this._tickPosition = data.currentTick; (this.positionChanged as EventEmitterOfT).trigger( new PositionChangedEventArgs(data.currentTime, data.endTime, data.currentTick, data.endTick, data.isSeek) + ); break; case 'alphaSynth.midiEventsPlayed': (this.midiEventsPlayed as EventEmitterOfT).trigger( diff --git a/src/platform/javascript/JQueryAlphaTab.ts b/src/platform/javascript/JQueryAlphaTab.ts index 3bc4ebf38..5890692f5 100644 --- a/src/platform/javascript/JQueryAlphaTab.ts +++ b/src/platform/javascript/JQueryAlphaTab.ts @@ -6,6 +6,7 @@ import { AlphaTabApi } from '@src/platform/javascript/AlphaTabApi'; import { IScoreRenderer } from '@src/rendering/IScoreRenderer'; import { Settings } from '@src/Settings'; import { Logger } from '@src/Logger'; +import { MidiEventType } from '@src/midi/MidiEvent'; /** * @target web @@ -173,6 +174,13 @@ export class JQueryAlphaTab { return context.countInVolume; } + public midiEventsPlayedFilter(element: jQuery, context: AlphaTabApi, midiEventsPlayedFilter?: MidiEventType[]): MidiEventType[] { + if (Array.isArray(midiEventsPlayedFilter)) { + context.midiEventsPlayedFilter = midiEventsPlayedFilter; + } + return context.midiEventsPlayedFilter; + } + public playbackSpeed(element: jQuery, context: AlphaTabApi, playbackSpeed?: number): number { if (typeof playbackSpeed === 'number') { context.playbackSpeed = playbackSpeed; diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 5fe80f075..8a67ec519 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -33,7 +33,7 @@ export class AlphaSynth implements IAlphaSynth { private _metronomeVolume: number = 0; private _countInVolume: number = 0; private _playedEventsQueue: Queue = new Queue(); - private _midiEventPlayedFilter: Set = new Set(); + private _midiEventsPlayedFilter: Set = new Set(); /** * Gets the {@link ISynthOutput} used for playing the generated samples. @@ -84,12 +84,12 @@ export class AlphaSynth implements IAlphaSynth { this._countInVolume = value; } - public get midiEventPlayedFilter(): MidiEventType[] { - return Array.from(this._midiEventPlayedFilter); + public get midiEventsPlayedFilter(): MidiEventType[] { + return Array.from(this._midiEventsPlayedFilter); } - public set midiEventPlayedFilter(value: MidiEventType[]) { - this._midiEventPlayedFilter = new Set(value); + public set midiEventsPlayedFilter(value: MidiEventType[]) { + this._midiEventsPlayedFilter = new Set(value); } public get playbackSpeed(): number { @@ -181,7 +181,7 @@ export class AlphaSynth implements IAlphaSynth { // push all processed events into the queue // for informing users about played events for (const e of synthesizedEvents) { - if (this._midiEventPlayedFilter.has(e.event.command)) { + if (this._midiEventsPlayedFilter.has(e.event.command)) { this._playedEventsQueue.enqueue(e); } } diff --git a/src/synth/IAlphaSynth.ts b/src/synth/IAlphaSynth.ts index 970985a1f..406b61a3b 100644 --- a/src/synth/IAlphaSynth.ts +++ b/src/synth/IAlphaSynth.ts @@ -74,13 +74,10 @@ export interface IAlphaSynth { countInVolume: number; /** - * Gets or sets the type fo midi events which will trigger the `midiEventsPlayed` event for - * midi events which have been played. - * - * Metronome events are signaled as `SystemExclusiveEvent` - * + * Gets or sets the midi events which will trigger the `midiEventsPlayed` event. + * To subscribe to Metronome events use the `SystemExclusiveEvent2` event type and check against `event.isMetronome` */ - midiEventPlayedFilter: MidiEventType[]; + midiEventsPlayedFilter: MidiEventType[]; /** * Destroys the synthesizer and all related components diff --git a/src/synth/synthesis/TinySoundFont.ts b/src/synth/synthesis/TinySoundFont.ts index 8eb75778f..4781d9c6c 100644 --- a/src/synth/synthesis/TinySoundFont.ts +++ b/src/synth/synthesis/TinySoundFont.ts @@ -26,7 +26,7 @@ import { SynthHelper } from '@src/synth/SynthHelper'; import { TypeConversions } from '@src/io/TypeConversions'; import { Logger } from '@src/Logger'; import { SynthConstants } from '@src/synth/SynthConstants'; -import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20ChannelVoiceEvent'; +import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; import { MetaEventType } from '@src/midi/MetaEvent'; import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; import { MetaDataEvent } from '@src/midi/MetaDataEvent'; diff --git a/test/audio/MidiFileGenerator.test.ts b/test/audio/MidiFileGenerator.test.ts index 24befbd2f..8bb2daa23 100644 --- a/test/audio/MidiFileGenerator.test.ts +++ b/test/audio/MidiFileGenerator.test.ts @@ -47,11 +47,11 @@ describe('MidiFileGeneratorTest', () => { it('midi-order', () => { let midiFile: MidiFile = new MidiFile(); - midiFile.addEvent(new MidiEvent(0, 0, 0, 0)); - midiFile.addEvent(new MidiEvent(0, 0, 1, 0)); - midiFile.addEvent(new MidiEvent(100, 0, 2, 0)); - midiFile.addEvent(new MidiEvent(50, 0, 3, 0)); - midiFile.addEvent(new MidiEvent(50, 0, 4, 0)); + midiFile.addEvent(new MidiEvent(0, 0, 0, 0, 0)); + midiFile.addEvent(new MidiEvent(0, 0, 0, 1, 0)); + midiFile.addEvent(new MidiEvent(0, 100, 0, 2, 0)); + midiFile.addEvent(new MidiEvent(0, 50, 0, 3, 0)); + midiFile.addEvent(new MidiEvent(0, 50, 0, 4, 0)); expect(midiFile.events[0].data1).toEqual(0); expect(midiFile.events[1].data1).toEqual(1); expect(midiFile.events[2].data1).toEqual(3); From 41e82dbcf27be3e93b3002a5c9b7acfca91159b5 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 17 Jan 2021 11:26:55 +0100 Subject: [PATCH 4/7] Added event for midi file load --- src/AlphaTabApiBase.ts | 7 +++++++ src/alphatab.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts index 957634cec..edb9199eb 100644 --- a/src/AlphaTabApiBase.ts +++ b/src/AlphaTabApiBase.ts @@ -577,6 +577,7 @@ export class AlphaTabApiBase { let generator: MidiFileGenerator = new MidiFileGenerator(this.score, this.settings, handler); generator.generate(); this._tickCache = generator.tickLookup; + this.onMidiLoad(midiFile); this.player.loadMidiFile(midiFile); } @@ -1237,6 +1238,12 @@ export class AlphaTabApiBase { this.uiFacade.triggerEvent(this.container, 'soundFontLoaded', null); } + public midiLoad: IEventEmitterOfT = new EventEmitterOfT(); + private onMidiLoad(e:MidiFile): void { + (this.midiLoad as EventEmitterOfT).trigger(e); + this.uiFacade.triggerEvent(this.container, 'midiFileLoad', e); + } + public midiLoaded: IEventEmitterOfT = new EventEmitterOfT(); private onMidiLoaded(e:PositionChangedEventArgs): void { (this.midiLoaded as EventEmitterOfT).trigger(e); diff --git a/src/alphatab.ts b/src/alphatab.ts index fa5f13efc..a05e51973 100644 --- a/src/alphatab.ts +++ b/src/alphatab.ts @@ -71,6 +71,7 @@ import { MetaDataEvent } from '@src/midi/MetaDataEvent'; import { MetaEvent, MetaEventType } from '@src/midi/MetaEvent'; import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; +import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; import { SystemCommonEvent, SystemCommonType } from '@src/midi/SystemCommonEvent'; import { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; import { MidiFileGenerator } from '@src/midi/MidiFileGenerator'; @@ -89,6 +90,7 @@ export const midi = { MetaNumberEvent, MidiEvent, MidiEventType, + Midi20PerNotePitchBendEvent, SystemCommonEvent, SystemCommonType, SystemExclusiveEvent, From 0523a2eab4494ecc17e858cfb48037d2b4eeb2a9 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 17 Jan 2021 12:17:11 +0100 Subject: [PATCH 5/7] Fix some issues on generic code generation for C# compilation --- src.compiler/csharp/CSharpAstPrinter.ts | 3 --- src.compiler/csharp/CSharpAstTransformer.ts | 6 +++++- src.compiler/csharp/CSharpEmitterContext.ts | 6 +++--- src.csharp/AlphaTab/AlphaTab.csproj | 2 +- src.csharp/AlphaTab/Core/EcmaScript/Iterable.cs | 10 ++++++++++ src.csharp/AlphaTab/Core/EcmaScript/Set.cs | 14 ++++++++++++-- .../AlphaTab/Core/EcmaScript/Uint8Array.cs | 7 ++++++- .../Platform/CSharp/AlphaSynthWorkerApiBase.cs | 17 ++++++++++++++++- src/AlphaTabApiBase.ts | 2 +- src/midi/MetaEvent.ts | 2 +- 10 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 src.csharp/AlphaTab/Core/EcmaScript/Iterable.cs diff --git a/src.compiler/csharp/CSharpAstPrinter.ts b/src.compiler/csharp/CSharpAstPrinter.ts index 3259e61c5..69f306e94 100644 --- a/src.compiler/csharp/CSharpAstPrinter.ts +++ b/src.compiler/csharp/CSharpAstPrinter.ts @@ -561,9 +561,6 @@ export default class CSharpAstPrinter { } private writeType(type: cs.TypeNode, forNew: boolean = false, asNativeArray: boolean = false, forTypeConstraint: boolean = false) { - if (!type) { - console.log('ERR'); - } switch (type.nodeType) { case cs.SyntaxKind.PrimitiveTypeNode: if (forTypeConstraint) { diff --git a/src.compiler/csharp/CSharpAstTransformer.ts b/src.compiler/csharp/CSharpAstTransformer.ts index 708e4dc58..e7d18a3ea 100644 --- a/src.compiler/csharp/CSharpAstTransformer.ts +++ b/src.compiler/csharp/CSharpAstTransformer.ts @@ -453,7 +453,11 @@ export default class CSharpAstTransformer { parent: parent } as cs.UnresolvedTypeNode; - const typeArguments = (tsType as ts.TypeReference)?.typeArguments; + let typeArguments = (tsType as ts.TypeReference)?.typeArguments; + if(tsType && !typeArguments) { + const nonNullable = this._context.typeChecker.getNonNullableType(tsType); + typeArguments = (nonNullable as ts.TypeReference)?.typeArguments; + } if (typeArguments) { unresolved.typeArguments = typeArguments.map(a => this.createUnresolvedTypeNode(parent, tsNode, a)); } diff --git a/src.compiler/csharp/CSharpEmitterContext.ts b/src.compiler/csharp/CSharpEmitterContext.ts index db0a2895c..92c6055c7 100644 --- a/src.compiler/csharp/CSharpEmitterContext.ts +++ b/src.compiler/csharp/CSharpEmitterContext.ts @@ -262,7 +262,7 @@ export default class CSharpEmitterContext { return csType; } - csType = this.resolveUnionType(node, tsType); + csType = this.resolveUnionType(node, tsType, typeArguments); if (csType) { return csType; } @@ -493,7 +493,7 @@ export default class CSharpEmitterContext { } } - private resolveUnionType(parent: cs.Node, tsType: ts.Type): cs.TypeNode | null { + private resolveUnionType(parent: cs.Node, tsType: ts.Type, typeArguments?: cs.UnresolvedTypeNode[]): cs.TypeNode | null { if (!tsType.isUnion()) { return null; } @@ -562,7 +562,7 @@ export default class CSharpEmitterContext { if (!actualType) { return null; } - const type = this.getTypeFromTsType(parent, actualType); + const type = this.getTypeFromTsType(parent, actualType, undefined, typeArguments); return { nodeType: cs.SyntaxKind.TypeReference, parent: parent, diff --git a/src.csharp/AlphaTab/AlphaTab.csproj b/src.csharp/AlphaTab/AlphaTab.csproj index 89e3e356f..3bc5d3485 100644 --- a/src.csharp/AlphaTab/AlphaTab.csproj +++ b/src.csharp/AlphaTab/AlphaTab.csproj @@ -1,4 +1,4 @@ - + AlphaTab diff --git a/src.csharp/AlphaTab/Core/EcmaScript/Iterable.cs b/src.csharp/AlphaTab/Core/EcmaScript/Iterable.cs new file mode 100644 index 000000000..4867b335c --- /dev/null +++ b/src.csharp/AlphaTab/Core/EcmaScript/Iterable.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace AlphaTab.Core.EcmaScript +{ + public interface Iterable : IEnumerable + { + } +} diff --git a/src.csharp/AlphaTab/Core/EcmaScript/Set.cs b/src.csharp/AlphaTab/Core/EcmaScript/Set.cs index 1a2aeb18d..09af796b8 100644 --- a/src.csharp/AlphaTab/Core/EcmaScript/Set.cs +++ b/src.csharp/AlphaTab/Core/EcmaScript/Set.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -7,7 +7,17 @@ namespace AlphaTab.Core.EcmaScript { public class Set : IEnumerable { - private readonly HashSet _data = new HashSet(); + private readonly HashSet _data; + + public Set() + { + _data = new HashSet(); + } + + public Set(IEnumerable values) + { + _data = new HashSet(values); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) diff --git a/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs b/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs index 8124b0b51..d21b141b9 100644 --- a/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs +++ b/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -16,6 +16,11 @@ public class Uint8Array : IEnumerable, IEnumerable public ArraySegment Data => _data; + public Uint8Array(IList data) + { + _data = new ArraySegment(data.Cast().ToArray()); + } + public Uint8Array(byte[] data) { _data = new ArraySegment(data); diff --git a/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs b/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs index 27b7956cb..5b8bc7fc2 100644 --- a/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs +++ b/src.csharp/AlphaTab/Platform/CSharp/AlphaSynthWorkerApiBase.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using AlphaTab.Core.EcmaScript; using AlphaTab.Midi; using AlphaTab.Synth; @@ -34,6 +35,7 @@ protected void Initialize() Player.MidiLoaded.On(OnMidiLoaded); Player.MidiLoadFailed.On(OnMidiLoadFailed); Player.ReadyForPlayback.On(OnReadyForPlayback); + Player.MidiEventsPlayed.On(OnMidiEventsPlayed); DispatchOnUiThread(OnReady); } @@ -65,6 +67,12 @@ public double CountInVolume set => DispatchOnWorkerThread(() => { Player.CountInVolume = value; }); } + public IList MidiEventsPlayedFilter + { + get => Player.MidiEventsPlayedFilter; + set => DispatchOnWorkerThread(() => { Player.MidiEventsPlayedFilter = value; }); + } + public double MetronomeVolume { get => Player.MetronomeVolume; @@ -172,10 +180,12 @@ 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 IEventEmitterOfT MidiLoad { get; } = new EventEmitterOfT(); public IEventEmitterOfT MidiLoaded { get; } = new EventEmitterOfT(); public IEventEmitterOfT MidiLoadFailed { get; } = new EventEmitterOfT(); public IEventEmitterOfT StateChanged { get; } = new EventEmitterOfT(); public IEventEmitterOfT PositionChanged { get; } = new EventEmitterOfT(); + public IEventEmitterOfT MidiEventsPlayed { get; } = new EventEmitterOfT(); protected virtual void OnReady() { @@ -212,6 +222,11 @@ protected virtual void OnMidiLoadFailed(Error e) DispatchOnUiThread(() => ((EventEmitterOfT)MidiLoadFailed).Trigger(e)); } + protected virtual void OnMidiEventsPlayed(MidiEventsPlayedEventArgs e) + { + DispatchOnUiThread(() => ((EventEmitterOfT)MidiEventsPlayed).Trigger(e)); + } + protected virtual void OnStateChanged(PlayerStateChangedEventArgs obj) { DispatchOnUiThread(() => ((EventEmitterOfT)StateChanged).Trigger(obj)); diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts index edb9199eb..1af18fddd 100644 --- a/src/AlphaTabApiBase.ts +++ b/src/AlphaTabApiBase.ts @@ -1241,7 +1241,7 @@ export class AlphaTabApiBase { public midiLoad: IEventEmitterOfT = new EventEmitterOfT(); private onMidiLoad(e:MidiFile): void { (this.midiLoad as EventEmitterOfT).trigger(e); - this.uiFacade.triggerEvent(this.container, 'midiFileLoad', e); + this.uiFacade.triggerEvent(this.container, 'midiLoad', e); } public midiLoaded: IEventEmitterOfT = new EventEmitterOfT(); diff --git a/src/midi/MetaEvent.ts b/src/midi/MetaEvent.ts index 66955648d..ff6240788 100644 --- a/src/midi/MetaEvent.ts +++ b/src/midi/MetaEvent.ts @@ -31,7 +31,7 @@ export class MetaEvent extends MidiEvent { } public get metaStatus(): MetaEventType { - return this.data1; + return this.data1 as MetaEventType; } protected constructor(track: number, delta: number, status: number, data1: number, data2: number) { From 5a1e49e9af260110a5ea5288950f11ba17597e1a Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 17 Jan 2021 14:39:40 +0100 Subject: [PATCH 6/7] Some tweaks for more convenient usage of the new system --- src/io/IOHelper.ts | 8 ++++++ src/midi/SystemExclusiveEvent.ts | 47 +++++++++++++++++++++++++++---- src/synth/AlphaSynth.ts | 19 ++++++++----- src/synth/MidiFileSequencer.ts | 35 ++++++++++++++++------- src/synth/ds/Queue.ts | 6 ++++ src/synth/synthesis/SynthEvent.ts | 11 +++++--- 6 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/io/IOHelper.ts b/src/io/IOHelper.ts index 7a4083ca4..45188f4f9 100644 --- a/src/io/IOHelper.ts +++ b/src/io/IOHelper.ts @@ -27,6 +27,14 @@ export class IOHelper { return (ch4 << 24) | (ch3 << 16) | (ch2 << 8) | ch1; } + public static decodeUInt32LE(data: Uint8Array, index: number): number { + let ch1: number = data[index]; + let ch2: number = data[index + 1]; + let ch3: number = data[index + 2]; + let ch4: number = data[index + 3]; + return (ch4 << 24) | (ch3 << 16) | (ch2 << 8) | ch1; + } + public static readUInt16LE(input: IReadable): number { let ch1: number = input.readByte(); let ch2: number = input.readByte(); diff --git a/src/midi/SystemExclusiveEvent.ts b/src/midi/SystemExclusiveEvent.ts index 3a5bd3a50..c2cc603c9 100644 --- a/src/midi/SystemExclusiveEvent.ts +++ b/src/midi/SystemExclusiveEvent.ts @@ -1,5 +1,7 @@ import { SystemCommonEvent } from '@src/midi/SystemCommonEvent'; import { IWriteable } from '@src/io/IWriteable'; +import { ByteBuffer } from '@src/io/ByteBuffer'; +import { IOHelper } from '@src/io/IOHelper'; export enum AlphaTabSystemExclusiveEvents { MetronomeTick = 0, @@ -8,24 +10,42 @@ export enum AlphaTabSystemExclusiveEvents { export class SystemExclusiveEvent extends SystemCommonEvent { public static readonly AlphaTabManufacturerId = 0x7D; - + public data: Uint8Array; - public get isMetronome():boolean { + public get isMetronome(): boolean { return this.manufacturerId == SystemExclusiveEvent.AlphaTabManufacturerId && - this.data[0] == AlphaTabSystemExclusiveEvents.MetronomeTick; + this.data[0] == AlphaTabSystemExclusiveEvents.MetronomeTick; + } + + public get metronomeNumerator(): number { + return this.isMetronome ? this.data[1] : -1; } - public get isRest():boolean { + public get metronomeDurationInTicks(): number { + if (!this.isMetronome) { + return -1; + } + return IOHelper.decodeUInt32LE(this.data, 2); + } + + public get metronomeDurationInMilliseconds(): number { + if (!this.isMetronome) { + return -1; + } + return IOHelper.decodeUInt32LE(this.data, 6); + } + + public get isRest(): boolean { return this.manufacturerId == SystemExclusiveEvent.AlphaTabManufacturerId && - this.data[0] == AlphaTabSystemExclusiveEvents.Rest; + this.data[0] == AlphaTabSystemExclusiveEvents.Rest; } public get manufacturerId(): number { return this.message >> 8; } - public constructor(track:number, delta: number, status: number, id: number, data: Uint8Array) { + public constructor(track: number, delta: number, status: number, id: number, data: Uint8Array) { super(track, delta, status, id & 0x00ff, (id >> 8) & 0xff); this.data = data; } @@ -38,4 +58,19 @@ export class SystemExclusiveEvent extends SystemCommonEvent { s.write(b, 0, b.length); s.writeByte(0xf7); } + + public static encodeMetronome(counter: number, durationInTicks: number, durationInMillis: number): Uint8Array { + // [0] type + // [1] counter + // [2-5] durationInTicks + // [6-9] durationInMillis + const data = ByteBuffer.withCapacity(2 + 2 * 4); + + data.writeByte(AlphaTabSystemExclusiveEvents.MetronomeTick); + data.writeByte(counter); + IOHelper.writeInt32LE(data, durationInTicks); + IOHelper.writeInt32LE(data, durationInMillis); + + return data.toArray(); + } } diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 8a67ec519..55ed6e2fd 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -415,14 +415,19 @@ export class AlphaSynth implements IAlphaSynth { } // build events which were actually played - const playedEvents = new Queue(); - while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < currentTime) { - const synthEvent = this._playedEventsQueue.dequeue(); - playedEvents.enqueue(synthEvent.event); - } - if (!playedEvents.isEmpty) { - (this.midiEventsPlayed as EventEmitterOfT).trigger(new MidiEventsPlayedEventArgs(playedEvents.toArray())) + if (isSeek) { + this._playedEventsQueue.clear(); + } else { + const playedEvents = new Queue(); + while (!this._playedEventsQueue.isEmpty && this._playedEventsQueue.peek().time < currentTime) { + const synthEvent = this._playedEventsQueue.dequeue(); + playedEvents.enqueue(synthEvent.event); + } + if (!playedEvents.isEmpty) { + (this.midiEventsPlayed as EventEmitterOfT).trigger(new MidiEventsPlayedEventArgs(playedEvents.toArray())) + } } + } readonly ready: IEventEmitter = new EventEmitter(); diff --git a/src/synth/MidiFileSequencer.ts b/src/synth/MidiFileSequencer.ts index 531dae7b0..79c2a955d 100644 --- a/src/synth/MidiFileSequencer.ts +++ b/src/synth/MidiFileSequencer.ts @@ -171,7 +171,8 @@ export class MidiFileSequencer { let absTime: number = 0.0; let metronomeCount: number = 0; - let metronomeLength: number = 0; + let metronomeLengthInTicks: number = 0; + let metronomeLengthInMillis: number = 0; let metronomeTick: number = 0; let metronomeTime: number = 0.0; @@ -186,13 +187,18 @@ export class MidiFileSequencer { synthData.time = absTime; previousTick = mEvent.tick; - if (metronomeLength > 0) { + if (metronomeLengthInTicks > 0) { while (metronomeTick < absTick) { - let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, Math.floor(metronomeTick / metronomeLength) % metronomeCount); + let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, + metronomeTick, + Math.floor(metronomeTick / metronomeLengthInTicks) % metronomeCount, + metronomeLengthInTicks, + metronomeLengthInMillis + ); state.synthData.push(metronome); metronome.time = metronomeTime; - metronomeTick += metronomeLength; - metronomeTime += metronomeLength * (60000.0 / (bpm * midiFile.division)); + metronomeTick += metronomeLengthInTicks; + metronomeTime += metronomeLengthInMillis; } } @@ -200,11 +206,13 @@ export class MidiFileSequencer { let meta: MetaNumberEvent = mEvent as MetaNumberEvent; bpm = 60000000 / meta.value; state.tempoChanges.push(new MidiFileSequencerTempoChange(bpm, absTick, absTime)); + metronomeLengthInMillis = metronomeLengthInTicks * (60000.0 / (bpm * midiFile.division)) } else if (mEvent.command === MidiEventType.Meta && mEvent.data1 === MetaEventType.TimeSignature) { let meta: MetaDataEvent = mEvent as MetaDataEvent; let timeSignatureDenominator: number = Math.pow(2, meta.data[1]); metronomeCount = meta.data[0]; - metronomeLength = (state.division * (4.0 / timeSignatureDenominator)) | 0; + metronomeLengthInTicks = (state.division * (4.0 / timeSignatureDenominator)) | 0; + metronomeLengthInMillis = metronomeLengthInTicks * (60000.0 / (bpm * midiFile.division)) if (state.firstTimeSignatureDenominator === 0) { state.firstTimeSignatureNumerator = meta.data[0]; state.firstTimeSignatureDenominator = timeSignatureDenominator; @@ -373,16 +381,23 @@ export class MidiFileSequencer { state.tempoChanges.push(new MidiFileSequencerTempoChange(bpm, 0, 0)); - let metronomeLength: number = (state.division * (4.0 / timeSignatureDenominator)) | 0; + let metronomeLengthInTicks: number = (state.division * (4.0 / timeSignatureDenominator)) | 0; + let metronomeLengthInMillis: number = metronomeLengthInTicks * (60000.0 / (bpm * this._mainState.division)); let metronomeTick: number = 0; let metronomeTime: number = 0.0; for (let i = 0; i < timeSignatureNumerator; i++) { - let metronome: SynthEvent = SynthEvent.newMetronomeEvent(state.synthData.length, i); + let metronome: SynthEvent = SynthEvent.newMetronomeEvent( + state.synthData.length, + metronomeTick, + i, + metronomeLengthInTicks, + metronomeLengthInMillis + ); state.synthData.push(metronome); metronome.time = metronomeTime; - metronomeTick += metronomeLength; - metronomeTime += metronomeLength * (60000.0 / (bpm * this._mainState.division)); + metronomeTick += metronomeLengthInTicks; + metronomeTime += metronomeLengthInMillis; } state.synthData.sort((a, b) => { diff --git a/src/synth/ds/Queue.ts b/src/synth/ds/Queue.ts index e6eacda56..542187614 100644 --- a/src/synth/ds/Queue.ts +++ b/src/synth/ds/Queue.ts @@ -4,6 +4,12 @@ export class Queue { public isEmpty: boolean = true; + public clear() { + this._items = []; + this._position = 0; + this.isEmpty = true; + } + public enqueue(item: T) { this.isEmpty = false; this._items.push(item); diff --git a/src/synth/synthesis/SynthEvent.ts b/src/synth/synthesis/SynthEvent.ts index 7b6e1bd0c..3c794fb77 100644 --- a/src/synth/synthesis/SynthEvent.ts +++ b/src/synth/synthesis/SynthEvent.ts @@ -3,7 +3,7 @@ // TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny // Licensed under: MPL-2.0 import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; -import { AlphaTabSystemExclusiveEvents, SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; +import { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; export class SynthEvent { public eventIndex: number; @@ -18,9 +18,12 @@ export class SynthEvent { } - private static readonly MetronomeTickData: Uint8Array = new Uint8Array([AlphaTabSystemExclusiveEvents.MetronomeTick]); - public static newMetronomeEvent(eventIndex: number, counter:number): SynthEvent { - const evt = new SystemExclusiveEvent(0, counter, MidiEventType.SystemExclusive2, SystemExclusiveEvent.AlphaTabManufacturerId, SynthEvent.MetronomeTickData); + public static newMetronomeEvent(eventIndex: number, tick: number, counter: number, durationInTicks: number, durationInMillis: number): SynthEvent { + const evt = new SystemExclusiveEvent(0, tick, + MidiEventType.SystemExclusive2, + SystemExclusiveEvent.AlphaTabManufacturerId, + SystemExclusiveEvent.encodeMetronome(counter, durationInTicks, durationInMillis) + ); const x: SynthEvent = new SynthEvent(eventIndex, evt); return x; } From 1e66dc95d0643b05261aa41cf262236dcf03c194 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 17 Jan 2021 14:45:13 +0100 Subject: [PATCH 7/7] Use cast syntax --- .../AlphaTab/Core/EcmaScript/Uint8Array.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs b/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs index d21b141b9..f0ce5bc7d 100644 --- a/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs +++ b/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs @@ -18,7 +18,7 @@ public class Uint8Array : IEnumerable, IEnumerable public Uint8Array(IList data) { - _data = new ArraySegment(data.Cast().ToArray()); + _data = new ArraySegment(data.Select(d => (byte)d).ToArray()); } public Uint8Array(byte[] data) @@ -32,34 +32,34 @@ private Uint8Array(ArraySegment data) } public Uint8Array(double size) - : this(new byte[(int) size]) + : this(new byte[(int)size]) { } public Uint8Array(IEnumerable values) - : this(values.Select(d => (byte) d).ToArray()) + : this(values.Select(d => (byte)d).ToArray()) { } public double this[double index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _data.Array[_data.Offset + (int) index]; + get => _data.Array[_data.Offset + (int)index]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => _data.Array[_data.Offset + (int) index] = (byte) value; + set => _data.Array[_data.Offset + (int)index] = (byte)value; } public Uint8Array Subarray(double begin, double end) { - return new Uint8Array(new ArraySegment(_data.Array, _data.Offset + (int) begin, - (int) (end - begin))); + return new Uint8Array(new ArraySegment(_data.Array, _data.Offset + (int)begin, + (int)(end - begin))); } public void Set(Uint8Array subarray, double pos) { var buffer = subarray.Buffer.Raw; - System.Buffer.BlockCopy(buffer.Array, (int) buffer.Offset, _data.Array, - _data.Offset + (int) pos, buffer.Count); + System.Buffer.BlockCopy(buffer.Array, (int)buffer.Offset, _data.Array, + _data.Offset + (int)pos, buffer.Count); } public static implicit operator Uint8Array(byte[] v) @@ -69,7 +69,7 @@ public static implicit operator Uint8Array(byte[] v) IEnumerator IEnumerable.GetEnumerator() { - return _data.Select(d => (double) d).GetEnumerator(); + return _data.Select(d => (double)d).GetEnumerator(); } public IEnumerator GetEnumerator()