]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
23ca1264f8f7de94c6234ff27be2db1cd52c4910
[lilypond.git] / scripts / midi2ly.py
1 #!@PYTHON@
2
3 import midi
4 import sys
5 import string
6
7 LINE_BELL = 60
8 scale_steps = [0,2,4,5,7,9,11]
9
10 whole_clocks = 1536
11 quart_clocks = 0
12
13 def split_track (track):
14         chs = {}
15         for i in range(16):
16                 chs[i] = []
17                 
18         for e in track:
19                 data = list (e[1])
20                 if data[0] > 0x7f and data[0] < 0xf0:
21                         c = data[0] & 0x0f
22                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
23                         chs[c].append (e)
24                 else:
25                         chs[0].append (e)
26
27         for i in range (16):
28                 if chs[i] == []:
29                         del chs[i]
30
31         whatarewes = []
32         for v in chs.values ():
33                 ns = notes_on_channel (v)
34                 dinges = unthread_notes (ns)
35                 if len (dinges):
36                         whatarewes.append (dinges)
37         return whatarewes
38
39
40 class Note:
41         def __init__ (self, clocks, pitch, velocity):
42                 self.velocity = velocity
43                 self.pitch = pitch
44                 self.clocks = clocks
45
46         def clocks_compare (a, b):
47                 if a.clocks < b.clocks:
48                         return -1
49                 elif a.clocks > b.clocks:
50                         return 1
51                 else:
52                         return 0
53
54         def dump (self):
55                 return dump_note (self)
56
57 class Time:
58         def __init__ (self, t, num, den):
59                 self.skip = t
60                 self.num = num
61                 self.den = den
62                 self.clocks = 0
63                 
64         def dump (self):
65                 s = ''
66                 if self.skip:
67                         s = dump_skip (self.skip)
68                 s = s + '\n  '
69                 return s  + '\\time %d/%d ' % (self.num, self.den) + '\n  '
70
71 class Key:
72         key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
73         key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
74
75         def __init__ (self, t, sharps, flats, minor):
76                 self.skip = t
77                 self.flats = flats
78                 self.sharps = sharps
79                 self.minor = minor
80                 self.clocks = 0
81                 
82         def dump (self):
83                 s = ''
84                 if self.skip:
85                         s = dump_skip (self.skip)
86                 s = s + '\n  '
87                 if self.sharps and self.flats:
88                         s = '\\keysignature %s ' % 'TODO'
89                 elif self.sharps:
90                         s = '\\notes\\key %s \major' % self.key_sharps[self.sharps]
91                 elif self.flats:
92                         s = '\\notes\\key %s \major' % self.key_flats[self.flats]
93                 return s + '\n  '
94
95
96 class Text:
97         def __init__ (self, t, text):
98                 self.skip = t
99                 self.text = text
100                 self.clocks = 0
101
102         def dump (self):
103                 s = ''
104                 if self.skip:
105                         s = dump_skip (self.skip)
106                 return s + dump_text (self)
107
108 def notes_on_channel (channel):
109         pitches = {}
110
111         nch = []
112         for e in channel:
113                 t = e[0]
114
115                 if e[1][0] == midi.NOTE_ON:
116                         if not pitches.has_key (e[1][1]):
117                                 pitches[e[1][1]] = (t, e[1][2])
118                 elif e[1][0] == midi.NOTE_OFF:
119                         try:
120                                 (lt, vel) = pitches[e[1][1]]
121                                 del pitches[e[1][1]]
122                                 
123                                 nch.append ((lt, Note (t-lt, e[1][1], vel)))
124                                 
125                         except KeyError:
126                                 pass
127                 elif e[1][0] == midi.META_EVENT:
128                         if e[1][1] == midi.TIME_SIGNATURE:
129                                 (num, den, clocks4, count32) = map (ord, e[1][2])
130                                 nch.append ((t, Time (t, num, den)))
131                         elif e[1][1] == midi.KEY_SIGNATURE:
132                                 (accidentals, minor) = map (ord, e[1][2])
133                                 sharps = 0
134                                 flats = 0
135                                 if accidentals < 127:
136                                         sharps = accidentals
137                                 else:
138                                         flats = 256 - accidentals
139                                 nch.append ((t, Key (t, sharps, flats, minor)))
140                         elif e[1][1] == midi.TEXT_EVENT:
141                                 nch.append ((t, Text (t, e[1][2])))
142                         else:
143                                 sys.stderr.write ("SKIP: %s\n" % `e`)
144                                 pass
145                 else:
146                         sys.stderr.write ("SKIP: %s\n" % `e`)
147                         pass
148         
149         return nch
150
151 def unthread_notes (channel):
152         threads = []
153         while channel:
154                 thread = []
155                 end_busy_t = 0
156                 start_busy_t = 0
157                 todo = []
158                 for e in channel:
159                         t = e[0]
160                         if e[1].__class__ == Note and ((t == start_busy_t and e[1].clocks + t == end_busy_t) \
161                             or t >= end_busy_t):
162                                 thread.append (e)
163                                 start_busy_t = t
164                                 end_busy_t = t + e[1].clocks
165                         elif e[1].__class__ == Time or e[1].__class__ == Key or e[1].__class__ == Text:
166                                 thread.append (e)
167                         else:
168                                 todo.append (e)
169                 threads.append (thread)
170                 channel = todo
171
172         return threads
173
174 def gcd (a,b):
175         if b == 0:
176                 return a
177         c = a
178         while c: 
179                 c = a % b
180                 a = b
181                 b = c
182         return a
183         
184 def dump_skip (clocks):
185         return 's' + dump_duration (clocks) + ' '
186
187 def dump_duration (clocks):
188         g = gcd (clocks, whole_clocks)
189         (d, n) = (whole_clocks/ g, clocks / g)
190         if n == 1:
191                 s = '%d' % d
192         elif n == 3 and d != 1:
193                 s = '%d.' % (d / 2)
194         else:
195                 s = '%d*%d' % (d, n)
196         return s
197         
198 def dump_note (note):
199         p = note.pitch
200         oct = p / 12
201         step = p % 12
202
203         i = 0
204         while  i < len (scale_steps):
205                 if scale_steps[i] > step:
206                         break
207                 i = i+1
208                 
209         i = i-1
210         str = chr ((i + 2)  % 7 + ord ('a'))
211         if scale_steps[i] <> step:
212                 str = str + 'is'
213
214         return str + dump_duration (note.clocks) + ' '
215
216 def dump (self):
217         return self.dump ()
218
219 def dump_text (text):
220         return '\n  % ' + text.text + '\n  '
221
222 def dump_chord (ch):
223         s = ''
224         if len(ch) == 1:
225                 s = dump (ch[0])
226         else:
227                 s = '<' + string.join (map (dump, ch)) + '>'
228         return s
229
230 # thread?
231 def dump_channel (thread):
232         last_e = None
233         chs = []
234         ch = []
235
236         for e in thread:
237                 if last_e and last_e[0] == e[0]:
238                         ch.append (e[1])
239                 else:
240                         if ch:
241                                 chs.append ((last_e[0], ch))
242                                 
243                         ch = [e[1]]
244                         
245                 last_e = e
246
247         if ch:
248                 chs.append ((last_e[0], ch))
249         t = 0
250         last_t = 0
251
252         lines = ['']
253         for ch in chs:
254                 if len (lines[-1]) > LINE_BELL:
255                         lines.append ('')
256                         
257                 t = ch[0]
258                 if t - last_t:
259                         lines[-1] = lines[-1] + dump_skip (t-last_t)
260                         
261                 lines[-1] = lines[-1] + dump_chord (ch[1])
262
263                 last_t = t + ch[1][0].clocks
264
265         return string.join (lines, '\n  ') + '\n'
266
267 def dump_notes (channel):
268         on_hold = []
269         s = ''
270         for e in channel:
271                 if e[0] <> last_t:
272                         s = s + dump_chord (on_hold)
273                         on_hold = []
274                         last_t = e[0]
275
276                 on_hold.append (e)
277         return s
278
279 def track_name (i):
280         return 'track%c' % (i + ord ('A'))
281
282 def channel_name (i):
283         return 'channel%c' % (i + ord ('A'))
284
285 def dump_track (channels, n):
286         s = '\n'
287         track = track_name (n)
288         for i in range (len (channels)):
289                 channel = channel_name (i)
290                 s = s + '%s = \\notes {\n' % (track + channel)
291                 s = s + '  ' + dump_channel (channels[i][0])
292                 s = s + '}\n\n'
293
294         s = s + '%s = <\n' % track
295
296         for i in range (len (channels)):
297                 channel = channel_name (i)
298                 s = s + '  \\context Voice = %s \\%s\n' % (channel, track + channel)
299         s = s + '>\n\n'
300         return s
301                         
302         
303 def convert_midi (f):
304         global whole_clocks, quart_clocks
305
306         str = open (f).read ()
307         midi_dump = midi.parse (str)
308
309         whole_clocks = midi_dump[0][1]
310         quart_clocks = whole_clocks / 4
311
312         tracks = []
313         for t in midi_dump[1]:
314                 tracks.append (split_track (t))
315
316         s = ''
317         for i in range (len (tracks)):
318                 s = s + dump_track (tracks[i], i)
319
320         s = s + '\n\\score {\n  <\n'
321         for i in range (len (tracks)):
322                 track = track_name (i)
323                 s = s + '    \\context Staff=%s \\%s\n' % (track, track)
324         s = s + '  >\n}\n'
325         
326         sys.stdout.write (s)
327                 
328
329 for f in sys.argv[1:]:
330         convert_midi (f)