| 
					
				 | 
			
			
				@@ -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 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |