-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMidiParser.py
119 lines (102 loc) · 4.41 KB
/
MidiParser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# returns elements of a midi file (in raw form as a bytes object)
# elements returned:
# Chuck ID along with chunk size
# The rest of the header chunk
# Midi Channel events, including delta time
# Meta Event
# System Exclusive Event
from Util import Util
class MidiParser:
# states, CHUCK_START means start of either header or track
CHUNK_START, IN_HEADER, IN_TRACK_CHUNK, = range(3)
def __init__(self, midi_filename):
self.midi_file = open(midi_filename, "rb")
self.bytes_left_in_chunk = 0
self.next_byte = self.midi_file.read(1)
self.state = self.CHUNK_START
self.NO_SECOND_PARAM_EVENTS = [
int('c0', 16), # program change
int('d0', 16), # channel pressure (aftertouch)
]
return
def has_more_data(self):
return self.next_byte != b''
# true if the current chunk has more bytes (false if a read would be
# end of file or the next chuck (header chuck or track chunk).
def chunk_has_more_data(self):
return self.bytes_left_in_chunk > 0
# returns the data of the next element in bytes
def read_next_data(self):
if self.next_byte == b'':
print("Tried to read end of file!")
return self.next_byte
if self.state == self.CHUNK_START: # return ID along with chunk size
return_val = self.read_next_bytes(8)
if return_val[0:4] == b'MThd':
self.state = self.IN_HEADER
if return_val[0:4] == b'MTrk':
self.state = self.IN_TRACK_CHUNK
self.bytes_left_in_chunk = int.from_bytes(return_val[4:8], "big")
return return_val
if self.state == self.IN_HEADER: # return body of header
return self.read_next_bytes(self.bytes_left_in_chunk)
if self.state == self.IN_TRACK_CHUNK: # return an event
return self.read_event()
raise MidiParserException("Midi parser state " + str(self.state) + " not recognized")
def read_event(self):
delta_time = self.read_variable_length()
first_byte = self.read_next_byte()
if first_byte == b'\xff':
return delta_time + self.read_meta_event(first_byte)
elif first_byte == b'\xf0' or first_byte == b'\xf7':
return delta_time + self.read_sys_ex_event(first_byte)
else:
return delta_time + self.read_channel_event(first_byte)
def read_channel_event(self, first_byte):
data_length = 1
if Util.msb_is_one(first_byte): # not running status
data_length = 2
# program change and channel aftertouch do not have second parameter
if (first_byte[0] & int('f0', 16)) in self.NO_SECOND_PARAM_EVENTS:
data_length = 1
return first_byte + self.read_next_bytes(data_length)
def read_meta_event(self, first_byte):
meta_event_type = self.read_next_byte()
meta_event_length = self.read_variable_length()
meta_event_data = self.read_next_bytes(Util.var_len_val(
meta_event_length))
return (first_byte + meta_event_type +
meta_event_length + meta_event_data)
def read_sys_ex_event(self, first_byte):
data_length = self.read_variable_length()
return (first_byte + data_length +
self.read_next_bytes(Util.var_len_val(data_length)))
def read_next_byte(self):
if self.bytes_left_in_chunk > -0:
self.bytes_left_in_chunk -= 1
elif self.state == self.IN_TRACK_CHUNK:
raise MidiParserException("More bytes in chunk than defined in chunk header")
if self.bytes_left_in_chunk == 0:
self.state = self.CHUNK_START
return_val = self.next_byte
self.next_byte = self.midi_file.read(1)
return return_val
def read_next_bytes(self, num_bytes):
return_bytes = b''
for i in range(num_bytes):
return_bytes = return_bytes + self.read_next_byte()
return return_bytes
def read_variable_length(self):
cur_byte = self.read_next_byte()
return_val = cur_byte
while Util.msb_is_one(cur_byte):
cur_byte = self.read_next_byte()
return_val = return_val + cur_byte
return return_val
def close(self):
self.midi_file.close()
class MidiParserException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)