import struct import os class MIDIEvent: def __init__(self, time): self.timeVal = time self.vel = 0 self.note = 0 def setChannel(self, val): self.channel = val def setType(self, type): self.type = type def setOffset(self, type): self.offset = type def setNote(self, note): self.note = note def setVelocity(self, vel): self.vel = vel def setAftertouch(self, val): self.aftertouch = val def setProgram(self, val): self.program = val def setController(self, val): self.controller = val def setValue(self, val): self.value = val def setChannelAftertouch(self, val): self.channelAftertouch = val def setPitchBend(self, val): self.pitchBend = val class MIDITrack: def __init__(self): self.endian = '>' self.events = [] self.setName('') self.setMaxTime(0) self.setHasNormalEvent(False) def parseBuffer(self, buffer): fmt = self.endian+'i' c = struct.calcsize(fmt) chunkSize, = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] #print "chunkSize=", chunkSize # read the chunk chunk = buffer[:chunkSize] # get data after chunk buffer = buffer[chunkSize:] timeOffset = 0 #count = 0 # loop through all events while len(chunk) > 0: timeVal, chunk = self.readVarLen(chunk) timeOffset += timeVal ### print "timeVal=", timeVal eventType, = struct.unpack('B', chunk[:1]) #print "event=", event if eventType == 0xff: #print "meta" chunk = chunk[1:] chunk = self.parseMetaEvent(timeVal, chunk) elif eventType == 0xF0: print "SysEx" chunk = chunk[1:] val, chunk = self.readVarLen(chunk) chunk = chunk[val:] elif eventType == 0xF7: print "DivSysEx" else: self.setHasNormalEvent(True) chunk = self.parseEvent(eventType, timeOffset, chunk) #count += 1 #if count > 10: # break self.setMaxTime(timeOffset) return buffer def setMaxTime(self, time): self._maxTime = time def maxTime(self): return self._maxTime def setHasNormalEvent(self, val): self.normalEvent = val def hasNormalEvent(self): return self.normalEvent def setName(self, name): self._name = name def name(self): return self._name def parseEvent(self, eventType, timeVal, buffer): type = (eventType & 0xF0) >> 4; ### print "NORMAL EVENT : ", type if type < 0x08: type = self.lastType # running status ### print "lastType=", type else: # advance buffer = buffer[1:] self.lastType = type channel = eventType & 0x0F e = MIDIEvent(timeVal) e.setChannel(channel) e.setType(type) e.setOffset(timeVal) if type == 0x08: # note off fmt = '2B' c = struct.calcsize(fmt) note, vel = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] e.setNote(note) e.setVelocity(vel) elif type == 0x09: # note on fmt = '2B' c = struct.calcsize(fmt) note, vel = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] e.setNote(note) e.setVelocity(vel) elif type == 0x0a: # note aftertouch fmt = '2B' c = struct.calcsize(fmt) note, aftertouch = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] e.setNote(note) e.setAftertouch(aftertouch) elif type == 0x0b: # controller fmt = '2B' c = struct.calcsize(fmt) controller, value = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] e.setController(controller) e.setValue(value) elif type == 0x0c: # program change fmt = '1B' c = struct.calcsize(fmt) pgm, = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] e.setProgram(pgm) elif type == 0x0d: # channel aftertouch fmt = '1B' c = struct.calcsize(fmt) chnAftertouch, = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] e.setChannelAftertouch(chnAftertouch) elif type == 0x0e: # pitch bend fmt = '2B' c = struct.calcsize(fmt) b1, b2 = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] val = (b1 << 7) | (b2 & 0x7f) e.setPitchBend(val) elif type == 0xf0: # sys ex val, buffer = self.readVarLen(buffer) buffer = buffer[val:] elif type == 0xf7: # div sys ex val, buffer = self.readVarLen(buffer) buffer = buffer[val:] elif type == 0xff: # meta event val, buffer = self.readVarLen(buffer) buffer = buffer[val:] self.events.append(e) return buffer def parseMetaEvent(self, timeVal, buffer): type, = struct.unpack('B', buffer[:1]) buffer = buffer[1:] ### print "parseMetaEvent = ", type if type == 0: # seq number seqLen, buffer = self.getByte(buffer) fmt = '2B' c = struct.calcsize(fmt) msb, lsb = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] elif type == 1: # text event strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) txt, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "txtEvent=", txt elif type == 2: # copyright strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) cr, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "cr=", cr elif type == 3: # seq name strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) seqName, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "seqName=", seqName self.setName(seqName) elif type == 4: # instr name strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) instrName, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "instrName=", instrName elif type == 5: # lyrics strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) lyrics, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "lyrics=", lyrics elif type == 6: # marker strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) marker, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "marker=", marker elif type == 7: # cue point strlen, buffer = self.getByte(buffer) fmt = str(strlen)+'s' c = struct.calcsize(fmt) cuePoint, = struct.unpack(fmt, buffer[:strlen]) buffer = buffer[c:] #print "cuePoint=", cuePoint elif type == 32: # chan prefix val, buffer = self.getByte(buffer) # TODO len, buffer = self.getByte(buffer) elif type == 47: # end of track buffer = buffer[1:] elif type == 81: # set tempo len, buffer = self.getByte(buffer) fmt = '3B' c = struct.calcsize(fmt) b1, b2, b3 = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] val = (b1 << 16) | (b2 << 8) | b3 self.bpm = 60000000 / val #print "b1=", b1 #print "b2=", b2 #print "b3=", b3 #print "tempo=", val #print "bpm=", bpm elif type == 84: # smpte len, buffer = self.getByte(buffer) fmt = '5B' c = struct.calcsize(fmt) b1, b2, b3, b4, b5 = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] elif type == 88: # time sig len, buffer = self.getByte(buffer) fmt = '4B' c = struct.calcsize(fmt) num, denom, metro, _32nds = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] self.num = num self.denom = denom self.metro = metro self._32nds = _32nds #print "num=", num #print "denom=", denom #print "metro=", metro #print "_32nds=", _32nds elif type == 89: # key sig len, buffer = self.getByte(buffer) fmt = '2B' c = struct.calcsize(fmt) b1, b2 = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] elif type == 127: # seq specific val, buffer = self.readVarLen(buffer) buffer = buffer[val:] else: #print "unknown meta-event" len, buffer = self.getByte(buffer) buffer = buffer[len:] return buffer def getByte(self, buffer): fmt = self.endian+'B' c = struct.calcsize(fmt) val, = struct.unpack(fmt, buffer[:c]) buffer = buffer[c:] return val, buffer def readVarLen(self, buffer): result = 0 for i in range(4): c, = struct.unpack('B', buffer[:1]) buffer = buffer[1:] result = (result << 7) | (c & 0x7f) if c & 0x80 == 0: break return (result, buffer) class MIDIFile: def __init__(self, filename): self._filename = filename f = file(filename) buffer = f.read() f.close() #print "buffer=", buffer #print len(buffer) self.tracks = [] self.endian = '>' fmt = self.endian+'4c' header = struct.unpack(fmt, buffer[0:4]) if header[0] == 'M' and header[1] == 'T' and header[2] == 'h' and header[3] == 'd': fmt = self.endian+'ihhh' c = struct.calcsize(fmt) buffer = buffer[4:] chunkSize, formatType, nTracks, self.timeDivision = struct.unpack(fmt, buffer[0:c]) #print chunkSize, formatType, nTracks, timeDivision self._numTracks = nTracks buffer = buffer[c:] # loop through tracks for i in range(nTracks): fmt = self.endian + '4c' header = struct.unpack(fmt, buffer[0:4]) if header[0] == 'M' and header[1] == 'T' and header[2] == 'r' and header[3] == 'k': buffer = buffer[4:] #print "========== TRACK ", i track = MIDITrack() buffer = track.parseBuffer(buffer) self.tracks.append(track) else: raise Exception('Invalid track') else: raise Exception('Invalid header') def numTracks(self): return self._numTracks def track(self, index): return self.tracks[index] if __name__ == "__main__": try: m = MIDIFile('midiFiles/children/3blindmiceMelody.mid') #print "num=", m.numTracks() track = m.track(0) print "name=", track.name() print "bpm=", track.bpm print track.num, "/", track.denom print "time", m.timeDivision print "metro=", track.metro print "_32nds=", track._32nds #print "events=", track.events except Exception, msg: print msg