summaryrefslogtreecommitdiffstats
path: root/Processing-js/libs/inc/jasmid
diff options
context:
space:
mode:
Diffstat (limited to 'Processing-js/libs/inc/jasmid')
-rw-r--r--Processing-js/libs/inc/jasmid/LICENSE24
-rw-r--r--Processing-js/libs/inc/jasmid/midifile.js238
-rw-r--r--Processing-js/libs/inc/jasmid/replayer.js96
-rw-r--r--Processing-js/libs/inc/jasmid/stream.js69
4 files changed, 427 insertions, 0 deletions
diff --git a/Processing-js/libs/inc/jasmid/LICENSE b/Processing-js/libs/inc/jasmid/LICENSE
new file mode 100644
index 0000000..407a442
--- /dev/null
+++ b/Processing-js/libs/inc/jasmid/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2010, Matt Westcott & Ben Firshman
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * The names of its contributors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Processing-js/libs/inc/jasmid/midifile.js b/Processing-js/libs/inc/jasmid/midifile.js
new file mode 100644
index 0000000..5e60e79
--- /dev/null
+++ b/Processing-js/libs/inc/jasmid/midifile.js
@@ -0,0 +1,238 @@
+/*
+class to parse the .mid file format
+(depends on stream.js)
+*/
+function MidiFile(data) {
+ function readChunk(stream) {
+ var id = stream.read(4);
+ var length = stream.readInt32();
+ return {
+ 'id': id,
+ 'length': length,
+ 'data': stream.read(length)
+ };
+ }
+
+ var lastEventTypeByte;
+
+ function readEvent(stream) {
+ var event = {};
+ event.deltaTime = stream.readVarInt();
+ var eventTypeByte = stream.readInt8();
+ if ((eventTypeByte & 0xf0) == 0xf0) {
+ /* system / meta event */
+ if (eventTypeByte == 0xff) {
+ /* meta event */
+ event.type = 'meta';
+ var subtypeByte = stream.readInt8();
+ var 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);
+ 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:
+ // console.log("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';
+ var length = stream.readVarInt();
+ event.data = stream.read(length);
+ return event;
+ } else if (eventTypeByte == 0xf7) {
+ event.type = 'dividedSysEx';
+ var length = stream.readVarInt();
+ event.data = stream.read(length);
+ return event;
+ } else {
+ throw "Unrecognised MIDI event type byte: " + eventTypeByte;
+ }
+ } else {
+ /* channel event */
+ var param1;
+ if ((eventTypeByte & 0x80) == 0) {
+ /* running status - reuse lastEventTypeByte as the event type.
+ eventTypeByte is actually the first parameter
+ */
+ param1 = eventTypeByte;
+ eventTypeByte = lastEventTypeByte;
+ } else {
+ param1 = stream.readInt8();
+ 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';
+ }
+ 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;
+ 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);
+ stream.readInt8();
+ event.subtype = 'unknown';
+ return event;
+ */
+ }
+ }
+ }
+
+ stream = Stream(data);
+ var headerChunk = readChunk(stream);
+ if (headerChunk.id != 'MThd' || headerChunk.length != 6) {
+ throw "Bad .mid file - header not found";
+ }
+ var headerStream = Stream(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"
+ } else {
+ ticksPerBeat = timeDivision;
+ }
+
+ var header = {
+ 'formatType': formatType,
+ 'trackCount': trackCount,
+ 'ticksPerBeat': ticksPerBeat
+ }
+ var tracks = [];
+ for (var i = 0; i < header.trackCount; i++) {
+ tracks[i] = [];
+ var trackChunk = readChunk(stream);
+ if (trackChunk.id != 'MTrk') {
+ throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id;
+ }
+ var trackStream = Stream(trackChunk.data);
+ while (!trackStream.eof()) {
+ var event = readEvent(trackStream);
+ tracks[i].push(event);
+ //console.log(event);
+ }
+ }
+
+ return {
+ 'header': header,
+ 'tracks': tracks
+ }
+} \ No newline at end of file
diff --git a/Processing-js/libs/inc/jasmid/replayer.js b/Processing-js/libs/inc/jasmid/replayer.js
new file mode 100644
index 0000000..347fa1d
--- /dev/null
+++ b/Processing-js/libs/inc/jasmid/replayer.js
@@ -0,0 +1,96 @@
+var clone = function (o) {
+ if (typeof o != 'object') return (o);
+ if (o == null) return (o);
+ var ret = (typeof o.length == 'number') ? [] : {};
+ for (var key in o) ret[key] = clone(o[key]);
+ return ret;
+};
+
+function Replayer(midiFile, timeWarp, eventProcessor) {
+ var trackStates = [];
+ var beatsPerMinute = 120;
+ var ticksPerBeat = midiFile.header.ticksPerBeat;
+
+ for (var i = 0; i < midiFile.tracks.length; i++) {
+ trackStates[i] = {
+ 'nextEventIndex': 0,
+ 'ticksToNextEvent': (
+ midiFile.tracks[i].length ?
+ midiFile.tracks[i][0].deltaTime :
+ null
+ )
+ };
+ }
+
+ var nextEventInfo;
+ var samplesToNextEvent = 0;
+
+ function getNextEvent() {
+ var ticksToNextEvent = null;
+ var nextEventTrack = null;
+ var nextEventIndex = null;
+
+ for (var i = 0; i < trackStates.length; i++) {
+ if (
+ trackStates[i].ticksToNextEvent != null
+ && (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent)
+ ) {
+ ticksToNextEvent = trackStates[i].ticksToNextEvent;
+ nextEventTrack = i;
+ nextEventIndex = trackStates[i].nextEventIndex;
+ }
+ }
+ if (nextEventTrack != null) {
+ /* consume event from that track */
+ var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex];
+ if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) {
+ trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime;
+ } else {
+ trackStates[nextEventTrack].ticksToNextEvent = null;
+ }
+ trackStates[nextEventTrack].nextEventIndex += 1;
+ /* advance timings on all tracks by ticksToNextEvent */
+ for (var i = 0; i < trackStates.length; i++) {
+ if (trackStates[i].ticksToNextEvent != null) {
+ trackStates[i].ticksToNextEvent -= ticksToNextEvent
+ }
+ }
+ return {
+ "ticksToEvent": ticksToNextEvent,
+ "event": nextEvent,
+ "track": nextEventTrack
+ }
+ } else {
+ return null;
+ }
+ };
+ //
+ var midiEvent;
+ var temporal = [];
+ //
+ function processEvents() {
+ function processNext() {
+ if ( midiEvent.event.type == "meta" && midiEvent.event.subtype == "setTempo" ) {
+ // tempo change events can occur anywhere in the middle and affect events that follow
+ beatsPerMinute = 60000000 / midiEvent.event.microsecondsPerBeat;
+ }
+ if (midiEvent.ticksToEvent > 0) {
+ var beatsToGenerate = midiEvent.ticksToEvent / ticksPerBeat;
+ var secondsToGenerate = beatsToGenerate / (beatsPerMinute / 60);
+ }
+ var time = (secondsToGenerate * 1000 * timeWarp) || 0;
+ temporal.push([ midiEvent, time]);
+ midiEvent = getNextEvent();
+ };
+ //
+ if (midiEvent = getNextEvent()) {
+ while(midiEvent) processNext(true);
+ }
+ };
+ processEvents();
+ return {
+ "getData": function() {
+ return clone(temporal);
+ }
+ };
+};
diff --git a/Processing-js/libs/inc/jasmid/stream.js b/Processing-js/libs/inc/jasmid/stream.js
new file mode 100644
index 0000000..27c69ba
--- /dev/null
+++ b/Processing-js/libs/inc/jasmid/stream.js
@@ -0,0 +1,69 @@
+/* Wrapper for accessing strings through sequential reads */
+function Stream(str) {
+ var position = 0;
+
+ function read(length) {
+ var result = str.substr(position, length);
+ position += length;
+ return result;
+ }
+
+ /* read a big-endian 32-bit integer */
+ function readInt32() {
+ var result = (
+ (str.charCodeAt(position) << 24)
+ + (str.charCodeAt(position + 1) << 16)
+ + (str.charCodeAt(position + 2) << 8)
+ + str.charCodeAt(position + 3));
+ position += 4;
+ return result;
+ }
+
+ /* read a big-endian 16-bit integer */
+ function readInt16() {
+ var result = (
+ (str.charCodeAt(position) << 8)
+ + str.charCodeAt(position + 1));
+ position += 2;
+ return result;
+ }
+
+ /* read an 8-bit integer */
+ function readInt8(signed) {
+ var result = str.charCodeAt(position);
+ if (signed && result > 127) result -= 256;
+ position += 1;
+ return result;
+ }
+
+ function eof() {
+ return position >= str.length;
+ }
+
+ /* read a MIDI-style variable-length integer
+ (big-endian value in groups of 7 bits,
+ with top bit set to signify that another byte follows)
+ */
+ function readVarInt() {
+ var result = 0;
+ while (true) {
+ var b = readInt8();
+ if (b & 0x80) {
+ result += (b & 0x7f);
+ result <<= 7;
+ } else {
+ /* b is the last byte */
+ return result + b;
+ }
+ }
+ }
+
+ return {
+ 'eof': eof,
+ 'read': read,
+ 'readInt32': readInt32,
+ 'readInt16': readInt16,
+ 'readInt8': readInt8,
+ 'readVarInt': readVarInt
+ }
+} \ No newline at end of file