]> git.donarmstrong.com Git - lilypond.git/blob - scripts/etf2ly.py
release: 1.3.82
[lilypond.git] / scripts / etf2ly.py
1 #!@PYTHON@
2
3 # info mostly taken from looking at files. See also
4 # http://www.cs.uu.nl/~hanwen/lily-devel/etf.html
5
6 # This supports
7 #
8 #  * notes
9 #  * rests
10 #  * ties
11 #  * slurs
12 #  * lyrics
13 #  * articulation
14
15
16 # todo:
17 #  * automatic PC/mac/unix conversion
18 #  * slur/stem directions
19 #  * voices (2nd half of frame?)
20 #  * more intelligent lyrics
21 #  * beams (better use autobeam?)
22 #  * more robust: try entertainer.etf (freenote), schubert ave maria (gmd)
23
24 program_name = 'etf2ly'
25 version = '@TOPLEVEL_VERSION@'
26 if version == '@' + 'TOPLEVEL_VERSION' + '@':
27         version = '(unknown version)'      # uGUHGUHGHGUGH
28   
29 import __main__
30 import getopt
31 import sys
32 import re
33 import string
34 import os
35
36 finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble8vb', 'bass8vb', 'baritone']
37
38 def lily_clef (fin):
39         return finale_clefs[fin]
40
41 def gulp_file(f):
42         return open (f).read ()
43
44 # notename 0 == central C
45 distances = [0, 2, 4, 5, 7, 9, 11, 12]
46 def semitones (name, acc):
47         return (name / 7 ) * 12 + distances[name % 7] + acc
48
49 # represent pitches as (notename, alteration), relative to C-major scale
50 def transpose(orig, delta):
51         (oname, oacc) = orig
52         (dname, dacc) = delta
53         
54         old_pitch =semitones (oname, oacc)
55         delta_pitch = semitones (dname, dacc)
56         nname = (oname + dname) 
57         nacc = oacc
58         new_pitch = semitones (nname, nacc) 
59
60         nacc = nacc - (new_pitch - old_pitch - delta_pitch)
61
62         return (nname, nacc)
63
64
65
66 # find transposition of C-major scale that belongs here. 
67 def interpret_finale_key_sig (finale_id):
68         p = (0,0)
69         if 0 <= finale_id < 7:
70                 while finale_id > 0:
71                         p = transpose (p, (4,0)) # a fifth up
72                         finale_id = finale_id - 1
73         elif 248 < finale_id <= 255:
74                 while finale_id < 256:
75                         p = transpose (p, (3,0))
76                         finale_id = finale_id + 1
77
78         p  = (p[0] % 7, p[1])
79         return p
80
81 # should cache this.
82 def find_scale (transposition):
83         cscale = map (lambda x: (x,0), range (0,7))
84         trscale = map(lambda x, k=transposition: transpose(x, k), cscale)
85
86         return trscale
87
88 def gcd (a,b):
89         if b == 0:
90                 return a
91         c = a
92         while c: 
93                 c = a % b
94                 a = b
95                 b = c
96         return a
97         
98
99 def rat_simplify (r):
100         (n,d) = r
101         if d < 0:
102                 d = -d
103                 n = -n
104         if n == 0:
105                 return (0,1)
106         else:
107                 g = gcd (n, d)
108                 return (n/g, d/g)
109         
110 def rat_multiply (a,b):
111         (x,y) = a
112         (p,q) = b
113
114         return rat_simplify ((x*p, y*q))
115
116 def rat_add (a,b):
117         (x,y) = a
118         (p,q) = b
119
120         return rat_simplify ((x*q + p*y, y*q))
121
122 def rat_neg (a):
123         (p,q) = a
124         return (-p,q)
125
126 def rat_subtract (a,b ):
127         return rat_add (a, rat_neg (b))
128
129 def lily_notename (tuple2):
130         (n, a) = tuple2
131         nn = chr ((n+ 2)%7 + ord ('a'))
132
133         if a == -1:
134                 nn = nn + 'es'
135         elif a == -2:
136                 nn = nn + 'eses'
137         elif a == 1:
138                 nn = nn + 'is'
139         elif a == 2:
140                 nn = nn + 'isis'
141
142         return nn
143
144
145 class Slur:
146         def __init__ (self, number):
147                 self.number = number
148                 self.finale = []
149
150         def append_entry (self, finale_e):
151                 self.finale.append (finale_e)
152
153         def calculate (self, chords):
154                 startnote = self.finale[0][5]
155                 endnote = self.finale[3][2]
156                 try:
157                         cs = chords[startnote]
158                         ce = chords[endnote]
159
160                         if not cs or not ce:
161                                 raise IndexError
162                         
163                         cs.note_suffix = '(' + cs.note_suffix 
164                         ce.note_prefix = ce.note_prefix + ')'
165                 except IndexError:
166                         sys.stderr.write ("""\nHuh? Incorrect slur start/endpoint
167 len(list) is %d, start/end is (%d,%d)\n""" % (len (chords), startnote, endnote))
168                                          
169                 
170 class Global_measure:
171         def __init__ (self, number):
172                 self.timesig = ''
173                 self.number = number
174                 self.keysignature = None
175                 self.scale = None
176
177                 self.finale = []
178
179         def __str__ (self):
180                 return `self.finale `
181         
182         def set_timesig (self, finale):
183                 (beats, fdur) = finale
184                 (log, dots) = EDU_to_duration (fdur)
185                 assert dots == 0
186                 self.timesig = (beats, log)
187
188         def length (self):
189                 return self.timesig
190         
191         def set_keysig (self, finale):
192                 k = interpret_finale_key_sig (finale)
193                 self.keysignature = k
194                 self.scale = find_scale (k)
195
196
197 articulation_dict ={
198         11: '\\prall',
199         12: '\\mordent',
200         8: '\\fermata',
201         18: '"arp"' , # arpeggio
202 };
203
204 class Articulation:
205         def __init__ (self, a,b, finale):
206                 self.type = finale[0]
207                 self.notenumber = b
208         def calculate (self, chords):
209                 c = chords[self.notenumber]
210
211                 try:
212                         a = articulation_dict[self.type]
213                 except KeyError:
214                         a = '"art"'
215                         
216                 c.note_suffix = '-' + a + c.note_suffix
217
218 class Syllable:
219         def __init__ (self, a,b , finale):
220                 self.chordnum = b
221                 self.syllable = finale[1]
222                 self.verse = finale[0]
223         def calculate (self, chords, lyrics):
224                 self.chord = chords[self.chordnum]
225
226 class Verse:
227         def __init__ (self, number, body):
228                 self.body = body
229                 self.number = number
230                 self.split_syllables ()
231         def split_syllables (self):
232                 ss = re.split ('(-| +)', self.body)
233
234                 sep = 0
235                 syls = [None]
236                 for s in ss:
237                         if sep:
238                                 septor = re.sub (" +", "", s)
239                                 septor = re.sub ("-", " -- ", septor) 
240                                 syls[-1] = syls[-1] + septor
241                         else:
242                                 syls.append (s)
243                         
244                         sep = not sep 
245
246                 self.syllables = syls
247
248         def dump (self):
249                 str = ''
250                 line = ''
251                 for s in self.syllables[1:]:
252                         line = line + ' ' + s
253                         if len (line) > 72:
254                                 str = str + ' ' * 4 + line + '\n'
255                                 line = ''
256                         
257                 str = """\nverse%s = \\lyrics {\n %s}\n""" %  (encodeint (self.number - 1) ,str)
258                 return str
259
260         
261 class Measure:
262         def __init__(self, no):
263                 self.number = no
264                 self.frames = [0] * 4
265                 self.flags = 0
266                 self.clef = 0
267                 self.finale = []
268                 self.global_measure = None
269                 self.staff = None
270                 
271         def add_finale_entry (self, entry):
272                 self.finale.append (entry)
273
274         def calculate (self):
275                 if len (self.finale) < 2:
276                         sys.stderr.write ("Measure %d in staff %d  has incomplete information.\n" % (self.number, self.staff.number))
277                         
278                         return 
279                         
280                 f0 = self.finale[0]
281                 f1 = self.finale[1]
282                 
283                 self.clef = string.atoi (f0[0])
284                 self.flags = string.atoi (f0[1])
285                 fs = map (string.atoi, list (f0[2:]) + [f1[0]])
286
287                 self.frames = fs
288
289 class Frame:
290         def __init__ (self, finale):
291                 self.measure = None
292                 self.finale = finale
293                 (number, start, end ) = finale
294                 self.number = number
295                 self.start = start
296                 self.end = end
297                 self.chords  = []
298
299         def set_measure (self, m):
300                 self.measure = m
301
302         def dump (self):
303                 str = ''
304                 left = self.measure.global_measure.length ()
305                 for c in self.chords:
306                         str = str + c.ly_string () + ' '
307                         left = rat_subtract (left, c.length ())
308                         
309                 if left[0] < 0:
310                         sys.stderr.write ("""Huh? Going backwards.
311 Frame no %d, start/end (%d,%d)
312 """ % (self.number, self.start, self.end))
313                         left = (0,1)
314                 if left[0]:
315                         str = str + 's*%d/%d' % left
316                         
317                 str = str + '\n'
318                 return str
319                 
320 def encodeint (i):
321         return chr ( i  + ord ('A'))
322
323 class Staff:
324         def __init__ (self, number):
325                 self.number = number
326                 self.measures = []
327
328         def get_measure (self, no):
329                 if len (self.measures) <= no:
330                         self.measures = self.measures + [None]* (1 + no - len (self.measures))
331
332                 if self.measures[no] == None:
333                         m = Measure (no)
334                         self.measures [no] =m
335                         m.staff = self
336
337
338                 return self.measures[no]
339         def staffid (self):
340                 return 'staff' + encodeint (self.number - 1)
341         def layerid (self, l):
342                 return self.staffid() +  'layer%s' % chr (l -1 + ord ('A'))
343         
344         def dump_time_key_sigs (self):
345                 k  = ''
346                 last_key = None
347                 last_time = None
348                 last_clef = None
349                 gap = (0,1)
350                 for m in self.measures[1:]:
351                         if not m :
352                                 continue # ugh.
353                         
354                         g = m.global_measure
355                         e = ''
356                         if last_key <> g.keysignature:
357                                 e = e + "\\key %s \\major; " % lily_notename (g.keysignature)
358                                 last_key = g.keysignature
359                         if last_time <> g.timesig :
360                                 e = e + "\\time %d/%d; " % g.timesig
361                                 last_time = g.timesig
362                         if last_clef <> m.clef :
363                                 e = e + '\\clef %s;' % lily_clef (m.clef)
364                                 last_clef = m.clef
365                         if e:
366                                 if gap <> (0,1):
367                                         k = k +' s1*%d/%d \n ' % gap
368                                 gap = (0,1)
369                                 k = k + e
370                         
371                         gap = rat_add (gap, g.length ())
372
373                                 
374                 k = '%sglobal = \\notes  { %s }\n\n ' % (self.staffid (), k)
375                 return k
376         
377         def dump (self):
378                 str = ''
379
380
381                 layerids = []
382                 for x in range (1,5): # 4 layers.
383                         laystr =  ''
384                         last_frame = None
385                         first_frame = None
386                         gap = (0,1)
387                         for m in self.measures[1:]:
388                                 if not m:
389                                         continue
390                                 
391                                 
392                                 fr = m.frames[x]
393                                 if fr:
394                                         first_frame = fr
395                                         if gap <> (0,1):
396                                                 laystr = laystr +'} s1*%d/%d {\n ' % gap
397                                                 gap = (0,1)
398                                         laystr = laystr + fr.dump ()
399                                 else:
400                                         gap = rat_add (gap, m.global_measure.length ())
401
402                         if first_frame:
403                                 l = self.layerid (x)
404                                 laystr = '%s =  \\notes { { %s } }\n\n' % (l, laystr)
405                                 str = str  + laystr
406                                 layerids.append (l)
407
408                 str = str +  self.dump_time_key_sigs ()         
409                 stafdef = '\\%sglobal' % self.staffid ()
410                 for i in layerids:
411                         stafdef = stafdef + ' \\' + i
412                         
413
414                 str = str + '%s = \\context Staff = %s <\n %s\n >\n' % \
415                       (self.staffid (), self.staffid (), stafdef)
416                 return str
417
418                                 
419 def EDU_to_duration (edu):
420         log = 1
421         d = 4096
422         while d > edu:
423                 d = d >> 1
424                 log = log << 1
425
426         edu = edu - d
427         dots = 0
428         if edu == d /2:
429                 dots = 1
430         elif edu == d*3/4:
431                 dots = 2
432         return (log, dots)      
433
434 class Chord:
435         def __init__ (self, finale_entry):
436                 self.pitches = []
437                 self.frame = None
438                 self.finale = finale_entry
439                 self.duration  = None
440                 self.next = None
441                 self.prev = None
442                 self.note_prefix= ''
443                 self.note_suffix = ''
444                 self.chord_suffix = ''
445                 self.chord_prefix = ''
446                 
447         def measure (self):
448                 if not self.frame:
449                         return None
450                 return self.frame.measure
451
452         def length (self):
453                 l = (1, self.duration[0])
454
455                 d = 1 << self.duration[1]
456
457                 dotfact = rat_subtract ((2,1), (1,d))
458                 return rat_multiply (dotfact, l)
459                 
460         def number (self):
461                 return self.finale[0][0]
462         def set_duration (self):
463                 ((no, prev, next, dur, pos, entryflag, extended, follow),
464                  notelist) = self.finale
465                 self.duration = EDU_to_duration(dur)
466         def find_realpitch (self):
467                 
468                 ((no, prev, next, dur, pos, entryflag, extended, follow), notelist) = self.finale
469
470                 meas = self.measure ()
471                 tiestart = 0
472                 if not meas or not meas.global_measure  :
473                         print 'note %d not in measure' % self.number ()
474                 elif not meas.global_measure.scale:
475                         print  'note %d: no scale in this measure.' % self.number ()
476                 else:
477                         for p in notelist:
478                                 (pitch, flag) = p
479                                 
480                                 nib1 = pitch & 0x0f
481                                 if nib1 > 8:
482                                         nib1 = -(nib1 - 8)
483                                 rest = pitch / 16
484
485                                 scale =  meas.global_measure.scale 
486                                 (sn, sa) =scale[rest % 7]
487                                 sn = sn + (rest - (rest%7)) + 7
488                                 acc = sa + nib1
489                                 self.pitches.append ((sn, acc))
490                                 tiestart =  tiestart or (flag & Chord.TIE_START_MASK)
491                 if tiestart :
492                         self.chord_suffix = self.chord_suffix + ' ~ '
493                 
494         REST_MASK = 0x40000000L
495         TIE_START_MASK = 0x40000000L
496         def ly_string (self):
497                 s = ''
498
499                 rest = ''
500
501                 if not (self.finale[0][5] & Chord.REST_MASK):
502                         rest = 'r'
503                 
504                 for p in self.pitches:
505                         (n,a) =  p
506                         o = n/ 7
507                         n = n % 7
508
509                         nn = lily_notename ((n,a))
510
511                         if o < 0:
512                                 nn = nn + (',' * -o)
513                         elif o > 0:
514                                 nn = nn + ('\'' * o)
515                                 
516                         if s:
517                                 s = s + ' '
518
519                         if rest:
520                                 nn = rest
521                                 
522                         s = s + '%s%d%s' % (nn, self.duration[0], '.'* self.duration[1])
523
524                 if not self.pitches:
525                         s  = 'r%d%s' % (self.duration[0] , '.'* self.duration[1])
526                 s = self.note_prefix + s + self.note_suffix
527                 if len (self.pitches) > 1:
528                         s = '<%s>' % s
529                 
530                 s = self.chord_prefix + s + self.chord_suffix
531                 return s
532
533 GFre = re.compile(r"""^\^GF\(([0-9-]+),([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
534 BCre = re.compile (r"""^\^BC\(([0-9-]+)\) ([0-9-]+) .*$""")
535 eEre = re.compile(r"""^\^eE\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) \$([0-9A-Fa-f]+) ([0-9-]+) ([0-9-]+)""")
536 FRre = re.compile (r"""^\^FR\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
537 MSre = re.compile (r"""^\^MS\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
538 note_re = re.compile (r"""^ +([0-9-]+) \$([A-Fa-f0-9]+)""")
539 Sxre  = re.compile (r"""^\^Sx\(([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
540 IMre = re.compile (r"""^\^IM\(([0-9-]+),([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
541 vere = re.compile(r"""^\^(ve|ch|se)\(([0-9-]+),([0-9-]+)\) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)""")
542 versere = re.compile(r"""^\^verse\(([0-9]+)\)(.*)\^end""")
543
544 class Etf_file:
545         def __init__ (self, name):
546                 self.measures = [None]
547                 self.entries = [None]
548                 self.chords = [None]
549                 self.frames = [None]
550                 self.staffs = [None]
551                 self.slurs = [None]
552                 self.articulations = [None]
553                 self.syllables = [None]
554                 self.verses = [None]
555                 
556                 ## do it
557                 self.parse (name)
558
559         def get_global_measure (self, no):
560                 if len (self.measures) <= no:
561                         self.measures = self.measures + [None]* (1 + no - len (self.measures))
562
563                 if self.measures[no] == None:
564                         self.measures [no] = Global_measure (no)
565
566                 return self.measures[no]
567
568                 
569         def get_staff(self,staffno):
570                 if len (self.staffs) <= staffno:
571                         self.staffs = self.staffs + [None] * (1 + staffno - len (self.staffs))
572
573                 if self.staffs[staffno] == None:
574                         self.staffs[staffno] = Staff (staffno)
575
576                 return self.staffs[staffno]
577
578         # staff-spec
579         def try_IS (self, l):
580                 pass
581
582         def try_BC (self, l):
583                 m =  BCre.match  (l)
584                 if m:
585                         bn = string.atoi (m.group (1))
586                         where = string.atoi (m.group (2)) / 1024.0
587                 return m
588
589         def try_IM (self, l):
590                 m = IMre.match (l)
591                 if m:
592                         a = string.atoi (m.group (1))
593                         b = string.atoi (m.group (2))
594
595                         fin = map (string.atoi, m.groups ()[2:])
596
597                         self.articulations.append (Articulation (a,b,fin))
598                 return m
599         def try_verse (self,l):
600                 m =  versere .match (l)
601                 if m:
602                         a = string.atoi (m.group (1))
603                         body =m.group (2)
604
605                         body = re.sub (r"""\^[a-z]+\([^)]+\)""", "", body)
606                         body = re.sub ("\^[a-z]+", "", body)
607                         self.verses.append (Verse (a, body))
608                         
609                 return m
610         def try_ve (self,l):
611                 m = vere .match (l)
612                 if m:
613                         a = string.atoi (m.group (1))
614                         b = string.atoi (m.group (2))
615
616                         fin = map (string.atoi, m.groups ()[2:])
617
618                         self.syllables.append (Syllable (a,b,fin))
619                 return m
620         def try_eE (self, l):
621                 m = eEre.match (l)
622                 if m:
623                         tup = m.groups()
624                         (no, prev, next, dur, pos, entryflag, extended, follow) = tup
625                         (no, prev, next, dur, pos,extended, follow) \
626                           = tuple (map (string.atoi, [no,prev,next,dur,pos,extended,follow]))
627
628                         entryflag = string.atol (entryflag,16)
629                         if no > len (self.entries):
630                                 sys.stderr.write ("Huh? Entry number to large,\nexpected %d got %d. Filling with void entries.\n" % (len(self.entries), no  ))
631                                 while len (self.entries) <> no:
632                                         c = ((len (self.entries), 0, 0, 0, 0, 0L, 0, 0), [])
633                                         self.entries.append (c)
634                                         
635                         current_entry = ((no, prev, next, dur, pos, entryflag, extended, follow), [])
636                         self.entries.append (current_entry)
637                 return m
638
639         def try_Sx(self,l):
640                 m = Sxre.match (l)
641                 if m:
642                         slurno = string.atoi (m.group (1))
643
644                         if len (self.slurs) == slurno:
645                                 self.slurs.append (Slur (slurno))
646
647                         params = list (m.groups ()[1:])
648                         params = map (string.atoi, params)
649                         self.slurs[-1].append_entry (params)
650
651                 return m        
652         def try_GF(self, l):
653                 m = GFre.match (l)
654                 if m:
655                         (staffno,measno) = m.groups ()[0:2]
656                         s = string.atoi (staffno)
657                         me = string.atoi (measno)
658                         
659                         entry = m.groups () [2:]
660                         st = self.get_staff (s)
661                         meas = st.get_measure (me)
662                         meas.add_finale_entry (entry)
663                 
664         # frame  ?
665         def try_FR(self, l):
666                 m = FRre.match (l)
667                 if m:
668                         (frameno, startnote, endnote, foo, bar) = m.groups ()
669                         (frameno, startnote, endnote)  = tuple (map (string.atoi, [frameno, startnote, endnote]))
670                         if frameno > len (self.frames):
671                                 sys.stderr.write ("Frame no %d missing, filling up to %d\n" % (len(self.frames), frameno))
672                                 while frameno <> len (self.frames):
673                                         self.frames.append (Frame ((len (self.frames), 0,0) ))
674                         
675                         self.frames.append (Frame ((frameno, startnote, endnote)))
676                         
677                 return m
678         def try_MS (self, l):
679                 m = MSre.match (l)
680                 if m:
681                         measno = string.atoi (m.group (1))
682                         keynum = string.atoi (m.group (3))
683                         meas =self. get_global_measure (measno)
684                         meas.set_keysig (keynum)
685
686                         beats = string.atoi (m.group (4))
687                         beatlen = string.atoi (m.group (5))
688                         meas.set_timesig ((beats, beatlen))
689                                                 
690                 return m
691
692         def try_note (self, l):
693                 m = note_re.match (l)
694                 if m:
695                         (pitch, flag) = m.groups ()
696                         pitch = string.atoi (pitch)
697                         flag = string.atol (flag,16)
698                         self.entries[-1][1].append ((pitch,flag))
699
700         def parse (self, name):
701                 sys.stderr.write ('parsing ...')
702                 sys.stderr.flush ()
703
704                 gulp = open (name).read ()
705
706                 gulp = re.sub ('[\n\r]+', '\n',  gulp)
707                 ls = string.split (gulp, '\n')
708                 
709                 for l in ls:
710                         m = None
711                         if not m: 
712                                 m = self.try_MS (l)
713                         if not m: 
714                                 m = self.try_FR (l)
715                         if not m: 
716                                 m = self.try_GF (l)
717                         if not m: 
718                                 m = self.try_note (l)
719                         if not m: 
720                                 m = self.try_eE (l)
721                         if not m:
722                                 m = self.try_IM (l)
723                         if not m:
724                                 m = self.try_Sx (l)
725                         if not m:
726                                 m = self.try_verse (l)
727
728                 sys.stderr.write ('processing ...')
729                 sys.stderr.flush ()
730
731                 self.unthread_entries ()
732
733                 for st in self.staffs[1:]:
734                         if not st:
735                                 continue
736                         mno = 1
737                         for m in st.measures[1:]:
738                                 if not m:
739                                         continue
740                                 
741                                 m.global_measure = self.measures[mno]
742                                 m.calculate()
743
744                                 frame_obj_list = [None]
745                                 for frno in m.frames:
746                                         fr = self.frames[frno]
747                                         frame_obj_list.append (fr)
748
749                                 m.frames = frame_obj_list
750                                 for fr in frame_obj_list[1:]:
751                                         if not fr:
752                                                 continue
753                                         
754                                         fr.set_measure (m)
755                                         
756                                         fr.chords = self.get_thread (fr.start, fr.end)
757                                         for c in fr.chords:
758                                                 c.frame = fr
759                                 mno = mno + 1
760
761                 for c in self.chords[1:]:
762                         c.find_realpitch ()
763                         c.set_duration ()
764                         
765                 for s in self.slurs [1:]:
766                         s.calculate (self.chords)
767                 for s in self.articulations[1:]:
768                         s.calculate (self.chords)
769                         
770         def get_thread (self, startno, endno):
771
772                 thread = []
773                 c = self.chords[startno]
774                 while c and c.number () <> endno:
775                         thread.append (c)
776                         c = c.next
777
778                 if c: 
779                         thread.append (c)
780                 
781                 return thread
782
783         def dump (self):
784                 str = ''
785                 staffs = []
786                 for s in self.staffs[1:]:
787                         if s:
788                                 str = str + '\n\n' + s.dump () 
789                                 staffs.append ('\\' + s.staffid ())
790
791                 if staffs:
792                         str = str + '\\score { < %s > } ' % string.join (staffs)
793
794                 # should use \addlyrics ?
795
796                 for v in self.verses[1:]:
797                         str = str + v.dump()
798
799                 if len (self.verses) > 1:
800                         sys.stderr.write ("\nLyrics found; edit to use \\addlyrics to couple to a staff\n")
801                         
802                 return str
803
804
805         def __str__ (self):
806                 return 'ETF FILE %s %s' % (self.measures,  self.entries)
807         
808         def unthread_entries (self):
809                 self.chords = [None]
810                 for e in self.entries[1:]:
811                         self.chords.append (Chord (e))
812
813                 for e in self.chords[1:]:
814                         e.prev = self.chords[e.finale[0][1]]
815                         e.next = self.chords[e.finale[0][2]]
816
817
818          
819
820
821
822 def identify():
823         sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
824
825 def help ():
826         print r"""
827 Convert ETF to LilyPond.
828
829 Usage: etf2ly [OPTION]... ETF-FILE
830
831 Options:
832   -h, --help          this help
833   -o, --output=FILE   set output filename to FILE
834   -v, --version       version information
835
836 Enigma Transport Format is a format used by Coda Music Technology's
837 Finale product. This program will convert a subset of ETF to a
838 ready-to-use lilypond file.
839
840
841 """
842
843 def print_version ():
844         print r"""etf2ly (GNU lilypond) %s""" % version
845
846
847
848 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
849 out_filename = None
850
851 for opt in options:
852         o = opt[0]
853         a = opt[1]
854         if o== '--help' or o == '-h':
855                 help ()
856                 sys.exit (0)
857         if o == '--version' or o == '-v':
858                 print_version ()
859                 sys.exit(0)
860                 
861         if o == '--output' or o == '-o':
862                 out_filename = a
863         else:
864                 print o
865                 raise getopt.error
866
867 identify()
868
869 # header['tagline'] = 'Lily was here %s -- automatically converted from ABC' % version
870 for f in files:
871         if f == '-':
872                 f = ''
873
874         sys.stderr.write ('Processing `%s\'\n' % f)
875         e = Etf_file(f)
876         if not out_filename:
877                 out_filename = os.path.basename (re.sub ('(?i).etf$', '.ly', f))
878                 
879         if out_filename == f:
880                 out_filename = os.path.basename (f + '.ly')
881                 
882         sys.stderr.write ('Writing `%s\'' % out_filename)
883         ly = e.dump()
884
885         
886         
887         fo = open (out_filename, 'w')
888         fo.write ('%% lily was here -- automatically converted by etf2ly from %s\n' % f)
889         fo.write(ly)
890         fo.close ()
891