]> git.donarmstrong.com Git - lilypond.git/blob - python/midi.py
Web-ja: update introduction
[lilypond.git] / python / midi.py
1 # This file is part of LilyPond, the GNU music typesetter.
2 #
3 # Copyright (C) 2001--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
4 #           Jan Nieuwenhuizen <janneke@gnu.org>
5 #
6 #
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.
11 #
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.
16 #
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/>.
19
20 # import midi
21 # s = open ("s.midi").read ()
22 # midi.parse_track (s)
23 # midi.parse (s)
24 #
25 #
26 # returns a MIDI file as the tuple
27 #
28 #  ((format, division), TRACKLIST)          # division (>0) = TPQN*4
29 #                                           # or (<0) TBD
30 #
31 # each track is an EVENTLIST, where EVENT is
32 #
33 #   (time, (type, ARG1, [ARG2]))            # time = cumulative delta time
34                                             # MIDI event: 
35                                             #   type = MIDI status+channel >= x80
36                                             # META-event  = xFF:
37                                             #   type = meta-event type <= x7F
38                                             #   ARG1 = length
39                                             #   ARG2 = data
40
41 import array
42 import struct
43
44 class error (Exception): pass
45
46 # class warning (Exception): pass
47
48 def _add_constants ():
49     channelVoiceMessages = (
50         (0x80, "NOTE_OFF"),
51         (0x90, "NOTE_ON"),
52         (0xA0, "POLYPHONIC_KEY_PRESSURE"),
53         (0xB0, "CONTROLLER_CHANGE"),
54         (0xC0, "PROGRAM_CHANGE"),
55         (0xD0, "CHANNEL_KEY_PRESSURE"),
56         (0xE0, "PITCH_BEND"),
57     )
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"),
67     )
68     metaEvents = (
69         (0x00, "SEQUENCE_NUMBER"),
70         (0x01, "TEXT_EVENT"),
71         (0x02, "COPYRIGHT_NOTICE"),
72         (0x03, "SEQUENCE_TRACK_NAME"),
73         (0x04, "INSTRUMENT_NAME"),
74         (0x05, "LYRIC"),        #renamed LYRIC_DISPLAY MIDI RP-26
75         (0x06, "MARKER"),
76         (0x07, "CUE_POINT"),
77         (0x08, "PROGRAM_NAME"), #added MIDI RP-19
78         (0X09, "DEVICE_NAME"),  #added MIDI RP-19 
79         (0x20, "MIDI_CHANNEL_PREFIX"),
80         (0x21, "MIDI_PORT"),
81         (0x2F, "END_OF_TRACK"),
82         (0x51, "SET_TEMPO"),
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"),
88         (0xFF, "META_EVENT"),
89     )
90     globals().update((desc, msg) for msg, desc in
91                        channelVoiceMessages + channelModeMessages + metaEvents)
92
93 _add_constants ()
94
95 def _get_variable_length_number (nextbyte, getbyte):
96     sum = 0
97     while nextbyte >= 0x80:
98         sum = (sum + (nextbyte & 0x7F)) << 7
99         nextbyte = getbyte()
100     return sum + nextbyte
101
102 def _first_command_is_repeat(status, nextbyte, getbyte):
103     raise error('the first midi command in the track is a repeat')
104
105 def _read_two_bytes (status, nextbyte, getbyte):
106     return status, nextbyte
107
108 def _read_three_bytes (status, nextbyte, getbyte):
109     return status, nextbyte, getbyte()
110
111 def _read_string (nextbyte, getbyte):
112     length = _get_variable_length_number (nextbyte, getbyte)
113     return ''.join(chr(getbyte()) for i in xrange(length))
114
115 def _read_f0_byte (status, nextbyte, getbyte):
116     if status == 0xff:
117         return status, nextbyte, _read_string(getbyte(), getbyte)
118     return status, _read_string(nextbyte, getbyte)
119
120 _read_midi_event = (
121     _first_command_is_repeat,  #  0
122     None,  # 10
123     None,  # 20
124     None,  # 30
125     None,  # 40
126     None,  # 50
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
136     _read_f0_byte,   # f0
137 )
138
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
143
144     time = 0
145     status = 0
146     try:
147         for nextbyte in dataiter:
148             time += _get_variable_length_number (nextbyte, getbyte)
149             if clocks_max and time > clocks_max:
150                 break
151             nextbyte = getbyte()
152             if nextbyte >= 0x80:
153                 status = nextbyte
154                 nextbyte = getbyte()
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')
161
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]))
165     try:
166         length, = struct.unpack ('>I', data[pos+4:pos+8])
167     except struct.error:
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)
173     return data, endpos
174
175 def _parse_tracks (midi, pos, num_tracks, clocks_max):
176     if num_tracks > 256:
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):
182     #     warn
183
184 def parse_track (track, clocks_max=None):
185     track_body, end = _parse_hunk (track, 0, 'track', 'MTrk')
186     # if end < len(track):
187     #     warn
188     return list (_parse_track_body (track_body, clocks_max))
189
190 def parse (midi, clocks_max=None):
191     header, first_track_pos = _parse_hunk(midi, 0, 'file', 'MThd')
192     try:
193         format, num_tracks, division = struct.unpack ('>3H', header[:6])
194     except struct.error:
195         raise error('the file header is too short')
196 #  if division < 0:
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