From 3beb172a2feb6dd788484bf4b626569298cee811 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 5 Aug 2023 17:34:25 +0200 Subject: [PATCH 1/5] Rework midi data model --- src/io/ByteBuffer.ts | 4 + src/io/JsonHelper.ts | 15 +- src/midi/AlphaSynthMidiFileHandler.ts | 131 +++------ src/midi/ControllerType.ts | 4 +- src/midi/IMidiFileHandler.ts | 4 +- src/midi/MetaDataEvent.ts | 20 -- src/midi/MetaEvent.ts | 40 --- src/midi/MetaNumberEvent.ts | 20 -- src/midi/Midi20PerNotePitchBendEvent.ts | 37 --- src/midi/MidiEvent.ts | 365 ++++++++++++++++++------ src/midi/SystemCommonEvent.ts | 24 -- src/midi/SystemExclusiveEvent.ts | 76 ----- src/midi/index.ts | 21 +- src/model/JsonConverter.ts | 236 ++++++++++----- src/platform/javascript/AlphaTabApi.ts | 2 +- src/synth/AlphaSynth.ts | 2 +- src/synth/MidiFileSequencer.ts | 25 +- src/synth/synthesis/SynthEvent.ts | 13 +- src/synth/synthesis/TinySoundFont.ts | 114 ++++---- test/audio/MidiFileGenerator.test.ts | 2 +- 20 files changed, 590 insertions(+), 565 deletions(-) delete mode 100644 src/midi/MetaDataEvent.ts delete mode 100644 src/midi/MetaEvent.ts delete mode 100644 src/midi/MetaNumberEvent.ts delete mode 100644 src/midi/Midi20PerNotePitchBendEvent.ts delete mode 100644 src/midi/SystemCommonEvent.ts delete mode 100644 src/midi/SystemExclusiveEvent.ts diff --git a/src/io/ByteBuffer.ts b/src/io/ByteBuffer.ts index 4d20a8757..ce59ad4c5 100644 --- a/src/io/ByteBuffer.ts +++ b/src/io/ByteBuffer.ts @@ -117,4 +117,8 @@ export class ByteBuffer implements IWriteable, IReadable { copy.set(this._buffer.subarray(0, 0 + this.length), 0); return copy; } + + public copyTo(destination:IWriteable) { + destination.write(this._buffer, 0, this.length); + } } diff --git a/src/io/JsonHelper.ts b/src/io/JsonHelper.ts index 3a648a1b4..7c6e0810c 100644 --- a/src/io/JsonHelper.ts +++ b/src/io/JsonHelper.ts @@ -31,11 +31,24 @@ export class JsonHelper { public static forEach(s: unknown, func: (v: unknown, k: string) => void): void { if (s instanceof Map) { (s as Map).forEach(func); - }else if (typeof s === 'object') { + } else if (typeof s === 'object') { for (const k in s) { func((s as any)[k], k) } } // skip } + + /** + * @target web + * @partial + */ + public static getValue(s: unknown, key: string): unknown { + if (s instanceof Map) { + return (s as Map).get(key); + } else if (typeof s === 'object') { + return (s as any)[key]; + } + return null; + } } \ No newline at end of file diff --git a/src/midi/AlphaSynthMidiFileHandler.ts b/src/midi/AlphaSynthMidiFileHandler.ts index d313bf6a6..e42ea6d79 100644 --- a/src/midi/AlphaSynthMidiFileHandler.ts +++ b/src/midi/AlphaSynthMidiFileHandler.ts @@ -1,13 +1,8 @@ -import { MetaDataEvent } from '@src/midi/MetaDataEvent'; -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 { AlphaTabSystemExclusiveEvents, SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; +import { AlphaTabRestEvent, ControlChangeEvent, EndOfTrackEvent, NoteBendEvent, NoteOffEvent, NoteOnEvent, PitchBendEvent, ProgramChangeEvent, TempoChangeEvent, TimeSignatureEvent } from '@src/midi/MidiEvent'; import { IMidiFileHandler } from '@src/midi/IMidiFileHandler'; import { MidiFile } from '@src/midi/MidiFile'; import { SynthConstants } from '@src/synth/SynthConstants'; -import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; +import { ControllerType } from './ControllerType'; /** * This implementation of the {@link IMidiFileHandler} @@ -15,44 +10,42 @@ import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEve */ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { private _midiFile: MidiFile; + private _smf1Mode: boolean; /** * Initializes a new instance of the {@link AlphaSynthMidiFileHandler} class. * @param midiFile The midi file. + * @param smf1Mode Whether to generate a SMF1 compatible midi file. This might break multi note bends. */ - public constructor(midiFile: MidiFile) { + public constructor(midiFile: MidiFile, smf1Mode: boolean = false) { this._midiFile = midiFile; + this._smf1Mode = smf1Mode; } public addTimeSignature(tick: number, timeSignatureNumerator: number, timeSignatureDenominator: number): void { let denominatorIndex: number = 0; - while(true) { - timeSignatureDenominator = timeSignatureDenominator >> 1; - if(timeSignatureDenominator > 0) { + let denominator = timeSignatureDenominator; + while (true) { + denominator = denominator >> 1; + if (denominator > 0) { denominatorIndex++; } else { break; } } - const message: MetaDataEvent = new MetaDataEvent( + + this._midiFile.addEvent(new TimeSignatureEvent( 0, tick, - 0xff, - MetaEventType.TimeSignature, - new Uint8Array([timeSignatureNumerator & 0xff, denominatorIndex & 0xff, 48, 8]) - ); - this._midiFile.addEvent(message); + timeSignatureNumerator, + denominatorIndex, + 48, + 8 + )); } public addRest(track: number, tick: number, channel: number): void { - const message: SystemExclusiveEvent = new SystemExclusiveEvent( - track, - tick, - SystemCommonType.SystemExclusive, - SystemExclusiveEvent.AlphaTabManufacturerId, - new Uint8Array([AlphaTabSystemExclusiveEvents.Rest]) - ); - this._midiFile.addEvent(message); + this._midiFile.addEvent(new AlphaTabRestEvent(track, tick, channel)); } public addNote( @@ -63,26 +56,13 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { velocity: number, channel: number ): void { - const noteOn: MidiEvent = new MidiEvent( - track, - start, - this.makeCommand(MidiEventType.NoteOn, channel), - AlphaSynthMidiFileHandler.fixValue(key), - AlphaSynthMidiFileHandler.fixValue(velocity) - ); - this._midiFile.addEvent(noteOn); - const noteOff: MidiEvent = new MidiEvent( - track, - start + length, - this.makeCommand(MidiEventType.NoteOff, channel), + this._midiFile.addEvent(new NoteOnEvent(track, start, channel, AlphaSynthMidiFileHandler.fixValue(key), - AlphaSynthMidiFileHandler.fixValue(velocity) - ); - this._midiFile.addEvent(noteOff); - } + AlphaSynthMidiFileHandler.fixValue(velocity))); - private makeCommand(command: number, channel: number): number { - return (command & 0xf0) | (channel & 0x0f); + this._midiFile.addEvent(new NoteOffEvent(track, start + length, channel, + AlphaSynthMidiFileHandler.fixValue(key), + AlphaSynthMidiFileHandler.fixValue(velocity))); } private static fixValue(value: number): number { @@ -95,33 +75,24 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { return value; } - public addControlChange(track: number, tick: number, channel: number, controller: number, value: number): void { - const message: MidiEvent = new MidiEvent( + public addControlChange(track: number, tick: number, channel: number, controller: ControllerType, value: number): void { + this._midiFile.addEvent(new ControlChangeEvent( track, tick, - this.makeCommand(MidiEventType.Controller, channel), - AlphaSynthMidiFileHandler.fixValue(controller), + channel, + controller, AlphaSynthMidiFileHandler.fixValue(value) - ); - this._midiFile.addEvent(message); + )); } 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), - 0 - ); - this._midiFile.addEvent(message); + this._midiFile.addEvent(new ProgramChangeEvent(track, tick, channel, program)); } public addTempo(tick: number, tempo: number): void { // bpm -> microsecond per quarter note const tempoInUsq: number = (60000000 / tempo) | 0; - const message: MetaNumberEvent = new MetaNumberEvent(0, tick, 0xff, MetaEventType.Tempo, tempoInUsq); - this._midiFile.addEvent(message); + this._midiFile.addEvent(new TempoChangeEvent(tick, tempoInUsq)); } public addBend(track: number, tick: number, channel: number, value: number): void { @@ -130,40 +101,28 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { } else { value = Math.floor(value); } - - const message: MidiEvent = new MidiEvent( - track, - tick, - this.makeCommand(MidiEventType.PitchBend, channel), - value & 0x7F, - (value >> 7) & 0x7F - ); - this._midiFile.addEvent(message); + this._midiFile.addEvent(new PitchBendEvent(track, tick, channel, value)); } public addNoteBend(track: number, tick: number, channel: number, key: number, value: number): void { - if (value >= SynthConstants.MaxPitchWheel) { - value = SynthConstants.MaxPitchWheel; + if (this._smf1Mode) { + this.addBend(track, tick, channel, value); } else { - value = Math.floor(value); + // map midi 1.0 range of 0-16384 (0x4000) + // to midi 2.0 range of 0-4294967296 (0x100000000) + value = value * SynthConstants.MaxPitchWheel20 / SynthConstants.MaxPitchWheel + + this._midiFile.addEvent(new NoteBendEvent( + track, + tick, + channel, + key, + value + )); } - - // map midi 1.0 range of 0-16384 (0x4000) - // to midi 2.0 range of 0-4294967296 (0x100000000) - value = value * SynthConstants.MaxPitchWheel20 / SynthConstants.MaxPitchWheel - - const message = new Midi20PerNotePitchBendEvent( - track, - tick, - this.makeCommand(MidiEventType.PerNotePitchBend, channel), - key, - value - ); - this._midiFile.addEvent(message); } public finishTrack(track: number, tick: number): void { - const message: MetaDataEvent = new MetaDataEvent(track, tick, 0xff, MetaEventType.EndOfTrack, new Uint8Array(0)); - this._midiFile.addEvent(message); + this._midiFile.addEvent(new EndOfTrackEvent(track, tick)); } } diff --git a/src/midi/ControllerType.ts b/src/midi/ControllerType.ts index 49256a375..9a17097cf 100644 --- a/src/midi/ControllerType.ts +++ b/src/midi/ControllerType.ts @@ -42,7 +42,7 @@ export enum ControllerType { //GeneralPurposeSlider2 = 0x11, //GeneralPurposeSlider3 = 0x12, //GeneralPurposeSlider4 = 0x13, - //BankSelectFine = 0x20, + BankSelectFine = 0x20, /** * Modulation wheel or level LSB */ @@ -129,7 +129,7 @@ export enum ControllerType { */ RegisteredParameterCourse = 0x65, - //AllSoundOff = 0x78, + AllSoundOff = 0x78, /** * Reset all controllers */ diff --git a/src/midi/IMidiFileHandler.ts b/src/midi/IMidiFileHandler.ts index 8e8dd6295..f33cd4fb1 100644 --- a/src/midi/IMidiFileHandler.ts +++ b/src/midi/IMidiFileHandler.ts @@ -1,3 +1,5 @@ +import { ControllerType } from "./ControllerType"; + /** * A handler is responsible for writing midi events to a custom structure */ @@ -44,7 +46,7 @@ export interface IMidiFileHandler { * @param controller The midi controller that should change. * @param value The value to which the midi controller should change */ - addControlChange(track: number, tick: number, channel: number, controller: number, value: number): void; + addControlChange(track: number, tick: number, channel: number, controller: ControllerType, value: number): void; /** * Add a program change to the generated midi file diff --git a/src/midi/MetaDataEvent.ts b/src/midi/MetaDataEvent.ts deleted file mode 100644 index b0cc18e4a..000000000 --- a/src/midi/MetaDataEvent.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MetaEvent } from '@src/midi/MetaEvent'; -import { MidiFile } from '@src/midi/MidiFile'; -import { IWriteable } from '@src/io/IWriteable'; - -export class MetaDataEvent extends MetaEvent { - public data: Uint8Array; - - public constructor(track:number, delta: number, status: number, metaId: number, data: Uint8Array) { - super(track, delta, status, metaId, 0); - this.data = data; - } - - public override writeTo(s: IWriteable): void { - s.writeByte(0xff); - 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 deleted file mode 100644 index 2cf9d91d4..000000000 --- a/src/midi/MetaEvent.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; - -export enum MetaEventType { - SequenceNumber = 0x00, - TextEvent = 0x01, - CopyrightNotice = 0x02, - SequenceOrTrackName = 0x03, - InstrumentName = 0x04, - LyricText = 0x05, - MarkerText = 0x06, - CuePoint = 0x07, - PatchName = 0x08, - PortName = 0x09, - MidiChannel = 0x20, - MidiPort = 0x21, - EndOfTrack = 0x2F, - Tempo = 0x51, - SmpteOffset = 0x54, - TimeSignature = 0x58, - KeySignature = 0x59, - SequencerSpecific = 0x7F -} - -export class MetaEvent extends MidiEvent { - public override get channel(): number { - return -1; - } - - public override get command(): MidiEventType { - return (this.message & 0x00000ff) as MidiEventType; - } - - public get metaStatus(): MetaEventType { - return this.data1 as MetaEventType; - } - - 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 deleted file mode 100644 index e57289d97..000000000 --- a/src/midi/MetaNumberEvent.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MetaEvent } from '@src/midi/MetaEvent'; -import { MidiFile } from '@src/midi/MidiFile'; -import { IWriteable } from '@src/io/IWriteable'; - -export class MetaNumberEvent extends MetaEvent { - public value: number; - - public constructor(track:number, delta: number, status: number, metaId: number, value: number) { - super(track, delta, status, metaId, 0); - this.value = value; - } - - public override writeTo(s: IWriteable): void { - s.writeByte(0xff); - 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/Midi20PerNotePitchBendEvent.ts b/src/midi/Midi20PerNotePitchBendEvent.ts deleted file mode 100644 index 3616fa29b..000000000 --- a/src/midi/Midi20PerNotePitchBendEvent.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { IWriteable } from '@src/io/IWriteable'; -import { MidiEvent } from '@src/midi/MidiEvent'; - -/* - * Represents a MIDI 2.0 Channel Voice Message. - */ -export class Midi20PerNotePitchBendEvent extends MidiEvent { - - public noteKey: number; - public pitch: number; - - public constructor(track:number, tick: number, status: number, noteKey: number, pitch: number) { - super(track, tick, status, 0, 0); - this.noteKey = noteKey; - this.pitch = pitch; - } - - /** - * Writes the midi event as binary into the given stream. - * @param s The stream to write to. - */ - public override writeTo(s: IWriteable): void { - let b: Uint8Array = new Uint8Array([ - 0x40, - this.message & 0xff, - this.noteKey & 0xff, - - 0x00 /* reserved */, - /* 32bit pitch integer */ - (this.pitch >> 24) & 0xff, - (this.pitch >> 16) & 0xff, - (this.pitch >> 8) & 0xff, - this.pitch & 0xff - ]); - s.write(b, 0, b.length); - } -} diff --git a/src/midi/MidiEvent.ts b/src/midi/MidiEvent.ts index bf6a8df7e..38aa4903b 100644 --- a/src/midi/MidiEvent.ts +++ b/src/midi/MidiEvent.ts @@ -1,136 +1,325 @@ import { IWriteable } from '@src/io/IWriteable'; +import { MidiFile } from './MidiFile'; +import { ControllerType } from './ControllerType'; +import { AlphaTabError, AlphaTabErrorType } from '@src/AlphaTabError'; +import { ByteBuffer } from '@src/io/ByteBuffer'; +import { IOHelper } from '@src/io/IOHelper'; /** * Lists all midi events. */ export enum MidiEventType { + TimeSignature = 0, + NoteOn, + NoteOff, + ControlChange, + ProgramChange, + TempoChange, + PitchBend, + NoteBend, + EndOfTrack, + AlphaTabRest, + AlphaTabMetronome +} +/** + * Represents a midi event. + */ +export abstract class MidiEvent { /** - * A per note pitch bend. (Midi 2.0) + * Gets or sets the track to which the midi event belongs. */ - PerNotePitchBend = 0x60, + public track: number; /** - * A note is released. + * Gets or sets the absolute tick of this midi event. */ - NoteOff = 0x80, + public tick: number; /** - * A note is started. + * Gets or sets the midi command (type) of this event. */ - NoteOn = 0x90, + public type: MidiEventType; /** - * The pressure that was used to play the note. + * Initializes a new instance of the {@link MidiEvent} class. + * @param track The track this event belongs to. + * @param tick The absolute midi ticks of this event. + * @param command The type of this event. */ - NoteAftertouch = 0xA0, + public constructor(track: number, tick: number, command: MidiEventType) { + this.track = track; + this.tick = tick; + this.type = command; + } /** - * Change of a midi controller + * Writes the midi event as binary into the given stream. + * @param s The stream to write to. */ - Controller = 0xB0, + public abstract writeTo(s: IWriteable): void; +} - /** - * Change of a midi program - */ - ProgramChange = 0xC0, - /** - * The pressure that should be applied to the whole channel. - */ - ChannelAftertouch = 0xD0, +export class TimeSignatureEvent extends MidiEvent { + public numerator: number; + public denominatorIndex: number; + public midiClocksPerMetronomeClick: number; + public thirdySecondNodesInQuarter: number; - /** - * A change of the audio pitch. - */ - PitchBend = 0xE0, + public constructor(track: number, tick: number, + numerator: number, + denominatorIndex: number, + midiClocksPerMetronomeClick: number, + thirdySecondNodesInQuarter: number) { + super(track, tick, MidiEventType.TimeSignature); + this.track = track; + this.tick = tick; + this.numerator = numerator; + this.denominatorIndex = denominatorIndex; + this.midiClocksPerMetronomeClick = midiClocksPerMetronomeClick; + this.thirdySecondNodesInQuarter = thirdySecondNodesInQuarter; + } - /** - * A System Exclusive event. - */ - SystemExclusive = 0xF0, + public override writeTo(s: IWriteable): void { + // meta header + s.writeByte(0xFF); + // time signature + s.writeByte(0x58); + s.writeByte(this.numerator & 0xFF); + s.writeByte(this.denominatorIndex & 0xFF); + s.writeByte(this.midiClocksPerMetronomeClick & 0xFF); + s.writeByte(this.thirdySecondNodesInQuarter & 0xFF); + } +} - /** - * A System Exclusive event. - */ - SystemExclusive2 = 0xF7, - /** - * A meta event. See `MetaEventType` for details. - */ - Meta = 0xFF +export abstract class AlphaTabSysExEvent extends MidiEvent { + public static readonly AlphaTabManufacturerId = 0x7D; + public static readonly MetronomeEventId = 0x00; + public static readonly RestEventId = 0x01; + + public constructor(track: number, tick: number, type: MidiEventType) { + super(track, tick, type); + } + + public override writeTo(s: IWriteable): void { + // sysex + s.writeByte(0xF0); + + // data + const data = ByteBuffer.withCapacity(16); + data.writeByte(AlphaTabSysExEvent.AlphaTabManufacturerId); + this.writeEventData(data); + // syntactic sysex end + data.writeByte(0xF7); + + MidiFile.writeVariableInt(s, data.length); + data.copyTo(s); + } + + protected abstract writeEventData(s: IWriteable): void; } -/** - * Represents a midi event. - */ -export class MidiEvent { - /** - * Gets or sets the track to which the midi event belongs. - */ - public track:number; +export class AlphaTabMetronomeEvent extends AlphaTabSysExEvent { + public counter: number; + public durationInTicks: number; + public durationInMillis: number; - /** - * Gets or sets the raw midi message. - */ - public message: number; + public constructor(track: number, tick: number, + counter: number, + durationInTicks: number, + durationInMillis: number + ) { + super(track, tick, MidiEventType.AlphaTabMetronome); + this.counter = counter; + this.durationInMillis = durationInMillis; + this.durationInTicks = durationInTicks; + } - /** - * Gets or sets the absolute tick of this midi event. - */ - public tick: number; + protected override writeEventData(s: IWriteable) { + s.writeByte(AlphaTabSysExEvent.MetronomeEventId); + s.writeByte(this.counter); + IOHelper.writeInt32LE(s, this.durationInTicks); + IOHelper.writeInt32LE(s, this.durationInMillis); + } +} - public get channel(): number { - return this.message & 0x000000f; +export class AlphaTabRestEvent extends AlphaTabSysExEvent { + public channel: number; + + public constructor(track: number, tick: number, channel: number) { + super(track, tick, MidiEventType.AlphaTabRest); + this.channel = channel; } - public get command(): MidiEventType { - return (this.message & 0x00000f0) as MidiEventType; + protected override writeEventData(s: IWriteable) { + s.writeByte(AlphaTabSysExEvent.RestEventId); + s.writeByte(this.channel); } +} + + +export class NoteEvent extends MidiEvent { + public channel: number; + public noteKey: number; + public noteVelocity: number; + + public constructor(track: number, + tick: number, + type: MidiEventType, + channel: number, + noteKey: number, + noteVelocity: number) { + super(track, tick, type); - public get data1(): number { - return (this.message & 0x000ff00) >> 8; + this.channel = channel; + this.noteKey = noteKey; + this.noteVelocity = noteVelocity; } - public set data1(value: number) { - this.message &= ~0x000ff00; - this.message |= value << 8; + + public override writeTo(s: IWriteable): void { + // status byte + s.writeByte((this.channel & 0x0F) | 0x90) + s.writeByte(this.noteKey & 0xFF); + s.writeByte(this.noteVelocity & 0xFF); } +} - public get data2(): number { - return (this.message & 0x0ff0000) >> 16; +export class NoteOnEvent extends NoteEvent { + public constructor(track: number, + tick: number, + channel: number, + noteKey: number, + noteVelocity: number) { + super(track, tick, MidiEventType.NoteOn, channel, noteKey, noteVelocity); } +} + - public set data2(value: number) { - this.message &= ~0x0ff0000; - this.message |= value << 16; +export class NoteOffEvent extends NoteEvent { + public constructor(track: number, tick: number, + channel: number, + noteKey: number, + noteVelocity: number) { + super(track, tick, MidiEventType.NoteOff, channel, noteKey, noteVelocity); } +} - /** - * Initializes a new instance of the {@link MidiEvent} class. - * @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(track:number, tick: number, status: number, data1: number, data2: number) { - this.track = track; - this.tick = tick; - this.message = status | (data1 << 8) | (data2 << 16); +export class ControlChangeEvent extends MidiEvent { + public channel: number; + public controller: ControllerType; + public value: number; + + public constructor(track: number, + tick: number, + channel: number, + controller: ControllerType, + value: number) { + super(track, tick, MidiEventType.ControlChange); + this.channel = channel; + this.controller = controller; + this.value = value; } - /** - * Writes the midi event as binary into the given stream. - * @param s The stream to write to. - */ - public writeTo(s: IWriteable): void { - let b: Uint8Array = new Uint8Array([ - (this.message >> 24) & 0xff, - (this.message >> 16) & 0xff, - (this.message >> 8) & 0xff, - this.message & 0xff - ]); - s.write(b, 0, b.length); + public override writeTo(s: IWriteable): void { + s.writeByte((this.channel & 0x0F) | 0xB0) + s.writeByte((this.controller as number) & 0xFF); + s.writeByte(this.value & 0xFF); + } +} + +export class ProgramChangeEvent extends MidiEvent { + public channel: number; + public program: number; + + public constructor(track: number, + tick: number, + channel: number, + program: number) { + super(track, tick, MidiEventType.ProgramChange); + this.channel = channel; + this.program = program; + } + + public override writeTo(s: IWriteable): void { + s.writeByte((this.channel & 0x0F) | 0xC0) + s.writeByte(this.program & 0xFF); + } +} + +export class TempoChangeEvent extends MidiEvent { + public microSecondsPerQuarterNote: number; + + public constructor(tick: number, microSecondsPerQuarterNote: number) { + super(0, tick, MidiEventType.TempoChange); + this.microSecondsPerQuarterNote = microSecondsPerQuarterNote; + } + + public override writeTo(s: IWriteable): void { + // meta + s.writeByte(0xFF); + // set tempo + s.writeByte(0x51); + // size + s.writeByte(0x03); + // tempo + s.writeByte((this.microSecondsPerQuarterNote >> 16) & 0xFF); + s.writeByte((this.microSecondsPerQuarterNote >> 8) & 0xFF); + s.writeByte(this.microSecondsPerQuarterNote & 0xFF); + } +} + +export class PitchBendEvent extends MidiEvent { + public channel: number; + public value: number; + + public constructor(track: number, + tick: number, + channel: number, + value: number) { + super(track, tick, MidiEventType.PitchBend); + this.channel = channel; + this.value = value; + } + + public override writeTo(s: IWriteable): void { + s.writeByte((this.channel & 0x0F) | 0xE0) + s.writeByte(this.value & 0x7F); + s.writeByte((this.value >> 7) & 0x7F); + } +} + +export class NoteBendEvent extends MidiEvent { + public channel: number; + public noteKey: number; + public value: number; + + public constructor(track: number, + tick: number, + channel: number, + noteKey: number, + value: number) { + super(track, tick, MidiEventType.NoteBend); + this.channel = channel; + this.noteKey = noteKey; + this.value = value; + } + + public override writeTo(s: IWriteable): void { + throw new AlphaTabError(AlphaTabErrorType.General, 'Note Bend (Midi2.0) events cannot be exported to SMF1.0'); + } +} + +export class EndOfTrackEvent extends MidiEvent { + public constructor(track: number, tick: number) { + super(track, tick, MidiEventType.EndOfTrack); + } + + public override writeTo(s: IWriteable): void { + s.writeByte(0xFF); + s.writeByte(0x2F); + s.writeByte(0x00); } } diff --git a/src/midi/SystemCommonEvent.ts b/src/midi/SystemCommonEvent.ts deleted file mode 100644 index 9c26ea6e5..000000000 --- a/src/midi/SystemCommonEvent.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; - -export enum SystemCommonType { - SystemExclusive = 0xF0, - MtcQuarterFrame = 0xF1, - SongPosition = 0xF2, - SongSelect = 0xF3, - TuneRequest = 0xF6, - SystemExclusive2 = 0xF7 -} - -export class SystemCommonEvent extends MidiEvent { - public override get channel(): number { - return -1; - } - - public override get command(): MidiEventType { - return (this.message & 0x00000ff) as MidiEventType; - } - - 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 deleted file mode 100644 index 7e6c676df..000000000 --- a/src/midi/SystemExclusiveEvent.ts +++ /dev/null @@ -1,76 +0,0 @@ -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, - 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 metronomeNumerator(): number { - return this.isMetronome ? this.data[1] : -1; - } - - 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; - } - - public get manufacturerId(): number { - return this.message >> 8; - } - - public constructor(track: number, delta: number, status: number, id: number, data: Uint8Array) { - super(track, delta, status, id & 0x00ff, (id >> 8) & 0xff); - this.data = data; - } - - public override writeTo(s: IWriteable): void { - s.writeByte(0xf0); - let l: number = this.data.length + 2; - s.writeByte(this.manufacturerId); - let b: Uint8Array = new Uint8Array([(l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]); - 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/midi/index.ts b/src/midi/index.ts index 55bb0a2ec..5bb483c14 100644 --- a/src/midi/index.ts +++ b/src/midi/index.ts @@ -3,12 +3,19 @@ export { MasterBarTickLookup } from '@src/midi/MasterBarTickLookup'; export { MidiTickLookup, MidiTickLookupFindBeatResult } from '@src/midi/MidiTickLookup'; export { MidiFile } from '@src/midi/MidiFile'; export { ControllerType } from '@src/midi/ControllerType'; -export { MetaDataEvent } from '@src/midi/MetaDataEvent'; -export { MetaEvent, MetaEventType } from '@src/midi/MetaEvent'; -export { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; -export { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; -export { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; -export { SystemCommonEvent, SystemCommonType } from '@src/midi/SystemCommonEvent'; -export { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; +export { + MidiEvent, MidiEventType, TimeSignatureEvent, + AlphaTabRestEvent, + AlphaTabMetronomeEvent, + NoteEvent, + NoteOnEvent, + NoteOffEvent, + ControlChangeEvent, + ProgramChangeEvent, + TempoChangeEvent, + PitchBendEvent, + NoteBendEvent, + EndOfTrackEvent +} from '@src/midi/MidiEvent'; export { MidiFileGenerator } from '@src/midi/MidiFileGenerator'; export { AlphaSynthMidiFileHandler } from '@src/midi/AlphaSynthMidiFileHandler'; diff --git a/src/model/JsonConverter.ts b/src/model/JsonConverter.ts index 3dcd50e72..16fff71fd 100644 --- a/src/model/JsonConverter.ts +++ b/src/model/JsonConverter.ts @@ -1,13 +1,12 @@ -import { MetaDataEvent } from '@src/midi/MetaDataEvent'; -import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; -import { MidiEvent } from '@src/midi/MidiEvent'; -import { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; +import { AlphaTabMetronomeEvent, AlphaTabRestEvent, ControlChangeEvent, EndOfTrackEvent, MidiEvent, MidiEventType, NoteBendEvent, NoteEvent, NoteOffEvent, NoteOnEvent, PitchBendEvent, ProgramChangeEvent, TempoChangeEvent, TimeSignatureEvent } from '@src/midi/MidiEvent'; import { MidiFile } from '@src/midi/MidiFile'; import { Score } from '@src/model/Score'; import { Settings } from '@src/Settings'; -import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; import { ScoreSerializer } from '@src/generated/model/ScoreSerializer'; import { SettingsSerializer } from '@src/generated/SettingsSerializer'; +import { AlphaTabError, AlphaTabErrorType } from '@src/AlphaTabError'; +import { ControllerType } from '@src/midi'; +import { JsonHelper } from '@src/io/JsonHelper'; /** * This class can convert a full {@link Score} instance to a simple JavaScript object and back for further @@ -119,89 +118,180 @@ export class JsonConverter { return settings; } - /** - * @target web - */ - public static jsObjectToMidiFile(midi: any): MidiFile { + public static jsObjectToMidiFile(midi: unknown): MidiFile { let midi2: MidiFile = new MidiFile(); - midi2.division = midi.division; - let midiEvents: any[] = midi.events; - for (let midiEvent of midiEvents) { - let midiEvent2: MidiEvent = JsonConverter.jsObjectToMidiEvent(midiEvent); - midi2.events.push(midiEvent2); - } + + JsonHelper.forEach(midi, (v, k) => { + switch (k) { + case 'division': + midi2.division = v as number; + break; + case 'events': + for (let midiEvent of (v as unknown[])) { + let midiEvent2: MidiEvent = JsonConverter.jsObjectToMidiEvent(midiEvent); + midi2.events.push(midiEvent2); + } + break; + } + }); + return midi2; } /** * @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(track, tick, 0, 0, midiEvent.data); - midiEvent2.message = message; - break; - case 'MetaDataEvent': - midiEvent2 = new MetaDataEvent(track, tick, 0, 0, midiEvent.data); - midiEvent2.message = message; - break; - case 'MetaNumberEvent': - midiEvent2 = new MetaNumberEvent(track, tick, 0, 0, midiEvent.value); - midiEvent2.message = message; - break; - case 'Midi20PerNotePitchBendEvent': - midiEvent2 = new Midi20PerNotePitchBendEvent(track, tick, 0, midiEvent.noteKey, midiEvent.pitch); - midiEvent2.message = message; - break; - default: - midiEvent2 = new MidiEvent(track, tick, 0, 0, 0); - midiEvent2.message = message; - break; + public static jsObjectToMidiEvent(midiEvent: unknown): MidiEvent { + let track: number = JsonHelper.getValue(midiEvent, 'track') as number; + let tick: number = JsonHelper.getValue(midiEvent, 'tick') as number; + let type: MidiEventType = JsonHelper.getValue(midiEvent, 'type') as number as MidiEventType; + + switch (type) { + case MidiEventType.TimeSignature: + return new TimeSignatureEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'numerator') as number, + JsonHelper.getValue(midiEvent, 'denominatorIndex') as number, + JsonHelper.getValue(midiEvent, 'midiClocksPerMetronomeClick') as number, + JsonHelper.getValue(midiEvent, 'thirdySecondNodesInQuarter') as number + ); + case MidiEventType.AlphaTabRest: + return new AlphaTabRestEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number + ); + case MidiEventType.AlphaTabMetronome: + return new AlphaTabMetronomeEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'counter') as number, + JsonHelper.getValue(midiEvent, 'durationInTicks') as number, + JsonHelper.getValue(midiEvent, 'durationInMillis') as number + ); + case MidiEventType.NoteOn: + return new NoteOnEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number, + JsonHelper.getValue(midiEvent, 'noteKey') as number, + JsonHelper.getValue(midiEvent, 'noteVelocity') as number + ); + case MidiEventType.NoteOff: + return new NoteOffEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number, + JsonHelper.getValue(midiEvent, 'noteKey') as number, + JsonHelper.getValue(midiEvent, 'noteVelocity') as number + ); + case MidiEventType.ControlChange: + return new ControlChangeEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number, + JsonHelper.getValue(midiEvent, 'controller') as number as ControllerType, + JsonHelper.getValue(midiEvent, 'value') as number + ); + case MidiEventType.ProgramChange: + return new ProgramChangeEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number, + JsonHelper.getValue(midiEvent, 'program') as number + ); + case MidiEventType.TempoChange: + return new TempoChangeEvent( + tick, + JsonHelper.getValue(midiEvent, 'microSecondsPerQuarterNote') as number + ); + case MidiEventType.PitchBend: + return new PitchBendEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number, + JsonHelper.getValue(midiEvent, 'value') as number + ); + case MidiEventType.NoteBend: + return new NoteBendEvent( + track, + tick, + JsonHelper.getValue(midiEvent, 'channel') as number, + JsonHelper.getValue(midiEvent, 'noteKey') as number, + JsonHelper.getValue(midiEvent, 'value') as number + ); + case MidiEventType.EndOfTrack: + return new EndOfTrackEvent(track, tick); } - return midiEvent2; + + throw new AlphaTabError(AlphaTabErrorType.Format, 'Unknown Midi Event type: ' + type); } - /** - * @target web - */ - public static midiFileToJsObject(midi: MidiFile): unknown { - let midi2: any = {} as any; - midi2.division = midi.division; - let midiEvents: unknown[] = []; - midi2.events = midiEvents; + public static midiFileToJsObject(midi: MidiFile): Map { + const o = new Map(); + o.set('division', midi.division); + + const midiEvents: Map[] = []; for (let midiEvent of midi.events) { midiEvents.push(JsonConverter.midiEventToJsObject(midiEvent)); } - return midi2; + o.set('events', midiEvents); + + return o; } - /** - * @target web - */ - 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) { - 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; + public static midiEventToJsObject(midiEvent: MidiEvent): Map { + const o = new Map(); + o.set('track', midiEvent.track); + o.set('tick', midiEvent.tick); + o.set('type', midiEvent.type as number); + switch (midiEvent.type) { + case MidiEventType.TimeSignature: + o.set('numerator', (midiEvent as TimeSignatureEvent).numerator); + o.set('denominatorIndex', (midiEvent as TimeSignatureEvent).denominatorIndex); + o.set('midiClocksPerMetronomeClick', (midiEvent as TimeSignatureEvent).midiClocksPerMetronomeClick); + o.set('thirdySecondNodesInQuarter', (midiEvent as TimeSignatureEvent).thirdySecondNodesInQuarter); + break; + case MidiEventType.AlphaTabRest: + o.set('channel', (midiEvent as AlphaTabRestEvent).channel); + break; + case MidiEventType.AlphaTabMetronome: + o.set('channel', (midiEvent as AlphaTabMetronomeEvent).counter); + o.set('durationInMillis', (midiEvent as AlphaTabMetronomeEvent).durationInMillis); + o.set('durationInTicks', (midiEvent as AlphaTabMetronomeEvent).durationInTicks); + break; + case MidiEventType.NoteOn: + case MidiEventType.NoteOff: + o.set('channel', (midiEvent as NoteEvent).channel); + o.set('noteKey', (midiEvent as NoteEvent).noteKey); + o.set('noteVelocity', (midiEvent as NoteEvent).noteVelocity); + break; + case MidiEventType.ControlChange: + o.set('channel', (midiEvent as ControlChangeEvent).channel); + o.set('controller', (midiEvent as ControlChangeEvent).controller as number); + o.set('value', (midiEvent as ControlChangeEvent).value); + break; + case MidiEventType.ProgramChange: + o.set('channel', (midiEvent as ProgramChangeEvent).channel); + o.set('program', (midiEvent as ProgramChangeEvent).program); + break; + case MidiEventType.TempoChange: + o.set('microSecondsPerQuarterNote', (midiEvent as TempoChangeEvent).microSecondsPerQuarterNote); + break; + case MidiEventType.PitchBend: + o.set('channel', (midiEvent as PitchBendEvent).channel); + o.set('value', (midiEvent as PitchBendEvent).value); + break; + case MidiEventType.NoteBend: + o.set('channel', (midiEvent as NoteBendEvent).channel); + o.set('noteKey', (midiEvent as NoteBendEvent).noteKey); + o.set('value', (midiEvent as NoteBendEvent).value); + break; + case MidiEventType.EndOfTrack: + break; } - return midiEvent2; + + return o; } } diff --git a/src/platform/javascript/AlphaTabApi.ts b/src/platform/javascript/AlphaTabApi.ts index fb73d9d50..b99b0c8d9 100644 --- a/src/platform/javascript/AlphaTabApi.ts +++ b/src/platform/javascript/AlphaTabApi.ts @@ -124,7 +124,7 @@ export class AlphaTabApi extends AlphaTabApiBase { } let midiFile: MidiFile = new MidiFile(); - let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile); + let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile, true); let generator: MidiFileGenerator = new MidiFileGenerator(this.score, this.settings, handler); generator.generate(); let binary: Uint8Array = midiFile.toBinary(); diff --git a/src/synth/AlphaSynth.ts b/src/synth/AlphaSynth.ts index 2615273f2..6771ed930 100644 --- a/src/synth/AlphaSynth.ts +++ b/src/synth/AlphaSynth.ts @@ -201,7 +201,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._midiEventsPlayedFilter.has(e.event.command)) { + if (this._midiEventsPlayedFilter.has(e.event.type)) { this._playedEventsQueue.enqueue(e); } } diff --git a/src/synth/MidiFileSequencer.ts b/src/synth/MidiFileSequencer.ts index def949a63..51e934b0a 100644 --- a/src/synth/MidiFileSequencer.ts +++ b/src/synth/MidiFileSequencer.ts @@ -1,7 +1,4 @@ -import { MetaDataEvent } from '@src/midi/MetaDataEvent'; -import { MetaEventType } from '@src/midi/MetaEvent'; -import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; -import { MidiEventType } from '@src/midi/MidiEvent'; +import { MidiEventType, ProgramChangeEvent, TempoChangeEvent, TimeSignatureEvent } from '@src/midi/MidiEvent'; import { MidiFile } from '@src/midi/MidiFile'; import { PlaybackRange } from '@src/synth/PlaybackRange'; import { SynthEvent } from '@src/synth/synthesis/SynthEvent'; @@ -211,23 +208,23 @@ export class MidiFileSequencer { } } - if (mEvent.command === MidiEventType.Meta && mEvent.data1 === MetaEventType.Tempo) { - let meta: MetaNumberEvent = mEvent as MetaNumberEvent; - bpm = 60000000 / meta.value; + if (mEvent.type === MidiEventType.TempoChange) { + let meta: TempoChangeEvent = mEvent as TempoChangeEvent; + bpm = 60000000 / meta.microSecondsPerQuarterNote; 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]; + } else if (mEvent.type === MidiEventType.TimeSignature) { + let meta: TimeSignatureEvent = mEvent as TimeSignatureEvent; + let timeSignatureDenominator: number = Math.pow(2, meta.denominatorIndex); + metronomeCount = meta.numerator; metronomeLengthInTicks = (state.division * (4.0 / timeSignatureDenominator)) | 0; metronomeLengthInMillis = metronomeLengthInTicks * (60000.0 / (bpm * midiFile.division)) if (state.firstTimeSignatureDenominator === 0) { - state.firstTimeSignatureNumerator = meta.data[0]; + state.firstTimeSignatureNumerator = meta.numerator state.firstTimeSignatureDenominator = timeSignatureDenominator; } - } else if (mEvent.command === MidiEventType.ProgramChange) { - let channel: number = mEvent.channel; + } else if (mEvent.type === MidiEventType.ProgramChange) { + let channel: number = (mEvent as ProgramChangeEvent).channel; if (!state.firstProgramEventPerChannel.has(channel)) { state.firstProgramEventPerChannel.set(channel, synthData); } diff --git a/src/synth/synthesis/SynthEvent.ts b/src/synth/synthesis/SynthEvent.ts index 3c794fb77..ed8155134 100644 --- a/src/synth/synthesis/SynthEvent.ts +++ b/src/synth/synthesis/SynthEvent.ts @@ -2,8 +2,7 @@ // 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, MidiEventType } from '@src/midi/MidiEvent'; -import { SystemExclusiveEvent } from '@src/midi/SystemExclusiveEvent'; +import { AlphaTabMetronomeEvent, MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; export class SynthEvent { public eventIndex: number; @@ -14,15 +13,15 @@ export class SynthEvent { public constructor(eventIndex: number, e: MidiEvent) { this.eventIndex = eventIndex; this.event = e; - this.isMetronome = this.event instanceof SystemExclusiveEvent && (this.event as SystemExclusiveEvent).isMetronome; + this.isMetronome = this.event.type == MidiEventType.AlphaTabMetronome; } 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 evt = new AlphaTabMetronomeEvent(0, tick, + counter, + durationInTicks, + durationInMillis ); const x: SynthEvent = new SynthEvent(eventIndex, evt); return x; diff --git a/src/synth/synthesis/TinySoundFont.ts b/src/synth/synthesis/TinySoundFont.ts index eb0fc19f0..60e105983 100644 --- a/src/synth/synthesis/TinySoundFont.ts +++ b/src/synth/synthesis/TinySoundFont.ts @@ -2,7 +2,7 @@ // 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, MidiEventType } from '@src/midi/MidiEvent'; +import { ControlChangeEvent, MidiEvent, MidiEventType, NoteBendEvent, NoteOffEvent, NoteOnEvent, PitchBendEvent, ProgramChangeEvent, TempoChangeEvent, TimeSignatureEvent } from '@src/midi/MidiEvent'; import { Hydra, HydraIbag, @@ -25,11 +25,9 @@ import { VoiceEnvelopeSegment } from '@src/synth/synthesis/VoiceEnvelope'; import { SynthHelper } from '@src/synth/SynthHelper'; import { TypeConversions } from '@src/io/TypeConversions'; import { SynthConstants } from '@src/synth/SynthConstants'; -import { Midi20PerNotePitchBendEvent } from '@src/midi/Midi20PerNotePitchBendEvent'; -import { MetaEventType } from '@src/midi/MetaEvent'; -import { MetaNumberEvent } from '@src/midi/MetaNumberEvent'; -import { MetaDataEvent } from '@src/midi/MetaDataEvent'; import { Queue } from '@src/synth/ds/Queue'; +import { ControllerType } from '@src/midi'; +import { Logger } from '@src/Logger'; /** * This is a tiny soundfont based synthesizer. @@ -144,47 +142,44 @@ export class TinySoundFont { } private processMidiMessage(e: MidiEvent): void { - const command: MidiEventType = e.command; - const channel: number = e.channel; - const data1: number = e.data1; - const data2: number = e.data2; + Logger.debug('MIdi', 'Processing Midi message ' + MidiEventType[e.type] + '/' + e.tick) + const command: MidiEventType = e.type; switch (command) { - case MidiEventType.NoteOff: - this.channelNoteOff(channel, data1); - break; + case MidiEventType.TimeSignature: + const timeSignature = (e as TimeSignatureEvent); + this.timeSignatureNumerator = timeSignature.numerator; + this.timeSignatureDenominator = Math.pow(2, timeSignature.denominatorIndex); + break case MidiEventType.NoteOn: - this.channelNoteOn(channel, data1, data2 / 127.0); + const noteOn = e as NoteOnEvent; + this.channelNoteOn(noteOn.channel, noteOn.noteKey, noteOn.noteVelocity / 127.0); break; - case MidiEventType.NoteAftertouch: + case MidiEventType.NoteOff: + const noteOff = e as NoteOffEvent; + this.channelNoteOff(noteOff.channel, noteOff.noteKey); break; - case MidiEventType.Controller: - this.channelMidiControl(channel, data1, data2); + case MidiEventType.ControlChange: + const controlChange = e as ControlChangeEvent; + this.channelMidiControl(controlChange.channel, controlChange.controller, controlChange.value); break; case MidiEventType.ProgramChange: - this.channelSetPresetNumber(channel, data1, channel === 9); + const programChange = e as ProgramChangeEvent; + this.channelSetPresetNumber(programChange.channel, programChange.program, programChange.channel === 9); break; - case MidiEventType.ChannelAftertouch: + case MidiEventType.TempoChange: + const tempoChange = e as TempoChangeEvent + this.currentTempo = 60000000 / tempoChange.microSecondsPerQuarterNote; break; case MidiEventType.PitchBend: - this.channelSetPitchWheel(channel, data1 | (data2 << 7)); + const pitchBend = e as PitchBendEvent; + this.channelSetPitchWheel(pitchBend.channel, pitchBend.value); break; - case MidiEventType.PerNotePitchBend: - const midi20 = e as Midi20PerNotePitchBendEvent; - let perNotePitchWheel = midi20.pitch; + case MidiEventType.NoteBend: + const noteBend = e as NoteBendEvent; + let perNotePitchWheel = noteBend.value; // midi 2.0 -> midi 1.0 perNotePitchWheel = (perNotePitchWheel * SynthConstants.MaxPitchWheel) / SynthConstants.MaxPitchWheel20; - this.channelSetPerNotePitchWheel(channel, midi20.noteKey, perNotePitchWheel); - break; - case MidiEventType.Meta: - switch (e.data1 as MetaEventType) { - case MetaEventType.Tempo: - 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]); - break; - } + this.channelSetPerNotePitchWheel(noteBend.channel, noteBend.noteKey, perNotePitchWheel); break; } } @@ -859,23 +854,10 @@ export class TinySoundFont { /** * Apply a MIDI control change to the channel (not all controllers are supported!) */ - public channelMidiControl(channel: number, controller: number, controlValue: number): void { + public channelMidiControl(channel: number, controller: ControllerType, controlValue: number): void { let c: Channel = this.channelInit(channel); switch (controller) { - case 5: /*Portamento_Time_MSB*/ - case 96: /*DATA_BUTTON_INCREMENT*/ - case 97: /*DATA_BUTTON_DECREMENT*/ - case 64: /*HOLD_PEDAL*/ - case 65: /*Portamento*/ - case 66: /*SostenutoPedal */ - case 122: /*LocalKeyboard */ - case 124: /*OmniModeOff */ - case 125: /*OmniModeon */ - case 126: /*MonoMode */ - case 127 /*PolyMode*/: - return; - - case 38 /*DATA_ENTRY_LSB*/: + case ControllerType.DataEntryFine: c.midiData = TypeConversions.int32ToUint16((c.midiData & 0x3f80) | controlValue); if (c.midiRpn === 0) { @@ -887,35 +869,35 @@ export class TinySoundFont { } return; - case 7 /*VOLUME_MSB*/: + case ControllerType.VolumeCoarse: c.midiVolume = TypeConversions.int32ToUint16((c.midiVolume & 0x7f) | (controlValue << 7)); // Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI this.channelSetVolume(channel, Math.pow((c.midiVolume / 16383.0) * (c.midiExpression / 16383.0), 3.0)); return; - case 39 /*VOLUME_LSB*/: + case ControllerType.VolumeFine: c.midiVolume = TypeConversions.int32ToUint16((c.midiVolume & 0x3f80) | controlValue); // Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI this.channelSetVolume(channel, Math.pow((c.midiVolume / 16383.0) * (c.midiExpression / 16383.0), 3.0)); return; - case 11 /*EXPRESSION_MSB*/: + case ControllerType.ExpressionControllerCoarse: c.midiExpression = TypeConversions.int32ToUint16((c.midiExpression & 0x7f) | (controlValue << 7)); // Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI this.channelSetVolume(channel, Math.pow((c.midiVolume / 16383.0) * (c.midiExpression / 16383.0), 3.0)); return; - case 43 /*EXPRESSION_LSB*/: + case ControllerType.ExpressionControllerFine: c.midiExpression = TypeConversions.int32ToUint16((c.midiExpression & 0x3f80) | controlValue); // Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI this.channelSetVolume(channel, Math.pow((c.midiVolume / 16383.0) * (c.midiExpression / 16383.0), 3.0)); return; - case 10 /*PAN_MSB*/: + case ControllerType.PanCoarse: c.midiPan = TypeConversions.int32ToUint16((c.midiPan & 0x7f) | (controlValue << 7)); this.channelSetPan(channel, c.midiPan / 16383.0); return; - case 42 /*PAN_LSB*/: + case ControllerType.PanFine: c.midiPan = TypeConversions.int32ToUint16((c.midiPan & 0x3f80) | controlValue); this.channelSetPan(channel, c.midiPan / 16383.0); return; - case 6 /*DATA_ENTRY_MSB*/: + case ControllerType.DataEntryCoarse: c.midiData = TypeConversions.int32ToUint16((c.midiData & 0x7f) | (controlValue << 7)); if (c.midiRpn === 0) { this.channelSetPitchRange(channel, (c.midiData >> 7) + 0.01 * (c.midiData & 0x7f)); @@ -925,42 +907,42 @@ export class TinySoundFont { this.channelSetTuning(channel, controlValue - 64.0 + (c.tuning - (c.tuning | 0))); // coarse tune } return; - case 0 /*BANK_SELECT_MSB*/: + case ControllerType.BankSelectCoarse: c.bank = TypeConversions.int32ToUint16(0x8000 | controlValue); return; // bank select MSB alone acts like LSB - case 32 /*BANK_SELECT_LSB*/: + case ControllerType.BankSelectFine: c.bank = TypeConversions.int32ToUint16( ((c.bank & 0x8000) !== 0 ? (c.bank & 0x7f) << 7 : 0) | controlValue ); return; - case 101 /*RPN_MSB*/: + case ControllerType.RegisteredParameterCourse: c.midiRpn = TypeConversions.int32ToUint16( ((c.midiRpn === 0xffff ? 0 : c.midiRpn) & 0x7f) | (controlValue << 7) ); // TODO return; - case 100 /*RPN_LSB*/: + case ControllerType.RegisteredParameterFine: c.midiRpn = TypeConversions.int32ToUint16( ((c.midiRpn === 0xffff ? 0 : c.midiRpn) & 0x3f80) | controlValue ); // TODO return; - case 98 /*NRPN_LSB*/: + case ControllerType.NonRegisteredParameterFine: c.midiRpn = 0xffff; // TODO return; - case 99 /*NRPN_MSB*/: + case ControllerType.NonRegisteredParameterCourse: c.midiRpn = 0xffff; // TODO return; - case 120 /*ALL_SOUND_OFF*/: + case ControllerType.AllSoundOff: this.channelSoundsOffAll(channel); return; - case 123 /*ALL_NOTES_OFF*/: + case ControllerType.AllNotesOff: this.channelNoteOffAll(channel); return; - case 121 /*ALL_CTRL_OFF*/: + case ControllerType.ResetControllers: c.midiVolume = 16383; c.midiExpression = 16383; c.midiPan = 8192; @@ -1055,7 +1037,7 @@ export class TinySoundFont { } public loadPresets(hydra: Hydra, append: boolean): void { - const newPresets:Preset[] = []; + const newPresets: Preset[] = []; for (let phdrIndex: number = 0; phdrIndex < hydra.phdrs.length - 1; phdrIndex++) { const phdr: HydraPhdr = hydra.phdrs[phdrIndex]; let regionIndex: number = 0; diff --git a/test/audio/MidiFileGenerator.test.ts b/test/audio/MidiFileGenerator.test.ts index 3e6e29995..9b112fa2f 100644 --- a/test/audio/MidiFileGenerator.test.ts +++ b/test/audio/MidiFileGenerator.test.ts @@ -873,7 +873,7 @@ describe('MidiFileGeneratorTest', () => { let timeSignature: MidiEvent | null = null; for (const evt of file.events) { - if (evt.command === MidiEventType.Meta && evt.data1 === MetaEventType.TimeSignature) { + if (evt.type === MidiEventType.Meta && evt.data1 === MetaEventType.TimeSignature) { timeSignature = evt; break; } From 8c47bd63984735402c17839ea20c02854efe5a6e Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 5 Aug 2023 18:41:20 +0200 Subject: [PATCH 2/5] Add support for export formats --- src/io/IOHelper.ts | 5 + src/midi/AlphaSynthMidiFileHandler.ts | 9 +- src/midi/MidiEvent.ts | 35 +++--- src/midi/MidiFile.ts | 141 ++++++++++++++++++------- src/midi/index.ts | 2 +- src/model/JsonConverter.ts | 2 +- src/platform/javascript/AlphaTabApi.ts | 7 +- 7 files changed, 145 insertions(+), 56 deletions(-) diff --git a/src/io/IOHelper.ts b/src/io/IOHelper.ts index 1a7c5fec9..adaf932fb 100644 --- a/src/io/IOHelper.ts +++ b/src/io/IOHelper.ts @@ -179,4 +179,9 @@ export class IOHelper { o.writeByte((v >> 0) & 0xff); o.writeByte((v >> 8) & 0xff); } + + public static writeInt16BE(o: IWriteable, v: number) { + o.writeByte((v >> 8) & 0xff); + o.writeByte((v >> 0) & 0xff); + } } diff --git a/src/midi/AlphaSynthMidiFileHandler.ts b/src/midi/AlphaSynthMidiFileHandler.ts index e42ea6d79..c6ba31b79 100644 --- a/src/midi/AlphaSynthMidiFileHandler.ts +++ b/src/midi/AlphaSynthMidiFileHandler.ts @@ -45,7 +45,9 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { } public addRest(track: number, tick: number, channel: number): void { - this._midiFile.addEvent(new AlphaTabRestEvent(track, tick, channel)); + if(!this._smf1Mode) { + this._midiFile.addEvent(new AlphaTabRestEvent(track, tick, channel)); + } } public addNote( @@ -123,6 +125,9 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { } public finishTrack(track: number, tick: number): void { - this._midiFile.addEvent(new EndOfTrackEvent(track, tick)); + // for alphaSynth we only use single end of track (type 0 SMF). + if (track == 0) { + this._midiFile.addEvent(new EndOfTrackEvent(track, tick)); + } } } diff --git a/src/midi/MidiEvent.ts b/src/midi/MidiEvent.ts index 38aa4903b..4022b4ebc 100644 --- a/src/midi/MidiEvent.ts +++ b/src/midi/MidiEvent.ts @@ -65,20 +65,20 @@ export class TimeSignatureEvent extends MidiEvent { public numerator: number; public denominatorIndex: number; public midiClocksPerMetronomeClick: number; - public thirdySecondNodesInQuarter: number; + public thirtySecondNodesInQuarter: number; public constructor(track: number, tick: number, numerator: number, denominatorIndex: number, midiClocksPerMetronomeClick: number, - thirdySecondNodesInQuarter: number) { + thirtySecondNodesInQuarter: number) { super(track, tick, MidiEventType.TimeSignature); this.track = track; this.tick = tick; this.numerator = numerator; this.denominatorIndex = denominatorIndex; this.midiClocksPerMetronomeClick = midiClocksPerMetronomeClick; - this.thirdySecondNodesInQuarter = thirdySecondNodesInQuarter; + this.thirtySecondNodesInQuarter = thirtySecondNodesInQuarter; } public override writeTo(s: IWriteable): void { @@ -86,10 +86,13 @@ export class TimeSignatureEvent extends MidiEvent { s.writeByte(0xFF); // time signature s.writeByte(0x58); + // size + MidiFile.writeVariableInt(s, 4); + // Data s.writeByte(this.numerator & 0xFF); s.writeByte(this.denominatorIndex & 0xFF); s.writeByte(this.midiClocksPerMetronomeClick & 0xFF); - s.writeByte(this.thirdySecondNodesInQuarter & 0xFF); + s.writeByte(this.thirtySecondNodesInQuarter & 0xFF); } } @@ -160,7 +163,7 @@ export class AlphaTabRestEvent extends AlphaTabSysExEvent { } -export class NoteEvent extends MidiEvent { +export abstract class NoteEvent extends MidiEvent { public channel: number; public noteKey: number; public noteVelocity: number; @@ -177,14 +180,6 @@ export class NoteEvent extends MidiEvent { this.noteKey = noteKey; this.noteVelocity = noteVelocity; } - - - public override writeTo(s: IWriteable): void { - // status byte - s.writeByte((this.channel & 0x0F) | 0x90) - s.writeByte(this.noteKey & 0xFF); - s.writeByte(this.noteVelocity & 0xFF); - } } export class NoteOnEvent extends NoteEvent { @@ -195,6 +190,13 @@ export class NoteOnEvent extends NoteEvent { noteVelocity: number) { super(track, tick, MidiEventType.NoteOn, channel, noteKey, noteVelocity); } + + public override writeTo(s: IWriteable): void { + // status byte + s.writeByte((this.channel & 0x0F) | 0x90) + s.writeByte(this.noteKey & 0xFF); + s.writeByte(this.noteVelocity & 0xFF); + } } @@ -205,6 +207,13 @@ export class NoteOffEvent extends NoteEvent { noteVelocity: number) { super(track, tick, MidiEventType.NoteOff, channel, noteKey, noteVelocity); } + + public override writeTo(s: IWriteable): void { + // status byte + s.writeByte((this.channel & 0x0F) | 0x80) + s.writeByte(this.noteKey & 0xFF); + s.writeByte(this.noteVelocity & 0xFF); + } } export class ControlChangeEvent extends MidiEvent { diff --git a/src/midi/MidiFile.ts b/src/midi/MidiFile.ts index 348be1bbf..f98431535 100644 --- a/src/midi/MidiFile.ts +++ b/src/midi/MidiFile.ts @@ -2,16 +2,24 @@ import { MidiEvent } from '@src/midi/MidiEvent'; import { MidiUtils } from '@src/midi/MidiUtils'; import { ByteBuffer } from '@src/io/ByteBuffer'; import { IWriteable } from '@src/io/IWriteable'; +import { IOHelper } from '@src/io/IOHelper'; /** - * Represents a midi file with a single track that can be played via {@link AlphaSynth} + * Lists the different midi file formats which are supported for export. */ -export class MidiFile { +export enum MidiFileFormat { /** - * Gets or sets the division per quarter notes. + * A single track multi channel file (SMF Type 0) */ - public division: number = MidiUtils.QuarterTime; + SingleTrackMultiChannel = 0, + /** + * A multi track file (SMF Type 1) + */ + MultiTrack = 1 +} + +export class MidiTrack { /** * Gets a list of midi events sorted by time. */ @@ -21,9 +29,10 @@ export class MidiFile { * Adds the given midi event a the correct time position into the file. */ public addEvent(e: MidiEvent): void { - if (this.events.length === 0) { + if (this.events.length === 0 || e.tick >= this.events[this.events.length - 1].tick) { this.events.push(e); - } else { + } + else { let insertPos: number = this.events.length; while (insertPos > 0) { const prevItem: MidiEvent = this.events[insertPos - 1]; @@ -37,6 +46,86 @@ export class MidiFile { } } + /** + * Writes the midi track as binary into the given stream. + * @returns The stream to write to. + */ + public writeTo(s: IWriteable): void { + // build track data first + let trackData: ByteBuffer = ByteBuffer.empty(); + let previousTick: number = 0; + for (let midiEvent of this.events) { + let delta: number = midiEvent.tick - previousTick; + MidiFile.writeVariableInt(trackData, delta); + midiEvent.writeTo(trackData); + previousTick = midiEvent.tick; + } + // end of track + // magic number "MTrk" (0x4D54726B) + const b = new Uint8Array([0x4d, 0x54, 0x72, 0x6b]); + s.write(b, 0, b.length); + // size as integer + let data: Uint8Array = trackData.toArray(); + IOHelper.writeInt32BE(s, data.length); + s.write(data, 0, data.length); + } +} + +/** + * Represents a midi file with a single track that can be played via {@link AlphaSynth} + */ +export class MidiFile { + /** + * Gets or sets the midi file format to use. + */ + public format: MidiFileFormat = MidiFileFormat.SingleTrackMultiChannel; + + /** + * Gets or sets the division per quarter notes. + */ + public division: number = MidiUtils.QuarterTime; + + /** + * Gets a list of midi events sorted by time. + */ + public get events(): MidiEvent[] { + if (this.tracks.length == 1) { + return this.tracks[0].events; + } else { + const events: MidiEvent[] = []; + for (const t of this.tracks) { + this.events.push(...t.events); + } + + events.sort((a, b) => a.tick - b.tick); + return events; + } + } + + /** + * Gets a list of midi tracks. + */ + public readonly tracks: MidiTrack[] = []; + + private ensureTracks(trackCount: number) { + while (this.tracks.length < trackCount) { + this.tracks.push(new MidiTrack()); + } + } + + /** + * Adds the given midi event a the correct time position into the file. + */ + public addEvent(e: MidiEvent): void { + if (this.format == MidiFileFormat.SingleTrackMultiChannel) { + this.ensureTracks(1); + this.tracks[0].addEvent(e); + } else { + this.ensureTracks(e.track + 1); + this.tracks[e.track].addEvent(e); + } + } + /** * Writes the midi file into a binary format. * @returns The binary midi file. @@ -56,37 +145,17 @@ export class MidiFile { let b: Uint8Array = new Uint8Array([0x4d, 0x54, 0x68, 0x64]); s.write(b, 0, b.length); // Header Length 6 (0x00000006) - b = new Uint8Array([0x00, 0x00, 0x00, 0x06]); - s.write(b, 0, b.length); - // format - b = new Uint8Array([0x00, 0x00]); - s.write(b, 0, b.length); - // number of tracks - let v: number = 1; - b = new Uint8Array([(v >> 8) & 0xff, v & 0xff]); - s.write(b, 0, b.length); - v = this.division; - b = new Uint8Array([(v >> 8) & 0xff, v & 0xff]); - s.write(b, 0, b.length); - // build track data first - let trackData: ByteBuffer = ByteBuffer.empty(); - let previousTick: number = 0; - for (let midiEvent of this.events) { - let delta: number = midiEvent.tick - previousTick; - MidiFile.writeVariableInt(trackData, delta); - midiEvent.writeTo(trackData); - previousTick = midiEvent.tick; + IOHelper.writeInt32BE(s, 6); + // format (single multi channel track) + IOHelper.writeInt16BE(s, this.format as number); + // number of tracks (1) + IOHelper.writeInt16BE(s, this.tracks.length); + // division + IOHelper.writeInt16BE(s, this.division); + + for (const track of this.tracks) { + track.writeTo(s); } - // end of track - // magic number "MTrk" (0x4D54726B) - b = new Uint8Array([0x4d, 0x54, 0x72, 0x6b]); - s.write(b, 0, b.length); - // size as integer - let data: Uint8Array = trackData.toArray(); - let l: number = data.length; - b = new Uint8Array([(l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]); - s.write(b, 0, b.length); - s.write(data, 0, data.length); } public static writeVariableInt(s: IWriteable, value: number): void { diff --git a/src/midi/index.ts b/src/midi/index.ts index 5bb483c14..4b576745e 100644 --- a/src/midi/index.ts +++ b/src/midi/index.ts @@ -1,7 +1,7 @@ export { BeatTickLookup } from '@src/midi/BeatTickLookup'; export { MasterBarTickLookup } from '@src/midi/MasterBarTickLookup'; export { MidiTickLookup, MidiTickLookupFindBeatResult } from '@src/midi/MidiTickLookup'; -export { MidiFile } from '@src/midi/MidiFile'; +export { MidiFile, MidiFileFormat} from '@src/midi/MidiFile'; export { ControllerType } from '@src/midi/ControllerType'; export { MidiEvent, MidiEventType, TimeSignatureEvent, diff --git a/src/model/JsonConverter.ts b/src/model/JsonConverter.ts index 16fff71fd..0e65b8ae5 100644 --- a/src/model/JsonConverter.ts +++ b/src/model/JsonConverter.ts @@ -251,7 +251,7 @@ export class JsonConverter { o.set('numerator', (midiEvent as TimeSignatureEvent).numerator); o.set('denominatorIndex', (midiEvent as TimeSignatureEvent).denominatorIndex); o.set('midiClocksPerMetronomeClick', (midiEvent as TimeSignatureEvent).midiClocksPerMetronomeClick); - o.set('thirdySecondNodesInQuarter', (midiEvent as TimeSignatureEvent).thirdySecondNodesInQuarter); + o.set('thirdySecondNodesInQuarter', (midiEvent as TimeSignatureEvent).thirtySecondNodesInQuarter); break; case MidiEventType.AlphaTabRest: o.set('channel', (midiEvent as AlphaTabRestEvent).channel); diff --git a/src/platform/javascript/AlphaTabApi.ts b/src/platform/javascript/AlphaTabApi.ts index b99b0c8d9..19b595f9b 100644 --- a/src/platform/javascript/AlphaTabApi.ts +++ b/src/platform/javascript/AlphaTabApi.ts @@ -1,7 +1,7 @@ import { AlphaTabApiBase } from '@src/AlphaTabApiBase'; import { AlphaSynthMidiFileHandler } from '@src/midi/AlphaSynthMidiFileHandler'; import { MidiFileGenerator } from '@src/midi/MidiFileGenerator'; -import { MidiFile } from '@src/midi/MidiFile'; +import { MidiFile, MidiFileFormat } from '@src/midi/MidiFile'; import { LayoutMode } from '@src/LayoutMode'; import { IEventEmitterOfT, EventEmitterOfT } from '@src/EventEmitter'; import { Track } from '@src/model/Track'; @@ -85,7 +85,7 @@ export class AlphaTabApi extends AlphaTabApiBase { ? window.innerHeight : "clientHeight" in document.documentElement ? document.documentElement.clientHeight - : (window as Window).screen.height; + : (window as Window).screen.height; let w: number = a4.offsetWidth + 50; let h: number = window.innerHeight; let left: number = ((screenWidth / 2) | 0) - ((w / 2) | 0) + dualScreenLeft; @@ -118,12 +118,13 @@ export class AlphaTabApi extends AlphaTabApiBase { } - public downloadMidi(): void { + public downloadMidi(format: MidiFileFormat = MidiFileFormat.SingleTrackMultiChannel): void { if (!this.score) { return; } let midiFile: MidiFile = new MidiFile(); + midiFile.format = format; let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile, true); let generator: MidiFileGenerator = new MidiFileGenerator(this.score, this.settings, handler); generator.generate(); From aa219f9e7ccfbbc6706f8599ab40ca963aa9c191 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 5 Aug 2023 21:10:43 +0200 Subject: [PATCH 3/5] Ensure backwards compatibility and update docs --- .../VisualTests/VisualTestHelper.cs | 8 +- .../AlphaTab/Core/EcmaScript/Uint8Array.cs | 12 +- src.csharp/AlphaTab/Io/JsonHelper.cs | 18 + src/midi/AlphaSynthMidiFileHandler.ts | 5 +- src/midi/DeprecatedEvents.ts | 130 +++ src/midi/MidiEvent.ts | 232 ++++- src/model/JsonConverter.ts | 27 +- src/platform/IUiFacade.ts | 2 +- src/synth/IAlphaSynth.ts | 1 - src/synth/synthesis/TinySoundFont.ts | 4 +- test/audio/FlatMidiEventGenerator.ts | 60 +- test/audio/MidiFileGenerator.test.ts | 840 +++++++++--------- test/visualTests/VisualTestHelper.ts | 4 +- 13 files changed, 850 insertions(+), 493 deletions(-) create mode 100644 src/midi/DeprecatedEvents.ts diff --git a/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs b/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs index 64cc7e442..8b892d77f 100644 --- a/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs +++ b/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; using AlphaTab.Core; @@ -51,10 +52,7 @@ public static async Task RunVisualTestScoreWithResize(Score score, IList }); renderer.PartialRenderFinished.On(e => { - if (e != null) - { - results[^1].Add(e); - } + results[^1].Add(e); }); renderer.RenderFinished.On(e => { @@ -99,7 +97,7 @@ public static async Task RunVisualTestScoreWithResize(Score score, IList } } - private static void PrepareSettingsForTest(ref Settings? settings) + private static void PrepareSettingsForTest([NotNull] ref Settings? settings) { settings ??= new Settings(); settings.Core.Engine = "skia"; diff --git a/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs b/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs index 2745242e7..2f009a04f 100644 --- a/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs +++ b/src.csharp/AlphaTab/Core/EcmaScript/Uint8Array.cs @@ -22,6 +22,10 @@ public Uint8Array(IList data) _data = new ArraySegment(data.Select(d => (byte)d).ToArray()); } + public Uint8Array() : this(System.Array.Empty()) + { + } + public Uint8Array(byte[] data) { _data = new ArraySegment(data); @@ -45,21 +49,21 @@ public Uint8Array(IEnumerable values) 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, + 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, + System.Buffer.BlockCopy(buffer.Array!, buffer.Offset, _data.Array!, _data.Offset + (int)pos, buffer.Count); } diff --git a/src.csharp/AlphaTab/Io/JsonHelper.cs b/src.csharp/AlphaTab/Io/JsonHelper.cs index 9e8738fcb..a12c4b221 100644 --- a/src.csharp/AlphaTab/Io/JsonHelper.cs +++ b/src.csharp/AlphaTab/Io/JsonHelper.cs @@ -26,6 +26,24 @@ internal static partial class JsonHelper throw new AlphaTabError(AlphaTabErrorType.Format, $"Could not parse enum value '{o}' [({o.GetType()}]"); } + public static object? GetValue(object o, string key) + { + switch (o) + { + case IDictionary d: + d.TryGetValue(key, out var v); + return v; + case IDictionary d: + if (d.Contains(key)) + { + return d[key]; + } + return null; + } + + return null; + } + public static void ForEach(object o, Action func) { switch (o) diff --git a/src/midi/AlphaSynthMidiFileHandler.ts b/src/midi/AlphaSynthMidiFileHandler.ts index c6ba31b79..f086d3536 100644 --- a/src/midi/AlphaSynthMidiFileHandler.ts +++ b/src/midi/AlphaSynthMidiFileHandler.ts @@ -1,6 +1,6 @@ import { AlphaTabRestEvent, ControlChangeEvent, EndOfTrackEvent, NoteBendEvent, NoteOffEvent, NoteOnEvent, PitchBendEvent, ProgramChangeEvent, TempoChangeEvent, TimeSignatureEvent } from '@src/midi/MidiEvent'; import { IMidiFileHandler } from '@src/midi/IMidiFileHandler'; -import { MidiFile } from '@src/midi/MidiFile'; +import { MidiFile, MidiFileFormat } from '@src/midi/MidiFile'; import { SynthConstants } from '@src/synth/SynthConstants'; import { ControllerType } from './ControllerType'; @@ -125,8 +125,7 @@ export class AlphaSynthMidiFileHandler implements IMidiFileHandler { } public finishTrack(track: number, tick: number): void { - // for alphaSynth we only use single end of track (type 0 SMF). - if (track == 0) { + if (this._midiFile.format == MidiFileFormat.MultiTrack || track == 0) { this._midiFile.addEvent(new EndOfTrackEvent(track, tick)); } } diff --git a/src/midi/DeprecatedEvents.ts b/src/midi/DeprecatedEvents.ts new file mode 100644 index 000000000..f2ca462f6 --- /dev/null +++ b/src/midi/DeprecatedEvents.ts @@ -0,0 +1,130 @@ +import { IWriteable } from "@src/io/IWriteable"; +import { MidiEvent, MidiEventType } from "./MidiEvent"; +import { AlphaTabError, AlphaTabErrorType } from "@src/AlphaTabError"; + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class DeprecatedMidiEvent extends MidiEvent { + public constructor() { + super(0, 0, MidiEventType.EndOfTrack); + } + + public override writeTo(s: IWriteable): void { + throw new AlphaTabError(AlphaTabErrorType.General, 'Deprecated event, serialization not supported'); + } +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export enum MetaEventType { + SequenceNumber = 0x00, + TextEvent = 0x01, + CopyrightNotice = 0x02, + SequenceOrTrackName = 0x03, + InstrumentName = 0x04, + LyricText = 0x05, + MarkerText = 0x06, + CuePoint = 0x07, + PatchName = 0x08, + PortName = 0x09, + MidiChannel = 0x20, + MidiPort = 0x21, + EndOfTrack = 0x2F, + Tempo = 0x51, + SmpteOffset = 0x54, + TimeSignature = 0x58, + KeySignature = 0x59, + SequencerSpecific = 0x7F +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class MetaEvent extends DeprecatedMidiEvent { + public get metaStatus(): MetaEventType { + return MetaEventType.EndOfTrack; + } +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class MetaDataEvent extends MetaEvent { + public data: Uint8Array = new Uint8Array(); +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class MetaNumberEvent extends MetaEvent { + public value: number = 0; +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class Midi20PerNotePitchBendEvent extends DeprecatedMidiEvent { + public noteKey: number = 0; + public pitch: number = 0; +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export enum SystemCommonType { + SystemExclusive = 0xF0, + MtcQuarterFrame = 0xF1, + SongPosition = 0xF2, + SongSelect = 0xF3, + TuneRequest = 0xF6, + SystemExclusive2 = 0xF7 +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class SystemCommonEvent extends DeprecatedMidiEvent { +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export enum AlphaTabSystemExclusiveEvents { + MetronomeTick = 0, + Rest = 1 +} + +/** + * @deprecated Move to the new concrete Midi Event Types. + */ +export class SystemExclusiveEvent extends SystemCommonEvent { + public static readonly AlphaTabManufacturerId = 0x7D; + + public data: Uint8Array = new Uint8Array(); + + public get isMetronome(): boolean { + return false; + } + + public get metronomeNumerator(): number { + return -1; + } + + public get metronomeDurationInTicks(): number { + return -1; + } + + public get metronomeDurationInMilliseconds(): number { + return -1; + } + + public get isRest(): boolean { + return false; + } + + public get manufacturerId(): number { + return 0; + } +} \ No newline at end of file diff --git a/src/midi/MidiEvent.ts b/src/midi/MidiEvent.ts index 4022b4ebc..6f7727d02 100644 --- a/src/midi/MidiEvent.ts +++ b/src/midi/MidiEvent.ts @@ -6,20 +6,39 @@ import { ByteBuffer } from '@src/io/ByteBuffer'; import { IOHelper } from '@src/io/IOHelper'; /** - * Lists all midi events. + * Lists all midi event types. Based on the type the instance is a specific subclass. */ export enum MidiEventType { - TimeSignature = 0, - NoteOn, - NoteOff, - ControlChange, - ProgramChange, - TempoChange, - PitchBend, - NoteBend, - EndOfTrack, - AlphaTabRest, - AlphaTabMetronome + // NOTE: the values try to be backwards compatible with alphaTab 1.2. + // Some values are aligned with the MIDI1.0 bytes while some others + // try to resemble the kind (e.g. 0xF1 -> 0xF0 as system exclusive, and +1 for the first event we define) + // For the custom values we try to not overlap with real MIDI values. + + TimeSignature = 0x58, // 0xFF _0x58_ in Midi 1.0 + NoteOn = 0x80, // Aligned with Midi 1.0 + NoteOff = 0x90, // Aligned with Midi 1.0 + ControlChange = 0xB0, // Aligned with Midi 1.0 + ProgramChange = 0xC0, // Aligned with Midi 1.0 + TempoChange = 0x51, // 0xFF _0x51_ in Midi 1.0 + PitchBend = 0xE0, // Aligned with Midi 1.0 + PerNotePitchBend = 0x60, // Aligned with Midi 2.0 + EndOfTrack = 0x2F, // 0xFF _0x2F_ in Midi 1.0 + AlphaTabRest = 0xF1, // SystemExclusive + 1 + AlphaTabMetronome = 0xF2, // SystemExclusive + 2 + + // deprecated events + /** + * @deprecated Not used anymore internally. move to the other concrete types. + */ + SystemExclusive = 0xF0, // Aligned with Midi 1.0 + /** + * @deprecated Not used anymore internally. move to the other concrete types. + */ + SystemExclusive2 = 0xF7, // Aligned with Midi 1.0 + /** + * @deprecated Not used anymore internally. move to the other concrete types. + */ + Meta = 0xFF // Aligned with Midi 1.0 } /** @@ -53,6 +72,34 @@ export abstract class MidiEvent { this.type = command; } + // for backwards compatibility + /** + * @deprecated Change to `type` + */ + public get command(): MidiEventType { + return this.type; + } + + /** + * @deprecated Use individual properties to access data. + */ + public get message(): number { + return 0; + } + + /** + * @deprecated Use individual properties to access data. + */ + public get data1(): number { + return 0; + } + /** + * @deprecated Use individual properties to access data. + */ + public get data2(): number { + return 0; + } + /** * Writes the midi event as binary into the given stream. * @param s The stream to write to. @@ -60,11 +107,29 @@ export abstract class MidiEvent { public abstract writeTo(s: IWriteable): void; } - +/** + * Represents a time signature change event. + */ export class TimeSignatureEvent extends MidiEvent { + /** + * The time signature numerator. + */ public numerator: number; + + /** + * The denominator index is a negative power of two: 2 represents a quarter-note, 3 represents an eighth-note, etc. + * Denominator = 2^(index) + */ public denominatorIndex: number; + + /** + * The number of MIDI clocks in a metronome click + */ public midiClocksPerMetronomeClick: number; + + /** + * The number of notated 32nd-notes in what MIDI thinks of as a quarter-note (24 MIDI Clocks). + */ public thirtySecondNodesInQuarter: number; public constructor(track: number, tick: number, @@ -96,7 +161,9 @@ export class TimeSignatureEvent extends MidiEvent { } } - +/** + * The base class for alphaTab specific midi events (like metronomes and rests). + */ export abstract class AlphaTabSysExEvent extends MidiEvent { public static readonly AlphaTabManufacturerId = 0x7D; public static readonly MetronomeEventId = 0x00; @@ -124,11 +191,33 @@ export abstract class AlphaTabSysExEvent extends MidiEvent { protected abstract writeEventData(s: IWriteable): void; } +/** + * Represents a metronome event. This event is emitted by the synthesizer only during playback and + * is typically not part of the midi file itself. + */ export class AlphaTabMetronomeEvent extends AlphaTabSysExEvent { + /** + * The metronome counter as per current time signature. + */ public counter: number; + + /** + * The duration of the metronome tick in MIDI ticks. + */ public durationInTicks: number; + + /** + * The duration of the metronome tick in milliseconds. + */ public durationInMillis: number; + // for backwards compatibility. + + /** + * Gets a value indicating whether the current event is a metronome event. + */ + public readonly isMetronome: boolean = true; + public constructor(track: number, tick: number, counter: number, durationInTicks: number, @@ -148,6 +237,9 @@ export class AlphaTabMetronomeEvent extends AlphaTabSysExEvent { } } +/** + * Represents a REST beat being 'played'. This event supports alphaTab in placing the cursor. + */ export class AlphaTabRestEvent extends AlphaTabSysExEvent { public channel: number; @@ -162,10 +254,23 @@ export class AlphaTabRestEvent extends AlphaTabSysExEvent { } } - +/** + * The base class for note related events. + */ export abstract class NoteEvent extends MidiEvent { + /** + * The channel on which the note is played. + */ public channel: number; + + /** + * The key of the note being played (aka. the note height). + */ public noteKey: number; + + /** + * The velocity in which the 'key' of the note is pressed (aka. the loudness/intensity of the note). + */ public noteVelocity: number; public constructor(track: number, @@ -180,8 +285,19 @@ export abstract class NoteEvent extends MidiEvent { this.noteKey = noteKey; this.noteVelocity = noteVelocity; } + + public override get data1(): number { + return this.noteKey; + } + + public override get data2(): number { + return this.noteVelocity; + } } +/** + * Represents a note being played + */ export class NoteOnEvent extends NoteEvent { public constructor(track: number, tick: number, @@ -199,7 +315,9 @@ export class NoteOnEvent extends NoteEvent { } } - +/** + * Represents a note stop being played. + */ export class NoteOffEvent extends NoteEvent { public constructor(track: number, tick: number, channel: number, @@ -207,7 +325,7 @@ export class NoteOffEvent extends NoteEvent { noteVelocity: number) { super(track, tick, MidiEventType.NoteOff, channel, noteKey, noteVelocity); } - + public override writeTo(s: IWriteable): void { // status byte s.writeByte((this.channel & 0x0F) | 0x80) @@ -216,9 +334,23 @@ export class NoteOffEvent extends NoteEvent { } } +/** + * Represents the change of a value on a midi controller. + */ export class ControlChangeEvent extends MidiEvent { + /** + * The channel for which the controller is changing. + */ public channel: number; + + /** + * The type of the controller which is changing. + */ public controller: ControllerType; + + /** + * The new value of the controller. The meaning is depending on the controller type. + */ public value: number; public constructor(track: number, @@ -237,10 +369,28 @@ export class ControlChangeEvent extends MidiEvent { s.writeByte((this.controller as number) & 0xFF); s.writeByte(this.value & 0xFF); } + + public override get data1(): number { + return this.controller as number; + } + + public override get data2(): number { + return this.value; + } } +/** + * Represents the change of the midi program on a channel. + */ export class ProgramChangeEvent extends MidiEvent { + /** + * The midi channel for which the program changes. + */ public channel: number; + + /** + * The numeric value of the program indicating the instrument bank to choose. + */ public program: number; public constructor(track: number, @@ -256,9 +406,19 @@ export class ProgramChangeEvent extends MidiEvent { s.writeByte((this.channel & 0x0F) | 0xC0) s.writeByte(this.program & 0xFF); } + + public override get data1(): number { + return this.program; + } } +/** + * Represents a change of the tempo in the song. + */ export class TempoChangeEvent extends MidiEvent { + /** + * The tempo in microseconds per quarter note (aka USQ). A time format typically for midi. + */ public microSecondsPerQuarterNote: number; public constructor(tick: number, microSecondsPerQuarterNote: number) { @@ -280,8 +440,18 @@ export class TempoChangeEvent extends MidiEvent { } } +/** + * Represents a change of the pitch bend (aka. pitch wheel) on a specific channel. + */ export class PitchBendEvent extends MidiEvent { + /** + * The channel for which the pitch bend changes. + */ public channel: number; + + /** + * The value to which the pitch changes. This value is according to the MIDI specification. + */ public value: number; public constructor(track: number, @@ -298,11 +468,34 @@ export class PitchBendEvent extends MidiEvent { s.writeByte(this.value & 0x7F); s.writeByte((this.value >> 7) & 0x7F); } + + public override get data1(): number { + return this.value & 0x7F; + } + + public override get data2(): number { + return (this.value >> 7) & 0x7F; + } } + +/** + * Represents a single note pitch bend change. + */ export class NoteBendEvent extends MidiEvent { + /** + * The channel on which the note is played for which the pitch changes. + */ public channel: number; + + /** + * The key of the note for which the pitch changes. + */ public noteKey: number; + + /** + * The value to which the pitch changes. This value is according to the MIDI specification. + */ public value: number; public constructor(track: number, @@ -310,7 +503,7 @@ export class NoteBendEvent extends MidiEvent { channel: number, noteKey: number, value: number) { - super(track, tick, MidiEventType.NoteBend); + super(track, tick, MidiEventType.PerNotePitchBend); this.channel = channel; this.noteKey = noteKey; this.value = value; @@ -321,6 +514,9 @@ export class NoteBendEvent extends MidiEvent { } } +/** + * Represents the end of the track indicating that no more events for this track follow. + */ export class EndOfTrackEvent extends MidiEvent { public constructor(track: number, tick: number) { super(track, tick, MidiEventType.EndOfTrack); diff --git a/src/model/JsonConverter.ts b/src/model/JsonConverter.ts index 0e65b8ae5..b81744e0c 100644 --- a/src/model/JsonConverter.ts +++ b/src/model/JsonConverter.ts @@ -118,10 +118,15 @@ export class JsonConverter { return settings; } - public static jsObjectToMidiFile(midi: unknown): MidiFile { + /** + * Converts the given JavaScript object into a MidiFile object. + * @param jsObject The javascript object to deserialize. + * @returns The converted MidiFile. + */ + public static jsObjectToMidiFile(jsObject: unknown): MidiFile { let midi2: MidiFile = new MidiFile(); - JsonHelper.forEach(midi, (v, k) => { + JsonHelper.forEach(jsObject, (v, k) => { switch (k) { case 'division': midi2.division = v as number; @@ -139,7 +144,9 @@ export class JsonConverter { } /** - * @target web + * Converts the given JavaScript object into a MidiEvent object. + * @param jsObject The javascript object to deserialize. + * @returns The converted MidiEvent. */ public static jsObjectToMidiEvent(midiEvent: unknown): MidiEvent { let track: number = JsonHelper.getValue(midiEvent, 'track') as number; @@ -213,7 +220,7 @@ export class JsonConverter { JsonHelper.getValue(midiEvent, 'channel') as number, JsonHelper.getValue(midiEvent, 'value') as number ); - case MidiEventType.NoteBend: + case MidiEventType.PerNotePitchBend: return new NoteBendEvent( track, tick, @@ -228,6 +235,11 @@ export class JsonConverter { throw new AlphaTabError(AlphaTabErrorType.Format, 'Unknown Midi Event type: ' + type); } + /** + * Converts the given MidiFile object into a serialized JavaScript object. + * @param midi The midi file to convert. + * @returns A serialized MidiFile object without ciruclar dependencies that can be used for further serializations. + */ public static midiFileToJsObject(midi: MidiFile): Map { const o = new Map(); o.set('division', midi.division); @@ -241,6 +253,11 @@ export class JsonConverter { return o; } + /** + * Converts the given MidiEvent object into a serialized JavaScript object. + * @param midi The midi file to convert. + * @returns A serialized MidiEvent object without ciruclar dependencies that can be used for further serializations. + */ public static midiEventToJsObject(midiEvent: MidiEvent): Map { const o = new Map(); o.set('track', midiEvent.track); @@ -283,7 +300,7 @@ export class JsonConverter { o.set('channel', (midiEvent as PitchBendEvent).channel); o.set('value', (midiEvent as PitchBendEvent).value); break; - case MidiEventType.NoteBend: + case MidiEventType.PerNotePitchBend: o.set('channel', (midiEvent as NoteBendEvent).channel); o.set('noteKey', (midiEvent as NoteBendEvent).noteKey); o.set('value', (midiEvent as NoteBendEvent).value); diff --git a/src/platform/IUiFacade.ts b/src/platform/IUiFacade.ts index 7694e1332..323e8faeb 100644 --- a/src/platform/IUiFacade.ts +++ b/src/platform/IUiFacade.ts @@ -71,7 +71,7 @@ export interface IUiFacade { * Tells the UI layer to append the given render results to the UI. At this point * the partial result is not actually rendered yet, only the layouting process * completed. - * @param renderResults The rendered partial that should be added to the UI. + * @param renderResults The rendered partial that should be added to the UI. null indicates the rendering finished. */ beginAppendRenderResults(renderResults: RenderFinishedEventArgs | null): void; diff --git a/src/synth/IAlphaSynth.ts b/src/synth/IAlphaSynth.ts index e7a39f7a6..125b98f56 100644 --- a/src/synth/IAlphaSynth.ts +++ b/src/synth/IAlphaSynth.ts @@ -76,7 +76,6 @@ export interface IAlphaSynth { /** * 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` */ midiEventsPlayedFilter: MidiEventType[]; diff --git a/src/synth/synthesis/TinySoundFont.ts b/src/synth/synthesis/TinySoundFont.ts index 60e105983..97025f111 100644 --- a/src/synth/synthesis/TinySoundFont.ts +++ b/src/synth/synthesis/TinySoundFont.ts @@ -174,7 +174,7 @@ export class TinySoundFont { const pitchBend = e as PitchBendEvent; this.channelSetPitchWheel(pitchBend.channel, pitchBend.value); break; - case MidiEventType.NoteBend: + case MidiEventType.PerNotePitchBend: const noteBend = e as NoteBendEvent; let perNotePitchWheel = noteBend.value; // midi 2.0 -> midi 1.0 @@ -903,7 +903,7 @@ export class TinySoundFont { this.channelSetPitchRange(channel, (c.midiData >> 7) + 0.01 * (c.midiData & 0x7f)); } else if (c.midiRpn === 1) { this.channelSetTuning(channel, (c.tuning | 0) + (c.midiData - 8192.0) / 8192.0); // fine tune - } else if (c.midiRpn === 2 && controller === 6) { + } else if (c.midiRpn === 2 && controller === ControllerType.DataEntryCoarse) { this.channelSetTuning(channel, controlValue - 64.0 + (c.tuning - (c.tuning | 0))); // coarse tune } return; diff --git a/test/audio/FlatMidiEventGenerator.ts b/test/audio/FlatMidiEventGenerator.ts index dac6520dc..b5ff10e03 100644 --- a/test/audio/FlatMidiEventGenerator.ts +++ b/test/audio/FlatMidiEventGenerator.ts @@ -9,12 +9,12 @@ export class FlatMidiEventGenerator implements IMidiFileHandler { } public addTimeSignature(tick: number, timeSignatureNumerator: number, timeSignatureDenominator: number): void { - let e = new TimeSignatureEvent(tick, timeSignatureNumerator, timeSignatureDenominator); + let e = new FlatTimeSignatureEvent(tick, timeSignatureNumerator, timeSignatureDenominator); this.midiEvents.push(e); } public addRest(track: number, tick: number, channel: number): void { - let e = new RestEvent(tick, track, channel); + let e = new FlatRestEvent(tick, track, channel); this.midiEvents.push(e); } @@ -26,37 +26,37 @@ export class FlatMidiEventGenerator implements IMidiFileHandler { velocity: number, channel: number ): void { - let e = new NoteEvent(start, track, channel, length, key, velocity); + let e = new FlatNoteEvent(start, track, channel, length, key, velocity); this.midiEvents.push(e); } public addControlChange(track: number, tick: number, channel: number, controller: number, value: number): void { - let e = new ControlChangeEvent(tick, track, channel, controller as ControllerType, value); + let e = new FlatControlChangeEvent(tick, track, channel, controller as ControllerType, value); this.midiEvents.push(e); } public addProgramChange(track: number, tick: number, channel: number, program: number): void { - let e = new ProgramChangeEvent(tick, track, channel, program); + let e = new FlatProgramChangeEvent(tick, track, channel, program); this.midiEvents.push(e); } public addTempo(tick: number, tempo: number): void { - let e = new TempoEvent(tick, tempo); + let e = new FlatTempoEvent(tick, tempo); this.midiEvents.push(e); } public addBend(track: number, tick: number, channel: number, value: number): void { - let e = new BendEvent(tick, track, channel, value); + let e = new FlatBendEvent(tick, track, channel, value); this.midiEvents.push(e); } public addNoteBend(track: number, tick: number, channel: number, key: number, value: number): void { - let e = new NoteBendEvent(tick, track, channel, key, value); + let e = new FlatNoteBendEvent(tick, track, channel, key, value); this.midiEvents.push(e); } public finishTrack(track: number, tick: number): void { - let e = new TrackEndEvent(tick, track); + let e = new FlatTrackEndEvent(tick, track); this.midiEvents.push(e); } } @@ -92,7 +92,7 @@ export class FlatMidiEvent { } } -export class TempoEvent extends FlatMidiEvent { +export class FlatTempoEvent extends FlatMidiEvent { public tempo: number = 0; public constructor(tick: number, tempo: number) { @@ -109,7 +109,7 @@ export class TempoEvent extends FlatMidiEvent { return false; } - if (obj instanceof TempoEvent) { + if (obj instanceof FlatTempoEvent) { return this.tempo === obj.tempo; } @@ -117,7 +117,7 @@ export class TempoEvent extends FlatMidiEvent { } } -export class TimeSignatureEvent extends FlatMidiEvent { +export class FlatTimeSignatureEvent extends FlatMidiEvent { public numerator: number = 0; public denominator: number = 0; @@ -136,7 +136,7 @@ export class TimeSignatureEvent extends FlatMidiEvent { return false; } - if (obj instanceof TimeSignatureEvent) { + if (obj instanceof FlatTimeSignatureEvent) { return this.numerator === obj.numerator && this.denominator === obj.denominator; } @@ -144,7 +144,7 @@ export class TimeSignatureEvent extends FlatMidiEvent { } } -export class TrackMidiEvent extends FlatMidiEvent { +export class FlatTrackMidiEvent extends FlatMidiEvent { public track: number = 0; public constructor(tick: number, track: number) { @@ -161,7 +161,7 @@ export class TrackMidiEvent extends FlatMidiEvent { return false; } - if (obj instanceof TrackMidiEvent) { + if (obj instanceof FlatTrackMidiEvent) { return this.track === obj.track; } @@ -169,7 +169,7 @@ export class TrackMidiEvent extends FlatMidiEvent { } } -export class TrackEndEvent extends TrackMidiEvent { +export class FlatTrackEndEvent extends FlatTrackMidiEvent { public constructor(tick: number, track: number) { super(tick, track); } @@ -179,7 +179,7 @@ export class TrackEndEvent extends TrackMidiEvent { } } -export class ChannelMidiEvent extends TrackMidiEvent { +export class FlatChannelMidiEvent extends FlatTrackMidiEvent { public channel: number = 0; public constructor(tick: number, track: number, channel: number) { @@ -196,7 +196,7 @@ export class ChannelMidiEvent extends TrackMidiEvent { return false; } - if (obj instanceof ChannelMidiEvent) { + if (obj instanceof FlatChannelMidiEvent) { return this.channel === obj.channel; } @@ -204,7 +204,7 @@ export class ChannelMidiEvent extends TrackMidiEvent { } } -export class ControlChangeEvent extends ChannelMidiEvent { +export class FlatControlChangeEvent extends FlatChannelMidiEvent { public controller: ControllerType; public value: number = 0; @@ -223,7 +223,7 @@ export class ControlChangeEvent extends ChannelMidiEvent { return false; } - if (obj instanceof ControlChangeEvent) { + if (obj instanceof FlatControlChangeEvent) { return this.controller === obj.controller && this.channel === obj.channel && this.value === obj.value; } @@ -231,7 +231,7 @@ export class ControlChangeEvent extends ChannelMidiEvent { } } -export class RestEvent extends ChannelMidiEvent { +export class FlatRestEvent extends FlatChannelMidiEvent { public constructor(tick: number, track: number, channel: number) { super(tick, track, channel); } @@ -245,14 +245,14 @@ export class RestEvent extends ChannelMidiEvent { return false; } - if (obj instanceof TempoEvent) { + if (obj instanceof FlatTempoEvent) { return true; } return false; } } -export class ProgramChangeEvent extends ChannelMidiEvent { +export class FlatProgramChangeEvent extends FlatChannelMidiEvent { public program: number = 0; public constructor(tick: number, track: number, channel: number, program: number) { @@ -269,7 +269,7 @@ export class ProgramChangeEvent extends ChannelMidiEvent { return false; } - if (obj instanceof ProgramChangeEvent) { + if (obj instanceof FlatProgramChangeEvent) { return this.program === obj.program; } @@ -277,7 +277,7 @@ export class ProgramChangeEvent extends ChannelMidiEvent { } } -export class NoteEvent extends ChannelMidiEvent { +export class FlatNoteEvent extends FlatChannelMidiEvent { public length: number = 0; public key: number = 0; public velocity: number; @@ -305,7 +305,7 @@ export class NoteEvent extends ChannelMidiEvent { return false; } - if (obj instanceof NoteEvent) { + if (obj instanceof FlatNoteEvent) { return this.length === obj.length && this.key === obj.key && this.velocity === obj.velocity; } @@ -313,7 +313,7 @@ export class NoteEvent extends ChannelMidiEvent { } } -export class BendEvent extends ChannelMidiEvent { +export class FlatBendEvent extends FlatChannelMidiEvent { public value: number = 0; public constructor(tick: number, track: number, channel: number, value: number) { @@ -330,14 +330,14 @@ export class BendEvent extends ChannelMidiEvent { return false; } - if (obj instanceof BendEvent) { + if (obj instanceof FlatBendEvent) { return this.value === obj.value; } return false; } } -export class NoteBendEvent extends ChannelMidiEvent { +export class FlatNoteBendEvent extends FlatChannelMidiEvent { public key: number; public value: number; @@ -356,7 +356,7 @@ export class NoteBendEvent extends ChannelMidiEvent { return false; } - if (obj instanceof NoteBendEvent) { + if (obj instanceof FlatNoteBendEvent) { return this.value === obj.value && this.key === obj.key; } diff --git a/test/audio/MidiFileGenerator.test.ts b/test/audio/MidiFileGenerator.test.ts index 9b112fa2f..8eab80c0d 100644 --- a/test/audio/MidiFileGenerator.test.ts +++ b/test/audio/MidiFileGenerator.test.ts @@ -1,5 +1,5 @@ import { ControllerType } from '@src/midi/ControllerType'; -import { MidiEvent, MidiEventType } from '@src/midi/MidiEvent'; +import { MidiEvent, MidiEventType, NoteOnEvent, TimeSignatureEvent } from '@src/midi/MidiEvent'; import { MidiFileGenerator } from '@src/midi/MidiFileGenerator'; import { MidiFile } from '@src/midi/MidiFile'; import { MidiUtils } from '@src/midi/MidiUtils'; @@ -16,21 +16,19 @@ import { Score } from '@src/model/Score'; import { Settings } from '@src/Settings'; import { Logger } from '@src/Logger'; import { - NoteBendEvent, - ControlChangeEvent, + FlatNoteBendEvent, + FlatControlChangeEvent, FlatMidiEventGenerator, - FlatMidiEvent as FlatMidiEvent, - NoteEvent, - ProgramChangeEvent, - TempoEvent, - TimeSignatureEvent, - TrackEndEvent, - RestEvent + FlatMidiEvent, + FlatNoteEvent, + FlatProgramChangeEvent, + FlatTempoEvent, + FlatTimeSignatureEvent, + FlatTrackEndEvent, + FlatRestEvent } from '@test/audio/FlatMidiEventGenerator'; import { TestPlatform } from '@test/TestPlatform'; import { AlphaSynthMidiFileHandler } from '@src/midi/AlphaSynthMidiFileHandler'; -import { MetaEventType } from '@src/midi/MetaEvent'; -import { MetaDataEvent } from '@src/midi/MetaDataEvent'; import { AccentuationType, VibratoType } from '@src/model'; describe('MidiFileGeneratorTest', () => { @@ -40,7 +38,7 @@ describe('MidiFileGeneratorTest', () => { return importer.readScore(); }; - const assertEvents: (actualEvents:FlatMidiEvent[], expectedEvents:FlatMidiEvent[]) => void = (actualEvents:FlatMidiEvent[], expectedEvents:FlatMidiEvent[]) => { + const assertEvents: (actualEvents: FlatMidiEvent[], expectedEvents: FlatMidiEvent[]) => void = (actualEvents: FlatMidiEvent[], expectedEvents: FlatMidiEvent[]) => { for (let i: number = 0; i < actualEvents.length; i++) { Logger.info('Test', `i[${i}] ${actualEvents[i]}`); if (i < expectedEvents.length) { @@ -63,16 +61,16 @@ describe('MidiFileGeneratorTest', () => { it('midi-order', () => { let midiFile: MidiFile = new MidiFile(); - 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); - expect(midiFile.events[3].data1).toEqual(4); - expect(midiFile.events[4].data1).toEqual(2); + midiFile.addEvent(new NoteOnEvent(0, 0, 0, 0, 0)); + midiFile.addEvent(new NoteOnEvent(0, 0, 0, 1, 0)); + midiFile.addEvent(new NoteOnEvent(0, 100, 0, 2, 0)); + midiFile.addEvent(new NoteOnEvent(0, 50, 0, 3, 0)); + midiFile.addEvent(new NoteOnEvent(0, 50, 0, 4, 0)); + expect((midiFile.tracks[0].events[0] as NoteOnEvent).noteKey).toEqual(0); + expect((midiFile.tracks[0].events[1] as NoteOnEvent).noteKey).toEqual(1); + expect((midiFile.tracks[0].events[2] as NoteOnEvent).noteKey).toEqual(3); + expect((midiFile.tracks[0].events[3] as NoteOnEvent).noteKey).toEqual(4); + expect((midiFile.tracks[0].events[4] as NoteOnEvent).noteKey).toEqual(2); }); it('bend', () => { @@ -91,44 +89,44 @@ describe('MidiFileGeneratorTest', () => { let note: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // bend effect - new NoteBendEvent(0, 0, info.secondaryChannel, note.realValue, 8192), // no bend - new NoteBendEvent(0, 0, info.secondaryChannel, note.realValue, 8192), - new NoteBendEvent(1 * 80, 0, info.secondaryChannel, note.realValue, 8277), - new NoteBendEvent(2 * 80, 0, info.secondaryChannel, note.realValue, 8363), - new NoteBendEvent(3 * 80, 0, info.secondaryChannel, note.realValue, 8448), - new NoteBendEvent(4 * 80, 0, info.secondaryChannel, note.realValue, 8533), - new NoteBendEvent(5 * 80, 0, info.secondaryChannel, note.realValue, 8619), - new NoteBendEvent(6 * 80, 0, info.secondaryChannel, note.realValue, 8704), - new NoteBendEvent(7 * 80, 0, info.secondaryChannel, note.realValue, 8789), - new NoteBendEvent(8 * 80, 0, info.secondaryChannel, note.realValue, 8875), - new NoteBendEvent(9 * 80, 0, info.secondaryChannel, note.realValue, 8960), - new NoteBendEvent(10 * 80, 0, info.secondaryChannel, note.realValue, 9045), - new NoteBendEvent(11 * 80, 0, info.secondaryChannel, note.realValue, 9131), + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note.realValue, 8192), // no bend + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note.realValue, 8192), + new FlatNoteBendEvent(1 * 80, 0, info.secondaryChannel, note.realValue, 8277), + new FlatNoteBendEvent(2 * 80, 0, info.secondaryChannel, note.realValue, 8363), + new FlatNoteBendEvent(3 * 80, 0, info.secondaryChannel, note.realValue, 8448), + new FlatNoteBendEvent(4 * 80, 0, info.secondaryChannel, note.realValue, 8533), + new FlatNoteBendEvent(5 * 80, 0, info.secondaryChannel, note.realValue, 8619), + new FlatNoteBendEvent(6 * 80, 0, info.secondaryChannel, note.realValue, 8704), + new FlatNoteBendEvent(7 * 80, 0, info.secondaryChannel, note.realValue, 8789), + new FlatNoteBendEvent(8 * 80, 0, info.secondaryChannel, note.realValue, 8875), + new FlatNoteBendEvent(9 * 80, 0, info.secondaryChannel, note.realValue, 8960), + new FlatNoteBendEvent(10 * 80, 0, info.secondaryChannel, note.realValue, 9045), + new FlatNoteBendEvent(11 * 80, 0, info.secondaryChannel, note.realValue, 9131), // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -138,8 +136,8 @@ describe('MidiFileGeneratorTest', () => { ), // reset bend - new NoteBendEvent(960, 0, info.primaryChannel, note.realValue, 8192), - new NoteEvent( + new FlatNoteBendEvent(960, 0, info.primaryChannel, note.realValue, 8192), + new FlatNoteEvent( 960, 0, info.primaryChannel, @@ -149,7 +147,7 @@ describe('MidiFileGeneratorTest', () => { ), // end of track - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -206,65 +204,65 @@ describe('MidiFileGeneratorTest', () => { const mfVelocity = MidiUtils.dynamicToVelocity(DynamicValue.MF as number); let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 96), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 96), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 96), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 96), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // on beat - new NoteBendEvent(ticks[0], 0, info.primaryChannel, 67, 8192), - new NoteEvent(ticks[0], 0, info.primaryChannel, 3840, 67, mfVelocity), + new FlatNoteBendEvent(ticks[0], 0, info.primaryChannel, 67, 8192), + new FlatNoteEvent(ticks[0], 0, info.primaryChannel, 3840, 67, mfVelocity), - new NoteBendEvent(ticks[1], 0, info.primaryChannel, 67, 8192), - new NoteEvent(ticks[1], 0, info.primaryChannel, 120, 67, mfVelocity), + new FlatNoteBendEvent(ticks[1], 0, info.primaryChannel, 67, 8192), + new FlatNoteEvent(ticks[1], 0, info.primaryChannel, 120, 67, mfVelocity), - new NoteBendEvent(ticks[2], 0, info.primaryChannel, 67, 8192), - new NoteEvent(ticks[2], 0, info.primaryChannel, 3720, 67, mfVelocity), + new FlatNoteBendEvent(ticks[2], 0, info.primaryChannel, 67, 8192), + new FlatNoteEvent(ticks[2], 0, info.primaryChannel, 3720, 67, mfVelocity), // before beat - new NoteBendEvent(ticks[3], 0, info.primaryChannel, 67, 8192), - new NoteEvent(ticks[3], 0, info.primaryChannel, 3720, 67, mfVelocity), + new FlatNoteBendEvent(ticks[3], 0, info.primaryChannel, 67, 8192), + new FlatNoteEvent(ticks[3], 0, info.primaryChannel, 3720, 67, mfVelocity), - new NoteBendEvent(ticks[4], 0, info.primaryChannel, 67, 8192), - new NoteEvent(ticks[4], 0, info.primaryChannel, 120, 67, mfVelocity), + new FlatNoteBendEvent(ticks[4], 0, info.primaryChannel, 67, 8192), + new FlatNoteEvent(ticks[4], 0, info.primaryChannel, 120, 67, mfVelocity), - new NoteBendEvent(ticks[5], 0, info.primaryChannel, 67, 8192), - new NoteEvent(ticks[5], 0, info.primaryChannel, 3840, 67, mfVelocity), + new FlatNoteBendEvent(ticks[5], 0, info.primaryChannel, 67, 8192), + new FlatNoteEvent(ticks[5], 0, info.primaryChannel, 3840, 67, mfVelocity), // bend beat - new NoteBendEvent(ticks[6], 0, info.secondaryChannel, 67, 8192), - new NoteBendEvent(ticks[6] + 12 * 0, 0, info.secondaryChannel, 67, 8192), - new NoteBendEvent(ticks[6] + 12 * 1, 0, info.secondaryChannel, 67, 8277), - new NoteBendEvent(ticks[6] + 12 * 2, 0, info.secondaryChannel, 67, 8363), - new NoteBendEvent(ticks[6] + 12 * 3, 0, info.secondaryChannel, 67, 8448), - new NoteBendEvent(ticks[6] + 12 * 4, 0, info.secondaryChannel, 67, 8533), - new NoteBendEvent(ticks[6] + 12 * 5, 0, info.secondaryChannel, 67, 8619), - new NoteBendEvent(ticks[6] + 12 * 6, 0, info.secondaryChannel, 67, 8704), - new NoteBendEvent(ticks[6] + 12 * 7, 0, info.secondaryChannel, 67, 8789), - new NoteBendEvent(ticks[6] + 12 * 8, 0, info.secondaryChannel, 67, 8875), - new NoteBendEvent(ticks[6] + 12 * 9, 0, info.secondaryChannel, 67, 8960), - new NoteBendEvent(ticks[6] + 12 * 10, 0, info.secondaryChannel, 67, 9045), - new NoteBendEvent(ticks[6] + 12 * 11, 0, info.secondaryChannel, 67, 9131), - new NoteEvent(ticks[6], 0, info.secondaryChannel, 3840, 67, mfVelocity), + new FlatNoteBendEvent(ticks[6], 0, info.secondaryChannel, 67, 8192), + new FlatNoteBendEvent(ticks[6] + 12 * 0, 0, info.secondaryChannel, 67, 8192), + new FlatNoteBendEvent(ticks[6] + 12 * 1, 0, info.secondaryChannel, 67, 8277), + new FlatNoteBendEvent(ticks[6] + 12 * 2, 0, info.secondaryChannel, 67, 8363), + new FlatNoteBendEvent(ticks[6] + 12 * 3, 0, info.secondaryChannel, 67, 8448), + new FlatNoteBendEvent(ticks[6] + 12 * 4, 0, info.secondaryChannel, 67, 8533), + new FlatNoteBendEvent(ticks[6] + 12 * 5, 0, info.secondaryChannel, 67, 8619), + new FlatNoteBendEvent(ticks[6] + 12 * 6, 0, info.secondaryChannel, 67, 8704), + new FlatNoteBendEvent(ticks[6] + 12 * 7, 0, info.secondaryChannel, 67, 8789), + new FlatNoteBendEvent(ticks[6] + 12 * 8, 0, info.secondaryChannel, 67, 8875), + new FlatNoteBendEvent(ticks[6] + 12 * 9, 0, info.secondaryChannel, 67, 8960), + new FlatNoteBendEvent(ticks[6] + 12 * 10, 0, info.secondaryChannel, 67, 9045), + new FlatNoteBendEvent(ticks[6] + 12 * 11, 0, info.secondaryChannel, 67, 9131), + new FlatNoteEvent(ticks[6], 0, info.secondaryChannel, 3840, 67, mfVelocity), // end of track - new TrackEndEvent(19200, 0) // 3840 = end of bar + new FlatTrackEndEvent(19200, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -286,57 +284,57 @@ describe('MidiFileGeneratorTest', () => { let note: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // bend effect - new NoteBendEvent(0, 0, info.secondaryChannel, note.realValue, 8192), - new NoteBendEvent(0 * 40, 0, info.secondaryChannel, note.realValue, 8192), // no bend - new NoteBendEvent(1 * 40, 0, info.secondaryChannel, note.realValue, 8277), - new NoteBendEvent(2 * 40, 0, info.secondaryChannel, note.realValue, 8363), - new NoteBendEvent(3 * 40, 0, info.secondaryChannel, note.realValue, 8448), - new NoteBendEvent(4 * 40, 0, info.secondaryChannel, note.realValue, 8533), - new NoteBendEvent(5 * 40, 0, info.secondaryChannel, note.realValue, 8619), - new NoteBendEvent(6 * 40, 0, info.secondaryChannel, note.realValue, 8704), - new NoteBendEvent(7 * 40, 0, info.secondaryChannel, note.realValue, 8789), - new NoteBendEvent(8 * 40, 0, info.secondaryChannel, note.realValue, 8875), - new NoteBendEvent(9 * 40, 0, info.secondaryChannel, note.realValue, 8960), - new NoteBendEvent(10 * 40, 0, info.secondaryChannel, note.realValue, 9045), - new NoteBendEvent(11 * 40, 0, info.secondaryChannel, note.realValue, 9131), - new NoteBendEvent(12 * 40, 0, info.secondaryChannel, note.realValue, 9216), // full bend - new NoteBendEvent(13 * 40, 0, info.secondaryChannel, note.realValue, 9131), - new NoteBendEvent(14 * 40, 0, info.secondaryChannel, note.realValue, 9045), - new NoteBendEvent(15 * 40, 0, info.secondaryChannel, note.realValue, 8960), - new NoteBendEvent(16 * 40, 0, info.secondaryChannel, note.realValue, 8875), - new NoteBendEvent(17 * 40, 0, info.secondaryChannel, note.realValue, 8789), - new NoteBendEvent(18 * 40, 0, info.secondaryChannel, note.realValue, 8704), - new NoteBendEvent(19 * 40, 0, info.secondaryChannel, note.realValue, 8619), - new NoteBendEvent(20 * 40, 0, info.secondaryChannel, note.realValue, 8533), - new NoteBendEvent(21 * 40, 0, info.secondaryChannel, note.realValue, 8448), - new NoteBendEvent(22 * 40, 0, info.secondaryChannel, note.realValue, 8363), - new NoteBendEvent(23 * 40, 0, info.secondaryChannel, note.realValue, 8277), - new NoteBendEvent(24 * 40, 0, info.secondaryChannel, note.realValue, 8192), // no bend + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note.realValue, 8192), + new FlatNoteBendEvent(0 * 40, 0, info.secondaryChannel, note.realValue, 8192), // no bend + new FlatNoteBendEvent(1 * 40, 0, info.secondaryChannel, note.realValue, 8277), + new FlatNoteBendEvent(2 * 40, 0, info.secondaryChannel, note.realValue, 8363), + new FlatNoteBendEvent(3 * 40, 0, info.secondaryChannel, note.realValue, 8448), + new FlatNoteBendEvent(4 * 40, 0, info.secondaryChannel, note.realValue, 8533), + new FlatNoteBendEvent(5 * 40, 0, info.secondaryChannel, note.realValue, 8619), + new FlatNoteBendEvent(6 * 40, 0, info.secondaryChannel, note.realValue, 8704), + new FlatNoteBendEvent(7 * 40, 0, info.secondaryChannel, note.realValue, 8789), + new FlatNoteBendEvent(8 * 40, 0, info.secondaryChannel, note.realValue, 8875), + new FlatNoteBendEvent(9 * 40, 0, info.secondaryChannel, note.realValue, 8960), + new FlatNoteBendEvent(10 * 40, 0, info.secondaryChannel, note.realValue, 9045), + new FlatNoteBendEvent(11 * 40, 0, info.secondaryChannel, note.realValue, 9131), + new FlatNoteBendEvent(12 * 40, 0, info.secondaryChannel, note.realValue, 9216), // full bend + new FlatNoteBendEvent(13 * 40, 0, info.secondaryChannel, note.realValue, 9131), + new FlatNoteBendEvent(14 * 40, 0, info.secondaryChannel, note.realValue, 9045), + new FlatNoteBendEvent(15 * 40, 0, info.secondaryChannel, note.realValue, 8960), + new FlatNoteBendEvent(16 * 40, 0, info.secondaryChannel, note.realValue, 8875), + new FlatNoteBendEvent(17 * 40, 0, info.secondaryChannel, note.realValue, 8789), + new FlatNoteBendEvent(18 * 40, 0, info.secondaryChannel, note.realValue, 8704), + new FlatNoteBendEvent(19 * 40, 0, info.secondaryChannel, note.realValue, 8619), + new FlatNoteBendEvent(20 * 40, 0, info.secondaryChannel, note.realValue, 8533), + new FlatNoteBendEvent(21 * 40, 0, info.secondaryChannel, note.realValue, 8448), + new FlatNoteBendEvent(22 * 40, 0, info.secondaryChannel, note.realValue, 8363), + new FlatNoteBendEvent(23 * 40, 0, info.secondaryChannel, note.realValue, 8277), + new FlatNoteBendEvent(24 * 40, 0, info.secondaryChannel, note.realValue, 8192), // no bend // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -346,8 +344,8 @@ describe('MidiFileGeneratorTest', () => { ), // reset bend - new NoteBendEvent(960, 0, info.primaryChannel, note.realValue, 8192), // finish - new NoteEvent( + new FlatNoteBendEvent(960, 0, info.primaryChannel, note.realValue, 8192), // finish + new FlatNoteEvent( 960, 0, info.primaryChannel, @@ -355,7 +353,7 @@ describe('MidiFileGeneratorTest', () => { note.realValue, MidiUtils.dynamicToVelocity(note.dynamics as number) ), // end of track - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -372,44 +370,44 @@ describe('MidiFileGeneratorTest', () => { let note: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // bend up - new NoteBendEvent(0, 0, info.secondaryChannel, 62, 8192), - new NoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 8192), // no bend - new NoteBendEvent(1 * 80, 0, info.secondaryChannel, 62, 8277), - new NoteBendEvent(2 * 80, 0, info.secondaryChannel, 62, 8363), - new NoteBendEvent(3 * 80, 0, info.secondaryChannel, 62, 8448), - new NoteBendEvent(4 * 80, 0, info.secondaryChannel, 62, 8533), - new NoteBendEvent(5 * 80, 0, info.secondaryChannel, 62, 8619), - new NoteBendEvent(6 * 80, 0, info.secondaryChannel, 62, 8704), - new NoteBendEvent(7 * 80, 0, info.secondaryChannel, 62, 8789), - new NoteBendEvent(8 * 80, 0, info.secondaryChannel, 62, 8875), - new NoteBendEvent(9 * 80, 0, info.secondaryChannel, 62, 8960), - new NoteBendEvent(10 * 80, 0, info.secondaryChannel, 62, 9045), - new NoteBendEvent(11 * 80, 0, info.secondaryChannel, 62, 9131), + new FlatNoteBendEvent(0, 0, info.secondaryChannel, 62, 8192), + new FlatNoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 8192), // no bend + new FlatNoteBendEvent(1 * 80, 0, info.secondaryChannel, 62, 8277), + new FlatNoteBendEvent(2 * 80, 0, info.secondaryChannel, 62, 8363), + new FlatNoteBendEvent(3 * 80, 0, info.secondaryChannel, 62, 8448), + new FlatNoteBendEvent(4 * 80, 0, info.secondaryChannel, 62, 8533), + new FlatNoteBendEvent(5 * 80, 0, info.secondaryChannel, 62, 8619), + new FlatNoteBendEvent(6 * 80, 0, info.secondaryChannel, 62, 8704), + new FlatNoteBendEvent(7 * 80, 0, info.secondaryChannel, 62, 8789), + new FlatNoteBendEvent(8 * 80, 0, info.secondaryChannel, 62, 8875), + new FlatNoteBendEvent(9 * 80, 0, info.secondaryChannel, 62, 8960), + new FlatNoteBendEvent(10 * 80, 0, info.secondaryChannel, 62, 9045), + new FlatNoteBendEvent(11 * 80, 0, info.secondaryChannel, 62, 9131), // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -419,22 +417,22 @@ describe('MidiFileGeneratorTest', () => { ), // release on tied note - new NoteBendEvent(12 * 80, 0, info.secondaryChannel, 62, 9216), // reset bend for tied note - new NoteBendEvent(12 * 80, 0, info.secondaryChannel, 62, 9216), // full bend - new NoteBendEvent(13 * 80, 0, info.secondaryChannel, 62, 9131), - new NoteBendEvent(14 * 80, 0, info.secondaryChannel, 62, 9045), - new NoteBendEvent(15 * 80, 0, info.secondaryChannel, 62, 8960), - new NoteBendEvent(16 * 80, 0, info.secondaryChannel, 62, 8875), - new NoteBendEvent(17 * 80, 0, info.secondaryChannel, 62, 8789), - new NoteBendEvent(18 * 80, 0, info.secondaryChannel, 62, 8704), - new NoteBendEvent(19 * 80, 0, info.secondaryChannel, 62, 8619), - new NoteBendEvent(20 * 80, 0, info.secondaryChannel, 62, 8533), - new NoteBendEvent(21 * 80, 0, info.secondaryChannel, 62, 8448), - new NoteBendEvent(22 * 80, 0, info.secondaryChannel, 62, 8363), - new NoteBendEvent(23 * 80, 0, info.secondaryChannel, 62, 8277), - new NoteBendEvent(24 * 80, 0, info.secondaryChannel, 62, 8192), // no bend - - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatNoteBendEvent(12 * 80, 0, info.secondaryChannel, 62, 9216), // reset bend for tied note + new FlatNoteBendEvent(12 * 80, 0, info.secondaryChannel, 62, 9216), // full bend + new FlatNoteBendEvent(13 * 80, 0, info.secondaryChannel, 62, 9131), + new FlatNoteBendEvent(14 * 80, 0, info.secondaryChannel, 62, 9045), + new FlatNoteBendEvent(15 * 80, 0, info.secondaryChannel, 62, 8960), + new FlatNoteBendEvent(16 * 80, 0, info.secondaryChannel, 62, 8875), + new FlatNoteBendEvent(17 * 80, 0, info.secondaryChannel, 62, 8789), + new FlatNoteBendEvent(18 * 80, 0, info.secondaryChannel, 62, 8704), + new FlatNoteBendEvent(19 * 80, 0, info.secondaryChannel, 62, 8619), + new FlatNoteBendEvent(20 * 80, 0, info.secondaryChannel, 62, 8533), + new FlatNoteBendEvent(21 * 80, 0, info.secondaryChannel, 62, 8448), + new FlatNoteBendEvent(22 * 80, 0, info.secondaryChannel, 62, 8363), + new FlatNoteBendEvent(23 * 80, 0, info.secondaryChannel, 62, 8277), + new FlatNoteBendEvent(24 * 80, 0, info.secondaryChannel, 62, 8192), // no bend + + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -451,45 +449,45 @@ describe('MidiFileGeneratorTest', () => { let note: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // bend up - new NoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 9216), // pre-bend - new NoteBendEvent(0 * 160, 0, info.secondaryChannel, 62, 9216), // bend start - new NoteBendEvent(1 * 160, 0, info.secondaryChannel, 62, 9131), - new NoteBendEvent(2 * 160, 0, info.secondaryChannel, 62, 9045), - new NoteBendEvent(3 * 160, 0, info.secondaryChannel, 62, 8960), - new NoteBendEvent(4 * 160, 0, info.secondaryChannel, 62, 8875), - new NoteBendEvent(5 * 160, 0, info.secondaryChannel, 62, 8789), - new NoteBendEvent(6 * 160, 0, info.secondaryChannel, 62, 8704), - new NoteBendEvent(7 * 160, 0, info.secondaryChannel, 62, 8619), - new NoteBendEvent(8 * 160, 0, info.secondaryChannel, 62, 8533), - new NoteBendEvent(9 * 160, 0, info.secondaryChannel, 62, 8448), - new NoteBendEvent(10 * 160, 0, info.secondaryChannel, 62, 8363), - new NoteBendEvent(11 * 160, 0, info.secondaryChannel, 62, 8277), - new NoteBendEvent(12 * 160, 0, info.secondaryChannel, 62, 8192), + new FlatNoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 9216), // pre-bend + new FlatNoteBendEvent(0 * 160, 0, info.secondaryChannel, 62, 9216), // bend start + new FlatNoteBendEvent(1 * 160, 0, info.secondaryChannel, 62, 9131), + new FlatNoteBendEvent(2 * 160, 0, info.secondaryChannel, 62, 9045), + new FlatNoteBendEvent(3 * 160, 0, info.secondaryChannel, 62, 8960), + new FlatNoteBendEvent(4 * 160, 0, info.secondaryChannel, 62, 8875), + new FlatNoteBendEvent(5 * 160, 0, info.secondaryChannel, 62, 8789), + new FlatNoteBendEvent(6 * 160, 0, info.secondaryChannel, 62, 8704), + new FlatNoteBendEvent(7 * 160, 0, info.secondaryChannel, 62, 8619), + new FlatNoteBendEvent(8 * 160, 0, info.secondaryChannel, 62, 8533), + new FlatNoteBendEvent(9 * 160, 0, info.secondaryChannel, 62, 8448), + new FlatNoteBendEvent(10 * 160, 0, info.secondaryChannel, 62, 8363), + new FlatNoteBendEvent(11 * 160, 0, info.secondaryChannel, 62, 8277), + new FlatNoteBendEvent(12 * 160, 0, info.secondaryChannel, 62, 8192), // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -498,7 +496,7 @@ describe('MidiFileGeneratorTest', () => { MidiUtils.dynamicToVelocity(note.dynamics as number) ), - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -517,45 +515,45 @@ describe('MidiFileGeneratorTest', () => { let note: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // bend up - new NoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 9216), // pre-bend - new NoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 9216), // bend start - new NoteBendEvent(1 * 80, 0, info.secondaryChannel, 62, 9131), - new NoteBendEvent(2 * 80, 0, info.secondaryChannel, 62, 9045), - new NoteBendEvent(3 * 80, 0, info.secondaryChannel, 62, 8960), - new NoteBendEvent(4 * 80, 0, info.secondaryChannel, 62, 8875), - new NoteBendEvent(5 * 80, 0, info.secondaryChannel, 62, 8789), - new NoteBendEvent(6 * 80, 0, info.secondaryChannel, 62, 8704), - new NoteBendEvent(7 * 80, 0, info.secondaryChannel, 62, 8619), - new NoteBendEvent(8 * 80, 0, info.secondaryChannel, 62, 8533), - new NoteBendEvent(9 * 80, 0, info.secondaryChannel, 62, 8448), - new NoteBendEvent(10 * 80, 0, info.secondaryChannel, 62, 8363), - new NoteBendEvent(11 * 80, 0, info.secondaryChannel, 62, 8277), - new NoteBendEvent(12 * 80, 0, info.secondaryChannel, 62, 8192), + new FlatNoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 9216), // pre-bend + new FlatNoteBendEvent(0 * 80, 0, info.secondaryChannel, 62, 9216), // bend start + new FlatNoteBendEvent(1 * 80, 0, info.secondaryChannel, 62, 9131), + new FlatNoteBendEvent(2 * 80, 0, info.secondaryChannel, 62, 9045), + new FlatNoteBendEvent(3 * 80, 0, info.secondaryChannel, 62, 8960), + new FlatNoteBendEvent(4 * 80, 0, info.secondaryChannel, 62, 8875), + new FlatNoteBendEvent(5 * 80, 0, info.secondaryChannel, 62, 8789), + new FlatNoteBendEvent(6 * 80, 0, info.secondaryChannel, 62, 8704), + new FlatNoteBendEvent(7 * 80, 0, info.secondaryChannel, 62, 8619), + new FlatNoteBendEvent(8 * 80, 0, info.secondaryChannel, 62, 8533), + new FlatNoteBendEvent(9 * 80, 0, info.secondaryChannel, 62, 8448), + new FlatNoteBendEvent(10 * 80, 0, info.secondaryChannel, 62, 8363), + new FlatNoteBendEvent(11 * 80, 0, info.secondaryChannel, 62, 8277), + new FlatNoteBendEvent(12 * 80, 0, info.secondaryChannel, 62, 8192), // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -564,7 +562,7 @@ describe('MidiFileGeneratorTest', () => { MidiUtils.dynamicToVelocity(note.dynamics as number) ), - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -627,7 +625,7 @@ describe('MidiFileGeneratorTest', () => { let generator: MidiFileGenerator = new MidiFileGenerator(score, null, handler); generator.generate(); for (let midiEvent of handler.midiEvents) { - if (midiEvent instanceof NoteEvent) { + if (midiEvent instanceof FlatNoteEvent) { actualMidiStartTimes.push(midiEvent.tick); actualMidiDurations.push(midiEvent.length); } @@ -653,44 +651,44 @@ describe('MidiFileGeneratorTest', () => { let note2: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[1]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), // bend effect (note 1) - new NoteBendEvent(0, 0, info.secondaryChannel, note1.realValue, 8192), // no bend - new NoteBendEvent(0, 0, info.secondaryChannel, note1.realValue, 8192), - new NoteBendEvent(1 * 80, 0, info.secondaryChannel, note1.realValue, 8277), - new NoteBendEvent(2 * 80, 0, info.secondaryChannel, note1.realValue, 8363), - new NoteBendEvent(3 * 80, 0, info.secondaryChannel, note1.realValue, 8448), - new NoteBendEvent(4 * 80, 0, info.secondaryChannel, note1.realValue, 8533), - new NoteBendEvent(5 * 80, 0, info.secondaryChannel, note1.realValue, 8619), - new NoteBendEvent(6 * 80, 0, info.secondaryChannel, note1.realValue, 8704), - new NoteBendEvent(7 * 80, 0, info.secondaryChannel, note1.realValue, 8789), - new NoteBendEvent(8 * 80, 0, info.secondaryChannel, note1.realValue, 8875), - new NoteBendEvent(9 * 80, 0, info.secondaryChannel, note1.realValue, 8960), - new NoteBendEvent(10 * 80, 0, info.secondaryChannel, note1.realValue, 9045), - new NoteBendEvent(11 * 80, 0, info.secondaryChannel, note1.realValue, 9131), + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note1.realValue, 8192), // no bend + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(1 * 80, 0, info.secondaryChannel, note1.realValue, 8277), + new FlatNoteBendEvent(2 * 80, 0, info.secondaryChannel, note1.realValue, 8363), + new FlatNoteBendEvent(3 * 80, 0, info.secondaryChannel, note1.realValue, 8448), + new FlatNoteBendEvent(4 * 80, 0, info.secondaryChannel, note1.realValue, 8533), + new FlatNoteBendEvent(5 * 80, 0, info.secondaryChannel, note1.realValue, 8619), + new FlatNoteBendEvent(6 * 80, 0, info.secondaryChannel, note1.realValue, 8704), + new FlatNoteBendEvent(7 * 80, 0, info.secondaryChannel, note1.realValue, 8789), + new FlatNoteBendEvent(8 * 80, 0, info.secondaryChannel, note1.realValue, 8875), + new FlatNoteBendEvent(9 * 80, 0, info.secondaryChannel, note1.realValue, 8960), + new FlatNoteBendEvent(10 * 80, 0, info.secondaryChannel, note1.realValue, 9045), + new FlatNoteBendEvent(11 * 80, 0, info.secondaryChannel, note1.realValue, 9131), // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -700,34 +698,34 @@ describe('MidiFileGeneratorTest', () => { ), // bend effect (note 2) - new NoteBendEvent(0, 0, info.secondaryChannel, note2.realValue, 8192), // no bend - new NoteBendEvent(0, 0, info.secondaryChannel, note2.realValue, 8192), - new NoteBendEvent(1 * 40, 0, info.secondaryChannel, note2.realValue, 8277), - new NoteBendEvent(2 * 40, 0, info.secondaryChannel, note2.realValue, 8363), - new NoteBendEvent(3 * 40, 0, info.secondaryChannel, note2.realValue, 8448), - new NoteBendEvent(4 * 40, 0, info.secondaryChannel, note2.realValue, 8533), - new NoteBendEvent(5 * 40, 0, info.secondaryChannel, note2.realValue, 8619), - new NoteBendEvent(6 * 40, 0, info.secondaryChannel, note2.realValue, 8704), - new NoteBendEvent(7 * 40, 0, info.secondaryChannel, note2.realValue, 8789), - new NoteBendEvent(8 * 40, 0, info.secondaryChannel, note2.realValue, 8875), - new NoteBendEvent(9 * 40, 0, info.secondaryChannel, note2.realValue, 8960), - new NoteBendEvent(10 * 40, 0, info.secondaryChannel, note2.realValue, 9045), - new NoteBendEvent(11 * 40, 0, info.secondaryChannel, note2.realValue, 9131), - new NoteBendEvent(12 * 40, 0, info.secondaryChannel, note2.realValue, 9216), - new NoteBendEvent(13 * 40, 0, info.secondaryChannel, note2.realValue, 9301), - new NoteBendEvent(14 * 40, 0, info.secondaryChannel, note2.realValue, 9387), - new NoteBendEvent(15 * 40, 0, info.secondaryChannel, note2.realValue, 9472), - new NoteBendEvent(16 * 40, 0, info.secondaryChannel, note2.realValue, 9557), - new NoteBendEvent(17 * 40, 0, info.secondaryChannel, note2.realValue, 9643), - new NoteBendEvent(18 * 40, 0, info.secondaryChannel, note2.realValue, 9728), - new NoteBendEvent(19 * 40, 0, info.secondaryChannel, note2.realValue, 9813), - new NoteBendEvent(20 * 40, 0, info.secondaryChannel, note2.realValue, 9899), - new NoteBendEvent(21 * 40, 0, info.secondaryChannel, note2.realValue, 9984), - new NoteBendEvent(22 * 40, 0, info.secondaryChannel, note2.realValue, 10069), - new NoteBendEvent(23 * 40, 0, info.secondaryChannel, note2.realValue, 10155), + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note2.realValue, 8192), // no bend + new FlatNoteBendEvent(0, 0, info.secondaryChannel, note2.realValue, 8192), + new FlatNoteBendEvent(1 * 40, 0, info.secondaryChannel, note2.realValue, 8277), + new FlatNoteBendEvent(2 * 40, 0, info.secondaryChannel, note2.realValue, 8363), + new FlatNoteBendEvent(3 * 40, 0, info.secondaryChannel, note2.realValue, 8448), + new FlatNoteBendEvent(4 * 40, 0, info.secondaryChannel, note2.realValue, 8533), + new FlatNoteBendEvent(5 * 40, 0, info.secondaryChannel, note2.realValue, 8619), + new FlatNoteBendEvent(6 * 40, 0, info.secondaryChannel, note2.realValue, 8704), + new FlatNoteBendEvent(7 * 40, 0, info.secondaryChannel, note2.realValue, 8789), + new FlatNoteBendEvent(8 * 40, 0, info.secondaryChannel, note2.realValue, 8875), + new FlatNoteBendEvent(9 * 40, 0, info.secondaryChannel, note2.realValue, 8960), + new FlatNoteBendEvent(10 * 40, 0, info.secondaryChannel, note2.realValue, 9045), + new FlatNoteBendEvent(11 * 40, 0, info.secondaryChannel, note2.realValue, 9131), + new FlatNoteBendEvent(12 * 40, 0, info.secondaryChannel, note2.realValue, 9216), + new FlatNoteBendEvent(13 * 40, 0, info.secondaryChannel, note2.realValue, 9301), + new FlatNoteBendEvent(14 * 40, 0, info.secondaryChannel, note2.realValue, 9387), + new FlatNoteBendEvent(15 * 40, 0, info.secondaryChannel, note2.realValue, 9472), + new FlatNoteBendEvent(16 * 40, 0, info.secondaryChannel, note2.realValue, 9557), + new FlatNoteBendEvent(17 * 40, 0, info.secondaryChannel, note2.realValue, 9643), + new FlatNoteBendEvent(18 * 40, 0, info.secondaryChannel, note2.realValue, 9728), + new FlatNoteBendEvent(19 * 40, 0, info.secondaryChannel, note2.realValue, 9813), + new FlatNoteBendEvent(20 * 40, 0, info.secondaryChannel, note2.realValue, 9899), + new FlatNoteBendEvent(21 * 40, 0, info.secondaryChannel, note2.realValue, 9984), + new FlatNoteBendEvent(22 * 40, 0, info.secondaryChannel, note2.realValue, 10069), + new FlatNoteBendEvent(23 * 40, 0, info.secondaryChannel, note2.realValue, 10155), // note itself - new NoteEvent( + new FlatNoteEvent( 0, 0, info.secondaryChannel, @@ -737,8 +735,8 @@ describe('MidiFileGeneratorTest', () => { ), // reset bend - new NoteBendEvent(960, 0, info.primaryChannel, note1.realValue, 8192), - new NoteEvent( + new FlatNoteBendEvent(960, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteEvent( 960, 0, info.primaryChannel, @@ -748,7 +746,7 @@ describe('MidiFileGeneratorTest', () => { ), // end of track - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -764,43 +762,43 @@ describe('MidiFileGeneratorTest', () => { const settings = new Settings(); settings.player.vibrato.noteSlightLength = MidiUtils.QuarterTime / 2; // to reduce the number of vibrato events let generator: MidiFileGenerator = new MidiFileGenerator(score, settings, handler); - generator.vibratoResolution = settings.player.vibrato.noteSlightLength / 4; + generator.vibratoResolution = settings.player.vibrato.noteSlightLength / 4; generator.generate(); let info: PlaybackInformation = score.tracks[0].playbackInfo; let note1: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), - - new NoteBendEvent(0, 0, info.primaryChannel, note1.realValue, 8192), // no bend - new NoteBendEvent(480, 0, info.primaryChannel, note1.realValue, 8192), // vibrato main note - new NoteBendEvent(600, 0, info.primaryChannel, note1.realValue, 8704), - new NoteBendEvent(720, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(840, 0, info.primaryChannel, note1.realValue, 7680), - new NoteBendEvent(960, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(1080, 0, info.primaryChannel, note1.realValue, 8704), - new NoteBendEvent(1200, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(1320, 0, info.primaryChannel, note1.realValue, 7680), - new NoteEvent( + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), + + new FlatNoteBendEvent(0, 0, info.primaryChannel, note1.realValue, 8192), // no bend + new FlatNoteBendEvent(480, 0, info.primaryChannel, note1.realValue, 8192), // vibrato main note + new FlatNoteBendEvent(600, 0, info.primaryChannel, note1.realValue, 8704), + new FlatNoteBendEvent(720, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(840, 0, info.primaryChannel, note1.realValue, 7680), + new FlatNoteBendEvent(960, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(1080, 0, info.primaryChannel, note1.realValue, 8704), + new FlatNoteBendEvent(1200, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(1320, 0, info.primaryChannel, note1.realValue, 7680), + new FlatNoteEvent( 0, 0, info.primaryChannel, @@ -809,17 +807,17 @@ describe('MidiFileGeneratorTest', () => { MidiUtils.dynamicToVelocity(note1.dynamics as number) ), - new NoteBendEvent(1440, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(1560, 0, info.primaryChannel, note1.realValue, 8704), - new NoteBendEvent(1680, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(1800, 0, info.primaryChannel, note1.realValue, 7680), - new NoteBendEvent(1920, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(2040, 0, info.primaryChannel, note1.realValue, 8704), - new NoteBendEvent(2160, 0, info.primaryChannel, note1.realValue, 8192), - new NoteBendEvent(2280, 0, info.primaryChannel, note1.realValue, 7680), + new FlatNoteBendEvent(1440, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(1560, 0, info.primaryChannel, note1.realValue, 8704), + new FlatNoteBendEvent(1680, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(1800, 0, info.primaryChannel, note1.realValue, 7680), + new FlatNoteBendEvent(1920, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(2040, 0, info.primaryChannel, note1.realValue, 8704), + new FlatNoteBendEvent(2160, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteBendEvent(2280, 0, info.primaryChannel, note1.realValue, 7680), // end of track - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; assertEvents(handler.midiEvents, expectedEvents); @@ -853,9 +851,9 @@ describe('MidiFileGeneratorTest', () => { generator.generate(); noteOnTimes = []; for (const evt of handler.midiEvents) { - if (evt instanceof NoteEvent) { + if (evt instanceof FlatNoteEvent) { noteOnTimes.push(evt.tick); - } else if (evt instanceof RestEvent) { + } else if (evt instanceof FlatRestEvent) { noteOnTimes.push(evt.tick); } } @@ -873,16 +871,16 @@ describe('MidiFileGeneratorTest', () => { let timeSignature: MidiEvent | null = null; for (const evt of file.events) { - if (evt.type === MidiEventType.Meta && evt.data1 === MetaEventType.TimeSignature) { + if (evt.type === MidiEventType.TimeSignature) { timeSignature = evt; break; } } expect(timeSignature).toBeTruthy(); - const meta: MetaDataEvent = timeSignature as MetaDataEvent; - const timeSignatureNumerator: number = meta.data[0]; - const timeSignatureDenominator: number = Math.pow(2, meta.data[1]); + const meta: TimeSignatureEvent = timeSignature as TimeSignatureEvent; + const timeSignatureNumerator: number = meta.numerator; + const timeSignatureDenominator: number = Math.pow(2, meta.denominatorIndex); expect(timeSignatureNumerator).toEqual(3); expect(timeSignatureDenominator).toEqual(4); }); @@ -899,15 +897,15 @@ describe('MidiFileGeneratorTest', () => { const generator: MidiFileGenerator = new MidiFileGenerator(score, null, handler); generator.generate(); - const tempoChanges: TempoEvent[] = []; + const tempoChanges: FlatTempoEvent[] = []; for (const evt of handler.midiEvents) { - if (evt instanceof TempoEvent) { - tempoChanges.push(evt as TempoEvent); + if (evt instanceof FlatTempoEvent) { + tempoChanges.push(evt as FlatTempoEvent); } } - expect(tempoChanges.map(t=>t.tick).join(',')).toBe('0,3840'); - expect(tempoChanges.map(t=>t.tempo).join(',')).toBe('60,80'); + expect(tempoChanges.map(t => t.tick).join(',')).toBe('0,3840'); + expect(tempoChanges.map(t => t.tempo).join(',')).toBe('60,80'); }); it('has-valid-dynamics', () => { @@ -927,29 +925,29 @@ describe('MidiFileGeneratorTest', () => { let expectedEvents: FlatMidiEvent[] = [ // channel init - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.primaryChannel, info.program), - - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), - new ControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), - new ProgramChangeEvent(0, 0, info.secondaryChannel, info.program), - - new TimeSignatureEvent(0, 4, 4), - new TempoEvent(0, 120), - - new NoteBendEvent(0, 0, info.primaryChannel, note1.realValue, 8192), - new NoteEvent( + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.primaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.primaryChannel, info.program), + + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.VolumeCoarse, 120), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.PanCoarse, 64), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.ExpressionControllerCoarse, 127), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.RegisteredParameterCourse, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryFine, 0), + new FlatControlChangeEvent(0, 0, info.secondaryChannel, ControllerType.DataEntryCoarse, 16), + new FlatProgramChangeEvent(0, 0, info.secondaryChannel, info.program), + + new FlatTimeSignatureEvent(0, 4, 4), + new FlatTempoEvent(0, 120), + + new FlatNoteBendEvent(0, 0, info.primaryChannel, note1.realValue, 8192), + new FlatNoteEvent( 0, 0, info.primaryChannel, @@ -958,8 +956,8 @@ describe('MidiFileGeneratorTest', () => { MidiUtils.dynamicToVelocity((note1.dynamics as number) + 1) ), - new NoteBendEvent(1920, 0, info.primaryChannel, note2.realValue, 8192), - new NoteEvent( + new FlatNoteBendEvent(1920, 0, info.primaryChannel, note2.realValue, 8192), + new FlatNoteEvent( 1920, 0, info.primaryChannel, @@ -969,7 +967,7 @@ describe('MidiFileGeneratorTest', () => { ), // end of track - new TrackEndEvent(3840, 0) // 3840 = end of bar + new FlatTrackEndEvent(3840, 0) // 3840 = end of bar ]; const handler: FlatMidiEventGenerator = new FlatMidiEventGenerator(); diff --git a/test/visualTests/VisualTestHelper.ts b/test/visualTests/VisualTestHelper.ts index b4698b02e..0cd2f9eb6 100644 --- a/test/visualTests/VisualTestHelper.ts +++ b/test/visualTests/VisualTestHelper.ts @@ -249,9 +249,7 @@ export class VisualTestHelper { totalHeights.push(0); }); api.renderer.partialRenderFinished.on(e => { - if (e) { - results[results.length - 1].push(e); - } + results[results.length - 1].push(e); }); api.renderer.renderFinished.on(e => { totalWidths[totalWidths.length - 1] = e.totalWidth; From f247251603179e2ce0d7cdf54f9effcccf0900c3 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 5 Aug 2023 21:26:58 +0200 Subject: [PATCH 4/5] Fix kotlin compilation --- .../alphaTab/core/ecmaScript/Uint8Array.kt | 4 ++++ .../kotlin/alphaTab/io/JsonHelperPartials.kt | 19 +++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt b/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt index bd5b86f8d..f425eb238 100644 --- a/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt +++ b/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt @@ -13,6 +13,10 @@ class Uint8Array : Iterable { this.buffer = UByteArray(size.toInt()) } + public constructor() { + this.buffer = UByteArray(0) + } + public constructor(data: UByteArray) { this.buffer = data } diff --git a/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt b/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt index 5c11d9857..4023bd453 100644 --- a/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt +++ b/src.kotlin/alphaTab/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt @@ -16,12 +16,6 @@ internal open class JsonHelperPartials { func(kvp.value, (kvp.key!!) as String) } } - - if (o is alphaTab.collections.Map<*, *>) { - for (kvp in o) { - func(kvp.value, (kvp.key!!) as String) - } - } } public fun forEach(o: Any?, func: (v: Any?, k: String) -> Unit) { @@ -30,12 +24,17 @@ internal open class JsonHelperPartials { func(kvp.value, (kvp.key!!) as String) } } + } - if (o is alphaTab.collections.Map<*, *>) { - for (kvp in o) { - func(kvp.value, (kvp.key!!) as String) - } + public fun getValue(o: Any?, k: String): Any? { + if (o is Map<*, *>) { + // NOTE: We know that we only have Map in our serialization + // handling. we need this cast to satisfy Kotlin type checks. + @Suppress("UNCHECKED_CAST") val unsafeMap = o as Map + return if (unsafeMap.has(k)) unsafeMap.get(k) else null } + + return null } From 4ba6e9e210d82810d8450c90c5fd27c08c422fb8 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 5 Aug 2023 21:31:26 +0200 Subject: [PATCH 5/5] Fix compilation --- test/audio/FlatMidiEventGenerator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/audio/FlatMidiEventGenerator.ts b/test/audio/FlatMidiEventGenerator.ts index b5ff10e03..104b79bbf 100644 --- a/test/audio/FlatMidiEventGenerator.ts +++ b/test/audio/FlatMidiEventGenerator.ts @@ -30,8 +30,8 @@ export class FlatMidiEventGenerator implements IMidiFileHandler { this.midiEvents.push(e); } - public addControlChange(track: number, tick: number, channel: number, controller: number, value: number): void { - let e = new FlatControlChangeEvent(tick, track, channel, controller as ControllerType, value); + public addControlChange(track: number, tick: number, channel: number, controller: ControllerType, value: number): void { + let e = new FlatControlChangeEvent(tick, track, channel, controller, value); this.midiEvents.push(e); }