]> git.donarmstrong.com Git - lilypond.git/blob - scripts/etf2ly.py
add verbs.
[lilypond.git] / scripts / etf2ly.py
1 #!@PYTHON@
2
3 # info mostly taken from looking at files. See also
4 # http://lilypond.org/wiki/?EnigmaTransportFormat
5
6 # This supports
7 #
8 #  * notes
9 #  * rests
10 #  * ties
11 #  * slurs
12 #  * lyrics
13 #  * articulation
14 #  * grace notes
15 #  * tuplets
16 #
17
18 # todo:
19 #  * slur/stem directions
20 #  * voices (2nd half of frame?)
21 #  * more intelligent lyrics
22 #  * beams (better use autobeam?)
23 #  * more robust: try entertainer.etf (freenote)
24 #  * dynamics
25 #  * empty measures (eg. twopt03.etf from freenote)
26 #
27
28
29 program_name = 'etf2ly'
30 version = '@TOPLEVEL_VERSION@'
31 if version == '@' + 'TOPLEVEL_VERSION' + '@':
32         version = '(unknown version)'      # uGUHGUHGHGUGH
33   
34 import __main__
35 import getopt
36 import sys
37 import re
38 import string
39 import os
40
41 finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble_8', 'bass_8', 'baritone']
42
43 def lily_clef (fin):
44         try:
45                 return finale_clefs[fin]
46         except IndexError:
47                 sys.stderr.write ( '\nHuh? Found clef number %d\n' % fin)
48
49         return 'treble'
50         
51         
52
53 def gulp_file(f):
54         return open (f).read ()
55
56 # notename 0 == central C
57 distances = [0, 2, 4, 5, 7, 9, 11, 12]
58 def semitones (name, acc):
59         return (name / 7 ) * 12 + distances[name % 7] + acc
60
61 # represent pitches as (notename, alteration), relative to C-major scale
62 def transpose(orig, delta):
63         (oname, oacc) = orig
64         (dname, dacc) = delta
65         
66         old_pitch =semitones (oname, oacc)
67         delta_pitch = semitones (dname, dacc)
68         nname = (oname + dname) 
69         nacc = oacc
70         new_pitch = semitones (nname, nacc) 
71
72         nacc = nacc - (new_pitch - old_pitch - delta_pitch)
73
74         return (nname, nacc)
75
76
77
78 def interpret_finale_key_sig (finale_id):
79         """
80 find the transposition of C-major scale that belongs here.
81
82 we are not going to insert the correct major/minor, we only want to
83 have the correct number of accidentals
84 """
85
86         p = (0,0)
87
88         
89         bank_number = finale_id >> 8
90         accidental_bits = finale_id & 0xff
91
92         if 0 <= accidental_bits < 7:
93                 while accidental_bits > 0:
94                         p = transpose (p, (4,0)) # a fifth up
95                         accidental_bits = accidental_bits - 1
96         elif 248 < accidental_bits <= 255:
97                 while accidental_bits < 256:
98                         p = transpose (p, (3,0))
99                         accidental_bits = accidental_bits + 1
100                         
101         p  = (p[0] % 7, p[1])
102
103         return p
104
105 # should cache this.
106 def find_scale (transposition):
107         cscale = map (lambda x: (x,0), range (0,7))
108         trscale = map(lambda x, k=transposition: transpose(x, k), cscale)
109
110         return trscale
111 def EDU_to_duration (edu):
112         log = 1
113         d = 4096
114         while d > edu:
115                 d = d >> 1
116                 log = log << 1
117
118         edu = edu - d
119         dots = 0
120         if edu == d /2:
121                 dots = 1
122         elif edu == d*3/4:
123                 dots = 2
124         return (log, dots)      
125
126 def rat_to_lily_duration (rat):
127         (n,d) = rat
128
129         basedur = 1
130         while d and  d % 2 == 0:
131                 basedur = basedur << 1
132                 d = d >> 1
133
134         str = 's%d' % basedur
135         if n <> 1:
136                 str = str + '*%d' % n
137         if d <> 1:
138                 str = str + '/%d' % d
139
140         return str
141
142 def gcd (a,b):
143         if b == 0:
144                 return a
145         c = a
146         while c: 
147                 c = a % b
148                 a = b
149                 b = c
150         return a
151         
152
153 def rat_simplify (r):
154         (n,d) = r
155         if d < 0:
156                 d = -d
157                 n = -n
158         if n == 0:
159                 return (0,1)
160         else:
161                 g = gcd (n, d)
162                 return (n/g, d/g)
163         
164 def rat_multiply (a,b):
165         (x,y) = a
166         (p,q) = b
167
168         return rat_simplify ((x*p, y*q))
169
170 def rat_add (a,b):
171         (x,y) = a
172         (p,q) = b
173
174         return rat_simplify ((x*q + p*y, y*q))
175
176 def rat_neg (a):
177         (p,q) = a
178         return (-p,q)
179
180
181
182 def rat_subtract (a,b ):
183         return rat_add (a, rat_neg (b))
184
185 def lily_notename (tuple2):
186         (n, a) = tuple2
187         nn = chr ((n+ 2)%7 + ord ('a'))
188
189         return nn + {-2:'eses', -1:'es', 0:'', 1:'is', 2:'isis'}[a]
190
191
192 class Tuplet:
193         def __init__ (self, number):
194                 self.start_note = number
195                 self.finale = []
196
197         def append_finale (self, fin):
198                 self.finale.append (fin)
199
200         def factor (self):
201                 n = self.finale[0][2]*self.finale[0][3]
202                 d = self.finale[0][0]*self.finale[0][1]
203                 return rat_simplify( (n, d))
204         
205         def dump_start (self):
206                 return '\\times %d/%d { ' % self.factor ()
207         
208         def dump_end (self):
209                 return ' }'
210
211         def calculate (self, chords):
212                 edu_left = self.finale[0][0] * self.finale[0][1]
213
214                 startch = chords[self.start_note]
215                 c = startch
216                 while c and edu_left:
217                         c.tuplet = self
218                         if c == startch:
219                                 c.chord_prefix = self.dump_start () + c.chord_prefix 
220
221                         if not c.grace:
222                                 edu_left = edu_left - c.EDU_duration ()
223                         if edu_left == 0:
224                                 c.chord_suffix = c.chord_suffix+ self.dump_end ()
225                         c = c.next
226
227                 if edu_left:
228                         sys.stderr.write ("\nHuh? Tuplet starting at entry %d was too short." % self.start_note)
229                 
230 class Slur:
231         def __init__ (self, number, params):
232                 self.number = number
233                 self.finale = params
234
235         def append_entry (self, finale_e):
236                 self.finale.append (finale_e)
237
238         def calculate (self, chords):
239                 startnote = self.finale[5]
240                 endnote = self.finale[3*6 + 2]
241                 try:
242                         cs = chords[startnote]
243                         ce = chords[endnote]
244
245                         if not cs or not ce:
246                                 raise IndexError
247                         
248                         cs.note_suffix = '-(' + cs.note_suffix 
249                         ce.note_suffix = ce.note_suffix + '-)'
250                         
251                 except IndexError:
252                         sys.stderr.write ("""\nHuh? Slur no %d between (%d,%d), with %d notes""" % (self.number,  startnote, endnote, len (chords)))
253                                          
254                 
255 class Global_measure:
256         def __init__ (self, number):
257                 self.timesig = ''
258                 self.number = number
259                 self.key_signature = None
260                 self.scale = None
261                 self.force_break = 0
262                 
263                 self.repeats = []
264                 self.finale = []
265
266         def __str__ (self):
267                 return `self.finale `
268         
269         def set_timesig (self, finale):
270                 (beats, fdur) = finale
271                 (log, dots) = EDU_to_duration (fdur)
272
273                 if dots == 1:
274                         beats = beats * 3
275                         log = log * 2
276                         dots = 0
277
278                 if dots <> 0:
279                         sys.stderr.write ("\nHuh? Beat duration has  dots? (EDU Duration = %d)" % fdur) 
280                 self.timesig = (beats, log)
281
282         def length (self):
283                 return self.timesig
284         
285         def set_key_sig (self, finale):
286                 k = interpret_finale_key_sig (finale)
287                 self.key_signature = k
288                 self.scale = find_scale (k)
289
290         def set_flags (self,flag1, flag2):
291                 
292                 # flag1 isn't all that interesting.
293                 if flag2 & 0x8000:
294                         self.force_break = 1
295                         
296                 if flag2 & 0x0008:
297                         self.repeats.append ('start')
298                 if flag2 & 0x0004:
299                         self.repeats.append ('stop')
300                         
301                 if flag2 & 0x0002:
302                         if flag2 & 0x0004:
303                                 self.repeats.append ('bracket')
304
305 articulation_dict ={
306         94: '^',
307         109: '\\prall',
308         84: '\\turn',
309         62: '\\mordent',
310         85: '\\fermata',
311         46: '.',
312 #       3: '>',
313 #       18: '\arpeggio' ,
314 }
315
316 class Articulation_def:
317         def __init__ (self, n, a, b):
318                 self.finale_glyph = a & 0xff
319                 self.number = n
320
321         def dump (self):
322                 try:
323                         return articulation_dict[self.finale_glyph]
324                 except KeyError:
325                         sys.stderr.write ("\nUnknown articulation no. %d" % self.finale_glyph)
326                         sys.stderr.write ("\nPlease add an entry to articulation_dict in the Python source")                    
327                         return None
328         
329 class Articulation:
330         def __init__ (self, a,b, finale):
331                 self.definition = finale[0]
332                 self.notenumber = b
333                 
334         def calculate (self, chords, defs):
335                 c = chords[self.notenumber]
336
337                 adef = defs[self.definition]
338                 lystr =adef.dump()
339                 if lystr == None:
340                         lystr = '"art"'
341                         sys.stderr.write ("\nThis happened on note %d" % self.notenumber)
342
343                 c.note_suffix = '-' + lystr
344
345 class Syllable:
346         def __init__ (self, a,b , finale):
347                 self.chordnum = b
348                 self.syllable = finale[1]
349                 self.verse = finale[0]
350         def calculate (self, chords, lyrics):
351                 self.chord = chords[self.chordnum]
352
353 class Verse:
354         def __init__ (self, number, body):
355                 self.body = body
356                 self.number = number
357                 self.split_syllables ()
358         def split_syllables (self):
359                 ss = re.split ('(-| +)', self.body)
360
361                 sep = 0
362                 syls = [None]
363                 for s in ss:
364                         if sep:
365                                 septor = re.sub (" +", "", s)
366                                 septor = re.sub ("-", " -- ", septor) 
367                                 syls[-1] = syls[-1] + septor
368                         else:
369                                 syls.append (s)
370                         
371                         sep = not sep 
372
373                 self.syllables = syls
374
375         def dump (self):
376                 str = ''
377                 line = ''
378                 for s in self.syllables[1:]:
379                         line = line + ' ' + s
380                         if len (line) > 72:
381                                 str = str + ' ' * 4 + line + '\n'
382                                 line = ''
383                         
384                 str = """\nverse%s = \\lyrics {\n %s}\n""" %  (encodeint (self.number - 1) ,str)
385                 return str
386
387         
388 class Measure:
389         def __init__(self, no):
390                 self.number = no
391                 self.frames = [0] * 4
392                 self.flags = 0
393                 self.clef = 0
394                 self.finale = []
395                 self.global_measure = None
396                 self.staff = None
397                 self.valid = 1
398                 
399
400         def valid (self):
401                 return self.valid
402         def calculate (self):
403                 fs = []
404
405                 if len (self.finale) < 2:
406                         fs = self.finale[0]
407
408                         self.clef = fs[1]
409                         self.frames = [fs[0]]
410                 else:
411                         fs = self.finale
412                         self.clef = fs[0]
413                         self.flags = fs[1]
414                         self.frames = fs[2:]
415
416
417 class Frame:
418         def __init__ (self, finale):
419                 self.measure = None
420                 self.finale = finale
421                 (number, start, end ) = finale
422                 self.number = number
423                 self.start = start
424                 self.end = end
425                 self.chords  = []
426
427         def set_measure (self, m):
428                 self.measure = m
429
430         def calculate (self):
431
432                 # do grace notes.
433                 lastch = None
434                 for c in self.chords:
435                         if c.grace and (lastch == None or (not lastch.grace)):
436                                 c.chord_prefix = r'\grace {' + c.chord_prefix
437                         elif not c.grace and lastch and lastch.grace:
438                                 lastch.chord_suffix = lastch.chord_suffix + ' } '
439
440                         lastch = c
441                         
442
443                 
444         def dump (self):
445                 str = '%% FR(%d)\n' % self.number
446                 left = self.measure.global_measure.length ()
447
448                 
449                 ln = ''
450                 for c in self.chords:
451                         add = c.ly_string () + ' '
452                         if len (ln) + len(add) > 72:
453                                 str = str + ln + '\n'
454                                 ln = ''
455                         ln = ln + add
456                         left = rat_subtract (left, c.length ())
457
458                 str = str + ln 
459                 
460                 if left[0] < 0:
461                         sys.stderr.write ("""\nHuh? Going backwards in frame no %d, start/end (%d,%d)""" % (self.number, self.start, self.end))
462                         left = (0,1)
463                 if left[0]:
464                         str = str + rat_to_lily_duration (left)
465
466                 str = str + '  | \n'
467                 return str
468                 
469 def encodeint (i):
470         return chr ( i  + ord ('A'))
471
472 class Staff:
473         def __init__ (self, number):
474                 self.number = number
475                 self.measures = []
476
477         def get_measure (self, no):
478                 fill_list_to (self.measures, no)
479
480                 if self.measures[no] == None:
481                         m = Measure (no)
482                         self.measures [no] =m
483                         m.staff = self
484
485                 return self.measures[no]
486         def staffid (self):
487                 return 'staff' + encodeint (self.number - 1)
488         def layerid (self, l):
489                 return self.staffid() +  'layer%s' % chr (l -1 + ord ('A'))
490         
491         def dump_time_key_sigs (self):
492                 k  = ''
493                 last_key = None
494                 last_time = None
495                 last_clef = None
496                 gap = (0,1)
497                 for m in self.measures[1:]:
498                         if not m or not m.valid:
499                                 continue # ugh.
500                         
501                         g = m.global_measure
502                         e = ''
503                         
504                         if g:
505                                 if last_key <> g.key_signature:
506                                         pitch= g.key_signature                                  
507                                         e = e + "\\key %s \\major " % (lily_notename (pitch))
508                                         
509                                         last_key = g.key_signature
510                                 if last_time <> g.timesig :
511                                         e = e + "\\time %d/%d " % g.timesig
512                                         last_time = g.timesig
513
514                                 if 'start' in g.repeats:
515                                         e = e + ' \\bar "|:" ' 
516
517
518                                 # we don't attempt voltas since they fail easily.
519                                 if 0 : # and g.repeat_bar == '|:' or g.repeat_bar == ':|:' or g.bracket:
520                                         strs = []
521                                         if g.repeat_bar == '|:' or g.repeat_bar == ':|:' or g.bracket == 'end':
522                                                 strs.append ('#f')
523
524                                         
525                                         if g.bracket == 'start':
526                                                 strs.append ('"0."')
527
528                                         str = string.join (map (lambda x: '(volta %s)' % x, strs))
529                                         
530                                         e = e + ' \\property Score.repeatCommands =  #\'(%s) ' % str
531
532                                 if g.force_break:
533                                         e = e + ' \\break '  
534                         
535                         if last_clef <> m.clef :
536                                 e = e + '\\clef "%s"' % lily_clef (m.clef)
537                                 last_clef = m.clef
538                         if e:
539                                 if gap <> (0,1):
540                                         k = k +' ' + rat_to_lily_duration (gap) + '\n'
541                                 gap = (0,1)
542                                 k = k + e
543                                 
544                         if g:
545                                 gap = rat_add (gap, g.length ())
546                                 if 'stop' in g.repeats:
547                                         k = k + ' \\bar ":|" '
548                                 
549                 k = '%sglobal = \\notes  { %s }\n\n ' % (self.staffid (), k)
550                 return k
551         
552         def dump (self):
553                 str = ''
554
555
556                 layerids = []
557                 for x in range (1,5): # 4 layers.
558                         laystr =  ''
559                         last_frame = None
560                         first_frame = None
561                         gap = (0,1)
562                         for m in self.measures[1:]:
563                                 if not m or not m.valid:
564                                         sys.stderr.write ("Skipping non-existant or invalid measure\n")
565                                         continue
566
567                                 fr = None
568                                 try:
569                                         fr = m.frames[x]
570                                 except IndexError:
571                                         sys.stderr.write ("Skipping nonexistent frame %d\n" % x)
572                                         laystr = laystr + "%% non existent frame %d (skipped) \n" % x
573                                 if fr:
574                                         first_frame = fr
575                                         if gap <> (0,1):
576                                                 laystr = laystr +'} %s {\n ' % rat_to_lily_duration (gap)
577                                                 gap = (0,1)
578                                         laystr = laystr + fr.dump ()
579                                 else:
580                                         if m.global_measure :
581                                                 gap = rat_add (gap, m.global_measure.length ())
582                                         else:
583                                                 sys.stderr.write ( \
584                                                         "No global measure for staff %d measure %d\n"
585                                                         % (self.number, m.number))
586                         if first_frame:
587                                 l = self.layerid (x)
588                                 laystr = '%s =  \\notes { { %s } }\n\n' % (l, laystr)
589                                 str = str  + laystr
590                                 layerids.append (l)
591
592                 str = str +  self.dump_time_key_sigs ()         
593                 stafdef = '\\%sglobal' % self.staffid ()
594                 for i in layerids:
595                         stafdef = stafdef + ' \\' + i
596                         
597
598                 str = str + '%s = \\context Staff = %s <\n %s\n >\n' % \
599                       (self.staffid (), self.staffid (), stafdef)
600                 return str
601
602                                 
603
604 def ziplist (l):
605         if len (l) < 2:
606                 return []
607         else:
608                 return [(l[0], l[1])] + ziplist (l[2:])
609
610
611 class Chord:
612         def __init__ (self, number, contents):
613                 self.pitches = []
614                 self.frame = None
615                 self.finale = contents[:7]
616
617                 self.notelist = ziplist (contents[7:])
618                 self.duration  = None
619                 self.next = None
620                 self.prev = None
621                 self.number = number
622                 self.note_prefix= ''
623                 self.note_suffix = ''
624                 self.chord_suffix = ''
625                 self.chord_prefix = ''
626                 self.tuplet = None
627                 self.grace = 0
628                 
629         def measure (self):
630                 if not self.frame:
631                         return None
632                 return self.frame.measure
633
634         def length (self):
635                 if self.grace:
636                         return (0,1)
637                 
638                 l = (1, self.duration[0])
639
640                 d = 1 << self.duration[1]
641
642                 dotfact = rat_subtract ((2,1), (1,d))
643                 mylen =  rat_multiply (dotfact, l)
644
645                 if self.tuplet:
646                         mylen = rat_multiply (mylen, self.tuplet.factor())
647                 return mylen
648                 
649
650         def EDU_duration (self):
651                 return self.finale[2]
652         def set_duration (self):
653                 self.duration = EDU_to_duration(self.EDU_duration ())
654                 
655         def calculate (self):
656                 self.find_realpitch ()
657                 self.set_duration ()
658
659                 flag = self.finale[4]
660                 if Chord.GRACE_MASK & flag:
661                         self.grace = 1
662                 
663         
664         def find_realpitch (self):
665
666                 meas = self.measure ()
667                 tiestart = 0
668                 if not meas or not meas.global_measure  :
669                         sys.stderr.write ('note %d not in measure\n' % self.number)
670                 elif not meas.global_measure.scale:
671                         sys.stderr.write ('note %d: no scale in this measure.' % self.number)
672                 else:
673                         
674                         for p in self.notelist:
675                                 (pitch, flag) = p
676
677
678                                 nib1 = pitch & 0x0f
679                                 
680                                 if nib1 > 8:
681                                         nib1 = -(nib1 - 8)
682                                 rest = pitch / 16
683
684                                 scale =  meas.global_measure.scale 
685                                 (sn, sa) =scale[rest % 7]
686                                 sn = sn + (rest - (rest%7)) + 7
687                                 acc = sa + nib1
688                                 self.pitches.append ((sn, acc))
689                                 tiestart =  tiestart or (flag & Chord.TIE_START_MASK)
690                 if tiestart :
691                         self.chord_suffix = self.chord_suffix + ' ~ '
692                 
693         REST_MASK = 0x40000000L
694         TIE_START_MASK = 0x40000000L
695         GRACE_MASK = 0x00800000L
696         
697         def ly_string (self):
698                 s = ''
699
700                 rest = ''
701
702
703                 if not (self.finale[4] & Chord.REST_MASK):
704                         rest = 'r'
705                 
706                 for p in self.pitches:
707                         (n,a) =  p
708                         o = n/ 7
709                         n = n % 7
710
711                         nn = lily_notename ((n,a))
712
713                         if o < 0:
714                                 nn = nn + (',' * -o)
715                         elif o > 0:
716                                 nn = nn + ('\'' * o)
717                                 
718                         if s:
719                                 s = s + ' '
720
721                         if rest:
722                                 nn = rest
723                                 
724                         s = s + nn 
725
726                 if not self.pitches:
727                         s  = 'r'
728                 if len (self.pitches) > 1:
729                         s = '<< %s >>' % s
730
731                 s = s + '%d%s' % (self.duration[0], '.'* self.duration[1])
732                 s = self.note_prefix + s + self.note_suffix
733                 
734                 s = self.chord_prefix + s + self.chord_suffix
735
736                 return s
737
738
739 def fill_list_to (list, no):
740         """
741 Add None to LIST until it contains entry number NO.
742         """
743         while len (list) <= no:
744                 list.extend ([None] * (no - len(list) + 1))
745         return list
746
747 def read_finale_value (str):
748         """
749 Pry off one value from STR. The value may be $hex, decimal, or "string".
750 Return: (value, rest-of-STR)
751         """
752         while str and str[0] in ' \t\n':
753                 str = str[1:]
754
755         if not str:
756                 return (None,str)
757         
758         if str[0] == '$':
759                 str = str [1:]
760
761                 hex = ''
762                 while str and str[0] in '0123456789ABCDEF':
763                         hex = hex  + str[0]
764                         str = str[1:]
765
766                 
767                 return (string.atol (hex, 16), str)
768         elif str[0] == '"':
769                 str = str[1:]
770                 s = ''
771                 while str and str[0] <> '"':
772                         s = s + str[0]
773                         str = str[1:]
774
775                 return (s,str)
776         elif str[0] in '-0123456789':
777                 dec = ''
778                 while str and str[0] in '-0123456789':
779                         dec = dec  + str[0]
780                         str = str[1:]
781                         
782                 return (string.atoi (dec), str)
783         else:
784                 sys.stderr.write ("Can't convert `%s'\n" % str)
785                 return (None, str)
786
787
788
789         
790 def parse_etf_file (fn, tag_dict):
791
792         """ Read FN, putting ETF info into
793         a giant dictionary.  The keys of TAG_DICT indicate which tags
794         to put into the dict.
795         """
796         
797         sys.stderr.write ('parsing ... ' )
798         f = open (fn)
799         
800         gulp = re.sub ('[\n\r]+', '\n',  f.read ())
801         ls = string.split (gulp, '\n^')
802
803         etf_file_dict = {}
804         for k in tag_dict.keys (): 
805                 etf_file_dict[k] = {}
806
807         last_tag = None
808         last_numbers = None
809
810
811         for l in  ls:
812                 m = re.match ('^([a-zA-Z0-9&]+)\(([^)]+)\)', l)
813                 if m and tag_dict.has_key (m.group (1)):
814                         tag = m.group (1)
815
816                         indices = tuple (map (string.atoi, string.split (m.group (2), ',')))
817                         content = l[m.end (2)+1:]
818
819
820                         tdict = etf_file_dict[tag]
821                         if not tdict.has_key (indices):
822                                 tdict[indices] = []
823
824
825                         parsed = []
826
827                         if tag == 'verse' or tag == 'block':
828                                 m2 = re.match ('(.*)\^end', content)
829                                 if m2:
830                                         parsed = [m2.group (1)]
831                         else:
832                                 while content:
833                                         (v, content) = read_finale_value (content)
834                                         if v <> None:
835                                                 parsed.append (v)
836
837                         tdict [indices].extend (parsed)
838
839                         last_indices = indices
840                         last_tag = tag
841
842                         continue
843
844 # let's not do this: this really confuses when eE happens to be before  a ^text.
845 #               if last_tag and last_indices:
846 #                       etf_file_dict[last_tag][last_indices].append (l)
847                         
848         sys.stderr.write ('\n') 
849         return etf_file_dict
850
851         
852
853
854
855 class Etf_file:
856         def __init__ (self, name):
857                 self.measures = [None]
858                 self.chords = [None]
859                 self.frames = [None]
860                 self.tuplets = [None]
861                 self.staffs = [None]
862                 self.slurs = [None]
863                 self.articulations = [None]
864                 self.syllables = [None]
865                 self.verses = [None]
866                 self.articulation_defs = [None]
867
868                 ## do it
869                 self.parse (name)
870
871         def get_global_measure (self, no):
872                 fill_list_to (self.measures, no)
873                 if self.measures[no] == None:
874                         self.measures [no] = Global_measure (no)
875
876                 return self.measures[no]
877
878                 
879         def get_staff(self,staffno):
880                 fill_list_to (self.staffs, staffno)
881                 if self.staffs[staffno] == None:
882                         self.staffs[staffno] = Staff (staffno)
883
884                 return self.staffs[staffno]
885
886         # staff-spec
887         def try_IS (self, indices, contents):
888                 pass
889
890         def try_BC (self, indices, contents):
891                 bn = indices[0]
892                 where = contents[0] / 1024.0
893         def try_TP(self,  indices, contents):
894                 (nil, num) = indices
895
896                 if self.tuplets[-1] == None or num <> self.tuplets[-1].start_note:
897                         self.tuplets.append (Tuplet (num))
898
899                 self.tuplets[-1].append_finale (contents)
900
901         def try_IM (self, indices, contents):
902                 (a,b) = indices
903                 fin = contents
904                 self.articulations.append (Articulation (a,b,fin))
905         def try_verse (self, indices, contents):
906                 a = indices[0]
907                 body = contents[0]
908
909                 body = re.sub (r"""\^[a-z]+\([^)]+\)""", "", body)
910                 body = re.sub ("\^[a-z]+", "", body)
911                 self.verses.append (Verse (a, body))
912         def try_ve (self,indices, contents):
913                 (a,b) = indices
914                 self.syllables.append (Syllable (a,b,contents))
915
916         def try_eE (self,indices, contents):
917                 no = indices[0]
918                 (prev, next, dur, pos, entryflag, extended, follow) = contents[:7]
919
920                 fill_list_to (self.chords, no)
921                 self.chords[no]  =Chord (no, contents)
922
923         def try_Sx(self,indices, contents):
924                 slurno = indices[0]
925                 fill_list_to (self.slurs, slurno)
926                 self.slurs[slurno] = Slur(slurno, contents)
927
928         def try_IX (self, indices, contents):
929                 n = indices[0]
930                 a = contents[0]
931                 b = contents[1]
932
933                 ix= None
934                 try:
935                         ix = self.articulation_defs[n]
936                 except IndexError:
937                         ix = Articulation_def (n,a,b)
938                         self.articulation_defs.append (Articulation_def (n, a, b))
939
940         def try_GF(self, indices, contents):
941                 (staffno,measno) = indices
942
943                 st = self.get_staff (staffno)
944                 meas = st.get_measure (measno)
945                 meas.finale = contents
946                 
947         def try_FR(self, indices, contents):
948                 frameno = indices [0]
949                 
950                 startnote = contents[0]
951                 endnote = contents[1]
952
953                 fill_list_to (self.frames, frameno)
954         
955                 self.frames[frameno] = Frame ((frameno, startnote, endnote))
956         
957         def try_MS (self, indices, contents):
958                 measno = indices[0]
959                 keynum = contents[1]
960                 meas =self. get_global_measure (measno)
961
962                 meas.set_key_sig (keynum)
963
964                 beats = contents[2]
965                 beatlen = contents[3]
966                 meas.set_timesig ((beats, beatlen))
967
968                 meas_flag1 = contents[4]
969                 meas_flag2 = contents[5]
970
971                 meas.set_flags (meas_flag1, meas_flag2);
972
973
974         routine_dict = {
975                 'MS': try_MS,
976                 'FR': try_FR,
977                 'GF': try_GF,
978                 'IX': try_IX,
979                 'Sx' : try_Sx,
980                 'eE' : try_eE,
981                 'verse' : try_verse,
982                 've' : try_ve,
983                 'IM' : try_IM,
984                 'TP' : try_TP,
985                 'BC' : try_BC,
986                 'IS' : try_IS,
987                 }
988         
989         def parse (self, etf_dict):
990                 sys.stderr.write ('reconstructing ...')
991                 sys.stderr.flush ()
992
993                 for (tag,routine) in Etf_file.routine_dict.items ():
994                         ks = etf_dict[tag].keys ()
995                         ks.sort ()
996                         for k in ks:
997                                 routine (self, k, etf_dict[tag][k])
998                         
999                 sys.stderr.write ('processing ...')
1000                 sys.stderr.flush ()
1001
1002                 self.unthread_entries ()
1003
1004                 for st in self.staffs[1:]:
1005                         if not st:
1006                                 continue
1007                         mno = 1
1008                         for m in st.measures[1:]:
1009                                 if not m:
1010                                         continue
1011                                 
1012                                 m.calculate()
1013                                 try:
1014                                         m.global_measure = self.measures[mno]
1015                                 except IndexError:
1016                                         sys.stderr.write ("Non-existent global measure %d" % mno)
1017                                         continue
1018                                 
1019                                 frame_obj_list = [None]
1020                                 for frno in m.frames:
1021                                         try:
1022                                                 fr = self.frames[frno]
1023                                                 frame_obj_list.append (fr)
1024                                         except IndexError:
1025                                                 sys.stderr.write ("\nNon-existent frame %d"  % frno)
1026
1027                                 m.frames = frame_obj_list
1028                                 for fr in frame_obj_list[1:]:
1029                                         if not fr:
1030                                                 continue
1031                                         
1032                                         fr.set_measure (m)
1033                                         
1034                                         fr.chords = self.get_thread (fr.start, fr.end)
1035                                         for c in fr.chords:
1036                                                 c.frame = fr
1037                                 mno = mno + 1
1038
1039                 for c in self.chords[1:]:
1040                         if c:
1041                                 c.calculate()
1042
1043                 for f in self.frames[1:]:
1044                         if f:
1045                                 f.calculate ()
1046                         
1047                 for t in self.tuplets[1:]:
1048                         t.calculate (self.chords)
1049                         
1050                 for s in self.slurs[1:]:
1051                         if s:
1052                                 s.calculate (self.chords)
1053                         
1054                 for s in self.articulations[1:]:
1055                         s.calculate (self.chords, self.articulation_defs)
1056                         
1057         def get_thread (self, startno, endno):
1058
1059                 thread = []
1060
1061                 c = None
1062                 try:
1063                         c = self.chords[startno]
1064                 except IndexError:
1065                         sys.stderr.write ("Huh? Frame has invalid bounds (%d,%d)\n" % (startno, endno))
1066                         return []
1067
1068                 
1069                 while c and c.number <> endno:
1070                         thread.append (c)
1071                         c = c.next
1072
1073                 if c: 
1074                         thread.append (c)
1075                 
1076                 return thread
1077
1078         def dump (self):
1079                 str = ''
1080                 staffs = []
1081                 for s in self.staffs[1:]:
1082                         if s:
1083                                 str = str + '\n\n' + s.dump () 
1084                                 staffs.append ('\\' + s.staffid ())
1085
1086
1087                 # should use \addlyrics ?
1088
1089                 for v in self.verses[1:]:
1090                         str = str + v.dump()
1091
1092                 if len (self.verses) > 1:
1093                         sys.stderr.write ("\nLyrics found; edit to use \\addlyrics to couple to a staff\n")
1094                         
1095                 if staffs:
1096                         str = str + '\\score { < %s > } ' % string.join (staffs)
1097                         
1098                 return str
1099
1100
1101         def __str__ (self):
1102                 return 'ETF FILE %s %s' % (self.measures,  self.entries)
1103         
1104         def unthread_entries (self):
1105                 for e in self.chords[1:]:
1106                         if not e:
1107                                 continue
1108
1109                         e.prev = self.chords[e.finale[0]]
1110                         e.next = self.chords[e.finale[1]]
1111
1112 def identify():
1113         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
1114
1115 def help ():
1116         sys.stdout.write("""Usage: etf2ly [OPTION]... ETF-FILE
1117
1118 Convert ETF to LilyPond.
1119
1120 Options:
1121   -h,--help          print this help
1122   -o,--output=FILE   set output filename to FILE
1123   -v,--version       show version information
1124
1125 Enigma Transport Format is a format used by Coda Music Technology's
1126 Finale product. This program will convert a subset of ETF to a
1127 ready-to-use lilypond file.
1128
1129 Report bugs to bug-lilypond@gnu.org
1130
1131 Written by  Han-Wen Nienhuys <hanwen@cs.uu.nl>
1132 """)
1133
1134 def print_version ():
1135         sys.stdout.write (r"""etf2ly (GNU lilypond) %s
1136
1137 This is free software.  It is covered by the GNU General Public License,
1138 and you are welcome to change it and/or distribute copies of it under
1139 certain conditions.  Invoke as `midi2ly --warranty' for more information.
1140
1141 Copyright (c)  2000--2003 by Han-Wen Nienhuys <hanwen@cs.uu.nl>
1142 """ % version)
1143
1144
1145
1146 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
1147 out_filename = None
1148
1149 for opt in options:
1150         o = opt[0]
1151         a = opt[1]
1152         if o== '--help' or o == '-h':
1153                 help ()
1154                 sys.exit (0)
1155         if o == '--version' or o == '-v':
1156                 print_version ()
1157                 sys.exit(0)
1158                 
1159         if o == '--output' or o == '-o':
1160                 out_filename = a
1161         else:
1162                 print o
1163                 raise getopt.error
1164
1165 identify()
1166
1167 e = None
1168 for f in files:
1169         if f == '-':
1170                 f = ''
1171
1172         sys.stderr.write ('Processing `%s\'\n' % f)
1173
1174         dict = parse_etf_file (f, Etf_file.routine_dict)
1175         e = Etf_file(dict)
1176         if not out_filename:
1177                 out_filename = os.path.basename (re.sub ('(?i).etf$', '.ly', f))
1178                 
1179         if out_filename == f:
1180                 out_filename = os.path.basename (f + '.ly')
1181                 
1182         sys.stderr.write ('Writing `%s\'' % out_filename)
1183         ly = e.dump()
1184
1185         
1186         
1187         fo = open (out_filename, 'w')
1188         fo.write ('%% lily was here -- automatically converted by etf2ly from %s\n' % f)
1189         fo.write(ly)
1190         fo.close ()
1191