Skip to content

Commit

Permalink
Added basic MetaEvents and refactored and simplified they way a Track…
Browse files Browse the repository at this point in the history
… handles its events.
  • Loading branch information
sergi committed Nov 8, 2010
1 parent 9639277 commit 40eb701
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Since timings are not specified the defaults will be taken.
});

// Create a track that contains the events to play the notes above
var track = new MidiTrack(noteEvents);
var track = new MidiTrack({ events: noteEvents });

// Creates an object that contains the final MIDI track in base64 and some
// useful methods.
Expand Down
126 changes: 104 additions & 22 deletions midi.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,25 @@ var EVT_PROGRAM_CHANGE = 0xC;
var EVT_CHANNEL_AFTERTOUCH = 0xD;
var EVT_PITCH_BEND = 0xE;

var META_SEQUENCE = 0x00;
var META_TEXT = 0x01;
var META_COPYRIGHT = 0x02;
var META_TRACK_NAME = 0x03;
var META_INSTRUMENT = 0x04;
var META_LYRIC = 0x05;
var META_MARKER = 0x06;
var META_CUE_POINT = 0x07;
var META_CHANNEL_PREFIX = 0x20;
var META_END_OF_TRACK = 0x2f;
var META_TEMPO = 0x51;
var META_SMPTE = 0x54;
var META_TIME_SIG = 0x58;
var META_KEY_SIG = 0x59;
var META_SEQ_EVENT = 0x7f;

