507 lines
12 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|