k-l-lambda's picture
updated node_modules
4cadbaf
/*
class to parse the .mid file format
(depends on stream.js)
*/
const Stream = require("./stream.js");
module.exports = function MidiFile (data) {
function readChunk (stream) {
const id = stream.readString(4);
const length = stream.readInt32();
return {
id,
length,
data: stream.read(length),
};
}
let lastEventTypeByte;
function readEvent (stream) {
const event = {};
event.deltaTime = stream.readVarInt();
let eventTypeByte = stream.readInt8();
if ((eventTypeByte & 0xf0) === 0xf0) {
// system / meta event
if (eventTypeByte === 0xff) {
// meta event
event.type = "meta";
const subtypeByte = stream.readInt8();
const length = stream.readVarInt();
switch (subtypeByte) {
case 0x00:
event.subtype = "sequenceNumber";
if (length !== 2)
throw new Error("Expected length for sequenceNumber event is 2, got " + length);
event.number = stream.readInt16();
return event;
case 0x01:
event.subtype = "text";
event.text = stream.readString(length);
return event;
case 0x02:
event.subtype = "copyrightNotice";
event.text = stream.readString(length);
return event;
case 0x03:
event.subtype = "trackName";
event.text = stream.readString(length);
return event;
case 0x04:
event.subtype = "instrumentName";
event.text = stream.readString(length);
return event;
case 0x05:
event.subtype = "lyrics";
event.text = stream.readString(length);
return event;
case 0x06:
event.subtype = "marker";
event.text = stream.readString(length);
return event;
case 0x07:
event.subtype = "cuePoint";
event.text = stream.readString(length);
return event;
case 0x20:
event.subtype = "midiChannelPrefix";
if (length !== 1)
throw new Error("Expected length for midiChannelPrefix event is 1, got " + length);
event.channel = stream.readInt8();
return event;
case 0x2f:
event.subtype = "endOfTrack";
if (length !== 0)
throw new Error("Expected length for endOfTrack event is 0, got " + length);
return event;
case 0x51:
event.subtype = "setTempo";
if (length !== 3)
throw new Error("Expected length for setTempo event is 3, got " + length);
event.microsecondsPerBeat = (
(stream.readInt8() << 16) +
(stream.readInt8() << 8) +
stream.readInt8()
);
return event;
case 0x54:
event.subtype = "smpteOffset";
if (length !== 5)
throw new Error("Expected length for smpteOffset event is 5, got " + length);
const hourByte = stream.readInt8();
event.frameRate = {
0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30,
}[hourByte & 0x60];
event.hour = hourByte & 0x1f;
event.min = stream.readInt8();
event.sec = stream.readInt8();
event.frame = stream.readInt8();
event.subframe = stream.readInt8();
return event;
case 0x58:
event.subtype = "timeSignature";
if (length !== 4)
throw new Error("Expected length for timeSignature event is 4, got " + length);
event.numerator = stream.readInt8();
event.denominator = Math.pow(2, stream.readInt8());
event.metronome = stream.readInt8();
event.thirtyseconds = stream.readInt8();
return event;
case 0x59:
event.subtype = "keySignature";
if (length !== 2)
throw new Error("Expected length for keySignature event is 2, got " + length);
event.key = stream.readInt8(true);
event.scale = stream.readInt8();
return event;
case 0x7f:
event.subtype = "sequencerSpecific";
event.data = stream.readString(length);
return event;
default:
// console.log("Unrecognised meta event subtype: " + subtypeByte);
event.subtype = "unknown";
event.data = stream.readString(length);
return event;
}
//event.data = stream.readString(length);
//return event;
}
else if (eventTypeByte === 0xf0) {
event.type = "sysEx";
const length = stream.readVarInt();
event.data = stream.readString(length);
return event;
}
else if (eventTypeByte === 0xf7) {
event.type = "dividedSysEx";
const length = stream.readVarInt();
event.data = stream.readString(length);
return event;
}
else
throw new Error("Unrecognised MIDI event type byte: " + eventTypeByte);
}
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();
lastEventTypeByte = eventTypeByte;
}
const eventType = eventTypeByte >> 4;
event.channel = eventTypeByte & 0x0f;
event.type = "channel";
switch (eventType) {
case 0x08:
event.subtype = "noteOff";
event.noteNumber = param1;
event.velocity = stream.readInt8();
return event;
case 0x09:
event.noteNumber = param1;
event.velocity = stream.readInt8();
if (event.velocity === 0)
event.subtype = "noteOff";
else
event.subtype = "noteOn";
return event;
case 0x0a:
event.subtype = "noteAftertouch";
event.noteNumber = param1;
event.amount = stream.readInt8();
return event;
case 0x0b:
event.subtype = "controller";
event.controllerType = param1;
event.value = stream.readInt8();
return event;
case 0x0c:
event.subtype = "programChange";
event.programNumber = param1;
return event;
case 0x0d:
event.subtype = "channelAftertouch";
event.amount = param1;
return event;
case 0x0e:
event.subtype = "pitchBend";
event.value = param1 + (stream.readInt8() << 7);
return event;
default:
throw new Error("Unrecognised MIDI event type: " + eventType);
/*
console.log("Unrecognised MIDI event type: " + eventType);
stream.readInt8();
event.subtype = 'unknown';
return event;
*/
}
}
}
let source = data;
if (typeof data === "string")
source = data.split("").map(c => c.charCodeAt(0));
const stream = new Stream(source);
const headerChunk = readChunk(stream);
if (headerChunk.id !== "MThd" || headerChunk.length !== 6)
throw new Error("Bad .mid file - header not found");
const headerStream = new Stream(headerChunk.data);
const formatType = headerStream.readInt16();
const trackCount = headerStream.readInt16();
const timeDivision = headerStream.readInt16();
let ticksPerBeat;
if (timeDivision & 0x8000)
throw new Error("Expressing time division in SMTPE frames is not supported yet");
else
ticksPerBeat = timeDivision;
const header = {
formatType,
trackCount,
ticksPerBeat,
};
const tracks = [];
for (let i = 0; i < header.trackCount; i++) {
tracks[i] = [];
const trackChunk = readChunk(stream);
if (trackChunk.id !== "MTrk")
throw new Error("Unexpected chunk - expected MTrk, got " + trackChunk.id);
const trackStream = new Stream(trackChunk.data);
while (!trackStream.eof()) {
const event = readEvent(trackStream);
tracks[i].push(event);
}
}
return {
header,
tracks,
};
};