Spaces:
Sleeping
Sleeping
/* | |
class to encode the .mid file format | |
(depends on streamEx.js) | |
*/ | |
const OStream = require("./streamEx.js"); | |
module.exports = function OMidiFile ({ header, tracks }) { | |
function writeChunk (stream, id, data) { | |
console.assert(id.length === 4, "chunk id must be 4 byte"); | |
stream.write(id); | |
stream.writeInt32(data.length); | |
stream.write(data); | |
} | |
function writeEvent (stream, event) { | |
if (event.subtype === "unknown") | |
return; | |
stream.writeVarInt(event.deltaTime); | |
switch (event.type) { | |
case "meta": | |
stream.writeInt8(0xff); | |
switch (event.subtype) { | |
case "sequenceNumber": | |
stream.writeInt8(0x00); | |
stream.writeVarInt(2); | |
stream.writeInt16(event.number); | |
break; | |
case "text": | |
stream.writeInt8(0x01); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "copyrightNotice": | |
stream.writeInt8(0x02); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "trackName": | |
stream.writeInt8(0x03); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "instrumentName": | |
stream.writeInt8(0x04); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "lyrics": | |
stream.writeInt8(0x05); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "marker": | |
stream.writeInt8(0x06); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "cuePoint": | |
stream.writeInt8(0x07); | |
stream.writeVarInt(event.text.length); | |
stream.write(event.text); | |
break; | |
case "midiChannelPrefix": | |
stream.writeInt8(0x20); | |
stream.writeVarInt(1); | |
stream.writeInt8(event.channel); | |
break; | |
case "endOfTrack": | |
stream.writeInt8(0x2f); | |
stream.writeVarInt(0); | |
break; | |
case "setTempo": | |
stream.writeInt8(0x51); | |
stream.writeVarInt(3); | |
stream.writeInt8((event.microsecondsPerBeat >> 16) & 0xff); | |
stream.writeInt8((event.microsecondsPerBeat >> 8) & 0xff); | |
stream.writeInt8(event.microsecondsPerBeat & 0xff); | |
break; | |
case "smpteOffset": | |
stream.writeInt8(0x54); | |
stream.writeVarInt(5); | |
var frameByte = { 24: 0x00, 25: 0x20, 29: 0x40, 30: 0x60 }[event.frameRate]; | |
stream.writeInt8(event.hour | frameByte); | |
stream.writeInt8(event.min); | |
stream.writeInt8(event.sec); | |
stream.writeInt8(event.frame); | |
stream.writeInt8(event.subframe); | |
break; | |
case "timeSignature": | |
stream.writeInt8(0x58); | |
stream.writeVarInt(4); | |
stream.writeInt8(event.numerator); | |
stream.writeInt8(Math.log2(event.denominator)); | |
stream.writeInt8(event.metronome); | |
stream.writeInt8(event.thirtyseconds); | |
break; | |
case "keySignature": | |
stream.writeInt8(0x59); | |
stream.writeVarInt(2); | |
stream.writeInt8(event.key); | |
stream.writeInt8(event.scale); | |
break; | |
case "sequencerSpecific": | |
stream.writeInt8(0x7f); | |
stream.writeVarInt(event.data.length); | |
stream.write(event.data); | |
break; | |
default: | |
throw new Error("unhandled event subtype:" + event.subtype); | |
} | |
break; | |
case "sysEx": | |
stream.writeInt8(0xf0); | |
stream.writeVarInt(event.data.length); | |
stream.write(event.data); | |
break; | |
case "dividedSysEx": | |
stream.writeInt8(0xf7); | |
stream.writeVarInt(event.data.length); | |
stream.write(event.data); | |
break; | |
case "channel": | |
switch (event.subtype) { | |
case "noteOn": | |
stream.writeInt8(0x90 | event.channel); | |
stream.writeInt8(event.noteNumber); | |
stream.writeInt8(event.velocity); | |
break; | |
case "noteOff": | |
stream.writeInt8(0x80 | event.channel); | |
stream.writeInt8(event.noteNumber); | |
stream.writeInt8(event.velocity ? event.velocity : 0); | |
break; | |
case "noteAftertouch": | |
stream.writeInt8(0xa0 | event.channel); | |
stream.writeInt8(event.noteNumber); | |
stream.writeInt8(event.amount); | |
break; | |
case "controller": | |
stream.writeInt8(0xb0 | event.channel); | |
stream.writeInt8(event.controllerType); | |
stream.writeInt8(event.value); | |
break; | |
case "programChange": | |
stream.writeInt8(0xc0 | event.channel); | |
stream.writeInt8(event.programNumber); | |
break; | |
case "channelAftertouch": | |
stream.writeInt8(0xd0 | event.channel); | |
stream.writeInt8(event.amount); | |
break; | |
case "pitchBend": | |
stream.writeInt8(0xe0 | event.channel); | |
stream.writeInt8(event.value & 0xff); | |
stream.writeInt8((event.value >> 7) & 0xff); | |
break; | |
default: | |
throw new Error("unhandled event subtype:" + event.subtype); | |
} | |
break; | |
default: | |
throw new Error("unhandled event type:" + event.type); | |
} | |
} | |
const stream = new OStream(); | |
const headerChunk = new OStream(); | |
headerChunk.writeInt16(header.formatType); | |
headerChunk.writeInt16(tracks.length); | |
headerChunk.writeInt16(header.ticksPerBeat); | |
writeChunk(stream, "MThd", headerChunk.getBuffer()); | |
for (let i = 0; i < tracks.length; ++i) { | |
const trackChunk = new OStream(); | |
for (let ei = 0; ei < tracks[i].length; ++ei) | |
writeEvent(trackChunk, tracks[i][ei]); | |
writeChunk(stream, "MTrk", trackChunk.getBuffer()); | |
} | |
return stream.getArrayBuffer(); | |
}; | |