class Buffer { constructor(buffer, length) { this.buffer = buffer; this.dv = new DataView(this.buffer); this.index = 0; this.byteLength = length === undefined ? buffer.byteLength : length; this.done = false; } get length() { return this.byteLength - this.index; } readUInt8() { return this.dv.getUint8(this.index++); } readUInt16BE() { let value = this.dv.getUint16(this.index, false); this.index += 2; return value; } readUInt16LE() { let value = this.dv.getUint16(this.index, true); this.index += 2; return value; } readUInt24BE() { let value = this.readUInt8() << 16; value |= this.readUInt8() << 8; value |= this.readUInt8() << 0; return value; } readUInt32BE() { let value = this.dv.getUint32(this.index, false); this.index += 4; return value; } readUInt32LE() { let value = this.dv.getUint32(this.index, true); this.index += 4; return value; } readVarInt() { let value = 0; let byte; do { byte = this.readUInt8(); value = (value << 7) | (byte & 0x7F); if (value > 0x0FFFFFFF) throw new Error("VarInt too large"); } while ((byte & 0x80) !== 0); return value; } readString(length) { let string = ""; if (length === undefined) { while (true) { let byte = this.readUInt8(); if (byte === 0) break; string += String.fromCharCode(byte); } } else { for (let i=0; i 0) { let name = this.readString(4); let data = this.readBuffer(this.readUInt32LE()); chunks.push({ name: name, data: data }); } return chunks; } readBuffer(length, copy) { if (this.length < length) throw new RangeError("Buffer is out of bounds"); let buffer; if (copy) { buffer = new Buffer(this.buffer.slice(this.index, this.index + length)); this.index += length; } else { buffer = new Buffer(this.buffer, this.index + length); buffer.index = this.index; this.index += length; } return buffer; } } class MidiChunk { constructor(type, buffer) { this.type = type; this.buffer = buffer; } } class MidiEvent { constructor(deltaTime) { this.deltaTime = deltaTime; } } class MidiVoiceEvent extends MidiEvent { constructor(deltaTime, channel) { super(deltaTime); this.channel = channel; } } class MidiNoteOffEvent extends MidiVoiceEvent { constructor(deltaTime, channel, key, velocity) { super(deltaTime, channel); this.key = key; this.velocity = velocity; } } class MidiNoteOnEvent extends MidiVoiceEvent { constructor(deltaTime, channel, key, velocity) { super(deltaTime, channel); this.key = key; this.velocity = velocity; } } class MidiAftertouchEvent extends MidiVoiceEvent { constructor(deltaTime, channel, key, pressure) { super(deltaTime, channel); this.key = key; this.pressure = pressure; } } class MidiControllerChangeEvent extends MidiVoiceEvent { constructor(deltaTime, channel, controller, value) { super(deltaTime, channel); this.controller = controller; this.value = value; } } class MidiProgramChangeEvent extends MidiVoiceEvent { constructor(deltaTime, channel, program) { super(deltaTime, channel); this.program = program; } } class MidiChannelKeyPressureEvent extends MidiVoiceEvent { constructor(deltaTime, channel, pressure) { super(deltaTime, channel); this.pressure = pressure; } } class MidiPitchBendEvent extends MidiVoiceEvent { constructor(deltaTime, channel, value) { super(deltaTime, channel); this.value = value; } } class MidiSysexEvent extends MidiEvent { constructor(deltaTime) { super(deltaTime); } } class MidiMetaEvent extends MidiEvent { constructor(deltaTime, data) { super(deltaTime); this.data = data; } } class MidiSequenceNumberEvent extends MidiMetaEvent { constructor(deltaTime, data, number) { super(deltaTime, data); this.number = number; } } class MidiTextEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiCopyrightNoticeEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiTrackNameEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiInstrumentNameEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiLyricEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiMarkerEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiCuePointEvent extends MidiMetaEvent { constructor(deltaTime, data, text) { super(deltaTime, data); this.text = text; } } class MidiChannelPrefixEvent extends MidiMetaEvent { constructor(deltaTime, data, channel) { super(deltaTime, data); this.channel = channel; } } class MidiPortPrefixEvent extends MidiMetaEvent { constructor(deltaTime, data, port) { super(deltaTime, data); this.port = port; } } class MidiEndOfTrackEvent extends MidiMetaEvent { constructor(deltaTime, data) { super(deltaTime, data); } } class MidiSetTempoEvent extends MidiMetaEvent { constructor(deltaTime, data, tempo) { super(deltaTime, data); this.tempo = tempo; } } class MidiSetSMTPEOffsetEvent extends MidiMetaEvent { constructor(deltaTime, data, hours, minutes, seconds, frames, frameFractions) { super(deltaTime, data); this.hours = hours; this.minutes = minutes; this.seconds = seconds; this.frames = frames; this.frameFractions = frameFractions; } } class MidiTimeSignatureEvent extends MidiMetaEvent { constructor(deltaTime, data, numerator, denominator, clocks, notes) { super(deltaTime, data); this.numerator = numerator; this.denominator = denominator; this.clocks = clocks; this.notes = notes; } } class MidiKeySignatureEvent extends MidiMetaEvent { constructor(deltaTime, data, halfNotes, major) { super(deltaTime, data); this.halfNotes = halfNotes; this.major = major; } } class MidiSequencerEvent extends MidiMetaEvent { constructor(deltaTime, data, id, event) { super(deltaTime, data); this.id = id; this.event = event; } } class MidiUnknkownMetaEvent extends MidiMetaEvent { constructor(deltaTime, data) { super(deltaTime, data); } } class MidiTrackChunk extends MidiChunk { constructor(buffer) { super("MTtr", buffer); } } class MidiFile { constructor(buffer) { this.buffer = new Buffer(buffer); while(this.buffer.length > 8) { let type = this.buffer.readString(4); let length = this.buffer.readUInt32BE(); let buffer = this.buffer.readBuffer(length); switch(type) { case "MThd": this.readMThd(); break; case "MTrk": this.readMTrk(buffer); break; default: console.warn("Unknown chunk:", type); } } } readMThd(buffer) { this.format = buffer.readUInt16BE(); this.tracksCount = buffer.readUInt16BE(); this.division = buffer.readUInt16BE(); } readMTrk(buffer) { let events = []; let lastStatus = null; while (buffer.length > 0) { let deltaTime = buffer.readVarInt(); let status = buffer.readUInt8(); if ((status & 0x80) === 0) { if (lastStatus === null) { throw new Error("First event in track cannot ommit status"); } status = lastStatus; buffer.index--; // kinf of ugly } else { lastStatus = status; } let eventId = status >> 4; let channel = status & 0x0F; let event; switch (eventId) { case 0x8: { // Note off let key = buffer.readUInt8(); let velocity = buffer.readUInt8(); event = new MidiNoteOffEvent(deltaTime, channel, key, velocity); break; } case 0x9: { // Note on let key = this.buffer.readUInt8(); let velocity = this.buffer.readUInt8(); event = new MidiNoteOnEvent(deltaTime, channel, key, velocity); break; } case 0xA: { // Aftertouch let key = this.buffer.readUInt8(); let pressure = this.buffer.readUInt8(); event = new MidiAftertouchEvent(deltaTime, channel, key, pressure); break; } case 0xB: { // Controller change let controller = this.buffer.readUInt8(); let value = this.buffer.readUInt8(); event = new MidiControllerChangeEvent(deltaTime, channel, controller, value); break; } case 0xC: { // Program change let program = this.buffer.readUInt8(); event = new MidiProgramChangeEvent(deltaTime, channel, program); break; } case 0xD: { // Channel key pressure let pressure = this.buffer.readUInt8(); event = new MidiChannelKeyPressureEvent(deltaTime, channel, pressure); break; } case 0xE: { // Pitch bend let lsb = this.buffer.readUInt8(); let msb = this.buffer.readUInt8(); let value = (msb << 7) | lsb; event = new MidiPitchBendEvent(deltaTime, channel, value); break; } case 0xF: { // Sysex & meta event if (channel === 0x0 || channel === 0x7) { // Sysex let length = this.buffer.readVarInt(); let data = this.buffer.readBuffer(length); event = new MidiSysexEvent(deltaTime, data); } else if (channel === 0xF) { // Meta let metaEvent = this.buffer.readUInt8(); let length = this.buffer.readVarInt(); let data = this.buffer.readBuffer(length); switch(metaEvent) { case 0x00: { // Sequence number let sequence = data.readUInt16BE(); event = new MidiSequenceNumberEvent(deltaTime, data, sequence); break; } case 0x01: { // Text let text = data.readString(length); event = new MidiTextEvent(deltaTime, data, text); break; } case 0x02: { // Copyright notice let text = data.readString(length); event = new MidiCopyrightNoticeEvent(deltaTime, data, text); break; } case 0x03: { // Sequence / track name let text = data.readString(length); event = new MidiTrackNameEvent(deltaTime, data, text); break; } case 0x04: { // Instrument name let text = data.readString(length); event = new MidiInstrumentNameEvent(deltaTime, data, text); break; } case 0x05: { // Lyric let text = data.readString(length); event = new MidiLyricEvent(deltaTime, data, text); break; } case 0x06: { // Marker let text = data.readString(length); event = new MidiMarkerEvent(deltaTime, data, text); break; } case 0x07: { // Cue point let text = data.readString(length); event = new MidiCuePointEvent(deltaTime, data, text); break; } case 0x20: { // Channel prefix let channel = data.readUInt8(); event = new MidiChannelPrefixEvent(deltaTime, data, channel); break; } case 0x2F: { // End of track event = new MidiEndOfTrackEvent(deltaTime, data); break; } case 0x51: { // Set tempo let value = data.readUInt24BE(); event = new MidiSetTempoEvent(deltaTime, data, value); break; } case 0x54: { // SMTPE offset let hours = data.readUInt8(); let minutes = data.readUInt8(); let seconds = data.readUInt8(); let frames = data.readUInt8(); let frameFractions = data.readUInt8(); event = new MidiSetSMTPEOffsetEvent(deltaTime, data, hours, minutes, seconds, frames, frameFractions); break; } case 0x58: { // Time signature let numerator = data.readUInt8(); let denominator = Math.pow(2, data.readUInt8()); let clocks = data.readUInt8(); let notes = data.readUInt8(); event = new MidiTimeSignatureEvent(deltaTime, data, numerator, denominator, clocks, notes); break; } case 0x59: { // Key signature let halfNotes = data.readUInt8(); let major = !!data.readUInt8(); event = new MidiKeySignatureEvent(deltaTime, data, halfNotes, major); break; } case 0x7F: { // Sequencer event let id = data.readUInt8(); let seqEvent = data.readBuffer(length - 1); event = new MidiSequencerEvent(deltaTime, data, id, seqEvent); break; } default: { event = new MidiUnknkownMetaEvent(deltaTime, data); break; } } } else { throw "Invalid event type"; } } } this.events.push(event); } } }