1 # This file is part of LilyPond, the GNU music typesetter.
3 # Copyright (C) 2001--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
4 # Jan Nieuwenhuizen <janneke@gnu.org>
7 # LilyPond is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # LilyPond is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
21 # s = open ("s.midi").read ()
22 # midi.parse_track (s)
26 # returns a MIDI file as the tuple
28 # ((format, division), TRACKLIST) # division (>0) = TPQN*4
31 # each track is an EVENTLIST, where EVENT is
33 # (time, (type, ARG1, [ARG2])) # time = cumulative delta time
35 # type = MIDI status+channel >= x80
37 # type = meta-event type <= x7F
44 class error (Exception): pass
46 # class warning (Exception): pass
48 def _add_constants ():
49 channelVoiceMessages = (
52 (0xA0, "POLYPHONIC_KEY_PRESSURE"),
53 (0xB0, "CONTROLLER_CHANGE"),
54 (0xC0, "PROGRAM_CHANGE"),
55 (0xD0, "CHANNEL_KEY_PRESSURE"),
58 channelModeMessages = (
59 (0x78, "ALL_SOUND_OFF"),
60 (0x79, "RESET_ALL_CONTROLLERS"),
61 (0x7A, "LOCAL_CONTROL"),
62 (0x7B, "ALL_NOTES_OFF"),
63 (0x7C, "OMNI_MODE_OFF"),
64 (0x7D, "OMNI_MODE_ON"),
65 (0x7E, "MONO_MODE_ON"),
66 (0x7F, "POLY_MODE_ON"),
69 (0x00, "SEQUENCE_NUMBER"),
71 (0x02, "COPYRIGHT_NOTICE"),
72 (0x03, "SEQUENCE_TRACK_NAME"),
73 (0x04, "INSTRUMENT_NAME"),
74 (0x05, "LYRIC"), #renamed LYRIC_DISPLAY MIDI RP-26
77 (0x08, "PROGRAM_NAME"), #added MIDI RP-19
78 (0X09, "DEVICE_NAME"), #added MIDI RP-19
79 (0x20, "MIDI_CHANNEL_PREFIX"),
81 (0x2F, "END_OF_TRACK"),
83 (0x54, "SMTPE_OFFSET"),
84 (0x58, "TIME_SIGNATURE"),
85 (0x59, "KEY_SIGNATURE"),
86 (0x60, "XMF_PATCH_TYPE_PREFIX"), #added MIDI RP-32
87 (0x7F, "SEQUENCER_SPECIFIC_META_EVENT"),
90 globals().update((desc, msg) for msg, desc in
91 channelVoiceMessages + channelModeMessages + metaEvents)
95 def _get_variable_length_number (nextbyte, getbyte):
97 while nextbyte >= 0x80:
98 sum = (sum + (nextbyte & 0x7F)) << 7
100 return sum + nextbyte
102 def _first_command_is_repeat(status, nextbyte, getbyte):
103 raise error('the first midi command in the track is a repeat')
105 def _read_two_bytes (status, nextbyte, getbyte):
106 return status, nextbyte
108 def _read_three_bytes (status, nextbyte, getbyte):
109 return status, nextbyte, getbyte()
111 def _read_string (nextbyte, getbyte):
112 length = _get_variable_length_number (nextbyte, getbyte)
113 return ''.join(chr(getbyte()) for i in xrange(length))
115 def _read_f0_byte (status, nextbyte, getbyte):
117 return status, nextbyte, _read_string(getbyte(), getbyte)
118 return status, _read_string(nextbyte, getbyte)
121 _first_command_is_repeat, # 0
127 None, # 60 data entry???
128 None, # 70 all notes off???
129 _read_three_bytes, # 80 note off
130 _read_three_bytes, # 90 note on
131 _read_three_bytes, # a0 poly aftertouch
132 _read_three_bytes, # b0 control
133 _read_two_bytes, # c0 prog change
134 _read_two_bytes, # d0 ch aftertouch
135 _read_three_bytes, # e0 pitchwheel range
139 def _parse_track_body (data, clocks_max):
140 # This seems to be the fastest way of getting bytes in order as integers.
141 dataiter = iter(array.array('B', data))
142 getbyte = dataiter.next
147 for nextbyte in dataiter:
148 time += _get_variable_length_number (nextbyte, getbyte)
149 if clocks_max and time > clocks_max:
155 yield time, _read_midi_event[status >> 4] (status, nextbyte, getbyte)
156 except StopIteration:
157 # If the track ended just before the start of an event, the for loop
158 # will exit normally. If it ends anywhere else, we end up here.
159 print len(list(dataiter))
160 raise error('a track ended in the middle of a MIDI command')
162 def _parse_hunk (data, pos, type, magic):
163 if data[pos:pos+4] != magic:
164 raise error ('expected %r, got %r' % (magic, data[pos:pos+4]))
166 length, = struct.unpack ('>I', data[pos+4:pos+8])
168 raise error ('the %s header is truncated (may be an incomplete download)' % type)
169 endpos = pos + 8 + length
170 data = data[pos+8:endpos]
171 if len(data) != length:
172 raise error('the %s is truncated (may be an incomplete download)' % type)
175 def _parse_tracks (midi, pos, num_tracks, clocks_max):
177 raise error('too many tracks: %d' % num_tracks)
178 for i in xrange(num_tracks):
179 trackdata, pos = _parse_hunk (midi, pos, 'track', 'MTrk')
180 yield list (_parse_track_body (trackdata, clocks_max))
181 # if pos < len(midi):
184 def parse_track (track, clocks_max=None):
185 track_body, end = _parse_hunk (track, 0, 'track', 'MTrk')
186 # if end < len(track):
188 return list (_parse_track_body (track_body, clocks_max))
190 def parse (midi, clocks_max=None):
191 header, first_track_pos = _parse_hunk(midi, 0, 'file', 'MThd')
193 format, num_tracks, division = struct.unpack ('>3H', header[:6])
195 raise error('the file header is too short')
197 # raise error ('cannot handle non-metrical time')
198 tracks = list (_parse_tracks (midi, first_track_pos, num_tracks, clocks_max))
199 return (format, division*4), tracks