]> git.donarmstrong.com Git - lilypond.git/blob - scripts/etf2ly.py
release: 1.3.79
[lilypond.git] / scripts / etf2ly.py
1 #!@PYTHON@
2
3 # info taken from
4
5 #  * convertmusic (see sourceforge)
6 #  * Margaret Cahill's thesis
7 #  * http://www.codamusic.com/coda/Fin2000_pdk_download.asp (you have
8 #    to register, but you don't need to have bought a code product
9
10 #
11 # This supports
12
13 #
14 #  * notes
15 #  * rests
16 #  * ties
17 #  * slurs
18 #
19
20 # todo:
21 #  * slur/stem directions
22 #  * articulation
23 #  * voices (2nd half of frame?)
24 #  * lyrics
25 #  * beams (better use autobeam?)
26
27 program_name = 'etf2ly'
28 version = '@TOPLEVEL_VERSION@'
29 if version == '@' + 'TOPLEVEL_VERSION' + '@':
30         version = '(unknown version)'      # uGUHGUHGHGUGH
31   
32 import __main__
33 import getopt
34 import sys
35 import re
36 import string
37 import os
38
39
40
41 finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble8vb', 'bass8vb', 'baritone']
42
43
44 def lily_clef (fin):
45         return finale_clefs[fin]
46
47 def gulp_file(f):
48         return open (f).read ()
49
50 # notename 0 == central C
51 distances = [0, 2, 4, 5, 7, 9, 11, 12]
52 def semitones (name, acc):
53         return (name / 7 ) * 12 + distances[name % 7] + acc
54
55 def transpose(orig, delta):
56         (oname, oacc) = orig
57         (dname, dacc) = delta
58         
59         old_pitch =semitones (oname, oacc)
60         delta_pitch = semitones (dname, dacc)
61         nname = (oname + dname) 
62         nacc = oacc
63         new_pitch = semitones (nname, nacc) 
64
65         nacc = nacc - (new_pitch - old_pitch - delta_pitch)
66
67         return (nname, nacc)
68
69
70
71 # find transposition of C-major scale that belongs here. 
72 def interpret_finale_key_sig (finale_id):
73         p = (0,0)
74         if 0 <= finale_id < 7:
75                 while finale_id > 0:
76                         p = transpose (p, (4,0)) # a fifth up
77                         finale_id = finale_id - 1
78         elif 248 < finale_id <= 255:
79                 while finale_id < 256:
80                         p = transpose (p, (3,0))
81                         finale_id = finale_id + 1
82
83         p  = (p[0] % 7, p[1])
84         return p
85
86 # should cache this.
87 def find_scale (transposition):
88         cscale = map (lambda x: (x,0), range (0,7))
89         trscale = map(lambda x, k=transposition: transpose(x, k), cscale)
90
91         return trscale
92
93 def gcd (a,b):
94         if b == 0:
95                 return a
96         c = a
97         while c: 
98                 c = a % b
99                 a = b
100                 b = c
101         return a
102         
103
104 def rat_simplify (r):
105         (n,d) = r
106         if d < 0:
107                 d = -d
108                 n = -n
109         if n == 0:
110                 return (0,1)
111         else:
112                 g = gcd (n, d)
113                 return (n/g, d/g)
114         
115 def rat_multiply (a,b):
116         (x,y) = a
117         (p,q) = b
118
119         return rat_simplify ((x*p, y*q))
120
121 def rat_add (a,b):
122         (x,y) = a
123         (p,q) = b
124
125         return rat_simplify ((x*q + p*y, y*q))
126
127 def rat_neg (a):
128         (p,q) = a
129         return (-p,q)
130
131 def rat_subtract (a,b ):
132         return rat_add (a, rat_neg (b))
133
134 def lily_notename (tuple2):
135         (n, a) = tuple2
136         nn = chr ((n+ 2)%7 + ord ('a'))
137
138         if a == -1:
139                 nn = nn + 'es'
140         elif a == -2:
141                 nn = nn + 'eses'
142         elif a == 1:
143                 nn = nn + 'is'
144         elif a == 2:
145                 nn = nn + 'isis'
146
147         return nn
148
149 class Slur:
150         def __init__ (self, number):
151                 self.number = number
152                 self.finale = []
153
154         def append_entry (self, finale_e):
155                 self.finale.append (finale_e)
156
157         def calculate (self, chords):
158                 startnote = self.finale[0][5]
159                 endnote = self.finale[3][2]
160
161                 cs = chords[startnote]
162                 cs.suffix = '(' + cs.suffix 
163                 ce = chords[endnote]
164                 ce.prefix = ce.prefix + ')'
165                 
166 class Global_measure:
167         def __init__ (self, number):
168                 self.timesig = ''
169                 self.number = number
170                 self.keysignature = None
171                 self.scale = None
172
173                 self.finale = []
174
175         def set_timesig (self, finale):
176                 (beats, fdur) = finale
177                 (log, dots) = EDU_to_duration (fdur)
178                 assert dots == 0
179                 self.timesig = (beats, log)
180
181         def length (self):
182                 return self.timesig
183         
184         def set_keysig (self, finale):
185                 k = interpret_finale_key_sig (finale)
186                 self.keysignature = k
187                 self.scale = find_scale (k)
188
189
190
191 class Measure:
192         def __init__(self, no):
193                 self.number = no
194                 self.frames = []
195                 self.flags = 0
196                 self.clef = 0
197                 self.finale = []
198                 self.global_measure = None
199                 
200         def add_finale_entry (self, entry):
201                 self.finale.append (entry)
202
203         def calculate (self):
204                 f0  = self.finale[0]
205                 f1 = self.finale[1]
206                 
207                 self.clef = string.atoi (f0[0])
208                 self.flags = string.atoi (f0[1])
209                 fs = map (string.atoi, list (f0[2:]) + [f1[0]])
210
211                 self.frames = fs
212
213 class Frame:
214         def __init__ (self, finale):
215                 self.measure = None
216                 self.finale = finale
217                 (number, start, end ) = finale
218                 self.number = number
219                 self.start = start
220                 self.end = end
221                 self.chords  = []
222
223         def set_measure (self, m):
224                 self.measure = m
225
226         def dump (self):
227                 str = ''
228                 left = self.measure.global_measure.length ()
229                 for c in self.chords:
230                         str = str + c.ly_string () + ' '
231                         left = rat_subtract (left, c.length ())
232                 if left[0] < 0:
233                         print self.number
234                         print self.start, self.end
235                         print left
236                         raise 'bla'
237                 if left[0]:
238                         str = str + 's*%d/%d' % left
239                         
240                 str = str + '\n'
241                 return str
242                 
243 class Staff:
244         def __init__ (self, number):
245                 self.number = number
246                 self.measures = []
247
248         def get_measure (self, no):
249                 if len (self.measures) <= no:
250                         self.measures = self.measures + [None]* (1 + no - len (self.measures))
251
252                 if self.measures[no] == None:
253                         self.measures [no] = Measure (no)
254
255                 return self.measures[no]
256         def staffid (self):
257                 return 'staff%s'% chr (self.number - 1 +ord ('A'))
258         def layerid (self, l):
259                 return self.staffid() +  'layer%s' % chr (l -1 + ord ('A'))
260         
261         def dump_time_key_sigs (self):
262                 k  = ''
263                 last_key = None
264                 last_time = None
265                 last_clef = None
266                 gap = (0,1)
267                 for m in self.measures[1:]:
268                         g = m.global_measure
269                         e = ''
270                         if last_key <> g.keysignature:
271                                 e = e + "\\key %s \\major; " % lily_notename (g.keysignature)
272                                 last_key = g.keysignature
273                         if last_time <> g.timesig :
274                                 e = e + "\\time %d/%d; " % g.timesig
275                                 last_time = g.timesig
276                         if last_clef <> m.clef :
277                                 e = e + '\\clef %s;' % lily_clef (m.clef)
278                                 last_clef = m.clef
279                         if e:
280                                 if gap <> (0,1):
281                                         k = k +' s1*%d/%d \n ' % gap
282                                 gap = (0,1)
283                                 k = k + e
284                         
285                         gap = rat_add (gap, g.length ())
286
287                                 
288                 k = '%sglobal = \\notes  { %s }\n\n ' % (self.staffid (), k)
289                 return k
290         
291         def dump (self):
292                 str = ''
293
294
295                 layerids = []
296                 for x in range (1,5): # 4 layers.
297                         laystr =  ''
298                         last_frame = None
299                         first_frame = None
300                         gap = (0,1)
301                         for m in self.measures[1:]:
302                                 fr = m.frames[x]
303                                 if fr:
304                                         first_frame = fr
305                                         if gap <> (0,1):
306                                                 laystr = laystr +'} s1*%d/%d {\n ' % gap
307                                                 gap = (0,1)
308                                         laystr = laystr + fr.dump ()
309                                 else:
310                                         gap = rat_add (gap, m.global_measure.length ())
311
312                         if first_frame:
313                                 l = self.layerid (x)
314                                 laystr = '%s =  \\notes { { %s } }\n\n' % (l, laystr)
315                                 str = str  + laystr
316                                 layerids.append (l)
317
318                 str = str +  self.dump_time_key_sigs ()         
319                 stafdef = '\\%sglobal' % self.staffid ()
320                 for i in layerids:
321                         stafdef = stafdef + ' \\' + i
322                         
323
324                 str = str + '%s = \\context Staff = %s <\n %s\n >\n' % \
325                       (self.staffid (), self.staffid (), stafdef)
326                 return str
327
328                                 
329 def EDU_to_duration (edu):
330         log = 1
331         d = 4096
332         while d > edu:
333                 d = d >> 1
334                 log = log << 1
335
336         edu = edu - d
337         dots = 0
338         if edu == d /2:
339                 dots = 1
340         elif edu == d*3/4:
341                 dots = 2
342         return (log, dots)      
343
344 class Chord:
345         def __init__ (self, finale_entry):
346                 self.pitches = []
347                 self.frame = None
348                 self.finale = finale_entry
349                 self.duration  = None
350                 self.next = None
351                 self.prev = None
352                 self.prefix= ''
353                 self.suffix = ''
354         def measure (self):
355                 if not self.frame:
356                         return None
357                 return self.frame.measure
358
359         def length (self):
360                 l = (1, self.duration[0])
361
362                 d = 1 << self.duration[1]
363
364                 dotfact = rat_subtract ((2,1), (1,d))
365                 return rat_multiply (dotfact, l)
366                 
367         def number (self):
368                 return self.finale[0][0]
369         def set_duration (self):
370                 ((no, prev, next, dur, pos, entryflag, extended, follow),
371                  notelist) = self.finale
372                 self.duration = EDU_to_duration(dur)
373         def find_realpitch (self):
374                 
375                 ((no, prev, next, dur, pos, entryflag, extended, follow), notelist) = self.finale
376
377                 meas = self.measure ()
378                 tiestart = 0
379                 if not meas or not meas.global_measure  :
380                         print 'note %d not in measure' % self.number ()
381                 elif not meas.global_measure.scale:
382                         print  'note %d: no scale in this measure.' % self.number ()
383                 else:
384                         for p in notelist:
385                                 (pitch, flag) = p
386                                 
387                                 nib1 = pitch & 0x0f
388                                 if nib1 > 8:
389                                         nib1 = -(nib1 - 8)
390                                 rest = pitch / 16
391
392                                 scale =  meas.global_measure.scale 
393                                 (sn, sa) =scale[rest % 7]
394                                 sn = sn + (rest - (rest%7)) + 7
395                                 acc = sa + nib1
396                                 self.pitches.append ((sn, acc))
397                                 tiestart =  tiestart or (flag & Chord.TIE_START_MASK)
398                 if tiestart :
399                         self.suffix = self.suffix + ' ~ '
400                 
401         REST_MASK = 0x40000000L
402         TIE_START_MASK = 0x40000000L
403         def ly_string (self):
404                 s = ''
405
406                 rest = ''
407
408                 if not (self.finale[0][5] & Chord.REST_MASK):
409                         rest = 'r'
410                 
411                 for p in self.pitches:
412                         (n,a) =  p
413                         o = n/ 7
414                         n = n % 7
415
416                         nn = lily_notename ((n,a))
417
418                         if o < 0:
419                                 nn = nn + (',' * -o)
420                         elif o > 0:
421                                 nn = nn + ('\'' * o)
422                                 
423                         if s:
424                                 s = s + ' '
425
426                         if rest:
427                                 nn = rest
428                                 
429                         s = s + '%s%d%s' % (nn, self.duration[0], '.'* self.duration[1])
430                         
431                 if len (self.pitches) > 1:
432                         s = '<%s>' % s
433                 elif not self.pitches:
434                         s  = 'r%d%s' % (self.duration[0] , '.'* self.duration[1])
435
436                 s = self.prefix + s + self.suffix
437                 return s
438
439 GFre = re.compile(r"""^\^GF\(([0-9-]+),([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
440 BCre = re.compile (r"""^\^BC\(([0-9-]+)\) ([0-9-]+) .*$""")
441 eEre = re.compile(r"""^\^eE\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) \$([0-9A-Fa-f]+) ([0-9-]+) ([0-9-]+)""")
442 FRre = re.compile (r"""^\^FR\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
443 MSre = re.compile (r"""^\^MS\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
444 note_re = re.compile (r"""^ +([0-9-]+) \$([A-Fa-f0-9]+)""")
445 Sxre  = re.compile (r"""^\^Sx\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
446
447 class Etf_file:
448         def __init__ (self, name):
449                 self.measures = [None]
450                 self.entries = [None]
451                 self.chords = [None]
452                 self.frames = [None]
453                 self.staffs = [None]
454                 self.slurs = [None]
455
456                 ## do it
457                 self.parse (name)
458
459         def get_global_measure (self, no):
460                 if len (self.measures) <= no:
461                         self.measures = self.measures + [None]* (1 + no - len (self.measures))
462
463                 if self.measures[no] == None:
464                         self.measures [no] = Global_measure (no)
465
466                 return self.measures[no]
467
468                 
469         def get_staff(self,staffno):
470                 if len (self.staffs) <= staffno:
471                         self.staffs = self.staffs + [None] * (1 + staffno - len (self.staffs))
472
473                 if self.staffs[staffno] == None:
474                         self.staffs[staffno] = Staff (staffno)
475
476                 return self.staffs[staffno]
477
478         # staff-spec
479         def try_IS (self, l):
480                 pass
481
482         def try_BC (self, l):
483                 m =  BCre.match  (l)
484                 if m:
485                         bn = string.atoi (m.group (1))
486                         where = string.atoi (m.group (2)) / 1024.0
487                 return m
488
489         def try_eE (self, l):
490                 m = eEre.match (l)
491                 if m:
492                         tup = m.groups()
493                         (no, prev, next, dur, pos, entryflag, extended, follow) = tup
494                         (no, prev, next, dur, pos,extended, follow) \
495                           = tuple (map (string.atoi, [no,prev,next,dur,pos,extended,follow]))
496
497                         entryflag = string.atol (entryflag,16)
498                         assert (no==len (self.entries))
499                         current_entry = ((no, prev, next, dur, pos, entryflag, extended, follow), [])
500                         self.entries.append (current_entry)
501                 return m
502
503         def try_Sx(self,l):
504                 m = Sxre.match (l)
505                 if m:
506                         slurno = string.atoi (m.group (1))
507
508                         if len (self.slurs) == slurno:
509                                 self.slurs.append (Slur (slurno))
510
511                         params = list (m.groups ()[1:])
512                         params = map (string.atoi, params)
513                         self.slurs[-1].append_entry (params)
514
515                 return m        
516         def try_GF(self, l):
517                 m = GFre.match (l)
518                 if m:
519                         (staffno,measno) = m.groups ()[0:2]
520                         s = string.atoi (staffno)
521                         me = string.atoi (measno)
522                         
523                         entry = m.groups () [2:]
524                         st = self.get_staff (s)
525                         meas = st.get_measure (me)
526                         meas.add_finale_entry (entry)
527                 
528         # frame  ?
529         def try_FR(self, l):
530                 m = FRre.match (l)
531                 if m:
532                         (frameno, startnote, endnote, foo, bar) = m.groups ()
533                         (frameno, startnote, endnote)  = tuple (map (string.atoi, [frameno, startnote, endnote]))
534                         self.frames.append (Frame ((frameno, startnote, endnote)))
535                         
536                 return m
537         def try_MS (self, l):
538                 m = MSre.match (l)
539                 if m:
540                         measno = string.atoi (m.group (1))
541                         keynum = string.atoi (m.group (3))
542                         meas =self. get_global_measure (measno)
543                         meas.set_keysig (keynum)
544
545                         beats = string.atoi (m.group (4))
546                         beatlen = string.atoi (m.group (5))
547                         meas.set_timesig ((beats, beatlen))
548                                                 
549                 return m
550
551         def try_note (self, l):
552                 m = note_re.match (l)
553                 if m:
554                         (pitch, flag) = m.groups ()
555                         pitch = string.atoi (pitch)
556                         flag = string.atol (flag,16)
557                         self.entries[-1][1].append ((pitch,flag))
558
559         def parse (self, name):
560                 sys.stderr.write ('parsing ...')
561                 sys.stderr.flush ()
562                 
563                 ls = open (name).readlines ()
564                 for l in ls:
565                         m = None
566                         if not m: 
567                                 m = self.try_MS (l)
568                         if not m: 
569                                 m = self.try_FR (l)
570                         if not m: 
571                                 m = self.try_GF (l)
572                         if not m: 
573                                 m = self.try_note (l)
574                         if not m: 
575                                 m = self.try_eE (l)
576                         if not m:
577                                 m = self.try_Sx (l)
578
579                 sys.stderr.write ('processing ...')
580                 sys.stderr.flush ()
581
582                 self.unthread_entries ()
583
584                 for st in self.staffs[1:]:
585                         if not st:
586                                 continue
587                         mno = 1
588                         for m in st.measures[1:]:
589                                 m.global_measure = self.measures[mno]
590                                 m.calculate()
591
592                                 frame_obj_list = [None]
593                                 for frno in m.frames:
594                                         fr = self.frames[frno]
595                                         frame_obj_list.append (fr)
596
597                                 m.frames = frame_obj_list
598                                 for fr in frame_obj_list[1:]:
599                                         if not fr:
600                                                 continue
601                                         
602                                         fr.set_measure (m)
603                                         
604                                         fr.chords = self.get_thread (fr.start, fr.end)
605                                         for c in fr.chords:
606                                                 c.frame = fr
607                                 mno = mno + 1
608
609                 for c in self.chords[1:]:
610                         c.find_realpitch ()
611                         c.set_duration ()
612                         
613                 for s in self.slurs [1:]:
614                         s.calculate (self.chords)
615                         
616         def get_thread (self, startno, endno):
617
618                 thread = []
619                 c = self.chords[startno]
620                 while c and c.number () <> endno:
621                         thread.append (c)
622                         c = c.next
623
624                 if c: 
625                         thread.append (c)
626                 
627                 return thread
628
629         def dump (self):
630                 str = ''
631                 staffs = []
632                 for s in self.staffs[1:]:
633                         if s:
634                                 str = str + '\n\n' + s.dump () 
635                                 staffs.append ('\\' + s.staffid ())
636
637                 if staffs:
638                         str = str + '\\score { < %s > } ' % string.join (staffs)
639
640                 
641                 return str
642
643
644         def __str__ (self):
645                 return self.dump ()
646         
647         def unthread_entries (self):
648                 self.chords = [None]
649                 for e in self.entries[1:]:
650                         self.chords.append (Chord (e))
651
652                 for e in self.chords[1:]:
653                         e.prev = self.chords[e.finale[0][1]]
654                         e.next = self.chords[e.finale[0][2]]
655
656
657          
658
659
660
661 def identify():
662         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
663
664 def help ():
665         print r"""
666 Convert ETF to LilyPond.
667
668 Usage: etf2ly [OPTION]... ETF-FILE
669
670 Options:
671   -h, --help          this help
672   -o, --output=FILE   set output filename to FILE
673   -v, --version       version information
674
675 Enigma Transport Format is a format used by Coda Music Technology's
676 Finale product. This program will convert a subset of ETF to a
677 ready-to-use lilypond file.
678
679
680 """
681
682 def print_version ():
683         print r"""etf2ly (GNU lilypond) %s""" % version
684
685
686
687 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
688 out_filename = None
689
690 for opt in options:
691         o = opt[0]
692         a = opt[1]
693         if o== '--help' or o == '-h':
694                 help ()
695                 sys.exit (0)
696         if o == '--version' or o == '-v':
697                 print_version ()
698                 sys.exit(0)
699                 
700         if o == '--output' or o == '-o':
701                 out_filename = a
702         else:
703                 print o
704                 raise getopt.error
705
706 identify()
707
708 # header['tagline'] = 'Lily was here %s -- automatically converted from ABC' % version
709 for f in files:
710         if f == '-':
711                 f = ''
712
713         sys.stderr.write ('Processing `%s\'\n' % f)
714         e = Etf_file(f)
715         if not out_filename:
716                 out_filename = os.path.basename (re.sub ('.etf$', '.ly', f))
717                 
718         if out_filename == f:
719                 out_filename = os.path.basename (f + '.ly')
720                 
721         sys.stderr.write ('Writing `%s\'' % out_filename)
722         ly = e.dump()
723
724         
725         
726         fo = open (out_filename, 'w')
727         fo.write ('%% lily was here -- automatically converted by etf2ly from %s\n' % f)
728         fo.write(ly)
729         fo.close ()
730