webdaw-modules
Version:
a set of modules for building a web-based DAW
317 lines (300 loc) • 10.2 kB
JavaScript
/*
based on: https://github.com/gasman/jasmid
adapted to work with heartbeatjs' type MidiEvent and Track
*/
function midiParse() {
"use strict";
var lastEventTypeByte,
trackName,
instrumentName,
//import
createStream; // defined in midi_stream.js
function readChunk(stream) {
var id = stream.read(4, true);
var length = stream.readInt32();
//console.log(length);
return {
id: id,
length: length,
data: stream.read(length, false),
};
}
function readEvent(stream) {
var event = {};
//var lastEventTypeByte; // for running status
event.deltaTime = stream.readVarInt();
var eventTypeByte = stream.readInt8();
var length;
//console.log(eventTypeByte, eventTypeByte & 0x80, 146 & 0x0f);
if ((eventTypeByte & 0xf0) == 0xf0) {
/* system / meta event */
if (eventTypeByte == 0xff) {
/* meta event */
event.type = "meta";
var subtypeByte = stream.readInt8();
// console.log(event);
length = stream.readVarInt();
switch (subtypeByte) {
case 0x00:
event.subtype = "sequenceNumber";
if (length !== 2) throw "Expected length for sequenceNumber event is 2, got " + length;
event.number = stream.readInt16();
return event;
case 0x01:
event.subtype = "text";
event.text = stream.read(length);
return event;
case 0x02:
event.subtype = "copyrightNotice";
event.text = stream.read(length);
return event;
case 0x03:
event.subtype = "trackName";
event.text = stream.read(length);
// console.log("trackName", event.text);
return event;
case 0x04:
event.subtype = "instrumentName";
event.text = stream.read(length);
instrumentName = event.text;
return event;
case 0x05:
event.subtype = "lyrics";
event.text = stream.read(length);
return event;
case 0x06:
event.subtype = "marker";
event.text = stream.read(length);
return event;
case 0x07:
event.subtype = "cuePoint";
event.text = stream.read(length);
return event;
case 0x20:
event.subtype = "midiChannelPrefix";
if (length !== 1) throw "Expected length for midiChannelPrefix event is 1, got " + length;
event.channel = stream.readInt8();
return event;
case 0x2f:
event.subtype = "endOfTrack";
if (length !== 0) throw "Expected length for endOfTrack event is 0, got " + length;
return event;
case 0x51:
event.subtype = "setTempo";
if (length !== 3) throw "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 "Expected length for smpteOffset event is 5, got " + length;
var 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 "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 "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.read(length);
return event;
default:
//if(sequencer.debug >= 2){
// console.warn('Unrecognised meta event subtype: ' + subtypeByte);
//}
event.subtype = "unknown";
event.data = stream.read(length);
return event;
}
event.data = stream.read(length);
return event;
} else if (eventTypeByte == 0xf0) {
event.type = "sysEx";
length = stream.readVarInt();
event.data = stream.read(length);
return event;
} else if (eventTypeByte == 0xf7) {
event.type = "dividedSysEx";
length = stream.readVarInt();
event.data = stream.read(length);
return event;
} else {
throw "Unrecognised MIDI event type byte: " + eventTypeByte;
}
} else {
/* channel event */
var param1;
if ((eventTypeByte & 0x80) === 0) {
/* running status - reuse lastEventTypeByte as the event type.
eventTypeByte is actually the first parameter
*/
//console.log('running status');
param1 = eventTypeByte;
eventTypeByte = lastEventTypeByte;
} else {
param1 = stream.readInt8();
//console.log('last', eventTypeByte);
lastEventTypeByte = eventTypeByte;
}
var 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";
//console.log('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;
//if(trackName === 'SH-S1-44-C09 L=SML IN=3'){
// console.log('channel pressure', trackName, param1);
//}
return event;
case 0x0e:
event.subtype = "pitchBend";
event.value = param1 + (stream.readInt8() << 7);
return event;
default:
/*
throw 'Unrecognised MIDI event type: ' + eventType;
console.log('Unrecognised MIDI event type: ' + eventType);
*/
event.value = stream.readInt8();
event.subtype = "unknown";
//console.log(event);
/*
event.noteNumber = param1;
event.velocity = stream.readInt8();
event.subtype = 'noteOn';
console.log('weirdo', trackName, param1, event.velocity);
*/
return event;
}
}
}
function parseStream(stream) {
var formatType,
trackCount,
timeDivision,
ticksPerBeat,
tracks = [],
i,
trackNames = [],
trackChunk,
trackStream,
headerChunk,
headerStream;
headerChunk = readChunk(stream);
if (headerChunk.id !== "MThd" || headerChunk.length !== 6) {
throw "Bad .mid file - header not found";
}
//console.log(headerChunk);
headerStream = createStream(headerChunk.data);
formatType = headerStream.readInt16();
trackCount = headerStream.readInt16();
timeDivision = headerStream.readInt16();
if (timeDivision & 0x8000) {
throw "Expressing time division in SMTPE frames is not supported yet";
} else {
ticksPerBeat = timeDivision;
}
var header = {
formatType: formatType,
trackCount: trackCount,
ticksPerBeat: ticksPerBeat,
};
for (i = 0; i < trackCount; i++) {
tracks[i] = [];
// trackNames[i] = trackName;
trackChunk = readChunk(stream);
if (trackChunk.id !== "MTrk") {
throw "Unexpected chunk - expected MTrk, got " + trackChunk.id;
}
trackStream = createStream(trackChunk.data);
while (!trackStream.eof()) {
var event = readEvent(trackStream);
tracks[i].push(event);
if (event.subtype === "trackName") {
// console.log(i, event);
trackNames[i] = event.text;
}
}
}
return {
header: header,
tracks: tracks,
trackNames: trackNames,
};
}
/* read a MIDI-style variable-length integer
(big-endian value in groups of 7 bits,
with top bit set to signify that another byte follows)
function readVarInt() {
var result = 0;
while (true) {
var b = readInt8();
if (b & 0x80) {
result += (b & 0x7f);
result <<= 7;
} else {
// b is the last byte
return result + b;
}
}
}
*/
sequencer.protectedScope.parseMidiFile = function(buffer) {
return parseStream(createStream(buffer));
//var dv = new DataView(buffer);
//return parseStream(dv);
};
sequencer.protectedScope.addInitMethod(function() {
createStream = sequencer.protectedScope.createStream;
});
}