Package echonest :: Package remix :: Package support :: Package midi :: Module MidiFileParser
[hide private]
[frames] | no frames]

Source Code for Module echonest.remix.support.midi.MidiFileParser

  1  # -*- coding: ISO-8859-1 -*-
 
  2  
 
  3  # std library
 
  4  from struct import unpack 
  5  
 
  6  # uhh I don't really like this, but there are so many constants to 
 
  7  # import otherwise
 
  8  from constants import * 
  9  
 
 10  from EventDispatcher import EventDispatcher 
 11  
 
12 -class MidiFileParser:
13 14 """ 15 16 The MidiFileParser is the lowest level parser that see the data as 17 midi data. It generates events that gets triggered on the outstream. 18 19 """ 20
21 - def __init__(self, raw_in, outstream):
22 23 """ 24 raw_data is the raw content of a midi file as a string. 25 """ 26 27 # internal values, don't mess with 'em directly 28 self.raw_in = raw_in 29 self.dispatch = EventDispatcher(outstream) 30 31 # Used to keep track of stuff 32 self._running_status = None
33 34 35 36
37 - def parseMThdChunk(self):
38 39 "Parses the header chunk" 40 41 raw_in = self.raw_in 42 43 header_chunk_type = raw_in.nextSlice(4) 44 header_chunk_zise = raw_in.readBew(4) 45 46 # check if it is a proper midi file 47 if header_chunk_type != 'MThd': 48 raise TypeError, "It is not a valid midi file!" 49 50 # Header values are at fixed locations, so no reason to be clever 51 self.format = raw_in.readBew(2) 52 self.nTracks = raw_in.readBew(2) 53 self.division = raw_in.readBew(2) 54 55 # Theoretically a header larger than 6 bytes can exist 56 # but no one has seen one in the wild 57 # But correctly ignore unknown data if it is though 58 if header_chunk_zise > 6: 59 raw_in.moveCursor(header_chunk_zise-6) 60 61 # call the header event handler on the stream 62 self.dispatch.header(self.format, self.nTracks, self.division)
63 64 65
66 - def parseMTrkChunk(self):
67 68 "Parses a track chunk. This is the most important part of the parser." 69 70 # set time to 0 at start of a track 71 self.dispatch.reset_time() 72 73 dispatch = self.dispatch 74 raw_in = self.raw_in 75 76 # Trigger event at the start of a track 77 dispatch.start_of_track(self._current_track) 78 # position cursor after track header 79 raw_in.moveCursor(4) 80 # unsigned long is 4 bytes 81 tracklength = raw_in.readBew(4) 82 track_endposition = raw_in.getCursor() + tracklength # absolute position! 83 84 while raw_in.getCursor() < track_endposition: 85 86 # find relative time of the event 87 time = raw_in.readVarLen() 88 dispatch.update_time(time) 89 90 # be aware of running status!!!! 91 peak_ahead = raw_in.readBew(move_cursor=0) 92 if (peak_ahead & 0x80): 93 # the status byte has the high bit set, so it 94 # was not running data but proper status byte 95 status = self._running_status = raw_in.readBew() 96 else: 97 # use that darn running status 98 status = self._running_status 99 # could it be illegal data ?? Do we need to test for that? 100 # I need more example midi files to be shure. 101 102 # Also, while I am almost certain that no realtime 103 # messages will pop up in a midi file, I might need to 104 # change my mind later. 105 106 # we need to look at nibbles here 107 hi_nible, lo_nible = status & 0xF0, status & 0x0F 108 109 # match up with events 110 111 # Is it a meta_event ?? 112 # these only exists in midi files, not in transmitted midi data 113 # In transmitted data META_EVENT (0xFF) is a system reset 114 if status == META_EVENT: 115 meta_type = raw_in.readBew() 116 meta_length = raw_in.readVarLen() 117 meta_data = raw_in.nextSlice(meta_length) 118 dispatch.meta_event(meta_type, meta_data) 119 120 121 # Is it a sysex_event ?? 122 elif status == SYSTEM_EXCLUSIVE: 123 # ignore sysex events 124 sysex_length = raw_in.readVarLen() 125 # don't read sysex terminator 126 sysex_data = raw_in.nextSlice(sysex_length-1) 127 # only read last data byte if it is a sysex terminator 128 # It should allways be there, but better safe than sorry 129 if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE: 130 eo_sysex = raw_in.readBew() 131 dispatch.sysex_event(sysex_data) 132 # the sysex code has not been properly tested, and might be fishy! 133 134 135 # is it a system common event? 136 elif hi_nible == 0xF0: # Hi bits are set then 137 data_sizes = { 138 MTC:1, 139 SONG_POSITION_POINTER:2, 140 SONG_SELECT:1, 141 } 142 data_size = data_sizes.get(hi_nible, 0) 143 common_data = raw_in.nextSlice(data_size) 144 common_type = lo_nible 145 dispatch.system_common(common_type, common_data) 146 147 148 # Oh! Then it must be a midi event (channel voice message) 149 else: 150 data_sizes = { 151 PATCH_CHANGE:1, 152 CHANNEL_PRESSURE:1, 153 NOTE_OFF:2, 154 NOTE_ON:2, 155 AFTERTOUCH:2, 156 CONTINUOUS_CONTROLLER:2, 157 PITCH_BEND:2, 158 } 159 data_size = data_sizes.get(hi_nible, 0) 160 channel_data = raw_in.nextSlice(data_size) 161 event_type, channel = hi_nible, lo_nible 162 dispatch.channel_messages(event_type, channel, channel_data)
163 164
165 - def parseMTrkChunks(self):
166 "Parses all track chunks." 167 for t in range(self.nTracks): 168 self._current_track = t 169 self.parseMTrkChunk() # this is where it's at! 170 self.dispatch.eof()
171 172 173 174 if __name__ == '__main__': 175 176 # get data 177 test_file = 'test/midifiles/minimal.mid' 178 test_file = 'test/midifiles/cubase-minimal.mid' 179 test_file = 'test/midifiles/Lola.mid' 180 # f = open(test_file, 'rb') 181 # raw_data = f.read() 182 # f.close() 183 # 184 # 185 # # do parsing 186 from MidiToText import MidiToText 187 from RawInstreamFile import RawInstreamFile 188 189 midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText()) 190 midi_in.parseMThdChunk() 191 midi_in.parseMTrkChunks() 192