piano/frontend/midiParser.js

507 lines
12 KiB
JavaScript

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<length; i++) {
string += String.fromCharCode(this.readUInt8());
}
}
return string;
}
readChunks() {
let chunks = [];
while (this.length > 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);
}
}
}