import { MIDIChannelEvents } from "."; import { MIDIMetaEvents } from "./constants/MIDIMetaEvents"; import { ActiveSensingEvent, AnyEvent, ChannelAftertouchEvent, ChannelPrefixEvent, ContinueEvent, ControllerEvent, CopyrightNoticeEvent, CuePointEvent, DividedSysExEvent, EndOfTrackEvent, InstrumentNameEvent, KeySignatureEvent, LyricsEvent, MarkerEvent, NoteAftertouchEvent, NoteOffEvent, NoteOnEvent, PitchBendEvent, PortPrefixEvent, ProgramChangeEvent, SequenceNumberEvent, SequencerSpecificEvent, SetTempoEvent, SmpteOffsetEvent, SongPositionPointerEvent, SongSelectEvent, StartEvent, StopEvent, SysExEvent, TextEvent, TimeCodeQuarterFrameEvent, TimeSignatureEvent, TimingClockEvent, TrackNameEvent, TuneRequestEvent, UnknownChannelEvent, UnknownMetaEvent } from "./event"; import { Stream } from "./stream"; export function deserialize( stream: Stream, lastEventTypeByte: number = 0, setLastEventTypeByte?: (eventType: number) => void ): AnyEvent { const deltaTime = stream.readVarInt() return deserializeSingleEvent( stream, deltaTime, lastEventTypeByte, setLastEventTypeByte ) } export function deserializeSingleEvent( stream: Stream, deltaTime: number = 0, lastEventTypeByte: number = 0, setLastEventTypeByte?: (eventType: number) => void ) { let eventTypeByte = stream.readInt8() if ((eventTypeByte & 0xf0) === 0xf0) { /* system / meta event */ if (eventTypeByte === 0xff) { /* meta event */ const type = "meta" const subtypeByte = stream.readInt8() const length = stream.readVarInt() switch (subtypeByte) { case MIDIMetaEvents.sequenceNumber: if (length !== 2) throw new Error( "Expected length for sequenceNumber event is 2, got " + length ) return { deltaTime, type, subtype: "sequenceNumber", number: stream.readInt16(), } case MIDIMetaEvents.text: return { deltaTime, type, subtype: "text", text: stream.readStr(length), } case MIDIMetaEvents.copyrightNotice: return { deltaTime, type, subtype: "copyrightNotice", text: stream.readStr(length), } case MIDIMetaEvents.trackName: return { deltaTime, type, subtype: "trackName", text: stream.readStr(length), } case MIDIMetaEvents.instrumentName: return { deltaTime, type, subtype: "instrumentName", text: stream.readStr(length), } case MIDIMetaEvents.lyrics: return { deltaTime, type, subtype: "lyrics", text: stream.readStr(length), } case MIDIMetaEvents.marker: return { deltaTime, type, subtype: "marker", text: stream.readStr(length), } case MIDIMetaEvents.cuePoint: return { deltaTime, type, subtype: "cuePoint", text: stream.readStr(length), } case MIDIMetaEvents.midiChannelPrefix: if (length !== 1) throw new Error( "Expected length for midiChannelPrefix event is 1, got " + length ) return { deltaTime, type, subtype: "midiChannelPrefix", value: stream.readInt8(), } case MIDIMetaEvents.portPrefix: if (length !== 1) throw new Error( "Expected length for midiChannelPrefix event is 1, got " + length ) return { deltaTime, type, subtype: "portPrefix", port: stream.readInt8(), } case MIDIMetaEvents.endOfTrack: if (length !== 0) throw new Error( "Expected length for endOfTrack event is 0, got " + length ) return { deltaTime, type, subtype: "endOfTrack", } case MIDIMetaEvents.setTempo: if (length !== 3) throw new Error( "Expected length for setTempo event is 3, got " + length ) return { deltaTime, type, subtype: "setTempo", microsecondsPerBeat: (stream.readInt8() << 16) + (stream.readInt8() << 8) + stream.readInt8(), } case MIDIMetaEvents.smpteOffset: { if (length !== 5) throw new Error( "Expected length for smpteOffset event is 5, got " + length ) const hourByte = stream.readInt8() const table: { [key: number]: number } = { 0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30, } return { deltaTime, type, subtype: "smpteOffset", frameRate: table[hourByte & 0x60], hour: hourByte & 0x1f, min: stream.readInt8(), sec: stream.readInt8(), frame: stream.readInt8(), subframe: stream.readInt8(), } } case MIDIMetaEvents.timeSignature: if (length !== 4) throw new Error( "Expected length for timeSignature event is 4, got " + length ) return { deltaTime, type, subtype: "timeSignature", numerator: stream.readInt8(), denominator: Math.pow(2, stream.readInt8()), metronome: stream.readInt8(), thirtyseconds: stream.readInt8(), } case MIDIMetaEvents.keySignature: if (length !== 2) throw new Error( "Expected length for keySignature event is 2, got " + length ) return { deltaTime, type, subtype: "keySignature", key: stream.readInt8(true), scale: stream.readInt8(), } case MIDIMetaEvents.sequencerSpecific: return { deltaTime, type, subtype: "sequencerSpecific", data: stream.read(length), } default: return { deltaTime, type, subtype: "unknown", data: stream.read(length), } } } else if (eventTypeByte === 0xf0) { const length = stream.readVarInt() return { deltaTime, type: "sysEx", data: stream.read(length), } } else if (eventTypeByte === 0xf7) { const length = stream.readVarInt() return { deltaTime, type: "dividedSysEx", data: stream.read(length), } } else { // Handle System Common and Real-Time Messages switch (eventTypeByte) { // --- System Common Messages --- case 0xf1: // Time Code Quarter Frame return { deltaTime, type: "system", subtype: "timeCodeQuarterFrame", data: stream.readInt8(), }; case 0xf2: // Song Position Pointer const lsb = stream.readInt8(); const msb = stream.readInt8(); return { deltaTime, type: "system", subtype: "songPositionPointer", value: (msb << 7) | lsb, // 14-bit value }; case 0xf3: // Song Select return { deltaTime, type: "system", subtype: "songSelect", songNumber: stream.readInt8(), }; case 0xf6: // Tune Request return { deltaTime, type: "system", subtype: "tuneRequest", }; // --- Real-Time Messages --- case 0xf8: // Timing Clock return { deltaTime, type: "system", subtype: "timingClock", }; case 0xfa: // Start return { deltaTime, type: "system", subtype: "start", }; case 0xfb: // Continue return { deltaTime, type: "system", subtype: "continue", }; case 0xfc: // Stop return { deltaTime, type: "system", subtype: "stop", }; case 0xfe: // Active Sensing return { deltaTime, type: "system", subtype: "activeSensing", }; // Handle undefined/reserved messages default: throw new Error(`Unsupported system event: 0x${eventTypeByte.toString(16)}`); } } } else { /* channel event */ let param1 if ((eventTypeByte & 0x80) === 0) { /* running status - reuse lastEventTypeByte as the event type. eventTypeByte is actually the first parameter */ param1 = eventTypeByte eventTypeByte = lastEventTypeByte } else { param1 = stream.readInt8() setLastEventTypeByte?.(eventTypeByte) } const eventType = eventTypeByte >> 4 const channel = eventTypeByte & 0x0f const type = "channel" switch (eventType) { case MIDIChannelEvents.noteOff: return { deltaTime, type, channel, subtype: "noteOff", noteNumber: param1, velocity: stream.readInt8(), } case MIDIChannelEvents.noteOn: { const velocity = stream.readInt8() return { deltaTime, type, channel, subtype: velocity === 0 ? "noteOff" : "noteOn", noteNumber: param1, velocity: velocity, } } case MIDIChannelEvents.noteAftertouch: return { deltaTime, type, channel, subtype: "noteAftertouch", noteNumber: param1, amount: stream.readInt8(), } case MIDIChannelEvents.controller: return { deltaTime, type, channel, subtype: "controller", controllerType: param1, value: stream.readInt8(), } case MIDIChannelEvents.programChange: return { deltaTime, type, channel, subtype: "programChange", value: param1, } case MIDIChannelEvents.channelAftertouch: return { deltaTime, type, channel, subtype: "channelAftertouch", amount: param1, } case MIDIChannelEvents.pitchBend: return { deltaTime, type, channel, subtype: "pitchBend", value: param1 + (stream.readInt8() << 7), } default: return { deltaTime, type, channel, subtype: "unknown", data: stream.readInt8(), } } } }