1 # -*- coding: ISO-8859-1 -*-
4 from struct
import unpack
6 # uhh I don't really like this, but there are so many constants to
8 from constants
import *
10 from EventDispatcher
import EventDispatcher
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.
21 def __init__(self
, raw_in
, outstream
):
24 raw_data is the raw content of a midi file as a string.
27 # internal values, don't mess with 'em directly
29 self
.dispatch
= EventDispatcher(outstream
)
31 # Used to keep track of stuff
32 self
._running
_status
= None
37 def parseMThdChunk(self
):
39 "Parses the header chunk"
43 header_chunk_type
= raw_in
.nextSlice(4)
44 header_chunk_zise
= raw_in
.readBew(4)
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!"
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)
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)
61 # call the header event handler on the stream
62 self
.dispatch
.header(self
.format
, self
.nTracks
, self
.division
)
66 def parseMTrkChunk(self
):
68 "Parses a track chunk. This is the most important part of the parser."
70 # set time to 0 at start of a track
71 self
.dispatch
.reset_time()
73 dispatch
= self
.dispatch
76 # Trigger event at the start of a track
77 dispatch
.start_of_track(self
._current
_track
)
78 # position cursor after track header
80 # unsigned long is 4 bytes
81 tracklength
= raw_in
.readBew(4)
82 track_endposition
= raw_in
.getCursor() + tracklength
# absolute position!
84 while raw_in
.getCursor() < track_endposition
:
86 # find relative time of the event
87 time
= raw_in
.readVarLen()
88 dispatch
.update_time(time
)
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()
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.
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.
106 # we need to look at nibbles here
107 hi_nible
, lo_nible
= status
& 0xF0, status
& 0x0F
109 # match up with events
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
)
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!
135 # is it a system common event?
136 elif hi_nible
== 0xF0: # Hi bits are set then
139 SONG_POSITION_POINTER
:2,
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
)
148 # Oh! Then it must be a midi event (channel voice message)
156 CONTINUOUS_CONTROLLER
:2,
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
)
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!
174 if __name__
== '__main__':
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()
186 from MidiToText
import MidiToText
187 from RawInstreamFile
import RawInstreamFile
189 midi_in
= MidiFileParser(RawInstreamFile(test_file
), MidiToText())
190 midi_in
.parseMThdChunk()
191 midi_in
.parseMTrkChunks()