webdaw-modules
Version:
a set of modules for building a web-based DAW
493 lines • 17.9 kB
JavaScript
"use strict";
// based on: https://github.com/pravdomil/jasmid.ts
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMIDIFile = void 0;
// import { BufferReader } from 'jasmid.ts';
var uniqid_1 = __importDefault(require("uniqid"));
var bufferreader_1 = require("./bufferreader");
var midi_1 = require("./util/midi");
var calculateMillis_1 = require("./calculateMillis");
var createTrack_1 = require("./createTrack");
var createNotes_1 = require("./createNotes");
function parseMIDIFile(buffer) {
var reader = new bufferreader_1.BufferReader(buffer);
var header = parseHeader(reader);
// const { timeTrack, tracks } = parseTracks(reader, header.ticksPerBeat)
var _a = parseTracks(reader, header.ticksPerBeat), tracks = _a.tracks, events = _a.events, initialTempo = _a.initialTempo, initialNumerator = _a.initialNumerator, initialDenominator = _a.initialDenominator;
return {
ppq: header.ticksPerBeat,
latency: 17,
bufferTime: 100,
tracks: tracks,
tracksById: tracks.reduce(function (acc, value) {
acc[value.id] = value;
return acc;
}, {}),
events: calculateMillis_1.calculateMillis(events, {
ppq: header.ticksPerBeat,
bpm: initialTempo,
}),
notes: createNotes_1.createNotes(events),
initialTempo: initialTempo,
initialNumerator: initialNumerator,
initialDenominator: initialDenominator,
};
}
exports.parseMIDIFile = parseMIDIFile;
function parseHeader(reader) {
var headerChunk = reader.midiChunk();
if (headerChunk.id !== "MThd" || headerChunk.length !== 6) {
throw new Error("Bad .mid file, header not found");
}
var headerReader = new bufferreader_1.BufferReader(headerChunk.data);
var formatType = headerReader.uint16();
var trackCount = headerReader.uint16();
var timeDivision = headerReader.uint16();
if (timeDivision & 0x8000) {
throw new Error("Expressing time division in SMTPE frames is not supported yet");
}
var ticksPerBeat = timeDivision;
return { formatType: formatType, trackCount: trackCount, ticksPerBeat: ticksPerBeat };
}
function parseTracks(reader, ppq) {
var initialTempo = -1;
var initialNumerator = -1;
var initialDenominator = -1;
var tracks = [];
var events = [];
while (!reader.eof()) {
var trackChunk = reader.midiChunk();
if (trackChunk.id !== "MTrk") {
throw new Error("Unexpected chunk, expected MTrk, got " + trackChunk.id);
}
var trackId = "T-" + uniqid_1.default();
var track = createTrack_1.createTrack(trackId);
var trackTrack = new bufferreader_1.BufferReader(trackChunk.data);
var ticks = 0;
var lastTypeByte = null;
while (!trackTrack.eof()) {
var data = parseEvent(trackTrack, lastTypeByte);
// console.log(data);
var event_1 = data.event, deltaTime = data.deltaTime, bpm = data.bpm, numerator = data.numerator, denominator = data.denominator, trackName = data.trackName;
if (bpm && initialTempo === -1) {
initialTempo = bpm;
}
if (numerator && initialNumerator === -1) {
initialNumerator = numerator;
}
if (denominator && initialDenominator === -1) {
initialDenominator = denominator;
}
if (trackName) {
track.name = trackName;
}
ticks += deltaTime;
// console.log('TICKS', ticks, bpm, numerator);
lastTypeByte = event_1.type;
events.push(__assign(__assign({}, event_1), { trackId: trackId,
ticks: ticks }));
}
tracks.push(track);
}
return {
events: midi_1.sortMIDIEvents(events),
tracks: tracks,
initialTempo: initialTempo,
initialNumerator: initialNumerator,
initialDenominator: initialDenominator,
};
}
function parseEvent(reader, lastTypeByte) {
var deltaTime = reader.midiInt();
var typeByte = reader.uint8();
// meta events: 0xff
// system events: 0xf0, 0xf7
// midi events: all other bytes
if (typeByte === 0xff) {
var subTypeByte = reader.uint8();
var length_1 = reader.midiInt();
switch (subTypeByte) {
// sequence number
case 0x00:
if (length_1 !== 2) {
throw new Error("Expected length for sequenceNumber event is 2, got " + length_1);
}
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.SEQUENCE_NUMBER,
number: reader.uint16(),
},
deltaTime: deltaTime,
};
// text
case 0x01:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.TEXT,
text: reader.string(length_1),
},
deltaTime: deltaTime,
};
// copyright
case 0x02:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.COPYRIGHT_NOTICE,
text: reader.string(length_1),
},
deltaTime: deltaTime,
};
// track name
case 0x03:
var trackName = reader.string(length_1);
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.TRACK_NAME,
text: trackName,
},
deltaTime: deltaTime,
trackName: trackName,
};
// instrument name
case 0x04:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.INSTRUMENT_NAME,
text: reader.string(length_1),
},
deltaTime: deltaTime,
};
// lyrics
case 0x05:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.LYRICS,
text: reader.string(length_1),
},
deltaTime: deltaTime,
};
// marker
case 0x06:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.MARKER,
text: reader.string(length_1),
},
deltaTime: deltaTime,
};
// cue point
case 0x07:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.CUE_POINT,
text: reader.string(length_1),
},
deltaTime: deltaTime,
};
// channel prefix
case 0x20:
if (length_1 !== 1) {
throw new Error("Expected length for midiChannelPrefix event is 1, got " + length_1);
}
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.CHANNEL_PREFIX,
channel: reader.uint8(),
},
deltaTime: deltaTime,
};
// end of track
case 0x2f:
if (length_1 !== 0) {
throw new Error("Expected length for endOfTrack event is 0, got " + length_1);
}
return {
event: {
descr: midi_1.END_OF_TRACK,
type: typeByte,
subType: subTypeByte,
},
deltaTime: deltaTime,
};
// tempo
case 0x51:
if (length_1 !== 3) {
throw new Error("Expected length for setTempo event is 3, got " + length_1);
}
var microsecondsPerBeat = (reader.uint8() << 16) + (reader.uint8() << 8) + reader.uint8();
var bpm = 60000000 / microsecondsPerBeat;
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.TEMPO,
bpm: bpm,
},
bpm: bpm,
deltaTime: deltaTime,
};
// smpte offset
case 0x54:
if (length_1 != 5) {
throw new Error("Expected length for smpteOffset event is 5, got " + length_1);
}
var hourByte = reader.uint8();
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.SMPTE_OFFSET,
frameRate: getFrameRate(hourByte),
hour: hourByte & 0x1f,
min: reader.uint8(),
sec: reader.uint8(),
frame: reader.uint8(),
subFrame: reader.uint8(),
},
deltaTime: deltaTime,
};
// time signature
case 0x58:
if (length_1 != 4) {
throw new Error("Expected length for timeSignature event is 4, got " + length_1);
}
var numerator = reader.uint8();
var denominator = Math.pow(2, reader.uint8());
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.TIME_SIGNATURE,
numerator: numerator,
denominator: denominator,
metronome: reader.uint8(),
thirtySeconds: reader.uint8(),
},
numerator: numerator,
denominator: denominator,
deltaTime: deltaTime,
};
// key signature
case 0x59:
if (length_1 != 2) {
throw new Error("Expected length for keySignature event is 2, got " + length_1);
}
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.KEY_SIGNATURE,
key: reader.int8(),
scale: reader.uint8(),
},
deltaTime: deltaTime,
};
// sequencer specific
case 0x7f:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: midi_1.SEQUENCER_SPECIFIC,
data: reader.read(length_1),
},
deltaTime: deltaTime,
};
// undefined
default:
return {
event: {
type: typeByte,
subType: subTypeByte,
descr: "undefined",
data: reader.read(length_1),
},
deltaTime: deltaTime,
};
}
}
else if (typeByte === 0xf0) {
// system exclusive
var length_2 = reader.midiInt();
return {
event: {
type: 0xf0,
descr: midi_1.SYSTEM_EXCLUSIVE,
data: reader.read(length_2),
},
deltaTime: deltaTime,
};
}
else if (typeByte === 0xf7) {
// divided system exclusive
var length_3 = reader.midiInt();
return {
event: {
type: 0xf0,
descr: midi_1.DIVIDED_SYSTEM_EXCLUSIVE,
data: reader.read(length_3),
},
deltaTime: deltaTime,
};
}
else {
/**
* running status - reuse lastEventTypeByte as the event type
* typeByte is actually the first parameter
*/
var isRunningStatus = (typeByte & 128) === 0;
var value = isRunningStatus ? typeByte : reader.uint8();
typeByte = isRunningStatus ? (lastTypeByte === null ? 0 : lastTypeByte) : typeByte;
// console.log(isRunningStatus, typeByte, value);
var channel = typeByte & 0x0f;
// channels[channel] = true;
switch (typeByte >> 4) {
// note off
case 0x08:
return {
event: {
type: 0x80,
descr: midi_1.NOTE_OFF,
channel: channel,
noteNumber: value,
velocity: reader.uint8(),
},
deltaTime: deltaTime,
};
// note on
case 0x09:
var velocity = reader.uint8();
return {
event: {
type: velocity === 0 ? 0x80 : 0x90,
descr: velocity === 0 ? midi_1.NOTE_OFF : midi_1.NOTE_ON,
channel: channel,
noteNumber: value,
velocity: velocity,
},
deltaTime: deltaTime,
};
// note aftertouch
case 0x0a:
return {
event: {
type: 0xa0,
descr: midi_1.NOTE_AFTERTOUCH,
channel: channel,
noteNumber: value,
amount: reader.uint8(),
},
deltaTime: deltaTime,
};
// controller
case 0x0b:
return {
event: {
type: 0xb0,
descr: midi_1.CONTROLLER,
channel: channel,
controllerNumber: value,
value: reader.uint8(),
},
deltaTime: deltaTime,
};
// program change
case 0x0c:
return {
event: {
type: 0xc0,
descr: midi_1.PROGRAM_CHANGE,
channel: channel,
program: value,
},
deltaTime: deltaTime,
};
// channel aftertouch
case 0x0d:
return {
event: {
type: 0xd0,
descr: midi_1.CHANNEL_AFTERTOUCH,
channel: channel,
amount: value,
},
deltaTime: deltaTime,
};
// pitch bend
case 0x0e:
return {
event: {
type: 0xe0,
descr: midi_1.PITCH_BEND,
channel: channel,
value: value + (reader.uint8() << 7),
},
deltaTime: deltaTime,
};
// default:
// return {
// event: {
// type: typeByte >> 4,
// descr: "unrecognized",
// channel,
// },
// deltaTime,
// };
}
}
console.log("Unrecognized MIDI event type byte: " + typeByte + " (fix this)");
return {
event: {
type: typeByte === 255 ? 0 : typeByte,
},
deltaTime: deltaTime,
};
throw new Error("Unrecognized MIDI event type byte: " + typeByte);
}
function getFrameRate(hourByte) {
switch (hourByte & 96) {
case 0x00:
return 24;
case 0x20:
return 25;
case 0x40:
return 29;
case 0x60:
return 30;
default:
return 0;
}
}
//# sourceMappingURL=parseMIDIFile.js.map