3 # info mostly taken from looking at files. See also
4 # http://www.cs.uu.nl/~hanwen/lily-devel/etf.html
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)
24 program_name = 'etf2ly'
25 version = '@TOPLEVEL_VERSION@'
26 if version == '@' + 'TOPLEVEL_VERSION' + '@':
27 version = '(unknown version)' # uGUHGUHGHGUGH
36 finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble8vb', 'bass8vb', 'baritone']
39 return finale_clefs[fin]
42 return open (f).read ()
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
49 # represent pitches as (notename, alteration), relative to C-major scale
50 def transpose(orig, delta):
54 old_pitch =semitones (oname, oacc)
55 delta_pitch = semitones (dname, dacc)
56 nname = (oname + dname)
58 new_pitch = semitones (nname, nacc)
60 nacc = nacc - (new_pitch - old_pitch - delta_pitch)
66 # find transposition of C-major scale that belongs here.
67 def interpret_finale_key_sig (finale_id):
69 if 0 <= finale_id < 7:
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
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)
110 def rat_multiply (a,b):
114 return rat_simplify ((x*p, y*q))
120 return rat_simplify ((x*q + p*y, y*q))
126 def rat_subtract (a,b ):
127 return rat_add (a, rat_neg (b))
129 def lily_notename (tuple2):
131 nn = chr ((n+ 2)%7 + ord ('a'))
146 def __init__ (self, number):
150 def append_entry (self, finale_e):
151 self.finale.append (finale_e)
153 def calculate (self, chords):
154 startnote = self.finale[0][5]
155 endnote = self.finale[3][2]
157 cs = chords[startnote]
163 cs.note_suffix = '(' + cs.note_suffix
164 ce.note_prefix = ce.note_prefix + ')'
166 sys.stderr.write ("""\nHuh? Incorrect slur start/endpoint
167 len(list) is %d, start/end is (%d,%d)\n""" % (len (chords), startnote, endnote))
170 class Global_measure:
171 def __init__ (self, number):
174 self.keysignature = None
180 return `self.finale `
182 def set_timesig (self, finale):
183 (beats, fdur) = finale
184 (log, dots) = EDU_to_duration (fdur)
186 self.timesig = (beats, log)
191 def set_keysig (self, finale):
192 k = interpret_finale_key_sig (finale)
193 self.keysignature = k
194 self.scale = find_scale (k)
201 18: '"arp"' , # arpeggio
205 def __init__ (self, a,b, finale):
206 self.type = finale[0]
208 def calculate (self, chords):
209 c = chords[self.notenumber]
212 a = articulation_dict[self.type]
216 c.note_suffix = '-' + a + c.note_suffix
219 def __init__ (self, a,b , finale):
221 self.syllable = finale[1]
222 self.verse = finale[0]
223 def calculate (self, chords, lyrics):
224 self.chord = chords[self.chordnum]
227 def __init__ (self, number, body):
230 self.split_syllables ()
231 def split_syllables (self):
232 ss = re.split ('(-| +)', self.body)
238 septor = re.sub (" +", "", s)
239 septor = re.sub ("-", " -- ", septor)
240 syls[-1] = syls[-1] + septor
246 self.syllables = syls
251 for s in self.syllables[1:]:
252 line = line + ' ' + s
254 str = str + ' ' * 4 + line + '\n'
257 str = """\nverse%s = \\lyrics {\n %s}\n""" % (encodeint (self.number - 1) ,str)
262 def __init__(self, no):
264 self.frames = [0] * 4
268 self.global_measure = None
271 def add_finale_entry (self, entry):
272 self.finale.append (entry)
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))
283 self.clef = string.atoi (f0[0])
284 self.flags = string.atoi (f0[1])
285 fs = map (string.atoi, list (f0[2:]) + [f1[0]])
290 def __init__ (self, finale):
293 (number, start, end ) = finale
299 def set_measure (self, m):
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 ())
310 sys.stderr.write ("""Huh? Going backwards.
311 Frame no %d, start/end (%d,%d)
312 """ % (self.number, self.start, self.end))
315 str = str + 's*%d/%d' % left
321 return chr ( i + ord ('A'))
324 def __init__ (self, number):
328 def get_measure (self, no):
329 if len (self.measures) <= no:
330 self.measures = self.measures + [None]* (1 + no - len (self.measures))
332 if self.measures[no] == None:
334 self.measures [no] =m
338 return self.measures[no]
340 return 'staff' + encodeint (self.number - 1)
341 def layerid (self, l):
342 return self.staffid() + 'layer%s' % chr (l -1 + ord ('A'))
344 def dump_time_key_sigs (self):
350 for m in self.measures[1:]:
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)
367 k = k +' s1*%d/%d \n ' % gap
371 gap = rat_add (gap, g.length ())
374 k = '%sglobal = \\notes { %s }\n\n ' % (self.staffid (), k)
382 for x in range (1,5): # 4 layers.
387 for m in self.measures[1:]:
396 laystr = laystr +'} s1*%d/%d {\n ' % gap
398 laystr = laystr + fr.dump ()
400 gap = rat_add (gap, m.global_measure.length ())
404 laystr = '%s = \\notes { { %s } }\n\n' % (l, laystr)
408 str = str + self.dump_time_key_sigs ()
409 stafdef = '\\%sglobal' % self.staffid ()
411 stafdef = stafdef + ' \\' + i
414 str = str + '%s = \\context Staff = %s <\n %s\n >\n' % \
415 (self.staffid (), self.staffid (), stafdef)
419 def EDU_to_duration (edu):
435 def __init__ (self, finale_entry):
438 self.finale = finale_entry
443 self.note_suffix = ''
444 self.chord_suffix = ''
445 self.chord_prefix = ''
450 return self.frame.measure
453 l = (1, self.duration[0])
455 d = 1 << self.duration[1]
457 dotfact = rat_subtract ((2,1), (1,d))
458 return rat_multiply (dotfact, l)
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):
468 ((no, prev, next, dur, pos, entryflag, extended, follow), notelist) = self.finale
470 meas = self.measure ()
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 ()
485 scale = meas.global_measure.scale
486 (sn, sa) =scale[rest % 7]
487 sn = sn + (rest - (rest%7)) + 7
489 self.pitches.append ((sn, acc))
490 tiestart = tiestart or (flag & Chord.TIE_START_MASK)
492 self.chord_suffix = self.chord_suffix + ' ~ '
494 REST_MASK = 0x40000000L
495 TIE_START_MASK = 0x40000000L
496 def ly_string (self):
501 if not (self.finale[0][5] & Chord.REST_MASK):
504 for p in self.pitches:
509 nn = lily_notename ((n,a))
522 s = s + '%s%d%s' % (nn, self.duration[0], '.'* self.duration[1])
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:
530 s = self.chord_prefix + s + self.chord_suffix
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""")
545 def __init__ (self, name):
546 self.measures = [None]
547 self.entries = [None]
552 self.articulations = [None]
553 self.syllables = [None]
559 def get_global_measure (self, no):
560 if len (self.measures) <= no:
561 self.measures = self.measures + [None]* (1 + no - len (self.measures))
563 if self.measures[no] == None:
564 self.measures [no] = Global_measure (no)
566 return self.measures[no]
569 def get_staff(self,staffno):
570 if len (self.staffs) <= staffno:
571 self.staffs = self.staffs + [None] * (1 + staffno - len (self.staffs))
573 if self.staffs[staffno] == None:
574 self.staffs[staffno] = Staff (staffno)
576 return self.staffs[staffno]
579 def try_IS (self, l):
582 def try_BC (self, l):
585 bn = string.atoi (m.group (1))
586 where = string.atoi (m.group (2)) / 1024.0
589 def try_IM (self, l):
592 a = string.atoi (m.group (1))
593 b = string.atoi (m.group (2))
595 fin = map (string.atoi, m.groups ()[2:])
597 self.articulations.append (Articulation (a,b,fin))
599 def try_verse (self,l):
600 m = versere .match (l)
602 a = string.atoi (m.group (1))
605 body = re.sub (r"""\^[a-z]+\([^)]+\)""", "", body)
606 body = re.sub ("\^[a-z]+", "", body)
607 self.verses.append (Verse (a, body))
613 a = string.atoi (m.group (1))
614 b = string.atoi (m.group (2))
616 fin = map (string.atoi, m.groups ()[2:])
618 self.syllables.append (Syllable (a,b,fin))
620 def try_eE (self, l):
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]))
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)
635 current_entry = ((no, prev, next, dur, pos, entryflag, extended, follow), [])
636 self.entries.append (current_entry)
642 slurno = string.atoi (m.group (1))
644 if len (self.slurs) == slurno:
645 self.slurs.append (Slur (slurno))
647 params = list (m.groups ()[1:])
648 params = map (string.atoi, params)
649 self.slurs[-1].append_entry (params)
655 (staffno,measno) = m.groups ()[0:2]
656 s = string.atoi (staffno)
657 me = string.atoi (measno)
659 entry = m.groups () [2:]
660 st = self.get_staff (s)
661 meas = st.get_measure (me)
662 meas.add_finale_entry (entry)
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) ))
675 self.frames.append (Frame ((frameno, startnote, endnote)))
678 def try_MS (self, l):
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)
686 beats = string.atoi (m.group (4))
687 beatlen = string.atoi (m.group (5))
688 meas.set_timesig ((beats, beatlen))
692 def try_note (self, l):
693 m = note_re.match (l)
695 (pitch, flag) = m.groups ()
696 pitch = string.atoi (pitch)
697 flag = string.atol (flag,16)
698 self.entries[-1][1].append ((pitch,flag))
700 def parse (self, name):
701 sys.stderr.write ('parsing ...')
704 gulp = open (name).read ()
706 gulp = re.sub ('[\n\r]+', '\n', gulp)
707 ls = string.split (gulp, '\n')
718 m = self.try_note (l)
726 m = self.try_verse (l)
728 sys.stderr.write ('processing ...')
731 self.unthread_entries ()
733 for st in self.staffs[1:]:
737 for m in st.measures[1:]:
741 m.global_measure = self.measures[mno]
744 frame_obj_list = [None]
745 for frno in m.frames:
746 fr = self.frames[frno]
747 frame_obj_list.append (fr)
749 m.frames = frame_obj_list
750 for fr in frame_obj_list[1:]:
756 fr.chords = self.get_thread (fr.start, fr.end)
761 for c in self.chords[1:]:
765 for s in self.slurs [1:]:
766 s.calculate (self.chords)
767 for s in self.articulations[1:]:
768 s.calculate (self.chords)
770 def get_thread (self, startno, endno):
773 c = self.chords[startno]
774 while c and c.number () <> endno:
786 for s in self.staffs[1:]:
788 str = str + '\n\n' + s.dump ()
789 staffs.append ('\\' + s.staffid ())
792 str = str + '\\score { < %s > } ' % string.join (staffs)
794 # should use \addlyrics ?
796 for v in self.verses[1:]:
799 if len (self.verses) > 1:
800 sys.stderr.write ("\nLyrics found; edit to use \\addlyrics to couple to a staff\n")
806 return 'ETF FILE %s %s' % (self.measures, self.entries)
808 def unthread_entries (self):
810 for e in self.entries[1:]:
811 self.chords.append (Chord (e))
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]]
823 sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
827 Convert ETF to LilyPond.
829 Usage: etf2ly [OPTION]... ETF-FILE
833 -o, --output=FILE set output filename to FILE
834 -v, --version version information
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.
843 def print_version ():
844 print r"""etf2ly (GNU lilypond) %s""" % version
848 (options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output='])
854 if o== '--help' or o == '-h':
857 if o == '--version' or o == '-v':
861 if o == '--output' or o == '-o':
869 # header['tagline'] = 'Lily was here %s -- automatically converted from ABC' % version
874 sys.stderr.write ('Processing `%s\'\n' % f)
877 out_filename = os.path.basename (re.sub ('(?i).etf$', '.ly', f))
879 if out_filename == f:
880 out_filename = os.path.basename (f + '.ly')
882 sys.stderr.write ('Writing `%s\'' % out_filename)
887 fo = open (out_filename, 'w')
888 fo.write ('%% lily was here -- automatically converted by etf2ly from %s\n' % f)