qambi
Version:
MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio
293 lines (275 loc) • 9.18 kB
JavaScript
/*
Extracts all midi events from a binary midi file, uses midi_stream.js
based on: https://github.com/gasman/jasmid
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseMIDIFile = parseMIDIFile;
var _midi_stream = require('./midi_stream');
var _midi_stream2 = _interopRequireDefault(_midi_stream);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var lastEventTypeByte = void 0;
var originalTrackName = void 0;
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 length;
event.deltaTime = stream.readVarInt();
var eventTypeByte = stream.readInt8();
//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();
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);
originalTrackName = event.text;
return event;
case 0x04:
event.subtype = 'instrumentName';
event.text = stream.read(length);
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 = void 0;
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 parseMIDIFile(buffer) {
if (buffer instanceof Uint8Array === false && buffer instanceof ArrayBuffer === false) {
console.error('buffer should be an instance of Uint8Array of ArrayBuffer');
return;
}
if (buffer instanceof ArrayBuffer) {
buffer = new Uint8Array(buffer);
}
var tracks = new Map();
var stream = new _midi_stream2.default(buffer);
var headerChunk = readChunk(stream);
if (headerChunk.id !== 'MThd' || headerChunk.length !== 6) {
throw 'Bad .mid file - header not found';
}
var headerStream = new _midi_stream2.default(headerChunk.data);
var formatType = headerStream.readInt16();
var trackCount = headerStream.readInt16();
var timeDivision = headerStream.readInt16();
if (timeDivision & 0x8000) {
throw 'Expressing time division in SMTPE frames is not supported yet';
}
var header = {
ticksPerBeat: timeDivision
};
for (var i = 0; i < trackCount; i++) {
originalTrackName = false;
var track = [];
var trackChunk = readChunk(stream);
if (trackChunk.id !== 'MTrk') {
throw 'Unexpected chunk - expected MTrk, got ' + trackChunk.id;
}
var trackStream = new _midi_stream2.default(trackChunk.data);
while (!trackStream.eof()) {
var event = readEvent(trackStream);
track.push(event);
}
var trackName = originalTrackName || 'track_' + i;
tracks.set(trackName, track);
}
return {
header: header,
tracks: tracks
};
}