midijs
Version:
Read and write Standard MIDI files and enable communication with MIDI devices!
297 lines (255 loc) • 8.99 kB
JavaScript
/**
* @private
* @module midijs/lib/file/parser/event
*/
'use strict';
var buffer = require('buffer');
var BufferCursor = require('buffercursor');
var error = require('../../error');
var MetaEvent = require('../event').MetaEvent;
var SysexEvent = require('../event').SysexEvent;
var ChannelEvent = require('../event').ChannelEvent;
/**
* Encode an integer as a variable-length integer
* (up to 4 bytes in big-endian order, if
* the MSB is set, it means another byte is following).
*
* @private
* @param {Number} value - Integer to encode
* @return {Buffer} Encoded variable-length integer
*/
function encodeVarInt(value) {
var bytes = [], cursor, length, i;
do {
bytes.push(value & 0x7F);
value >>= 7;
} while (value > 0);
length = bytes.length;
cursor = new BufferCursor(new buffer.Buffer(length));
for (i = length - 1; i >= 0; i -= 1) {
if (i > 0) {
bytes[i] |= 0x80;
}
cursor.writeUInt8(bytes[i]);
}
return cursor.buffer;
}
/**
* Encode a meta MIDI event
*
* @private
* @param {module:midijs/lib/file/event~MetaEvent} event - Meta event to parse
* @exception {module:midijs/lib/error~MIDIEncoderError}
* Failed to encode the meta event
* @return {Buffer} Encoded meta event
*/
function encodeMetaEvent(event) {
var length, cursor, eventData, value, rates;
rates = [24, 25, 30, 30];
switch (event.type) {
case MetaEvent.TYPE.SEQUENCE_NUMBER:
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt16LE(event.number);
break;
case MetaEvent.TYPE.TEXT:
case MetaEvent.TYPE.COPYRIGHT_NOTICE:
case MetaEvent.TYPE.SEQUENCE_NAME:
case MetaEvent.TYPE.INSTRUMENT_NAME:
case MetaEvent.TYPE.LYRICS:
case MetaEvent.TYPE.MARKER:
case MetaEvent.TYPE.CUE_POINT:
case MetaEvent.TYPE.PROGRAM_NAME:
case MetaEvent.TYPE.DEVICE_NAME:
eventData = new BufferCursor(new buffer.Buffer(
buffer.Buffer.byteLength(event.text, 'utf8')
));
eventData.write(event.text, 'utf8');
break;
case MetaEvent.TYPE.MIDI_CHANNEL:
eventData = new BufferCursor(new buffer.Buffer(1));
eventData.writeUInt8(event.channel);
break;
case MetaEvent.TYPE.MIDI_PORT:
eventData = new BufferCursor(new buffer.Buffer(1));
eventData.writeUInt8(event.port);
break;
case MetaEvent.TYPE.END_OF_TRACK:
eventData = new BufferCursor(new buffer.Buffer(0));
break;
case MetaEvent.TYPE.SET_TEMPO:
eventData = new BufferCursor(new buffer.Buffer(3));
value = 60000000 / event.tempo;
eventData.writeUInt8(value >> 16);
eventData.writeUInt8((value >> 8) & 0xFF);
eventData.writeUInt8(value & 0xFF);
break;
case MetaEvent.TYPE.SMPTE_OFFSET:
eventData = new BufferCursor(new buffer.Buffer(5));
eventData.writeUInt8(
(rates.indexOf(event.rate) << 6) + (event.hours & 0x3F)
);
eventData.writeUInt8(event.minutes);
eventData.writeUInt8(event.seconds);
eventData.writeUInt8(event.frames);
eventData.writeUInt8(event.subframes);
break;
case MetaEvent.TYPE.TIME_SIGNATURE:
eventData = new BufferCursor(new buffer.Buffer(4));
eventData.writeUInt8(event.numerator);
eventData.writeUInt8(Math.log(event.denominator) / Math.LN2);
eventData.writeUInt8(event.metronome);
eventData.writeUInt8(192 / event.clockSignalsPerBeat);
break;
case MetaEvent.TYPE.KEY_SIGNATURE:
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt8(event.note);
eventData.writeUInt8(+!event.major);
break;
case MetaEvent.TYPE.SEQUENCER_SPECIFIC:
eventData = new BufferCursor(new buffer.Buffer(event.data.length));
eventData.copy(event.data);
break;
default:
throw new error.MIDIEncoderError(
event.type,
'known MetaEvent type'
);
}
length = encodeVarInt(eventData.length);
cursor = new BufferCursor(new buffer.Buffer(
2 + length.length + eventData.length
));
cursor.writeUInt8(0xFF);
cursor.writeUInt8(event.type);
cursor.copy(length);
cursor.copy(eventData.buffer);
return cursor.buffer;
}
/**
* Encode a system exclusive MIDI event
*
* @private
* @param {module:midijs/lib/file/event~SysexEvent} event - Sysex event to encode
* @return {Buffer} Encoded sysex event
*/
function encodeSysexEvent(event) {
var cursor, length;
length = encodeVarInt(event.data.length);
cursor = new BufferCursor(new buffer.Buffer(
1 + length.length + event.data.length
));
cursor.writeUInt8(0xF0 | event.type);
cursor.copy(length);
cursor.copy(event.data);
return cursor.buffer;
}
/**
* Encode a channel event
*
* @private
* @param {module:midijs/lib/file/event~ChannelEvent} event - Channel event to encode
* @exception {module:midijs/lib/error~MIDIEncoderError}
* Failed to encode the channel event
* @return {Buffer} Encoded channel event
*/
function encodeChannelEvent(event) {
var cursor, eventData, value;
switch (event.type) {
case ChannelEvent.TYPE.NOTE_OFF:
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt8(event.note);
eventData.writeUInt8(event.velocity);
break;
case ChannelEvent.TYPE.NOTE_ON:
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt8(event.note);
eventData.writeUInt8(event.velocity);
break;
case ChannelEvent.TYPE.NOTE_AFTERTOUCH:
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt8(event.note);
eventData.writeUInt8(event.pressure);
break;
case ChannelEvent.TYPE.CONTROLLER:
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt8(event.controller);
eventData.writeUInt8(event.value);
break;
case ChannelEvent.TYPE.PROGRAM_CHANGE:
eventData = new BufferCursor(new buffer.Buffer(1));
eventData.writeUInt8(event.program);
break;
case ChannelEvent.TYPE.CHANNEL_AFTERTOUCH:
eventData = new BufferCursor(new buffer.Buffer(1));
eventData.writeUInt8(event.pressure);
break;
case ChannelEvent.TYPE.PITCH_BEND:
value = event.value + 8192;
eventData = new BufferCursor(new buffer.Buffer(2));
eventData.writeUInt8(value & 0x7F);
eventData.writeUInt8(value >> 7);
break;
default:
throw new error.MIDIEncoderError(
event.type,
'known ChannelEvent type'
);
}
cursor = new BufferCursor(new buffer.Buffer(
1 + eventData.length
));
cursor.writeUInt8((event.type << 4) + event.channel);
cursor.copy(eventData.buffer);
return cursor.buffer;
}
/**
* Encode a MIDI event
*
* @param {module:midijs/lib/file/event~Event} event - Event to encode
* @param {?Number} runningStatus - Previous status if applicable
* @exception {module:midijs/lib/error~MIDIInvalidArgument}
* Unknown event encountered
* @return {Object} Encoded data and new running status
*/
function encodeEvent(event, runningStatus) {
var delay, eventData, cursor;
delay = encodeVarInt(event.delay);
if (event instanceof MetaEvent) {
eventData = encodeMetaEvent(event);
} else if (event instanceof SysexEvent) {
eventData = encodeSysexEvent(event);
} else if (event instanceof ChannelEvent) {
eventData = encodeChannelEvent(event);
} else {
throw new error.MIDIEncoderError(
event.constructor.name,
'known Event type'
);
}
// if current type is the same as the last one,
// we don't include it
// (sysex events cancel the running status and
// meta events ignore it)
if (event instanceof ChannelEvent) {
if (runningStatus === eventData[0]) {
eventData = eventData.slice(1);
} else {
runningStatus = eventData[0];
}
} else if (event instanceof SysexEvent) {
runningStatus = null;
}
cursor = new BufferCursor(new buffer.Buffer(
delay.length + eventData.length
));
cursor.copy(delay);
cursor.copy(eventData);
return {
runningStatus: runningStatus,
data: cursor.buffer
};
}
exports.encodeMetaEvent = encodeMetaEvent;
exports.encodeSysexEvent = encodeSysexEvent;
exports.encodeChannelEvent = encodeChannelEvent;
exports.encodeEvent = encodeEvent;