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