UNPKG

qambi

Version:

MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio

293 lines (275 loc) 9.18 kB
/* 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 }; }