+++ /dev/null
-# -*- coding: ISO-8859-1 -*-\r
-\r
-# std library\r
-from struct import unpack\r
-\r
-# uhh I don't really like this, but there are so many constants to \r
-# import otherwise\r
-from constants import *\r
-\r
-from EventDispatcher import EventDispatcher\r
-\r
-class MidiFileParser:\r
-\r
- """\r
- \r
- The MidiFileParser is the lowest level parser that see the data as \r
- midi data. It generates events that gets triggered on the outstream.\r
- \r
- """\r
-\r
- def __init__(self, raw_in, outstream):\r
-\r
- """\r
- raw_data is the raw content of a midi file as a string.\r
- """\r
-\r
- # internal values, don't mess with 'em directly\r
- self.raw_in = raw_in\r
- self.dispatch = EventDispatcher(outstream)\r
-\r
- # Used to keep track of stuff\r
- self._running_status = None\r
-\r
-\r
-\r
-\r
- def parseMThdChunk(self):\r
- \r
- "Parses the header chunk"\r
- \r
- raw_in = self.raw_in\r
-\r
- header_chunk_type = raw_in.nextSlice(4)\r
- header_chunk_zise = raw_in.readBew(4)\r
-\r
- # check if it is a proper midi file\r
- if header_chunk_type != 'MThd':\r
- raise TypeError, "It is not a valid midi file!"\r
-\r
- # Header values are at fixed locations, so no reason to be clever\r
- self.format = raw_in.readBew(2)\r
- self.nTracks = raw_in.readBew(2)\r
- self.division = raw_in.readBew(2)\r
- \r
- # Theoretically a header larger than 6 bytes can exist\r
- # but no one has seen one in the wild\r
- # But correctly ignore unknown data if it is though\r
- if header_chunk_zise > 6:\r
- raw_in.moveCursor(header_chunk_zise-6)\r
-\r
- # call the header event handler on the stream\r
- self.dispatch.header(self.format, self.nTracks, self.division)\r
-\r
-\r
-\r
- def parseMTrkChunk(self):\r
- \r
- "Parses a track chunk. This is the most important part of the parser."\r
- \r
- # set time to 0 at start of a track\r
- self.dispatch.reset_time()\r
- \r
- dispatch = self.dispatch\r
- raw_in = self.raw_in\r
- \r
- # Trigger event at the start of a track\r
- dispatch.start_of_track(self._current_track)\r
- # position cursor after track header\r
- raw_in.moveCursor(4)\r
- # unsigned long is 4 bytes\r
- tracklength = raw_in.readBew(4)\r
- track_endposition = raw_in.getCursor() + tracklength # absolute position!\r
-\r
- while raw_in.getCursor() < track_endposition:\r
- \r
- # find relative time of the event\r
- time = raw_in.readVarLen()\r
- dispatch.update_time(time)\r
- \r
- # be aware of running status!!!!\r
- peak_ahead = raw_in.readBew(move_cursor=0)\r
- if (peak_ahead & 0x80): \r
- # the status byte has the high bit set, so it\r
- # was not running data but proper status byte\r
- status = self._running_status = raw_in.readBew()\r
- else:\r
- # use that darn running status\r
- status = self._running_status\r
- # could it be illegal data ?? Do we need to test for that?\r
- # I need more example midi files to be shure.\r
- \r
- # Also, while I am almost certain that no realtime \r
- # messages will pop up in a midi file, I might need to \r
- # change my mind later.\r
-\r
- # we need to look at nibbles here\r
- hi_nible, lo_nible = status & 0xF0, status & 0x0F\r
- \r
- # match up with events\r
-\r
- # Is it a meta_event ??\r
- # these only exists in midi files, not in transmitted midi data\r
- # In transmitted data META_EVENT (0xFF) is a system reset\r
- if status == META_EVENT:\r
- meta_type = raw_in.readBew()\r
- meta_length = raw_in.readVarLen()\r
- meta_data = raw_in.nextSlice(meta_length)\r
- dispatch.meta_event(meta_type, meta_data)\r
-\r
-\r
- # Is it a sysex_event ??\r
- elif status == SYSTEM_EXCLUSIVE:\r
- # ignore sysex events\r
- sysex_length = raw_in.readVarLen()\r
- # don't read sysex terminator\r
- sysex_data = raw_in.nextSlice(sysex_length-1)\r
- # only read last data byte if it is a sysex terminator\r
- # It should allways be there, but better safe than sorry\r
- if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:\r
- eo_sysex = raw_in.readBew()\r
- dispatch.sysex_event(sysex_data)\r
- # the sysex code has not been properly tested, and might be fishy!\r
-\r
-\r
- # is it a system common event?\r
- elif hi_nible == 0xF0: # Hi bits are set then\r
- data_sizes = {\r
- MTC:1,\r
- SONG_POSITION_POINTER:2,\r
- SONG_SELECT:1,\r
- }\r
- data_size = data_sizes.get(hi_nible, 0)\r
- common_data = raw_in.nextSlice(data_size)\r
- common_type = lo_nible\r
- dispatch.system_common(common_type, common_data)\r
- \r
-\r
- # Oh! Then it must be a midi event (channel voice message)\r
- else:\r
- data_sizes = {\r
- PATCH_CHANGE:1,\r
- CHANNEL_PRESSURE:1,\r
- NOTE_OFF:2,\r
- NOTE_ON:2,\r
- AFTERTOUCH:2,\r
- CONTINUOUS_CONTROLLER:2,\r
- PITCH_BEND:2,\r
- }\r
- data_size = data_sizes.get(hi_nible, 0)\r
- channel_data = raw_in.nextSlice(data_size)\r
- event_type, channel = hi_nible, lo_nible\r
- dispatch.channel_messages(event_type, channel, channel_data)\r
-\r
-\r
- def parseMTrkChunks(self):\r
- "Parses all track chunks."\r
- for t in range(self.nTracks):\r
- self._current_track = t\r
- self.parseMTrkChunk() # this is where it's at!\r
- self.dispatch.eof()\r
-\r
-\r
-\r
-if __name__ == '__main__':\r
-\r
- # get data\r
- test_file = 'test/midifiles/minimal.mid'\r
- test_file = 'test/midifiles/cubase-minimal.mid'\r
- test_file = 'test/midifiles/Lola.mid'\r
-# f = open(test_file, 'rb')\r
-# raw_data = f.read()\r
-# f.close()\r
-# \r
-# \r
-# # do parsing\r
- from MidiToText import MidiToText\r
- from RawInstreamFile import RawInstreamFile\r
-\r
- midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText())\r
- midi_in.parseMThdChunk()\r
- midi_in.parseMTrkChunks()\r
-
\ No newline at end of file