// This is the conversion table from notes to its MIDI number. Provided for
// convenience, it is not used in this code.
var noteTable =
window.noteTable = { "G9": 0x7F, "Gb9": 0x7E, "F9": 0x7D, "E9": 0x7C, "Eb9": 0x7B,
var noteTable = { "G9": 0x7F, "Gb9": 0x7E, "F9": 0x7D, "E9": 0x7C, "Eb9": 0x7B,
"D9": 0x7A, "Db9": 0x79, "C9": 0x78, "B8": 0x77, "Bb8": 0x76, "A8": 0x75, "Ab8": 0x74,
"G8": 0x73, "Gb8": 0x72, "F8": 0x71, "E8": 0x70, "Eb8": 0x6F, "D8": 0x6E, "Db8": 0x6D,
"C8": 0x6C, "B7": 0x6B, "Bb7": 0x6A, "A7": 0x69, "Ab7": 0x68, "G7": 0x67, "Gb7": 0x66,
Expand Down Expand Up @@ -104,6 +119,11 @@ function str2Bytes(str, finalBytes) {
return bytes;
}

function isArray(obj) {
return !!(obj && obj.concat && obj.unshift && !obj.callee);
};


/**
* Translates number of ticks to MIDI timestamp format, returning an array of
* bytes with the time values. Midi has a very particular time to express time,
Expand Down Expand Up @@ -306,22 +326,42 @@ MidiEvent.prototype = {
var MetaEvent = window.MetaEvent = function(params) {
if (params) {
this.setType(params.type);
this.setChannel(params.channel);
this.setParam1(params.param1);
this.setParam2(params.param2);
this.setData(params.data);
}
};

MetaEvent.prototype = {
setType: function(t) {
this.type = t;
},
setData: function(d) {
this.data = d;
},
toBytes: function() {
if (!this.type || !this.data) {
throw new Error("Type or data for meta-event not specified.");
}

var MidiTrack = window.MidiTrack = function(events) {
this.events = {
meta: [],
midi: [],
sysex: []
};
var byteArray = [0xff, this.type];

if (events) {
for (var i=0, event; event = events[i]; i++) { this.addEvent(event); }
// If data is an array, we assume that it contains several bytes. We
// apend them to byteArray.
if (isArray(this.data)) {
Array.prototype.push.apply(byteArray, this.data);
}

return byteArray;
}
}

var MidiTrack = function(cfg) {
this.events = [];
for (var p in cfg) {
if (cfg.hasOwnProperty(p)) {
// Get the setter for the property. The property is capitalized.
// Probably a try/catch should go here.
this["set" + p.charAt(0).toUpperCase() + p.substring(1)](cfg[p]);
}
}
};

Expand All @@ -330,19 +370,59 @@ MidiTrack.TRACK_START = [0x4d, 0x54, 0x72, 0x6b];
MidiTrack.TRACK_END = [0x0, 0xFF, 0x2F, 0x0];

MidiTrack.prototype = {
/*
* Adds an event to the track.
*
* @param event {MidiEvent} Event to add to the track
* @returns the track where the event has been added
*/
addEvent: function(event) {
var type = "midi";

if (event instanceof MetaEvent) { type = "meta"; }
this.events[type].push(event);

this.events.push(event);
return this;
},
setEvents: function(events) {
Array.prototype.push.apply(this.events, events);
return this;
},
/*
* Adds a text meta-event to the track.
*
* @param type {Number} type of the text meta-event
* @param text {String} Optional. Text of the meta-event.
* @returns the track where the event ahs been added
*/
setText: function(type, text) {
// If the param text is not specified, it is assumed that a generic
// text is wanted and that the type parameter is the actual text to be
// used.
if (!text) {
type = META_TEXT;
text = type;
}
return this.addEvent(new MetaEvent({ type: type, data: text }));
},
// The following are setters for different kinds of text in MIDI, they all
// use the |setText| method as a proxy.
setCopyright: function(text) { return this.setText(META_COPYRIGHT, text); },
setTrackName: function(text) { return this.setText(META_TRACK_NAME, text); },
setInstrument: function(text) { return this.setText(META_INSTRUMENT, text); },
setLyric: function(text) { return this.setText(META_LYRIC, text); },
setMarker: function(text) { return this.setText(META_MARKER, text); },
setCuePoint: function(text) { return this.setText(META_CUE_POINT, text); },

setTempo: function(tempo) {
this.addEvent(new MetaEvent({ type: META_TEMPO, data: tempo }));
},
setTimeSig: function() {
// TBD
},
setKeySig: function() {
// TBD
},

toBytes: function() {
var trackLength = 0;
var eventBytes = [];
var metaEvents = this.events.meta;
var midiEvents = this.events.midi;
var startBytes = MidiTrack.TRACK_START;
var endBytes = MidiTrack.TRACK_END;

Expand All @@ -358,8 +438,7 @@ MidiTrack.prototype = {
Array.prototype.push.apply(eventBytes, bytes);
};

metaEvents.forEach(addEventBytes);
midiEvents.forEach(addEventBytes);
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).
Expand All @@ -373,4 +452,7 @@ MidiTrack.prototype = {
}
};

window.MidiTrack = MidiTrack;
window.noteTable = noteTable;

})(this);
14 changes: 6 additions & 8 deletions test/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
},
*/
testMidiWriter: function() {
var track = new MidiTrack([this.evt1, this.evt2]);
var track = new MidiTrack({ events: [this.evt1, this.evt2] });
var midiHex = MidiWriter({ tracks: [track] });
//midiHex.save();
//a.src = uri//"http://www.a1freesoundeffects.com/popular12008/sword.mp3";
Expand Down Expand Up @@ -153,14 +153,12 @@
A.isObject(track);
A.isInstanceOf(MidiTrack, track);
A.isObject(track.events);
A.areEqual(0, track.events.midi.length);
A.areEqual(0, track.events.meta.length);
A.areEqual(0, track.events.sysex.length);
A.areEqual(0, track.events.length);
A.areEqual([0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x4, 0x00, 0xff, 0x2f, 0x00].toString()
,track.toBytes().toString());
},
testCreateWithEvents: function() {
var track = new MidiTrack([this.evt1, this.evt2]);
var track = new MidiTrack({events: [this.evt1, this.evt2] });
//A.areEqual("4d54726b0000000f00903c0a8100803c0a00ff2f00", ArrayToHexString(track.toBytes()));
}
});
Expand All @@ -176,11 +174,11 @@
Array.prototype.push.apply(noteEvents, MidiEvent.createNote(note));
});

var track = new MidiTrack(noteEvents);
var track = new MidiTrack({ events: noteEvents });
var song = MidiWriter({ tracks: [track] });

console.log(song.b64);
song.play();
A.areEqual("TVRoZAAAAAYAAAABAIBNVHJrAAAAHwCQPFqBAIA8WgCQQFqBAIBAWgCQQ1qBAIBDWgD/LwA=", song.b64);
// song.play();
song.save();
}
});
Expand Down

0 comments on commit 40eb701

Please sign in to comment.