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