-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfileio.py
163 lines (150 loc) · 6.21 KB
/
fileio.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from warnings import *
from containers import *
from events import *
from struct import unpack, pack
from constants import *
from util import *
class FileReader(object):
def read(self, midifile):
pattern = self.parse_file_header(midifile)
for track in pattern:
self.parse_track(midifile, track)
return pattern
def parse_file_header(self, midifile):
# First four bytes are MIDI header
magic = midifile.read(4)
decoded = magic.decode("utf-8")
if decoded != 'MThd':
raise ValueError("Bad header in MIDI file.")
# next four bytes are header size
# next two bytes specify the format version
# next two bytes specify the number of tracks
# next two bytes specify the resolution/PPQ/Parts Per Quarter
# (in other words, how many ticks per quater note)
data = unpack(">LHHH", midifile.read(10))
hdrsz = data[0]
format = data[1]
tracks = [Track() for x in range(data[2])]
resolution = data[3]
# XXX: the assumption is that any remaining bytes
# in the header are padding
if hdrsz > DEFAULT_MIDI_HEADER_SIZE:
midifile.read(hdrsz - DEFAULT_MIDI_HEADER_SIZE)
return Pattern(tracks=tracks, resolution=resolution, format=format)
def parse_track_header(self, midifile):
# First four bytes are Track header
magic = midifile.read(4).decode("utf-8")
if magic != 'MTrk':
raise ValueError("Bad track header in MIDI file: " + magic)
# next four bytes are track size
trksz = unpack(">L", midifile.read(4))[0]
return trksz
def parse_track(self, midifile, track):
self.RunningStatus = None
trksz = self.parse_track_header(midifile)
trackdata = iter(midifile.read(trksz))
while True:
try:
event = self.parse_midi_event(trackdata)
track.append(event)
except StopIteration:
break
def parse_midi_event(self, trackdata):
# first datum is varlen representing delta-time
tick = read_varlen(trackdata)
# next byte is status message
stsmsg = str(trackdata).encode('utf-8').__iter__
# is the event a MetaEvent?
if MetaEvent.is_event(stsmsg):
cmd = next(trackdata)
if cmd not in EventRegistry.MetaEvents:
warn("Unknown Meta MIDI Event: " + cmd, Warning)
cls = UnknownMetaEvent
else:
cls = EventRegistry.MetaEvents[cmd]
datalen = read_varlen(trackdata)
data = [next(trackdata) for x in range(datalen)]
return cls(tick=tick, data=data, metacommand=cmd)
# is this event a Sysex Event?
elif SysexEvent.is_event(stsmsg):
data = []
while True:
datum = next(trackdata)
if datum == 0xF7:
break
data.append(datum)
return SysexEvent(tick=tick, data=data)
# not a Meta MIDI event or a Sysex event, must be a general message
else:
key = stsmsg & 0xF0
if key not in EventRegistry.Events:
assert self.RunningStatus, "Bad byte value"
data = []
key = self.RunningStatus & 0xF0
cls = EventRegistry.Events[key]
channel = self.RunningStatus & 0x0F
data.append(stsmsg)
data += [ord(next(trackdata)) for x in range(cls.length - 1)]
return cls(tick=tick, channel=channel, data=data)
else:
self.RunningStatus = stsmsg
cls = EventRegistry.Events[key]
channel = self.RunningStatus & 0x0F
data = [next(trackdata) for x in range(cls.length)]
return cls(tick=tick, channel=channel, data=data)
raise Warning("Unknown MIDI Event: " + stsmsg)
class FileWriter(object):
def write(self, midifile, pattern):
self.write_file_header(midifile, pattern)
for track in pattern:
self.write_track(midifile, track)
def write_file_header(self, midifile, pattern):
# First four bytes are MIDI header
packdata = pack(">LHHH", 6,
pattern.format,
len(pattern),
pattern.resolution)
midifile.write('MThd%s' % packdata)
def write_track(self, midifile, track):
buf = ''
self.RunningStatus = None
for event in track:
buf += self.encode_midi_event(event)
buf = self.encode_track_header(len(buf)) + buf
midifile.write(buf)
def encode_track_header(self, trklen):
return 'MTrk%s' % pack(">L", trklen)
def encode_midi_event(self, event):
ret = ''
ret += write_varlen(event.tick)
# is the event a MetaEvent?
if isinstance(event, MetaEvent):
ret += chr(event.statusmsg) + chr(event.metacommand)
ret += write_varlen(len(event.data))
ret += str.join('', map(chr, event.data))
# is this event a Sysex Event?
elif isinstance(event, SysexEvent):
ret += chr(0xF0)
ret += str.join('', map(chr, event.data))
ret += chr(0xF7)
# not a Meta MIDI event or a Sysex event, must be a general message
elif isinstance(event, Event):
if not self.RunningStatus or \
self.RunningStatus.statusmsg != event.statusmsg or \
self.RunningStatus.channel != event.channel:
self.RunningStatus = event
ret += chr(event.statusmsg | event.channel)
ret += str.join('', map(chr, event.data))
else:
raise ValueError("Unknown MIDI Event: " + str(event))
return ret
def write_midifile(midifile, pattern):
# if type(midifile) in (str, unicode):
midifile = open(midifile, 'wb')
writer = FileWriter()
return writer.write(midifile, pattern)
def read_midifile(midifile):
# if type(midifile) in (str, unicode):
midifile = open(midifile, 'rb')
reader = FileReader()
return reader.read(midifile)