-
Notifications
You must be signed in to change notification settings - Fork 8
/
MusAnimLexer.py
122 lines (107 loc) · 4.57 KB
/
MusAnimLexer.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
120
121
122
import sys
class MidiLexer:
ticks_per_quarter = 0
bpm = 120
# midi events just stores all relevant midi events and their global time in
# beats, without making any pretenses about figuring out timing in seconds.
# That has to be done later, once we have all the timing events sorted
midi_events = []
def get_v_time(self, data):
"""Picks off the variable-length time from a block of data and returns
both pieces as a tuple, with the time in ticks"""
i = 0
time_bytes = []
while (ord(data[i]) & 0x80) >> 7 == 1:
time_bytes.append(ord(data[i]) & 0x7F)
i += 1
time_bytes.append(ord(data[i]) & 0x7F)
time_bytes.reverse()
d_time = 0
for j in range(0, len(time_bytes)):
d_time += (time_bytes[j] << j * 7)
return d_time, data[i+1:]
def read_midi_event(self, track_data, time, track_num):
# have to read off vtime first!
d_time, track_data = self.get_v_time(track_data)
time += d_time
if track_data[0] == '\xff':
# event is meta event, we do nothing unless it's a tempo event
if ord(track_data[1]) == 0x51:
#print track_num, list(track_data)
# tempo event
mpqn = ((ord(track_data[3]) << 16) + (ord(track_data[4]) << 8)
+ ord(track_data[5])) # microseconds per quarter note
bpm = 60000000.0 / mpqn
self.midi_events.append({'type': 'tempo', 'time': time,
'bpm': bpm})
return track_data[6:], time
else: # just skip past it and do nothing
length = ord(track_data[2])
return track_data[length+3:], time
# otherwise we we assume it's a midi event
elif ((ord(track_data[0]) & 0xF0) >> 4) == 0x8:
# note off event
# don't add a note off event if keyswitch (pitch below 12)
pitch = ord(track_data[1])
if pitch >= 12:
self.midi_events.append({'type': 'note_off', 'time': time,
'pitch': pitch, 'track_num': track_num})
return track_data[3:], time
elif ((ord(track_data[0]) & 0xF0) >> 4) == 0x9:
# note on event
pitch = ord(track_data[1])
if pitch < 12: # it's a keyswitch!
if pitch == 0:
mode = "normal"
elif pitch == 1:
mode = "pizz"
else:
raise Exception("Unknown keyswitch")
self.midi_events.append({'type': 'keyswitch', 'time': time,
'track_num': track_num, 'mode': mode})
else:
self.midi_events.append({'type': 'note_on', 'time': time,
'pitch': ord(track_data[1]), 'track_num': track_num})
return track_data[3:], time
elif ((ord(track_data[0]) & 0xF0) >> 4) == 0xC:
return track_data[2:], time # ignore some other events
elif ((ord(track_data[0]) & 0xF0) >> 4) == 0xB:
return track_data[3:], time
else:
raise Exception("Unknown midi file data event: " + str(ord(track_data[0])))
def lex(self, filename):
"""Returns block list for musanim from a midi file given in filename"""
import re
# init stuff
self.midi_events = []
self.bpm = 120
self.ticks_per_quarter = 960
blocks = []
# open and read file
f = open(filename, 'rb')
s = f.read()
# grab header
header = s[0:14]
f_format = ord(s[8]) << 8 | ord(s[9])
num_tracks = ord(s[10]) << 8 | ord(s[11])
self.ticks_per_quarter = ord(s[12]) << 8 | ord(s[13])
tracks_chunk = s[14:]
# individual track data as entries in list
tracks = [track[4:] for track in re.split("MTrk", tracks_chunk)[1:]]
track_num = 0
for track in tracks:
time = 0
# parse midi events for a single track
while len(track) > 0:
# read off midi events and add to midi_events
track, time = self.read_midi_event(track, time, track_num)
track_num += 1
# convert all times from ticks to beats, for convenience
for event in self.midi_events:
event['time'] = (event['time'] + 0.0) / 960
self.midi_events.sort(lambda a, b: cmp(a['time'], b['time']))
return self.midi_events
if __name__ == '__main__':
lexer = MidiLexer()
blocks = lexer.lex('multitrackmidi01.MID')
print blocks