UNPKG

midi-file-io

Version:

Reads, parses and writes MIDI files. Fork of NHQ's midi-file-parser.

224 lines (223 loc) 9.5 kB
"use strict"; /** * User: curtis * Date: 2019-01-20 * Time: 15:02 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.writeMidiToBuffer = writeMidiToBuffer; exports.writeMidiToFile = writeMidiToFile; const fs_1 = require("fs"); const path_1 = require("path"); const write_1 = require("./stream/write"); const types_1 = require("./types"); /** * Maps midi code to the actual number of frames */ const frameCodeMap = { 24: 0, 25: 1, 29: 2, 30: 3 }; function writeMidiToBuffer(midiData) { function writeChunk(stream, id, data) { stream.write(id); stream.writeInt32(data.length); stream.write(data); } function writeHeader(stream) { const data = new write_1.WriteStream(); data.writeInt16(midiData.header.formatType); data.writeInt16(midiData.tracks.length); data.writeInt16(midiData.header.ticksPerQuarter); writeChunk(stream, "MThd", data); } /** * todo: implement running status if you find this and can't stop yourself */ function writeTrack(stream, track) { const trackStream = new write_1.WriteStream(); function writeEvent(event) { function writeChannelData() { switch (event.subtype) { case types_1.MidiIoEventSubtype.NoteOff: { trackStream.writeInt8(0x80 | event.channel); trackStream.writeInt8(event.noteNumber); trackStream.writeInt8(event.velocity); break; } case types_1.MidiIoEventSubtype.NoteOn: { trackStream.writeInt8(0x90 | event.channel); trackStream.writeInt8(event.noteNumber); trackStream.writeInt8(event.velocity); break; } case types_1.MidiIoEventSubtype.NoteAftertouch: { trackStream.writeInt8(0x0a | event.channel); trackStream.writeInt8(event.noteNumber); trackStream.writeInt8(event.amount); break; } case types_1.MidiIoEventSubtype.Controller: { trackStream.writeInt8(0x0b | event.channel); trackStream.writeInt8(event.controllerType); trackStream.writeInt8(event.value); break; } case types_1.MidiIoEventSubtype.ProgramChange: { trackStream.writeInt8(0x0c | event.channel); trackStream.writeInt8(event.programNumber); break; } case types_1.MidiIoEventSubtype.ChannelAftertouch: { trackStream.writeInt8(0x0d | event.channel); trackStream.writeInt8(event.amount); break; } case types_1.MidiIoEventSubtype.PitchBend: { trackStream.writeInt8(0x0e | event.channel); trackStream.writeInt8(event.value & 0x7f); trackStream.writeInt8(event.value >> 7); break; } default: { throw new Error(`unrecognised MIDI event : ${event.subtype}`); } } } function writeMetaData() { function _writeText(subType) { const text = event.text || ""; trackStream.writeInt8(subType); trackStream.writeVarInt(text.length); trackStream.write(text); } // write metadata's status byte trackStream.writeInt8(0xff); switch (event.subtype) { case types_1.MidiIoEventSubtype.SequenceNumber: { trackStream.writeInt8(0x00); trackStream.writeVarInt(2); trackStream.writeInt16(event.number); break; } case types_1.MidiIoEventSubtype.Text: { _writeText(0x01); break; } case types_1.MidiIoEventSubtype.CopyrightNotice: { _writeText(0x02); break; } case types_1.MidiIoEventSubtype.TrackName: { _writeText(0x03); break; } case types_1.MidiIoEventSubtype.InstrumentName: { _writeText(0x04); break; } case types_1.MidiIoEventSubtype.Lyrics: { _writeText(0x05); break; } case types_1.MidiIoEventSubtype.Marker: { _writeText(0x06); break; } case types_1.MidiIoEventSubtype.CuePoint: { _writeText(0x07); break; } case types_1.MidiIoEventSubtype.MidiChannelPrefix: { trackStream.writeInt8(0x20); trackStream.writeVarInt(1); trackStream.writeInt8(event.channel); break; } case types_1.MidiIoEventSubtype.EndOfTrack: { trackStream.writeInt8(0x2f); trackStream.writeVarInt(0); break; } case types_1.MidiIoEventSubtype.SetTempo: { trackStream.writeInt8(0x51); trackStream.writeVarInt(3); trackStream.writeInt8(event.microsecondsPerBeat >> 16 & 0xff); trackStream.writeInt8(event.microsecondsPerBeat >> 8 & 0xff); trackStream.writeInt8(event.microsecondsPerBeat & 0xff); break; } case types_1.MidiIoEventSubtype.SmpteOffset: { const frameCode = frameCodeMap[event.frameRate]; trackStream.writeInt8(0x54); trackStream.writeVarInt(5); trackStream.writeInt8(event.hour | frameCode << 5); trackStream.writeInt8(event.min); trackStream.writeInt8(event.sec); trackStream.writeInt8(event.frame); trackStream.writeInt8(event.subframe); break; } case types_1.MidiIoEventSubtype.TimeSignature: { trackStream.writeInt8(0x58); trackStream.writeVarInt(4); trackStream.writeInt8(event.numerator); trackStream.writeInt8(Math.log2(event.denominator)); trackStream.writeInt8(event.metronome); trackStream.writeInt8(event.thirtyseconds); break; } case types_1.MidiIoEventSubtype.KeySignature: { trackStream.writeInt8(0x59); trackStream.writeVarInt(2); trackStream.writeInt8(event.key); trackStream.writeInt8(event.scale); break; } case types_1.MidiIoEventSubtype.SequencerSpecific: { trackStream.writeInt8(0x7f); trackStream.writeVarInt(event.data.length); trackStream.write(event.data); break; } default: { throw new Error(`unknown channel event: ${event.subtype}`); } } } function writeSysexData() { throw new Error("sysex not supported yet"); } // write the event's delta offset trackStream.writeVarInt(event.deltaTime); if (event.type === types_1.MidiIoEventType.Channel) { writeChannelData(); } else if (event.type === types_1.MidiIoEventType.Meta) { writeMetaData(); } else if (event.type === types_1.MidiIoEventType.SysEx) { writeSysexData(); } } track.forEach(writeEvent.bind(null)); writeChunk(stream, "MTrk", trackStream); } const stream = new write_1.WriteStream(); writeHeader(stream); midiData.tracks.forEach(writeTrack.bind(null, stream)); return stream.buffer; } /** * @throws {Error} */ function writeMidiToFile(midiData, filePath) { const buffer = writeMidiToBuffer(midiData); if (/.mid$/i.test(filePath) === false) { filePath += ".mid"; } (0, fs_1.mkdirSync)((0, path_1.parse)(filePath).dir, { recursive: true }); (0, fs_1.writeFileSync)(filePath, buffer, { encoding: "binary" }); }