3 # info mostly taken from looking at files. See also
4 # http://lilypond.org/wiki/?EnigmaTransportFormat
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)
25 # * empty measures (eg. twopt03.etf from freenote)
36 program_name = sys.argv[0]
38 version = '@TOPLEVEL_VERSION@'
39 if version == '@' + 'TOPLEVEL_VERSION' + '@':
40 version = '(unknown version)' # uGUHGUHGHGUGH
43 ################################################################
44 # Users of python modules should include this snippet.
46 libdir = '@local_lilypond_libdir@'
47 if not os.path.isdir (libdir):
48 libdir = '@lilypond_libdir@'
51 datadir = '@local_lilypond_datadir@'
52 if os.environ.has_key ('LILYPONDPREFIX'):
53 datadir = os.environ['LILYPONDPREFIX']
54 while datadir[-1] == os.sep:
56 libdir = datadir.replace ('/share/', '/lib/')
58 if os.path.exists (os.path.join (datadir, 'lib/lilypond/@TOPLEVEL_VERSION@/')):
59 libdir = os.path.join (libdir, 'lib/lilypond/@TOPLEVEL_VERSION@/')
61 if os.path.exists (os.path.join (datadir, 'lib/lilypond/current/')):
62 libdir = os.path.join (libdir, 'lib/lilypond/current/')
64 sys.path.insert (0, os.path.join (libdir, 'python'))
66 # dynamic relocation, for GUB binaries.
67 bindir = os.path.split (sys.argv[0])[0]
68 for p in ['share', 'lib']:
69 datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % p)
70 sys.path.insert (0, datadir)
72 ################################################################
77 finale_clefs= ['treble', 'alto', 'tenor', 'bass', 'percussion', 'treble_8', 'bass_8', 'baritone']
81 return finale_clefs[fin]
83 sys.stderr.write ( '\nHuh? Found clef number %d\n' % fin)
90 return open (f).read ()
92 # notename 0 == central C
93 distances = [0, 2, 4, 5, 7, 9, 11, 12]
94 def semitones (name, acc):
95 return (name / 7 ) * 12 + distances[name % 7] + acc
97 # represent pitches as (notename, alteration), relative to C-major scale
98 def transpose(orig, delta):
100 (dname, dacc) = delta
102 old_pitch =semitones (oname, oacc)
103 delta_pitch = semitones (dname, dacc)
104 nname = (oname + dname)
106 new_pitch = semitones (nname, nacc)
108 nacc = nacc - (new_pitch - old_pitch - delta_pitch)
114 def interpret_finale_key_sig (finale_id):
116 find the transposition of C-major scale that belongs here.
118 we are not going to insert the correct major/minor, we only want to
119 have the correct number of accidentals
125 bank_number = finale_id >> 8
126 accidental_bits = finale_id & 0xff
128 if 0 <= accidental_bits < 7:
129 while accidental_bits > 0:
130 p = transpose (p, (4,0)) # a fifth up
131 accidental_bits = accidental_bits - 1
132 elif 248 < accidental_bits <= 255:
133 while accidental_bits < 256:
134 p = transpose (p, (3,0))
135 accidental_bits = accidental_bits + 1
139 p = transpose (p, (5, 0))
142 return KeySignature (p, bank_number)
145 def find_scale (keysig):
146 cscale = map (lambda x: (x,0), range (0,7))
147 # print "cscale: ", cscale
148 ascale = map (lambda x: (x,0), range (-2,5))
149 # print "ascale: ", ascale
150 transposition = keysig.pitch
151 if keysig.sig_type == 1:
152 transposition = transpose(transposition, (2, -1))
153 transposition = (transposition[0] % 7, transposition[1])
154 trscale = map(lambda x, k=transposition: transpose(x, k), ascale)
156 trscale = map(lambda x, k=transposition: transpose(x, k), cscale)
157 # print "trscale: ", trscale
160 def EDU_to_duration (edu):
175 def rational_to_lily_skip (rat):
179 while d and d % 2 == 0:
180 basedur = basedur << 1
183 str = 's%d' % basedur
185 str = str + '*%d' % n
187 str = str + '/%d' % d
202 def rat_simplify (r):
213 def rat_multiply (a,b):
217 return rat_simplify ((x*p, y*q))
223 return rat_simplify ((x*q + p*y, y*q))
231 def rat_subtract (a,b ):
232 return rat_add (a, rat_neg (b))
234 def lily_notename (tuple2):
236 nn = chr ((n+ 2)%7 + ord ('a'))
238 return nn + {-2:'eses', -1:'es', 0:'', 1:'is', 2:'isis'}[a]
242 def __init__ (self, number):
243 self.start_note = number
246 def append_finale (self, fin):
247 self.finale.append (fin)
250 n = self.finale[0][2]*self.finale[0][3]
251 d = self.finale[0][0]*self.finale[0][1]
252 return rat_simplify( (n, d))
254 def dump_start (self):
255 return '\\times %d/%d { ' % self.factor ()
260 def calculate (self, chords):
261 edu_left = self.finale[0][0] * self.finale[0][1]
263 startch = chords[self.start_note]
265 while c and edu_left:
268 c.chord_prefix = self.dump_start () + c.chord_prefix
271 edu_left = edu_left - c.EDU_duration ()
273 c.chord_suffix = c.chord_suffix+ self.dump_end ()
277 sys.stderr.write ("\nHuh? Tuplet starting at entry %d was too short." % self.start_note)
280 def __init__ (self, number, params):
284 def append_entry (self, finale_e):
285 self.finale.append (finale_e)
287 def calculate (self, chords):
288 startnote = self.finale[5]
289 endnote = self.finale[3*6 + 2]
291 cs = chords[startnote]
297 cs.note_suffix = '-(' + cs.note_suffix
298 ce.note_suffix = ce.note_suffix + '-)'
301 sys.stderr.write ("""\nHuh? Slur no %d between (%d,%d), with %d notes""" % (self.number, startnote, endnote, len (chords)))
304 class Global_measure:
305 def __init__ (self, number):
308 self.key_signature = None
316 return `self.finale `
318 def set_timesig (self, finale):
319 (beats, fdur) = finale
320 (log, dots) = EDU_to_duration (fdur)
328 sys.stderr.write ("\nHuh? Beat duration has dots? (EDU Duration = %d)" % fdur)
329 self.timesig = (beats, log)
334 def set_key_sig (self, finale):
335 k = interpret_finale_key_sig (finale)
336 self.key_signature = k
337 self.scale = find_scale (k)
339 def set_flags (self,flag1, flag2):
341 # flag1 isn't all that interesting.
346 self.repeats.append ('start')
348 self.repeats.append ('stop')
352 self.repeats.append ('bracket')
365 class Articulation_def:
366 def __init__ (self, n, a, b):
367 self.finale_glyph = a & 0xff
372 return articulation_dict[self.finale_glyph]
374 sys.stderr.write ("\nUnknown articulation no. %d" % self.finale_glyph)
375 sys.stderr.write ("\nPlease add an entry to articulation_dict in the Python source")
379 def __init__ (self, a,b, finale):
380 self.definition = finale[0]
383 def calculate (self, chords, defs):
384 c = chords[self.notenumber]
386 adef = defs[self.definition]
390 sys.stderr.write ("\nThis happened on note %d" % self.notenumber)
392 c.note_suffix = '-' + lystr
395 def __init__ (self, a,b , finale):
397 self.syllable = finale[1]
398 self.verse = finale[0]
399 def calculate (self, chords, lyrics):
400 self.chord = chords[self.chordnum]
403 def __init__ (self, number, body):
406 self.split_syllables ()
407 def split_syllables (self):
408 ss = re.split ('(-| +)', self.body)
414 septor = re.sub (" +", "", s)
415 septor = re.sub ("-", " -- ", septor)
416 syls[-1] = syls[-1] + septor
422 self.syllables = syls
427 for s in self.syllables[1:]:
428 line = line + ' ' + s
430 str = str + ' ' * 4 + line + '\n'
433 str = """\nverse%s = \\lyricmode {\n %s }\n""" % (encodeint (self.number - 1) ,str)
437 def __init__(self, pitch, sig_type = 0):
439 self.sig_type = sig_type
441 def signature_type (self):
442 if self.sig_type == 1:
445 # really only for 0, but we only know about 0 and 1
448 def equal (self, other):
449 if other and other.pitch == self.pitch and other.sig_type == self.sig_type:
456 def __init__(self, no):
458 self.frames = [0] * 4
462 self.global_measure = None
469 def calculate (self):
472 if len (self.finale) < 2:
476 self.frames = [fs[0]]
485 def __init__ (self, finale):
488 (number, start, end ) = finale
494 def set_measure (self, m):
497 def calculate (self):
502 for c in self.chords:
503 if c.grace and (lastch == None or (not lastch.grace)):
504 c.chord_prefix = r'\grace {' + c.chord_prefix
506 elif not c.grace and lastch and lastch.grace:
507 lastch.chord_suffix = lastch.chord_suffix + ' } '
512 if lastch and in_grace:
513 lastch.chord_suffix += '}'
517 str = '%% FR(%d)\n' % self.number
518 left = self.measure.global_measure.length ()
522 for c in self.chords:
523 add = c.ly_string () + ' '
524 if len (ln) + len(add) > 72:
525 str = str + ln + '\n'
528 left = rat_subtract (left, c.length ())
533 sys.stderr.write ("""\nHuh? Going backwards in frame no %d, start/end (%d,%d)""" % (self.number, self.start, self.end))
536 str = str + rational_to_lily_skip (left)
542 return chr ( i + ord ('A'))
545 def __init__ (self, number):
549 def get_measure (self, no):
550 fill_list_to (self.measures, no)
552 if self.measures[no] == None:
554 self.measures [no] =m
557 return self.measures[no]
559 return 'staff' + encodeint (self.number - 1)
560 def layerid (self, l):
561 return self.staffid() + 'layer%s' % chr (l -1 + ord ('A'))
563 def dump_time_key_sigs (self):
569 for m in self.measures[1:]:
570 if not m or not m.valid:
577 if g.key_signature and not g.key_signature.equal(last_key):
578 pitch= g.key_signature.pitch
579 e = e + "\\key %s %s " % (lily_notename (pitch),
580 g.key_signature.signature_type())
582 last_key = g.key_signature
583 if last_time <> g.timesig :
584 e = e + "\\time %d/%d " % g.timesig
585 last_time = g.timesig
587 if 'start' in g.repeats:
588 e = e + ' \\bar "|:" '
591 # we don't attempt voltas since they fail easily.
592 if 0 : # and g.repeat_bar == '|:' or g.repeat_bar == ':|:' or g.bracket:
594 if g.repeat_bar == '|:' or g.repeat_bar == ':|:' or g.bracket == 'end':
598 if g.bracket == 'start':
601 str = string.join (map (lambda x: '(volta %s)' % x, strs))
603 e = e + ' \\set Score.repeatCommands = #\'(%s) ' % str
608 if last_clef <> m.clef :
609 e = e + '\\clef "%s"' % lily_clef (m.clef)
613 k = k +' ' + rational_to_lily_skip (gap) + '\n'
618 gap = rat_add (gap, g.length ())
619 if 'stop' in g.repeats:
620 k = k + ' \\bar ":|" '
622 k = '%sglobal = { %s }\n\n ' % (self.staffid (), k)
630 for x in range (1,5): # 4 layers.
635 for m in self.measures[1:]:
636 if not m or not m.valid:
637 sys.stderr.write ("Skipping non-existant or invalid measure\n")
644 sys.stderr.write ("Skipping nonexistent frame %d\n" % x)
645 laystr = laystr + "%% non existent frame %d (skipped) \n" % x
649 laystr = laystr +'} %s {\n ' % rational_to_lily_skip (gap)
651 laystr = laystr + fr.dump ()
653 if m.global_measure :
654 gap = rat_add (gap, m.global_measure.length ())
657 "No global measure for staff %d measure %d\n"
658 % (self.number, m.number))
661 laystr = '%s = { { %s } }\n\n' % (l, laystr)
665 str = str + self.dump_time_key_sigs ()
666 stafdef = '\\%sglobal' % self.staffid ()
668 stafdef = stafdef + ' \\' + i
671 str = str + '%s = \\context Staff = %s <<\n %s\n >>\n' % \
672 (self.staffid (), self.staffid (), stafdef)
681 return [(l[0], l[1])] + ziplist (l[2:])
685 def __init__ (self, number, contents):
688 self.finale = contents[:7]
690 self.notelist = ziplist (contents[7:])
696 self.note_suffix = ''
697 self.chord_suffix = ''
698 self.chord_prefix = ''
705 return self.frame.measure
711 l = (1, self.duration[0])
713 d = 1 << self.duration[1]
715 dotfact = rat_subtract ((2,1), (1,d))
716 mylen = rat_multiply (dotfact, l)
719 mylen = rat_multiply (mylen, self.tuplet.factor())
723 def EDU_duration (self):
724 return self.finale[2]
725 def set_duration (self):
726 self.duration = EDU_to_duration(self.EDU_duration ())
728 def calculate (self):
729 self.find_realpitch ()
732 flag = self.finale[4]
733 if Chord.GRACE_MASK & flag:
737 def find_realpitch (self):
739 meas = self.measure ()
741 if not meas or not meas.global_measure :
742 sys.stderr.write ('note %d not in measure\n' % self.number)
743 elif not meas.global_measure.scale:
744 sys.stderr.write ('note %d: no scale in this measure.' % self.number)
747 for p in self.notelist:
757 scale = meas.global_measure.scale
758 (sn, sa) =scale[rest % 7]
759 sn = sn + (rest - (rest%7)) + 7
761 self.pitches.append ((sn, acc))
762 tiestart = tiestart or (flag & Chord.TIE_START_MASK)
764 self.chord_suffix = self.chord_suffix + ' ~ '
766 REST_MASK = 0x40000000L
767 TIE_START_MASK = 0x40000000L
768 GRACE_MASK = 0x00800000L
770 def ly_string (self):
776 if not (self.finale[4] & Chord.REST_MASK):
779 for p in self.pitches:
784 nn = lily_notename ((n,a))
801 if len (self.pitches) > 1:
804 s = s + '%d%s' % (self.duration[0], '.'* self.duration[1])
805 s = self.note_prefix + s + self.note_suffix
807 s = self.chord_prefix + s + self.chord_suffix
812 def fill_list_to (list, no):
814 Add None to LIST until it contains entry number NO.
816 while len (list) <= no:
817 list.extend ([None] * (no - len(list) + 1))
820 def read_finale_value (str):
822 Pry off one value from STR. The value may be $hex, decimal, or "string".
823 Return: (value, rest-of-STR)
825 while str and str[0] in ' \t\n':
835 while str and str[0] in '0123456789ABCDEF':
840 return (string.atol (hex, 16), str)
844 while str and str[0] <> '"':
849 elif str[0] in '-0123456789':
851 while str and str[0] in '-0123456789':
855 return (string.atoi (dec), str)
857 sys.stderr.write ("can't convert `%s'\n" % str)
863 def parse_etf_file (fn, tag_dict):
865 """ Read FN, putting ETF info into
866 a giant dictionary. The keys of TAG_DICT indicate which tags
867 to put into the dict.
870 sys.stderr.write ('parsing ... ' )
873 gulp = re.sub ('[\n\r]+', '\n', f.read ())
874 ls = string.split (gulp, '\n^')
877 for k in tag_dict.keys ():
878 etf_file_dict[k] = {}
885 m = re.match ('^([a-zA-Z0-9&]+)\(([^)]+)\)', l)
886 if m and tag_dict.has_key (m.group (1)):
889 indices = tuple (map (string.atoi, string.split (m.group (2), ',')))
890 content = l[m.end (2)+1:]
893 tdict = etf_file_dict[tag]
894 if not tdict.has_key (indices):
900 if tag == 'verse' or tag == 'block':
901 m2 = re.match ('(.*)\^end', content)
903 parsed = [m2.group (1)]
906 (v, content) = read_finale_value (content)
910 tdict [indices].extend (parsed)
912 last_indices = indices
917 # let's not do this: this really confuses when eE happens to be before a ^text.
918 # if last_tag and last_indices:
919 # etf_file_dict[last_tag][last_indices].append (l)
921 sys.stderr.write ('\n')
929 def __init__ (self, name):
930 self.measures = [None]
933 self.tuplets = [None]
936 self.articulations = [None]
937 self.syllables = [None]
939 self.articulation_defs = [None]
944 def get_global_measure (self, no):
945 fill_list_to (self.measures, no)
946 if self.measures[no] == None:
947 self.measures [no] = Global_measure (no)
949 return self.measures[no]
952 def get_staff(self,staffno):
953 fill_list_to (self.staffs, staffno)
954 if self.staffs[staffno] == None:
955 self.staffs[staffno] = Staff (staffno)
957 return self.staffs[staffno]
960 def try_IS (self, indices, contents):
963 def try_BC (self, indices, contents):
965 where = contents[0] / 1024.0
966 def try_TP(self, indices, contents):
969 if self.tuplets[-1] == None or num <> self.tuplets[-1].start_note:
970 self.tuplets.append (Tuplet (num))
972 self.tuplets[-1].append_finale (contents)
974 def try_IM (self, indices, contents):
977 self.articulations.append (Articulation (a,b,fin))
978 def try_verse (self, indices, contents):
982 body = re.sub (r"""\^[a-z]+\([^)]+\)""", "", body)
983 body = re.sub ("\^[a-z]+", "", body)
984 self.verses.append (Verse (a, body))
985 def try_ve (self,indices, contents):
987 self.syllables.append (Syllable (a,b,contents))
989 def try_eE (self,indices, contents):
991 (prev, next, dur, pos, entryflag, extended, follow) = contents[:7]
993 fill_list_to (self.chords, no)
994 self.chords[no] =Chord (no, contents)
996 def try_Sx(self,indices, contents):
998 fill_list_to (self.slurs, slurno)
999 self.slurs[slurno] = Slur(slurno, contents)
1001 def try_IX (self, indices, contents):
1008 ix = self.articulation_defs[n]
1010 ix = Articulation_def (n,a,b)
1011 self.articulation_defs.append (Articulation_def (n, a, b))
1013 def try_GF(self, indices, contents):
1014 (staffno,measno) = indices
1016 st = self.get_staff (staffno)
1017 meas = st.get_measure (measno)
1018 meas.finale = contents
1020 def try_FR(self, indices, contents):
1021 frameno = indices [0]
1023 startnote = contents[0]
1024 endnote = contents[1]
1026 fill_list_to (self.frames, frameno)
1028 self.frames[frameno] = Frame ((frameno, startnote, endnote))
1030 def try_MS (self, indices, contents):
1032 keynum = contents[1]
1033 meas =self. get_global_measure (measno)
1035 meas.set_key_sig (keynum)
1038 beatlen = contents[3]
1039 meas.set_timesig ((beats, beatlen))
1041 meas_flag1 = contents[4]
1042 meas_flag2 = contents[5]
1044 meas.set_flags (meas_flag1, meas_flag2);
1054 'verse' : try_verse,
1062 def parse (self, etf_dict):
1063 sys.stderr.write ('reconstructing ...')
1066 for (tag,routine) in Etf_file.routine_dict.items ():
1067 ks = etf_dict[tag].keys ()
1070 routine (self, k, etf_dict[tag][k])
1072 sys.stderr.write ('processing ...')
1075 self.unthread_entries ()
1077 for st in self.staffs[1:]:
1081 for m in st.measures[1:]:
1087 m.global_measure = self.measures[mno]
1089 sys.stderr.write ("Non-existent global measure %d" % mno)
1092 frame_obj_list = [None]
1093 for frno in m.frames:
1095 fr = self.frames[frno]
1096 frame_obj_list.append (fr)
1098 sys.stderr.write ("\nNon-existent frame %d" % frno)
1100 m.frames = frame_obj_list
1101 for fr in frame_obj_list[1:]:
1107 fr.chords = self.get_thread (fr.start, fr.end)
1112 for c in self.chords[1:]:
1116 for f in self.frames[1:]:
1120 for t in self.tuplets[1:]:
1121 t.calculate (self.chords)
1123 for s in self.slurs[1:]:
1125 s.calculate (self.chords)
1127 for s in self.articulations[1:]:
1128 s.calculate (self.chords, self.articulation_defs)
1130 def get_thread (self, startno, endno):
1136 c = self.chords[startno]
1138 sys.stderr.write ("Huh? Frame has invalid bounds (%d,%d)\n" % (startno, endno))
1142 while c and c.number <> endno:
1154 for s in self.staffs[1:]:
1156 str = str + '\n\n' + s.dump ()
1157 staffs.append ('\\' + s.staffid ())
1160 # should use \addlyrics ?
1162 for v in self.verses[1:]:
1163 str = str + v.dump()
1165 if len (self.verses) > 1:
1166 sys.stderr.write ("\nLyrics found; edit to use \\addlyrics to couple to a staff\n")
1169 str += '\\version "2.3.25"\n'
1170 str = str + '<<\n %s\n>> } ' % string.join (staffs)
1176 return 'ETF FILE %s %s' % (self.measures, self.entries)
1178 def unthread_entries (self):
1179 for e in self.chords[1:]:
1183 e.prev = self.chords[e.finale[0]]
1184 e.next = self.chords[e.finale[1]]
1187 sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version))
1191 sys.stdout.write ('''
1199 ''' % ( '2001--2006',
1200 _('Distributed under terms of the GNU General Public License.'),
1201 _('It comes with NO WARRANTY.')))
1205 def get_option_parser ():
1206 p = ly.get_option_parser (usage='etf2ly [OPTIONS]... ETF-FILE',
1207 version="etf2ly (LilyPond) @TOPLEVEL_VERSION@",
1208 description=_("""Enigma Transport Format is a format used by Coda Music Technology's
1209 Finale product. This program will convert a subset of ETF to a
1210 ready-to-use lilypond file."""))
1211 p.add_option ('-o', '--output', help=_("write output to FILE"),
1214 p.add_option ('-w', '--warranty', help=_ ("show warranty"),
1215 action='store_true',
1218 p.add_option_group ('bugs',
1219 description='''Report bugs via http://post.gmane.org/post.php'''
1220 '''?group=gmane.comp.gnu.lilypond.bugs\n''')
1224 opt_parser = get_option_parser()
1225 (options,args) = opt_parser.parse_args ()
1226 if options.warranty:
1230 return (options,args)
1232 (options, files) = do_options()
1235 out_filename = options.output
1242 sys.stderr.write ('Processing `%s\'\n' % f)
1244 dict = parse_etf_file (f, Etf_file.routine_dict)
1246 if not out_filename:
1247 out_filename = os.path.basename (re.sub ('(?i).etf$', '.ly', f))
1249 if out_filename == f:
1250 out_filename = os.path.basename (f + '.ly')
1252 sys.stderr.write ('Writing `%s\'' % out_filename)
1257 fo = open (out_filename, 'w')
1258 fo.write ('%% lily was here -- automatically converted by etf2ly from %s\n' % f)