685 lines
20 KiB
JavaScript
685 lines
20 KiB
JavaScript
var Midi = {};
|
|
|
|
(function(exported) {
|
|
|
|
var DEFAULT_VOLUME = exported.DEFAULT_VOLUME = 90;
|
|
var DEFAULT_DURATION = exported.DEFAULT_DURATION = 128;
|
|
var DEFAULT_CHANNEL = exported.DEFAULT_CHANNEL = 0;
|
|
|
|
/* ******************************************************************
|
|
* Utility functions
|
|
****************************************************************** */
|
|
|
|
var Util = {
|
|
|
|
midi_letter_pitches: { a:21, b:23, c:12, d:14, e:16, f:17, g:19 },
|
|
|
|
/**
|
|
* Convert a symbolic note name (e.g. "c4") to a numeric MIDI pitch (e.g.
|
|
* 60, middle C).
|
|
*
|
|
* @param {string} n - The symbolic note name to parse.
|
|
* @returns {number} The MIDI pitch that corresponds to the symbolic note
|
|
* name.
|
|
*/
|
|
midiPitchFromNote: function(n) {
|
|
var matches = /([a-g])(#+|b+)?([0-9]+)$/i.exec(n);
|
|
var note = matches[1].toLowerCase(), accidental = matches[2] || '', octave = parseInt(matches[3], 10);
|
|
return (12 * octave) + Util.midi_letter_pitches[note] + (accidental.substr(0,1)=='#'?1:-1) * accidental.length;
|
|
},
|
|
|
|
/**
|
|
* Ensure that the given argument is converted to a MIDI pitch. Note that
|
|
* it may already be one (including a purely numeric string).
|
|
*
|
|
* @param {string|number} p - The pitch to convert.
|
|
* @returns {number} The resulting numeric MIDI pitch.
|
|
*/
|
|
ensureMidiPitch: function(p) {
|
|
if (typeof p == 'number' || !/[^0-9]/.test(p)) {
|
|
// numeric pitch
|
|
return parseInt(p, 10);
|
|
} else {
|
|
// assume it's a note name
|
|
return Util.midiPitchFromNote(p);
|
|
}
|
|
},
|
|
|
|
midi_pitches_letter: { '12':'c', '13':'c#', '14':'d', '15':'d#', '16':'e', '17':'f', '18':'f#', '19':'g', '20':'g#', '21':'a', '22':'a#', '23':'b' },
|
|
midi_flattened_notes: { 'a#':'bb', 'c#':'db', 'd#':'eb', 'f#':'gb', 'g#':'ab' },
|
|
|
|
/**
|
|
* Convert a numeric MIDI pitch value (e.g. 60) to a symbolic note name
|
|
* (e.g. "c4").
|
|
*
|
|
* @param {number} n - The numeric MIDI pitch value to convert.
|
|
* @param {boolean} [returnFlattened=false] - Whether to prefer flattened
|
|
* notes to sharpened ones. Optional, default false.
|
|
* @returns {string} The resulting symbolic note name.
|
|
*/
|
|
noteFromMidiPitch: function(n, returnFlattened) {
|
|
var octave = 0, noteNum = n, noteName, returnFlattened = returnFlattened || false;
|
|
if (n > 23) {
|
|
// noteNum is on octave 1 or more
|
|
octave = Math.floor(n/12) - 1;
|
|
// subtract number of octaves from noteNum
|
|
noteNum = n - octave * 12;
|
|
}
|
|
|
|
// get note name (c#, d, f# etc)
|
|
noteName = Util.midi_pitches_letter[noteNum];
|
|
// Use flattened notes if requested (e.g. f# should be output as gb)
|
|
if (returnFlattened && noteName.indexOf('#') > 0) {
|
|
noteName = Util.midi_flattened_notes[noteName];
|
|
}
|
|
return noteName + octave;
|
|
},
|
|
|
|
/**
|
|
* Convert beats per minute (BPM) to microseconds per quarter note (MPQN).
|
|
*
|
|
* @param {number} bpm - A number in beats per minute.
|
|
* @returns {number} The number of microseconds per quarter note.
|
|
*/
|
|
mpqnFromBpm: function(bpm) {
|
|
var mpqn = Math.floor(60000000 / bpm);
|
|
var ret=[];
|
|
do {
|
|
ret.unshift(mpqn & 0xFF);
|
|
mpqn >>= 8;
|
|
} while (mpqn);
|
|
while (ret.length < 3) {
|
|
ret.push(0);
|
|
}
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Convert microseconds per quarter note (MPQN) to beats per minute (BPM).
|
|
*
|
|
* @param {number} mpqn - The number of microseconds per quarter note.
|
|
* @returns {number} A number in beats per minute.
|
|
*/
|
|
bpmFromMpqn: function(mpqn) {
|
|
var m = mpqn;
|
|
if (typeof mpqn[0] != 'undefined') {
|
|
m = 0;
|
|
for (var i=0, l=mpqn.length-1; l >= 0; ++i, --l) {
|
|
m |= mpqn[i] << l;
|
|
}
|
|
}
|
|
return Math.floor(60000000 / mpqn);
|
|
},
|
|
|
|
/**
|
|
* Converts an array of bytes to a string of hexadecimal characters. Prepares
|
|
* it to be converted into a base64 string.
|
|
*
|
|
* @param {Array} byteArray - Array of bytes to be converted.
|
|
* @returns {string} Hexadecimal string, e.g. "097B8A".
|
|
*/
|
|
codes2Str: function(byteArray) {
|
|
var string = "";
|
|
byteArray.forEach(byte => string += String.fromCharCode(byte));
|
|
return string;
|
|
},
|
|
|
|
/**
|
|
* Converts a string of hexadecimal values to an array of bytes. It can also
|
|
* add remaining "0" nibbles in order to have enough bytes in the array as the
|
|
* `finalBytes` parameter.
|
|
*
|
|
* @param {string} str - string of hexadecimal values e.g. "097B8A"
|
|
* @param {number} [finalBytes] - Optional. The desired number of bytes
|
|
* (not nibbles) that the returned array should contain.
|
|
* @returns {Array} An array of nibbles.
|
|
*/
|
|
str2Bytes: function (str, finalBytes) {
|
|
if (finalBytes) {
|
|
while ((str.length / 2) < finalBytes) { str = "0" + str; }
|
|
}
|
|
|
|
var bytes = [];
|
|
for (var i=str.length-1; i>=0; i = i-2) {
|
|
var chars = i === 0 ? str[i] : str[i-1] + str[i];
|
|
bytes.unshift(parseInt(chars, 16));
|
|
}
|
|
|
|
return bytes;
|
|
},
|
|
|
|
/**
|
|
* Translates number of ticks to MIDI timestamp format, returning an array
|
|
* of bytes with the time values. MIDI has a very particular way to express
|
|
* time; take a good look at the spec before ever touching this function.
|
|
*
|
|
* @param {number} ticks - Number of ticks to be translated.
|
|
* @returns {number} Array of bytes that form the MIDI time value.
|
|
*/
|
|
translateTickTime: function(ticks) {
|
|
var buffer = ticks & 0x7F;
|
|
|
|
while (ticks = ticks >> 7) {
|
|
buffer <<= 8;
|
|
buffer |= ((ticks & 0x7F) | 0x80);
|
|
}
|
|
|
|
var bList = [];
|
|
while (true) {
|
|
bList.push(buffer & 0xff);
|
|
|
|
if (buffer & 0x80) { buffer >>= 8; }
|
|
else { break; }
|
|
}
|
|
return bList;
|
|
},
|
|
|
|
};
|
|
|
|
/* ******************************************************************
|
|
* Event class
|
|
****************************************************************** */
|
|
|
|
/**
|
|
* Construct a MIDI event.
|
|
*
|
|
* Parameters include:
|
|
* - time [optional number] - Ticks since previous event.
|
|
* - type [required number] - Type of event.
|
|
* - channel [required number] - Channel for the event.
|
|
* - param1 [required number] - First event parameter.
|
|
* - param2 [optional number] - Second event parameter.
|
|
*/
|
|
var MidiEvent = function(params) {
|
|
if (!this) return new MidiEvent(params);
|
|
if (params &&
|
|
(params.type !== null || params.type !== undefined) &&
|
|
(params.channel !== null || params.channel !== undefined) &&
|
|
(params.param1 !== null || params.param1 !== undefined)) {
|
|
this.setTime(params.time);
|
|
this.setType(params.type);
|
|
this.setChannel(params.channel);
|
|
this.setParam1(params.param1);
|
|
this.setParam2(params.param2);
|
|
}
|
|
};
|
|
|
|
// event codes
|
|
MidiEvent.NOTE_OFF = 0x80;
|
|
MidiEvent.NOTE_ON = 0x90;
|
|
MidiEvent.AFTER_TOUCH = 0xA0;
|
|
MidiEvent.CONTROLLER = 0xB0;
|
|
MidiEvent.PROGRAM_CHANGE = 0xC0;
|
|
MidiEvent.CHANNEL_AFTERTOUCH = 0xD0;
|
|
MidiEvent.PITCH_BEND = 0xE0;
|
|
|
|
|
|
/**
|
|
* Set the time for the event in ticks since the previous event.
|
|
*
|
|
* @param {number} ticks - The number of ticks since the previous event. May
|
|
* be zero.
|
|
*/
|
|
MidiEvent.prototype.setTime = function(ticks) {
|
|
this.time = Util.translateTickTime(ticks || 0);
|
|
};
|
|
|
|
/**
|
|
* Set the type of the event. Must be one of the event codes on MidiEvent.
|
|
*
|
|
* @param {number} type - Event type.
|
|
*/
|
|
MidiEvent.prototype.setType = function(type) {
|
|
if (type < MidiEvent.NOTE_OFF || type > MidiEvent.PITCH_BEND) {
|
|
throw new Error("Trying to set an unknown event: " + type);
|
|
}
|
|
|
|
this.type = type;
|
|
};
|
|
|
|
/**
|
|
* Set the channel for the event. Must be between 0 and 15, inclusive.
|
|
*
|
|
* @param {number} channel - The event channel.
|
|
*/
|
|
MidiEvent.prototype.setChannel = function(channel) {
|
|
if (channel < 0 || channel > 15) {
|
|
throw new Error("Channel is out of bounds.");
|
|
}
|
|
|
|
this.channel = channel;
|
|
};
|
|
|
|
/**
|
|
* Set the first parameter for the event. Must be between 0 and 255,
|
|
* inclusive.
|
|
*
|
|
* @param {number} p - The first event parameter value.
|
|
*/
|
|
MidiEvent.prototype.setParam1 = function(p) {
|
|
this.param1 = p;
|
|
};
|
|
|
|
/**
|
|
* Set the second parameter for the event. Must be between 0 and 255,
|
|
* inclusive.
|
|
*
|
|
* @param {number} p - The second event parameter value.
|
|
*/
|
|
MidiEvent.prototype.setParam2 = function(p) {
|
|
this.param2 = p;
|
|
};
|
|
|
|
/**
|
|
* Serialize the event to an array of bytes.
|
|
*
|
|
* @returns {Array} The array of serialized bytes.
|
|
*/
|
|
MidiEvent.prototype.toBytes = function() {
|
|
var byteArray = [];
|
|
|
|
var typeChannelByte = this.type | (this.channel & 0xF);
|
|
|
|
byteArray.push.apply(byteArray, this.time);
|
|
byteArray.push(typeChannelByte);
|
|
byteArray.push(this.param1);
|
|
|
|
// Some events don't have a second parameter
|
|
if (this.param2 !== undefined && this.param2 !== null) {
|
|
byteArray.push(this.param2);
|
|
}
|
|
return byteArray;
|
|
};
|
|
|
|
/* ******************************************************************
|
|
* MetaEvent class
|
|
****************************************************************** */
|
|
|
|
/**
|
|
* Construct a meta event.
|
|
*
|
|
* Parameters include:
|
|
* - time [optional number] - Ticks since previous event.
|
|
* - type [required number] - Type of event.
|
|
* - data [optional array|string] - Event data.
|
|
*/
|
|
var MetaEvent = function(params) {
|
|
if (!this) return new MetaEvent(params);
|
|
var p = params || {};
|
|
this.setTime(params.time);
|
|
this.setType(params.type);
|
|
this.setData(params.data);
|
|
};
|
|
|
|
MetaEvent.SEQUENCE = 0x00;
|
|
MetaEvent.TEXT = 0x01;
|
|
MetaEvent.COPYRIGHT = 0x02;
|
|
MetaEvent.TRACK_NAME = 0x03;
|
|
MetaEvent.INSTRUMENT = 0x04;
|
|
MetaEvent.LYRIC = 0x05;
|
|
MetaEvent.MARKER = 0x06;
|
|
MetaEvent.CUE_POINT = 0x07;
|
|
MetaEvent.CHANNEL_PREFIX = 0x20;
|
|
MetaEvent.END_OF_TRACK = 0x2f;
|
|
MetaEvent.TEMPO = 0x51;
|
|
MetaEvent.SMPTE = 0x54;
|
|
MetaEvent.TIME_SIG = 0x58;
|
|
MetaEvent.KEY_SIG = 0x59;
|
|
MetaEvent.SEQ_EVENT = 0x7f;
|
|
|
|
/**
|
|
* Set the time for the event in ticks since the previous event.
|
|
*
|
|
* @param {number} ticks - The number of ticks since the previous event. May
|
|
* be zero.
|
|
*/
|
|
MetaEvent.prototype.setTime = function(ticks) {
|
|
this.time = Util.translateTickTime(ticks || 0);
|
|
};
|
|
|
|
/**
|
|
* Set the type of the event. Must be one of the event codes on MetaEvent.
|
|
*
|
|
* @param {number} t - Event type.
|
|
*/
|
|
MetaEvent.prototype.setType = function(t) {
|
|
this.type = t;
|
|
};
|
|
|
|
/**
|
|
* Set the data associated with the event. May be a string or array of byte
|
|
* values.
|
|
*
|
|
* @param {string|Array} d - Event data.
|
|
*/
|
|
MetaEvent.prototype.setData = function(d) {
|
|
this.data = d;
|
|
};
|
|
|
|
/**
|
|
* Serialize the event to an array of bytes.
|
|
*
|
|
* @returns {Array} The array of serialized bytes.
|
|
*/
|
|
MetaEvent.prototype.toBytes = function() {
|
|
if (!this.type) {
|
|
throw new Error("Type for meta-event not specified.");
|
|
}
|
|
|
|
var byteArray = [];
|
|
byteArray.push.apply(byteArray, this.time);
|
|
byteArray.push(0xFF, this.type);
|
|
|
|
// If data is an array, we assume that it contains several bytes. We
|
|
// apend them to byteArray.
|
|
if (Array.isArray(this.data)) {
|
|
byteArray.push(this.data.length);
|
|
byteArray.push.apply(byteArray, this.data);
|
|
} else if (typeof this.data == 'number') {
|
|
byteArray.push(1, this.data);
|
|
} else if (this.data !== null && this.data !== undefined) {
|
|
// assume string; may be a bad assumption
|
|
byteArray.push(this.data.length);
|
|
var dataBytes = this.data.split('').map(function(x){ return x.charCodeAt(0) });
|
|
byteArray.push.apply(byteArray, dataBytes);
|
|
} else {
|
|
byteArray.push(0);
|
|
}
|
|
|
|
return byteArray;
|
|
};
|
|
|
|
/* ******************************************************************
|
|
* Track class
|
|
****************************************************************** */
|
|
|
|
/**
|
|
* Construct a MIDI track.
|
|
*
|
|
* Parameters include:
|
|
* - events [optional array] - Array of events for the track.
|
|
*/
|
|
var Track = function(config) {
|
|
if (!this) return new Track(config);
|
|
var c = config || {};
|
|
this.events = c.events || [];
|
|
};
|
|
|
|
Track.START_BYTES = [0x4d, 0x54, 0x72, 0x6b];
|
|
Track.END_BYTES = [0x00, 0xFF, 0x2F, 0x00];
|
|
|
|
/**
|
|
* Add an event to the track.
|
|
*
|
|
* @param {MidiEvent|MetaEvent} event - The event to add.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.addEvent = function(event) {
|
|
this.events.push(event);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add a note-on event to the track.
|
|
*
|
|
* @param {number} channel - The channel to add the event to.
|
|
* @param {number|string} pitch - The pitch of the note, either numeric or
|
|
* symbolic.
|
|
* @param {number} [time=0] - The number of ticks since the previous event,
|
|
* defaults to 0.
|
|
* @param {number} [velocity=90] - The volume for the note, defaults to
|
|
* DEFAULT_VOLUME.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.addNoteOn = Track.prototype.noteOn = function(channel, pitch, time, velocity) {
|
|
this.events.push(new MidiEvent({
|
|
type: MidiEvent.NOTE_ON,
|
|
channel: channel,
|
|
param1: Util.ensureMidiPitch(pitch),
|
|
param2: velocity || DEFAULT_VOLUME,
|
|
time: time || 0,
|
|
}));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add a note-off event to the track.
|
|
*
|
|
* @param {number} channel - The channel to add the event to.
|
|
* @param {number|string} pitch - The pitch of the note, either numeric or
|
|
* symbolic.
|
|
* @param {number} [time=0] - The number of ticks since the previous event,
|
|
* defaults to 0.
|
|
* @param {number} [velocity=90] - The velocity the note was released,
|
|
* defaults to DEFAULT_VOLUME.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.addNoteOff = Track.prototype.noteOff = function(channel, pitch, time, velocity) {
|
|
this.events.push(new MidiEvent({
|
|
type: MidiEvent.NOTE_OFF,
|
|
channel: channel,
|
|
param1: Util.ensureMidiPitch(pitch),
|
|
param2: velocity || DEFAULT_VOLUME,
|
|
time: time || 0,
|
|
}));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add a note-on and -off event to the track.
|
|
*
|
|
* @param {number} channel - The channel to add the event to.
|
|
* @param {number|string} pitch - The pitch of the note, either numeric or
|
|
* symbolic.
|
|
* @param {number} dur - The duration of the note, in ticks.
|
|
* @param {number} [time=0] - The number of ticks since the previous event,
|
|
* defaults to 0.
|
|
* @param {number} [velocity=90] - The velocity the note was released,
|
|
* defaults to DEFAULT_VOLUME.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.addNote = Track.prototype.note = function(channel, pitch, dur, time, velocity) {
|
|
this.noteOn(channel, pitch, time, velocity);
|
|
if (dur) {
|
|
this.noteOff(channel, pitch, dur, velocity);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add a note-on and -off event to the track for each pitch in an array of pitches.
|
|
*
|
|
* @param {number} channel - The channel to add the event to.
|
|
* @param {array} chord - An array of pitches, either numeric or
|
|
* symbolic.
|
|
* @param {number} dur - The duration of the chord, in ticks.
|
|
* @param {number} [velocity=90] - The velocity of the chord,
|
|
* defaults to DEFAULT_VOLUME.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.addChord = Track.prototype.chord = function(channel, chord, dur, velocity) {
|
|
if (!Array.isArray(chord) && !chord.length) {
|
|
throw new Error('Chord must be an array of pitches');
|
|
}
|
|
chord.forEach(function(note) {
|
|
this.noteOn(channel, note, 0, velocity);
|
|
}, this);
|
|
chord.forEach(function(note, index) {
|
|
if (index === 0) {
|
|
this.noteOff(channel, note, dur);
|
|
} else {
|
|
this.noteOff(channel, note);
|
|
}
|
|
}, this);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Set instrument for the track.
|
|
*
|
|
* @param {number} channel - The channel to set the instrument on.
|
|
* @param {number} instrument - The instrument to set it to.
|
|
* @param {number} [time=0] - The number of ticks since the previous event,
|
|
* defaults to 0.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.setInstrument = Track.prototype.instrument = function(channel, instrument, time) {
|
|
this.events.push(new MidiEvent({
|
|
type: MidiEvent.PROGRAM_CHANGE,
|
|
channel: channel,
|
|
param1: instrument,
|
|
time: time || 0,
|
|
}));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Set the tempo for the track.
|
|
*
|
|
* @param {number} bpm - The new number of beats per minute.
|
|
* @param {number} [time=0] - The number of ticks since the previous event,
|
|
* defaults to 0.
|
|
* @returns {Track} The current track.
|
|
*/
|
|
Track.prototype.setTempo = Track.prototype.tempo = function(bpm, time) {
|
|
this.events.push(new MetaEvent({
|
|
type: MetaEvent.TEMPO,
|
|
data: Util.mpqnFromBpm(bpm),
|
|
time: time || 0,
|
|
}));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Serialize the track to an array of bytes.
|
|
*
|
|
* @returns {Array} The array of serialized bytes.
|
|
*/
|
|
Track.prototype.toBytes = function() {
|
|
var trackLength = 0;
|
|
var eventBytes = [];
|
|
var startBytes = Track.START_BYTES;
|
|
var endBytes = Track.END_BYTES;
|
|
|
|
var addEventBytes = function(event) {
|
|
var bytes = event.toBytes();
|
|
trackLength += bytes.length;
|
|
eventBytes.push.apply(eventBytes, bytes);
|
|
};
|
|
|
|
this.events.forEach(addEventBytes);
|
|
|
|
// Add the end-of-track bytes to the sum of bytes for the track, since
|
|
// they are counted (unlike the start-of-track ones).
|
|
trackLength += endBytes.length;
|
|
|
|
// Makes sure that track length will fill up 4 bytes with 0s in case
|
|
// the length is less than that (the usual case).
|
|
var lengthBytes = Util.str2Bytes(trackLength.toString(16), 4);
|
|
|
|
return startBytes.concat(lengthBytes, eventBytes, endBytes);
|
|
};
|
|
|
|
/* ******************************************************************
|
|
* File class
|
|
****************************************************************** */
|
|
|
|
/**
|
|
* Construct a file object.
|
|
*
|
|
* Parameters include:
|
|
* - ticks [optional number] - Number of ticks per beat, defaults to 128.
|
|
* Must be 1-32767.
|
|
* - tracks [optional array] - Track data.
|
|
*/
|
|
var File = function(config){
|
|
if (!this) return new File(config);
|
|
|
|
var c = config || {};
|
|
if (c.ticks) {
|
|
if (typeof c.ticks !== 'number') {
|
|
throw new Error('Ticks per beat must be a number!');
|
|
return;
|
|
}
|
|
if (c.ticks <= 0 || c.ticks >= (1 << 15) || c.ticks % 1 !== 0) {
|
|
throw new Error('Ticks per beat must be an integer between 1 and 32767!');
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.ticks = c.ticks || 128;
|
|
this.tracks = c.tracks || [];
|
|
};
|
|
|
|
File.HDR_CHUNKID = "MThd"; // File magic cookie
|
|
File.HDR_CHUNK_SIZE = "\x00\x00\x00\x06"; // Header length for SMF
|
|
File.HDR_TYPE0 = "\x00\x00"; // Midi Type 0 id
|
|
File.HDR_TYPE1 = "\x00\x01"; // Midi Type 1 id
|
|
|
|
/**
|
|
* Add a track to the file.
|
|
*
|
|
* @param {Track} track - The track to add.
|
|
*/
|
|
File.prototype.addTrack = function(track) {
|
|
if (track) {
|
|
this.tracks.push(track);
|
|
return this;
|
|
} else {
|
|
track = new Track();
|
|
this.tracks.push(track);
|
|
return track;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Serialize the MIDI file to an array of bytes.
|
|
*
|
|
* @returns {Array} The array of serialized bytes.
|
|
*/
|
|
File.prototype.toBytes = function() {
|
|
var trackCount = this.tracks.length.toString(16);
|
|
|
|
// prepare the file header
|
|
var bytes = File.HDR_CHUNKID + File.HDR_CHUNK_SIZE;
|
|
|
|
// set Midi type based on number of tracks
|
|
if (parseInt(trackCount, 16) > 1) {
|
|
bytes += File.HDR_TYPE1;
|
|
} else {
|
|
bytes += File.HDR_TYPE0;
|
|
}
|
|
|
|
// add the number of tracks (2 bytes)
|
|
bytes += Util.codes2Str(Util.str2Bytes(trackCount, 2));
|
|
// add the number of ticks per beat (currently hardcoded)
|
|
bytes += String.fromCharCode((this.ticks/256), this.ticks%256);;
|
|
|
|
// iterate over the tracks, converting to bytes too
|
|
this.tracks.forEach(function(track) {
|
|
bytes += Util.codes2Str(track.toBytes());
|
|
});
|
|
|
|
return bytes;
|
|
};
|
|
|
|
/* ******************************************************************
|
|
* Exports
|
|
****************************************************************** */
|
|
|
|
exported.Util = Util;
|
|
exported.File = File;
|
|
exported.Track = Track;
|
|
exported.Event = MidiEvent;
|
|
exported.MetaEvent = MetaEvent;
|
|
|
|
})( Midi );
|
|
|
|
if (typeof module != 'undefined' && module !== null) {
|
|
module.exports = Midi;
|
|
} else if (typeof exports != 'undefined' && exports !== null) {
|
|
exports = Midi;
|
|
} else {
|
|
this.Midi = Midi;
|
|
}
|