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