]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
patch::: 1.5.14.jcn5
[lilypond.git] / scripts / midi2ly.py
1 #!@PYTHON@
2
3 import midi
4 import sys
5 import string
6
7 scale_steps = [0,2,4,5,7,9,11]
8
9 def split_track (track):
10         chs = {}
11         for i in range(16):
12                 chs[i] = []
13                 
14         for e in track:
15                 data = list (e[1])              
16                 c = data[0] & 0x0f
17                 e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
18                 chs[c].append (e)
19
20         for i in range (16):
21                 if chs[i] == []:
22                         del chs[i]
23
24         whatarewes = []
25         for v in chs.values ():
26                 ns = notes_on_channel (v)
27                 dinges = unthread_notes (ns)
28                 if len (dinges):
29                         whatarewes.append (dinges)
30         return whatarewes
31
32
33 class Note:
34         def __init__ (self, duration, pitch, velocity):
35                 self.velocity = velocity
36                 self.pitch = pitch
37                 self.duration = duration
38
39         def duration_compare (a,b):
40                 if a.duration < b.duration :
41                         return -1
42                 elif a.duration > b.duration:
43                         return 1
44                 else:
45                         return 0
46         
47 def notes_on_channel (channel):
48         pitches = {}
49
50         nch = []
51         for e in channel:
52                 t = e[0]
53
54                 if e[1][0] == midi.NOTE_ON:
55                         if not pitches.has_key (e[1][1]):
56                                 pitches[e[1][1]] = (t, e[1][2])
57                 elif e[1][0] == midi.NOTE_OFF:
58                         try:
59                                 (lt, vel) = pitches[e[1][1]]
60                                 del pitches[e[1][1]]
61                                 
62                                 nch.append ((t, Note (t-lt, e[1][1], vel)))
63                                 
64                         except KeyError:
65                                 pass
66                 else:
67                         pass
68         
69         return nch
70
71 def unthread_notes (channel):
72         threads = []
73         while channel:
74                 thread = []
75                 end_busy_t = 0
76                 start_busy_t = 0
77                 todo = []
78                 for e in channel:
79                         t = e[0]
80                         if (t == start_busy_t and e[1].duration + t == end_busy_t) \
81                             or t >= end_busy_t:
82                                 thread.append (e)
83                                 start_busy_t = t
84                                 end_busy_t = t + e[1].duration
85                         else:
86                                 todo.append(e)
87                 threads.append (thread)
88                 channel = todo
89
90         return threads
91
92 def gcd (a,b):
93         if b == 0:
94                 return a
95         c = a
96         while c: 
97                 c = a % b
98                 a = b
99                 b = c
100         return a
101         
102 def dump_skip (dt):
103         return 's' + dump_duration (dt)
104
105 def dump_duration (dur):
106         g = gcd (dur, 384)
107         s = '4'
108         (p,q) = (dur / g, 384 / g)
109         if (p == 1 and q == 1) :
110                 pass
111         else:
112                 if p <> 1:      
113                         s = '*%d'% p
114                 if q <> 1:
115                         s = '*%d'% q
116         return s
117         
118
119 def dump_note (note):
120         p = note.pitch
121         oct = p / 12
122         step = p % 12
123
124         i = 0
125         while  i < len (scale_steps):
126                 if scale_steps[i] > step:
127                         break
128                 i = i+1
129                 
130         i = i-1
131         str = chr ((i + 2)  % 7 + ord ('a'))
132         if scale_steps[i] <> step:
133                 str = str + 'is'
134
135         return ' %s' % str + dump_duration (note.duration)
136
137 def dump_chord (ch):
138         s = ''
139         if len(ch) == 1:
140                 s = dump_note (ch[0])
141         else:
142                 s = '<' + string.join (map (dump_note, ch)) + '>'
143         return s
144
145 # thread?
146 def dump_channel (thread):
147         last_e = None
148         chs = []
149         ch = []
150
151         for e in thread:
152                 if last_e and last_e[0] == e[0]:
153                         ch.append (e[1])
154                 else:
155                         if ch:
156                                 chs.append ((last_e[0], ch))
157                                 
158                         ch = [e[1]]
159                         
160                 last_e = e
161
162         if ch:
163                 chs.append ((last_e[0], ch))
164         t = 0
165         last_t = 0
166
167         s = ''
168         for ch in chs:
169                 t = ch[0]
170                 if t - last_t:
171                         s = s + dump_skip (t-last_t)
172                         
173                 s = s + dump_chord (ch[1])
174                 last_t = t + ch[1][0].duration
175                 
176         return s
177
178 def dump_notes (channel):
179         on_hold = []
180         s = ''
181         for e in channel:
182                 if e[0] <> last_t:
183                         s = s + dump_chord (on_hold)
184                         on_hold = []
185                         last_t = e[0]
186
187                 on_hold.append (e)
188         return s
189
190 def dump_track (channels, n):
191         s = ''
192         track = 'track%c' % (n + ord ('A'))
193         for i in range (len (channels)):
194                 channel = 'channel%c' % (i + ord ('A'))
195                 s = s + '%s = \\context Thread=%s \\notes {\n' % (track + channel, track + channel)
196                 s = s + '  ' + dump_channel (channels[i][0])
197                 s = s + '\n}\n'
198
199         s = s + '%s = \\context Staff=%s <\n' % (track, track)
200
201         for i in range (len (channels)):
202                 channel = 'channel%c' % (i + ord ('A'))
203                 s = s + '  \\context Voice = %s \\%s\n' % (channel, track + channel)
204         s = s + '\n>\n'
205         return s
206                         
207         
208 def convert_midi (f):
209         str = open (f).read ()
210         midi_dump = midi.parse (str)
211
212         tracks = []
213         for t in midi_dump[1]:
214                 tracks.append (split_track (t))
215
216         s = ''
217         for i in range (len (tracks)):
218                 s = s + dump_track (tracks[i], i)
219
220         s = s + '\n\\score {\n  <\n'
221         for i in range (len (tracks)):
222                 track = 'track%c' % (i + ord ('A'))
223                 s = s + '    \\context Staff=%s \\%s\n' % (track, track)
224         s = s + '\n  >\n}\n'
225         
226         sys.stdout.write (s)
227                 
228
229 for f in sys.argv[1:]:
230         convert_midi (f)