comparison abc2xml/abc2xml.py @ 484:4fab69a1027d build-default-207

Add MusicXML conversion to tune pages. Might help someone.
author Jim Hague <jim.hague@acm.org>
date Tue, 17 Jun 2014 09:11:38 +0100
parents
children b1dbb76f4eb9
comparison
equal deleted inserted replaced
483:681274f40615 484:4fab69a1027d
1 # coding=latin-1
2 '''
3 Copyright (C) 2012: Willem G. Vree
4 Contributions: Nils Liberg, Nicolas Froment, Norman Schmidt, Reinier Maliepaard, Martin Tarenskeen
5
6 This program is free software; you can redistribute it and/or modify it under the terms of the
7 GNU General Public License as published by the Free Software Foundation; either version 2 of
8 the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 See the GNU General Public License for more details. <http://www.gnu.org/licenses/gpl.html>.
13 '''
14
15 from pyparsing import Word, OneOrMore, Optional, Literal, NotAny, MatchFirst
16 from pyparsing import Group, oneOf, Suppress, ZeroOrMore, Combine, FollowedBy
17 from pyparsing import srange, CharsNotIn, StringEnd, LineEnd, White, Regex
18 from pyparsing import nums, alphas, alphanums, ParseException, Forward
19 try: import xml.etree.cElementTree as E
20 except: import xml.etree.ElementTree as E
21 import types, sys, os, re, datetime
22
23 VERSION = 58
24
25 def info (s, warn=1):
26 x = (warn and '-- ' or '') + s
27 try: sys.stderr.write (x + '\n')
28 except: sys.stderr.write (repr (x) + '\n')
29
30 def abc_grammar (): # header, voice and lyrics grammar for ABC
31 b1 = Word (u"-,'<>\u2019#", exact=1) # catch misplaced chars in chords
32
33 #-----------------------------------------------------------------
34 # ABC header (fld_text elements are matched later with reg. epr's)
35 #-----------------------------------------------------------------
36
37 number = Word (nums).setParseAction (lambda t: int (t[0]))
38 field_str = Regex (r'(?:\\.|[^]\\])*') # match anything until end of field, skip escaped \]
39 field_str.setParseAction (lambda t: t[0].strip ()) # and strip spacing
40
41 userdef_symbol = Word (srange ('[H-Wh-w~]'), exact=1)
42 fieldId = oneOf ('K L M Q P I T C O A Z N G H R B D F S E r') # info fields
43 X_field = Literal ('X') + Suppress (':') + number + field_str
44 U_field = Literal ('U') + Suppress (':') + userdef_symbol + Suppress ('=') + field_str
45 V_field = Literal ('V') + Suppress (':') + Word (alphanums + '_') + field_str
46 inf_fld = fieldId + Suppress (':') + field_str
47 ifield = Suppress ('[') + (X_field | U_field | V_field | inf_fld) + Suppress (']')
48 abc_header = OneOrMore (ifield) + StringEnd ()
49
50 #---------------------------------------------------------------------------------
51 # I:score with recursive part groups and {* grand staff marker
52 #---------------------------------------------------------------------------------
53
54 voiceId = Suppress (Optional ('*')) + Word (alphanums + '_')
55 voice_gr = Suppress ('(') + OneOrMore (voiceId | Suppress ('|')) + Suppress (')')
56 simple_part = voiceId | voice_gr | Suppress ('|')
57 grand_staff = oneOf ('{* {') + OneOrMore (simple_part) + Suppress ('}')
58 part = Forward ()
59 part_seq = OneOrMore (part | Suppress ('|'))
60 brace_gr = Suppress ('{') + part_seq + Suppress ('}')
61 bracket_gr = Suppress ('[') + part_seq + Suppress ('\]') # closing brackets are escaped by splitHeaderVoices
62 part << MatchFirst (simple_part | grand_staff | brace_gr | bracket_gr | Suppress ('|'))
63 abc_scoredef = Suppress (oneOf ('staves score')) + OneOrMore (part)
64
65 #---------------------------------------------------------------------------------
66 # ABC voice (not white space sensitive, beams detected in note/rest parse actions)
67 #---------------------------------------------------------------------------------
68
69 inline_field = Suppress ('[') + (inf_fld | U_field | V_field) + Suppress (']')
70
71 note_length = Optional (number, 1) + Group (ZeroOrMore ('/')) + Optional (number, 2)
72 octaveHigh = OneOrMore ("'").setParseAction (lambda t: len(t))
73 octaveLow = OneOrMore (',').setParseAction (lambda t: -len(t))
74 octave = octaveHigh | octaveLow
75
76 basenote = oneOf ('C D E F G A B c d e f g a b y') # includes spacer for parse efficiency
77 accidental = oneOf ('^^ __ ^ _ =')
78 rest_sym = oneOf ('x X z Z')
79 slur_beg = oneOf ('( .(') + ~Word (nums) # no tuplet_start
80 slur_ends = OneOrMore (oneOf (') .)'))
81
82 long_decoration = Combine (oneOf ('! +') + CharsNotIn ('!+ \n') + oneOf ('! +'))
83 staccato = Literal ('.') + ~Literal ('|') # avoid dotted barline
84 decoration = staccato | userdef_symbol | long_decoration | slur_beg
85 decorations = OneOrMore (decoration)
86 staff_decos = decorations + ~oneOf (': | [|] []')
87
88 tie = oneOf ('.- -')
89 rest = Optional (accidental) + rest_sym + note_length
90 pitch = Optional (accidental) + basenote + Optional (octave, 0)
91 note = pitch + note_length + Optional (tie) + Optional (slur_ends)
92 chord_note = Optional (decorations) + pitch + note_length + Optional (tie) + Optional (slur_ends)
93 chord_notes = OneOrMore (chord_note | rest | b1)
94 grace_notes = Forward ()
95 chord = Suppress ('[') + OneOrMore (chord_notes | grace_notes) + Suppress (']') + note_length + Optional (tie) + Optional (slur_ends)
96 stem = note | chord | rest
97
98 broken = Combine (OneOrMore ('<') | OneOrMore ('>'))
99
100 tuplet_num = Suppress ('(') + number
101 tuplet_into = Suppress (':') + Optional (number, 0)
102 tuplet_notes = Suppress (':') + Optional (number, 0)
103 tuplet_start = tuplet_num + Optional (tuplet_into + Optional (tuplet_notes))
104
105 acciaccatura = Literal ('/')
106 grace_stem = Optional (decorations) + stem
107 grace_notes << Group (Suppress ('{') + Optional (acciaccatura) + OneOrMore (grace_stem) + Suppress ('}'))
108
109 text_expression = Optional (oneOf ('^ _ < > @'), '^') + Optional (CharsNotIn ('"'), "")
110 chord_accidental = oneOf ('# b =')
111 triad = oneOf ('ma Maj maj M mi min m aug dim o + -')
112 seventh = oneOf ('7 ma7 Maj7 M7 maj7 mi7 m7 dim7 o7 -7 aug7 +7 m7b5 mi7b5')
113 sixth = oneOf ('6 ma6 M6 m6 mi6')
114 ninth = oneOf ('9 ma9 M9 maj9 Maj9 mi9 m9')
115 elevn = oneOf ('11 ma11 M11 maj11 Maj11 mi m11')
116 suspended = oneOf ('sus sus2 sus4')
117 chord_degree = Combine (Optional (chord_accidental) + oneOf ('2 4 5 6 7 9 11 13'))
118 chord_kind = Optional (seventh | sixth | ninth | elevn | triad, '_') + Optional (suspended)
119 chord_root = oneOf ('C D E F G A B') + Optional (chord_accidental)
120 chord_bass = oneOf ('C D E F G A B') + Optional (chord_accidental) # needs a different parse action
121 chordsym = chord_root + chord_kind + ZeroOrMore (chord_degree) + Optional (Suppress ('/') + chord_bass)
122 chord_sym = chordsym + Optional (Literal ('(') + CharsNotIn (')') + Literal (')')).suppress ()
123 chord_or_text = Suppress ('"') + (chord_sym ^ text_expression) + Suppress ('"')
124
125 volta_nums = Optional ('[').suppress () + Combine (Word (nums) + ZeroOrMore (oneOf (', -') + Word (nums)))
126 volta_text = Literal ('[').suppress () + Regex (r'"[^"]+"')
127 volta = volta_nums | volta_text
128 invisible_barline = oneOf ('[|] []')
129 dashed_barline = oneOf (': .|')
130 double_rep = Literal (':') + FollowedBy (':') # otherwise ambiguity with dashed barline
131 voice_overlay = Combine (OneOrMore ('&'))
132 bare_volta = FollowedBy (Literal ('[') + Word (nums)) # no barline, but volta follows (volta is parsed in next measure)
133 bar_left = (oneOf ('[|: |: [: :') + Optional (volta)) | Optional ('|').suppress () + volta | oneOf ('| [|')
134 bars = ZeroOrMore (':') + ZeroOrMore ('[') + OneOrMore (oneOf ('| ]'))
135 bar_right = Optional (decorations) + (invisible_barline | double_rep | Combine (bars) | dashed_barline | voice_overlay | bare_volta)
136
137 errors = ~bar_right + Optional (Word (' \n')) + CharsNotIn (':&|', exact=1)
138 linebreak = Literal ('$') | ~decorations + Literal ('!') # no need for I:linebreak !!!
139 element = inline_field | broken | staff_decos | stem | chord_or_text | grace_notes | tuplet_start | linebreak | errors
140 measure = Group (ZeroOrMore (inline_field) + Optional (bar_left) + ZeroOrMore (element) + bar_right + Optional (linebreak))
141 noBarMeasure = Group (ZeroOrMore (inline_field) + Optional (bar_left) + OneOrMore (element) + Optional (linebreak))
142 abc_voice = ZeroOrMore (measure) + Optional (noBarMeasure | Group (bar_left)) + ZeroOrMore (inline_field).suppress () + StringEnd ()
143
144 #----------------------------------------
145 # ABC lyric lines (white space sensitive)
146 #----------------------------------------
147
148 skip_note = oneOf ('* - ~')
149 extend_note = Literal ('_')
150 measure_end = Literal ('|')
151 syl_chars = CharsNotIn ('*~-_| \t\n')
152 white = Word (' \t')
153 syllable = Combine (Optional ('~') + syl_chars + ZeroOrMore (Literal ('~') + syl_chars)) + Optional ('-')
154 lyr_elem = (syllable | skip_note | extend_note | measure_end) + Optional (white).suppress ()
155 lyr_head = (Literal ('w:') + Optional (white)).suppress ()
156 lyr_line = Group (lyr_head + ZeroOrMore (lyr_elem) + LineEnd ().suppress ())
157
158 #----------------------------------------------------------------
159 # Parse actions to convert all relevant results into an abstract
160 # syntax tree where all tree nodes are instances of pObj
161 #----------------------------------------------------------------
162
163 ifield.setParseAction (lambda t: pObj ('field', t))
164 grand_staff.setParseAction (lambda t: pObj ('grand', t, 1)) # 1 = keep ordered list of results
165 brace_gr.setParseAction (lambda t: pObj ('bracegr', t, 1))
166 bracket_gr.setParseAction (lambda t: pObj ('bracketgr', t, 1))
167 voice_gr.setParseAction (lambda t: pObj ('voicegr', t, 1))
168 voiceId.setParseAction (lambda t: pObj ('vid', t, 1))
169 abc_scoredef.setParseAction (lambda t: pObj ('score', t, 1))
170 note_length.setParseAction (lambda t: pObj ('dur', (t[0], (t[2] << len (t[1])) >> 1)))
171 chordsym.setParseAction (lambda t: pObj ('chordsym', t))
172 chord_root.setParseAction (lambda t: pObj ('root', t))
173 chord_kind.setParseAction (lambda t: pObj ('kind', t))
174 chord_degree.setParseAction (lambda t: pObj ('degree', t))
175 chord_bass.setParseAction (lambda t: pObj ('bass', t))
176 text_expression.setParseAction (lambda t: pObj ('text', t))
177 inline_field.setParseAction (lambda t: pObj ('inline', t))
178 grace_notes.setParseAction (doGrace) # (lambda t: pObj ('grace', t))
179 acciaccatura.setParseAction (lambda t: pObj ('accia', t))
180 note.setParseAction (noteActn)
181 chord_note.setParseAction (noteActn)
182 rest.setParseAction (restActn)
183 decorations.setParseAction (lambda t: pObj ('deco', t))
184 slur_ends.setParseAction (lambda t: pObj ('slurs', t))
185 chord.setParseAction (lambda t: pObj ('chord', t))
186 tie.setParseAction (lambda t: pObj ('tie', t))
187 pitch.setParseAction (lambda t: pObj ('pitch', t))
188 bare_volta.setParseAction (lambda t: ['|']) # return barline that user forgot
189 dashed_barline.setParseAction (lambda t: ['.|'])
190 bar_right.setParseAction (lambda t: pObj ('rbar', t))
191 bar_left.setParseAction (lambda t: pObj ('lbar', t))
192 broken.setParseAction (lambda t: pObj ('broken', t))
193 tuplet_start.setParseAction (lambda t: pObj ('tup', t))
194 linebreak.setParseAction (lambda t: pObj ('linebrk', t))
195 measure.setParseAction (doMaat)
196 noBarMeasure.setParseAction (doMaat)
197 syllable.setParseAction (lambda t: pObj ('syl', t))
198 skip_note.setParseAction (lambda t: pObj ('skip', t))
199 extend_note.setParseAction (lambda t: pObj ('ext', t))
200 measure_end.setParseAction (lambda t: pObj ('sbar', t))
201 b1.setParseAction (errorWarn)
202 errors.setParseAction (errorWarn)
203 lyr_block = OneOrMore (lyr_line).leaveWhitespace () # after leaveWhiteSpace no more parse actions can be set!!
204
205 return abc_header, abc_voice, lyr_block, abc_scoredef
206
207 class pObj (object): # every relevant parse result is converted into a pObj
208 def __init__ (s, name, t, seq=0): # t = list of nested parse results
209 s.name = name # name uniqueliy identifies this pObj
210 rest = [] # collect parse results that are not a pObj
211 attrs = {} # new attributes
212 for x in t: # nested pObj's become attributes of this pObj
213 if type (x) == pObj:
214 attrs [x.name] = attrs.get (x.name, []) + [x]
215 else:
216 rest.append (x) # collect non-pObj's (mostly literals)
217 for name, xs in attrs.items ():
218 if len (xs) == 1: xs = xs[0] # only list if more then one pObj
219 setattr (s, name, xs) # create the new attributes
220 s.t = rest # all nested non-pObj's (mostly literals)
221 s.objs = seq and t or [] # for nested ordered (lyric) pObj's
222
223 def __repr__ (s): # make a nice string representation of a pObj
224 r = []
225 for nm in dir (s):
226 if nm.startswith ('_'): continue # skip build in attributes
227 elif nm == 'name': continue # redundant
228 else:
229 x = getattr (s, nm)
230 if not x: continue # s.t may be empty (list of non-pObj's)
231 if type (x) == types.ListType: r.extend (x)
232 else: r.append (x)
233 xs = []
234 for x in r: # recursively call __repr__ and convert all strings to latin-1
235 if isinstance (x, types.StringTypes):
236 try: xs.append (x.encode ('latin-1'))
237 except: xs.append (repr (x)) # string -> no recursion
238 else: xs.append (repr (x)) # pObj -> recursive call
239 return '(' + s.name + ' ' +','.join (xs) + ')'
240
241 global prevloc # global to remember previous match position of a note/rest
242 prevloc = 0
243 def detectBeamBreak (line, loc, t):
244 global prevloc # location in string 'line' of previous note match
245 xs = line[prevloc:loc+1] # string between previous and current note match
246 xs = xs.lstrip () # first note match starts on a space!
247 prevloc = loc # location in string 'line' of current note match
248 b = pObj ('bbrk', [' ' in xs]) # space somewhere between two notes -> beambreak
249 t.insert (0, b) # insert beambreak as a nested parse result
250
251 def noteActn (line, loc, t): # detect beambreak between previous and current note/rest
252 if 'y' in t[0].t: return [] # discard spacer
253 detectBeamBreak (line, loc, t) # adds beambreak to parse result t as side effect
254 return pObj ('note', t)
255
256 def restActn (line, loc, t): # detect beambreak between previous and current note/rest
257 detectBeamBreak (line, loc, t) # adds beambreak to parse result t as side effect
258 return pObj ('rest', t)
259
260 def errorWarn (line, loc, t): # warning for misplaced symbols and skip them
261 info ('**misplaced symbol: %s' % t[0], warn=0)
262 lineCopy = line [:]
263 if loc > 40:
264 lineCopy = line [loc - 40: loc + 40]
265 loc = 40
266 info (lineCopy.replace ('\n', ' '), warn=0)
267 info (loc * '-' + '^', warn=0)
268 return []
269
270 #-------------------------------------------------------------
271 # transformations of a measure (called by parse action doMaat)
272 #-------------------------------------------------------------
273
274 def simplify (a, b): # divide a and b by their greatest common divisor
275 x, y = a, b
276 while b: a, b = b, a % b
277 return x / a, y / a
278
279 def doBroken (prev, brk, x):
280 if not prev: info ('error in broken rhythm: %s' % x); return # no changes
281 nom1, den1 = prev.dur.t # duration of first note/chord
282 nom2, den2 = x.dur.t # duration of second note/chord
283 if brk == '>':
284 nom1, den1 = simplify (3 * nom1, 2 * den1)
285 nom2, den2 = simplify (1 * nom2, 2 * den2)
286 elif brk == '<':
287 nom1, den1 = simplify (1 * nom1, 2 * den1)
288 nom2, den2 = simplify (3 * nom2, 2 * den2)
289 elif brk == '>>':
290 nom1, den1 = simplify (7 * nom1, 4 * den1)
291 nom2, den2 = simplify (1 * nom2, 4 * den2)
292 elif brk == '<<':
293 nom1, den1 = simplify (1 * nom1, 4 * den1)
294 nom2, den2 = simplify (7 * nom2, 4 * den2)
295 else: return # give up
296 prev.dur.t = nom1, den1 # change duration of previous note/chord
297 x.dur.t = nom2, den2 # and current note/chord
298
299 def convertBroken (t): # convert broken rhythms to normal note durations
300 prev = None # the last note/chord before the broken symbol
301 brk = '' # the broken symbol
302 remove = [] # indexes to broken symbols (to be deleted) in measure
303 for i, x in enumerate (t): # scan all elements in measure
304 if x.name == 'note' or x.name == 'chord' or x.name == 'rest':
305 if brk: # a broken symbol was encountered before
306 doBroken (prev, brk, x) # change duration previous note/chord/rest and current one
307 brk = ''
308 else:
309 prev = x # remember the last note/chord/rest
310 elif x.name == 'broken':
311 brk = x.t[0] # remember the broken symbol (=string)
312 remove.insert (0, i) # and its index, highest index first
313 for i in remove: del t[i] # delete broken symbols from high to low
314
315 def convertChord (t): # convert chord to sequence of notes in musicXml-style
316 ins = []
317 for i, x in enumerate (t):
318 if x.name == 'chord':
319 if hasattr (x, 'rest') and not hasattr (x, 'note'): # chords containing only rests
320 if type (x.rest) == types.ListType: x.rest = x.rest[0] # more rests == one rest
321 ins.insert (0, (i, [x.rest])) # just output a single rest, no chord
322 continue
323 num1, den1 = x.dur.t # chord duration
324 tie = getattr (x, 'tie', None) # chord tie
325 slurs = getattr (x, 'slurs', []) # slur endings
326 deco = getattr (x, 'deco', []) # chord decorations
327 if type (x.note) != types.ListType: x.note = [x.note] # when chord has only one note ...
328 for j, nt in enumerate (x.note): # all notes of the chord
329 num2, den2 = nt.dur.t # note duration * chord duration
330 nt.dur.t = simplify (num1 * num2, den1 * den2)
331 if tie: nt.tie = tie # tie on all chord notes
332 if j == 0 and deco: nt.deco = deco # decorations only on first chord note
333 if j == 0 and slurs: nt.slurs = slurs # slur endings only on first chord note
334 if j > 0: nt.chord = pObj ('chord', [1]) # label all but first as chord notes
335 else: # remember all pitches of the chord in the first note
336 pitches = [n.pitch for n in x.note] # to implement conversion of erroneous ties to slurs
337 nt.pitches = pObj ('pitches', pitches)
338 ins.insert (0, (i, x.note)) # high index first
339 for i, notes in ins: # insert from high to low
340 for nt in reversed (notes):
341 t.insert (i+1, nt) # insert chord notes after chord
342 del t[i] # remove chord itself
343
344 def doMaat (t): # t is a Group() result -> the measure is in t[0]
345 convertBroken (t[0]) # remove all broken rhythms and convert to normal durations
346 convertChord (t[0]) # replace chords by note sequences in musicXML style
347
348 def doGrace (t): # t is a Group() result -> the grace sequence is in t[0]
349 convertChord (t[0]) # a grace sequence may have chords
350 for nt in t[0]: # flag all notes within the grace sequence
351 if nt.name == 'note': nt.grace = 1 # set grace attribute
352 return t[0] # ungroup the parse result
353 #--------------------
354 # musicXML generation
355 #----------------------------------
356
357 def compChordTab (): # avoid some typing work: returns mapping constant {ABC chordsyms -> musicXML kind}
358 maj, min, aug, dim, dom, ch7, ch6, ch9, ch11, hd = 'major minor augmented diminished dominant -seventh -sixth -ninth -11th half-diminished'.split ()
359 triad = zip ('ma Maj maj M mi min m aug dim o + -'.split (), [maj, maj, maj, maj, min, min, min, aug, dim, dim, aug, min])
360 seventh = zip ('7 ma7 Maj7 M7 maj7 mi7 m7 dim7 o7 -7 aug7 +7 m7b5 mi7b5'.split (),
361 [dom, maj+ch7, maj+ch7, maj+ch7, maj+ch7, min+ch7, min+ch7, dim+ch7, dim+ch7, min+ch7, aug+ch7, aug+ch7, hd, hd])
362 sixth = zip ('6 ma6 M6 mi6 m6'.split (), [maj+ch6, maj+ch6, maj+ch6, min+ch6, min+ch6])
363 ninth = zip ('9 ma9 M9 maj9 Maj9 mi9 m9'.split (), [dom+ch9, maj+ch9, maj+ch9, maj+ch9, maj+ch9, min+ch9, min+ch9])
364 elevn = zip ('11 ma11 M11 maj11 Maj11 mi11 m11'.split (), [dom+ch11, maj+ch11, maj+ch11, maj+ch11, maj+ch11, min+ch11, min+ch11])
365 return dict (triad + seventh + sixth + ninth + elevn)
366
367 def addElem (parent, child, level):
368 indent = 2
369 chldrn = parent.getchildren ()
370 if chldrn:
371 chldrn[-1].tail += indent * ' '
372 else:
373 parent.text = '\n' + level * indent * ' '
374 parent.append (child)
375 child.tail = '\n' + (level-1) * indent * ' '
376
377 def addElemT (parent, tag, text, level):
378 e = E.Element (tag)
379 e.text = text
380 addElem (parent, e, level)
381
382 def mkTmod (tmnum, tmden, lev):
383 tmod = E.Element ('time-modification')
384 addElemT (tmod, 'actual-notes', str (tmnum), lev + 1)
385 addElemT (tmod, 'normal-notes', str (tmden), lev + 1)
386 return tmod
387
388 def addDirection (parent, elem, lev, gstaff, subelms=[], placement='below', cue_on=0):
389 dir = E.Element ('direction', placement=placement)
390 addElem (parent, dir, lev)
391 typ = E.Element ('direction-type')
392 addElem (dir, typ, lev + 1)
393 addElem (typ, elem, lev + 2)
394 for subel in subelms: addElem (elem, subel, lev + 3)
395 if cue_on: addElem (dir, E.Element ('level', size='cue'), lev + 1)
396 if gstaff: addElemT (dir, 'staff', str (gstaff), lev + 1)
397 return dir
398
399 def removeElems (root_elem, parent_str, elem_str):
400 for p in root_elem.findall (parent_str):
401 e = p.find (elem_str)
402 if e != None: p.remove (e)
403
404 def alignLyr (vce, lyrs):
405 empty_el = pObj ('leeg', '*')
406 for k, lyr in enumerate (lyrs): # lyr = one full line of lyrics
407 i = 0 # syl counter
408 for msre in vce: # reiterate the voice block for each lyrics line
409 for elem in msre:
410 if elem.name == 'note' and not (hasattr (elem, 'chord') or hasattr (elem, 'grace')):
411 if i >= len (lyr): lr = empty_el
412 else: lr = lyr [i]
413 elem.objs.append (lr)
414 if lr.name != 'sbar': i += 1
415 if i < len (lyr) and lyr[i].name == 'sbar': i += 1
416 return vce
417
418 slur_move = re.compile (r'(?<![!+])([}><][<>]?)(\)+)') # (?<!...) means: not preceeded by ...
419 mm_rest = re.compile (r'([XZ])(\d+)')
420 bar_space = re.compile (r'([:|][ |\[\]]+[:|])') # barlines with spaces
421 def fixSlurs (x): # repair slurs when after broken sign or grace-close
422 def f (mo): # replace a multi-measure rest by single measure rests
423 n = int (mo.group (2))
424 return (n * (mo.group (1) + '|')) [:-1]
425 def g (mo): # squash spaces in barline expressions
426 return mo.group (1).replace (' ','')
427 x = mm_rest.sub (f, x)
428 x = bar_space.sub (g, x)
429 return slur_move.sub (r'\2\1', x)
430
431 def splitHeaderVoices (abctext):
432 r1 = re.compile (r'%.*$') # comments
433 r2 = re.compile (r'^[A-Z]:.*$') # information field
434 r3 = re.compile (r'^%%(?=[^%])') # directive: ^%% folowed by not a %
435 xs, nx = [], 0
436 for x in abctext.splitlines ():
437 x = x.strip ()
438 if not x and nx == 1: break # end of tune
439 x = r3.sub ('I:', x) # replace %% -> I:
440 x2 = r1.sub ('', x) # remove comment
441 while x2.endswith ('*'): x2 = x2[:-1] # remove old syntax for right adjusting
442 if not x2: continue # empty line
443 if x2[:2] == 'W:': continue # skip W: lyrics
444 if x2[:2] == 'w:' and xs[-1][-1] == '\\':
445 xs[-1] = xs[-1][:-1] # ignore line continuation before lyrics line
446 ro = r2.match (x2)
447 if ro: # field -> inline_field, escape all ']'
448 if x2[-1] == '\\': x2 = x2[:-1] # ignore continuation after field line
449 x2 = '[' + x2.replace (']',r'\]') + ']'
450 if x2[:2] == '+:': # new style continuation
451 xs[-1] += x2[2:]
452 elif xs and xs[-1][-1] == '\\': # old style continuation
453 xs[-1] = xs[-1][:-1] + x2
454 else: # skip lines (except I:) until first X:
455 if x.startswith ('X:'):
456 if nx == 1: break # second tune starts without an empty line !!
457 nx = 1 # start of first tune
458 if nx == 1 or x.startswith ('I:'):
459 xs.append (x2)
460 if xs and xs[-1][-1] == '\\': # nothing left to continue with, remove last continuation
461 xs[-1] = xs[-1][:-1]
462
463 r1 = re.compile (r'\[[A-Z]:(\\.|[^]\\])*\]') # inline field with escaped ']'
464 r2 = re.compile (r'\[K:') # start of K: field
465 r3 = re.compile (r'\[V:|\[I:MIDI') # start of V: field or midi field
466 fields, voices, b = [], [], 0
467 for i, x in enumerate (xs):
468 n = len (r1.sub ('', x)) # remove all inline fields
469 if n > 0: b = 1; break # real abc present -> end of header
470 if r2.search (x): # start of K: field
471 fields.append (x)
472 i += 1; b = 1
473 break # first K: field -> end of header
474 if r3.search (x): # start of V: field
475 voices.append (x)
476 else:
477 fields.append (x)
478 if b: voices += xs[i:]
479 else: voices += [] # tune has only header fields
480 header = '\n'.join (fields)
481 abctext = '\n'.join (voices)
482
483 xs = abctext.split ('[V:')
484 if len (xs) == 1: abctext = '[V:1]' + abctext # abc has no voice defs at all
485 elif r1.sub ('', xs[0]).strip (): # remove inline fields from starting text, if any
486 abctext = '[V:1]' + abctext # abc with voices has no V: at start
487
488 r1 = re.compile (r'\[V:\s*(\S*)[ \]]') # get voice id from V: field (skip spaces betwee V: and ID)
489 vmap = {} # {voice id -> [voice abc string]}
490 vorder = {} # mark document order of voices
491 xs = re.split (r'(\[V:[^]]*\])', abctext) # split on every V-field (V-fields included in split result list)
492 if len (xs) == 1: raise (Exception ('bugs ...'))
493 else:
494 header += xs[0] # xs[0] = text between K: and first V:, normally empty, but we put it in the header
495 i = 1
496 while i < len (xs): # xs = ['', V-field, voice abc, V-field, voice abc, ...]
497 vce, abc = xs[i:i+2]
498 id = r1.search (vce).group (1) # get voice ID from V-field
499 vmap[id] = vmap.get (id, []) + [vce, abc] # collect abc-text for each voice id (include V-fields)
500 if id not in vorder: vorder [id] = i # store document order of first occurrence of voice id
501 i += 2
502 voices = []
503 ixs = sorted ([(i, id) for id, i in vorder.items ()]) # restore document order of voices
504 for i, id in ixs:
505 voice = ''.join (vmap [id]) # all abc of one voice
506 xs = re.split (r'((?:\nw:[^\n]*)+)', voice) # split voice into voice-lyrics blocks
507 if len (xs) == 1: # no lyrics
508 voice = fixSlurs (xs[0]) # put slurs right after the notes
509 vce_lyr = [[voice, '']]
510 else:
511 if xs[-1].strip () != '': xs.append ('w:') # last block had no lyrics
512 vce_lyr = [] # [[voice, lyrics],[],...] list of voice-lyrics blocks
513 for k in range (0, len (xs) - 1, 2):
514 voice, lyrics = xs [k:k+2]
515 voice = fixSlurs (voice) # put slurs right after the notes
516 vce_lyr.append ((voice, lyrics))
517 voices.append ((id, vce_lyr))
518 return header, voices
519
520 def mergeMeasure (m1, m2, slur_offset, voice_offset, is_grand=0):
521 slurs = m2.findall ('note/notations/slur')
522 for slr in slurs:
523 slrnum = int (slr.get ('number')) + slur_offset
524 slr.set ('number', str (slrnum)) # make unique slurnums in m2
525 vs = m2.findall ('note/voice') # set all voice number elements in m2
526 for v in vs: v.text = str (voice_offset + int (v.text))
527 ls = m1.findall ('note/lyric') # all lyric elements in m1
528 lnum_max = max ([int (l.get ('number')) for l in ls] + [0]) # highest lyric number in m1
529 ls = m2.findall ('note/lyric') # update lyric elements in m2
530 for el in ls:
531 n = int (el.get ('number'))
532 el.set ('number', str (n + lnum_max))
533 ns = m1.findall ('note') # determine the total duration of m1, subtract all backups
534 dur1 = sum (int (n.find ('duration').text) for n in ns
535 if n.find ('grace') == None and n.find ('chord') == None)
536 dur1 -= sum (int (b.text) for b in m1.findall ('backup/duration'))
537 nns, es = 0, [] # nns = number of real notes in m2
538 for e in m2.getchildren (): # scan all elements of m2
539 if e.tag == 'attributes':
540 if not is_grand: continue # no attribute merging for normal voices
541 else: nns += 1 # but we do merge (clef) attributes for a grand staff
542 if e.tag == 'print': continue
543 if e.tag == 'note' and (mxm.gmwr or e.find ('rest') == None): nns += 1
544 es.append (e) # buffer elements to be merged
545 if nns > 0: # only merge if m2 contains any real notes
546 if dur1 > 0: # only insert backup if duration of m1 > 0
547 b = E.Element ('backup')
548 addElem (m1, b, level=3)
549 addElemT (b, 'duration', str (dur1), level=4)
550 for e in es: addElem (m1, e, level=3) # merge buffered elements of m2
551
552 def mergePartList (parts, is_grand=0): # merge parts, make grand staff when is_grand true
553
554 def delAttrs (part): # for the time being we only keep clef attributes
555 xs = [(m, e) for m in part.findall ('measure') for e in m.findall ('attributes')]
556 for m, e in xs:
557 for c in e.getchildren ():
558 if c.tag == 'clef': continue # keep clef attribute
559 e.remove (c) # delete all other attrinutes for higher staff numbers
560 if len (e.getchildren ()) == 0: m.remove (e) # remove empty attributes element
561
562 p1 = parts[0]
563 for p2 in parts[1:]:
564 if is_grand: delAttrs (p2) # delete all attributes except clef
565 for i in range (len (p1) + 1, len (p2) + 1): # second part longer than first one
566 maat = E.Element ('measure', number = str(i)) # append empty measures
567 addElem (p1, maat, 2)
568 slurs = p1.findall ('measure/note/notations/slur') # find highest slur num in first part
569 slur_max = max ([int (slr.get ('number')) for slr in slurs] + [0])
570 vs = p1.findall ('measure/note/voice') # all voice number elements in first part
571 vnum_max = max ([int (v.text) for v in vs] + [0]) # highest voice number in first part
572 for im, m2 in enumerate (p2.findall ('measure')): # merge all measures of p2 into p1
573 mergeMeasure (p1[im], m2, slur_max, vnum_max, is_grand) # may change slur numbers in p1
574 return p1
575
576 def mergeParts (parts, vids, staves, is_grand=0):
577 if not staves: return parts, vids # no voice mapping
578 partsnew, vidsnew = [], []
579 for voice_ids in staves:
580 pixs = []
581 for vid in voice_ids:
582 if vid in vids: pixs.append (vids.index (vid))
583 else: info ('score partname %s does not exist' % vid)
584 if pixs:
585 xparts = [parts[pix] for pix in pixs]
586 if len (xparts) > 1: mergedpart = mergePartList (xparts, is_grand)
587 else: mergedpart = xparts [0]
588 partsnew.append (mergedpart)
589 vidsnew.append (vids [pixs[0]])
590 return partsnew, vidsnew
591
592 def mergePartMeasure (part, msre, ovrlaynum): # merge msre into last measure of part, only for overlays
593 slurs = part.findall ('measure/note/notations/slur') # find highest slur num in part
594 slur_max = max ([int (slr.get ('number')) for slr in slurs] + [0])
595 last_msre = part.getchildren ()[-1] # last measure in part
596 mergeMeasure (last_msre, msre, slur_max, ovrlaynum) # voice offset = s.overlayVNum
597
598 def setFristVoiceNameFromGroup (vids, vdefs): # vids = [vid], vdef = {vid -> (name, subname, voicedef)}
599 vids = [v for v in vids if v in vdefs] # only consider defined voices
600 if not vids: return vdefs
601 vid0 = vids [0] # first vid of the group
602 _, _, vdef0 = vdefs [vid0] # keep de voice definition (vdef0) when renaming vid0
603 for vid in vids:
604 nm, snm, vdef = vdefs [vid]
605 if nm: # first non empty name encountered will become
606 vdefs [vid0] = nm, snm, vdef0 # name of merged group == name of first voice in group (vid0)
607 break
608 return vdefs
609
610 def mkGrand (p, vdefs): # transform parse subtree into list needed for s.grands
611 xs = []
612 for i, x in enumerate (p.objs): # changing p.objs [i] alters the tree. changing x has no effect on the tree.
613 if type (x) == pObj:
614 us = mkGrand (x, vdefs) # first get transformation results of current pObj
615 if x.name == 'grand': # x.objs contains ordered list of nested parse results within x
616 vids = [y.objs[0] for y in x.objs[1:]] # the voice ids in the grand staff
617 nms = [vdefs [u][0] for u in vids if u in vdefs] # the names of those voices
618 accept = sum ([1 for nm in nms if nm]) == 1 # accept as grand staff when only one of the voices has a name
619 if accept or us[0] == '{*':
620 xs.append (us[1:]) # append voice ids as a list (discard first item '{' or '{*')
621 vdefs = setFristVoiceNameFromGroup (vids, vdefs)
622 p.objs [i] = x.objs[1] # replace voices by first one in the grand group (this modifies the parse tree)
623 else:
624 xs.extend (us[1:]) # extend current result with all voice ids of rejected grand staff
625 else: xs.extend (us) # extend current result with transformed pObj
626 else: xs.append (p.t[0]) # append the non pObj (== voice id string)
627 return xs
628
629 def mkStaves (p, vdefs): # transform parse tree into list needed for s.staves
630 xs = []
631 for i, x in enumerate (p.objs): # structure and comments identical to mkGrand
632 if type (x) == pObj:
633 us = mkStaves (x, vdefs)
634 if x.name == 'voicegr':
635 xs.append (us)
636 vids = [y.objs[0] for y in x.objs]
637 vdefs = setFristVoiceNameFromGroup (vids, vdefs)
638 p.objs [i] = x.objs[0]
639 else:
640 xs.extend (us)
641 else:
642 if p.t[0] not in '{*': xs.append (p.t[0])
643 return xs
644
645 def mkGroups (p): # transform parse tree into list needed for s.groups
646 xs = []
647 for x in p.objs:
648 if type (x) == pObj:
649 if x.name == 'vid': xs.extend (mkGroups (x))
650 elif x.name == 'bracketgr': xs.extend (['['] + mkGroups (x) + [']'])
651 elif x.name == 'bracegr': xs.extend (['{'] + mkGroups (x) + ['}'])
652 else: xs.extend (mkGroups (x) + ['}']) # x.name == 'grand' == rejected grand staff
653 else:
654 xs.append (p.t[0])
655 return xs
656
657 class MusicXml:
658 typeMap = {1:'long', 2:'breve', 4:'whole', 8:'half', 16:'quarter', 32:'eighth', 64:'16th', 128:'32nd', 256:'64th'}
659 dynaMap = {'p':1,'pp':1,'ppp':1,'f':1,'ff':1,'fff':1,'mp':1,'mf':1,'sfz':1}
660 wedgeMap = {'>(':1, '>)':1, '<(':1,'<)':1,'crescendo(':1,'crescendo)':1,'diminuendo(':1,'diminuendo)':1}
661 artMap = {'.':'staccato','>':'accent','accent':'accent','wedge':'staccatissimo','tenuto':'tenuto'}
662 ornMap = {'trill':'trill-mark','T':'trill-mark','turn':'turn','uppermordent':'inverted-mordent','lowermordent':'mordent',
663 'pralltriller':'inverted-mordent','mordent':'mordent','turn':'turn','invertedturn':'inverted-turn'}
664 tecMap = {'upbow':'up-bow', 'downbow':'down-bow'}
665 capoMap = {'fine':('Fine','fine','yes'), 'D.S.':('D.S.','dalsegno','segno'), 'D.C.':('D.C.','dacapo','yes'),'dacapo':('D.C.','dacapo','yes'),
666 'dacoda':('To Coda','tocoda','coda'), 'coda':('coda','coda','coda'), 'segno':('segno','segno','segno')}
667 sharpness = ['Fb', 'Cb','Gb','Db','Ab','Eb','Bb','F','C','G','D','A', 'E', 'B', 'F#','C#','G#','D#','A#','E#','B#']
668 offTab = {'maj':8, 'm':11, 'min':11, 'mix':9, 'dor':10, 'phr':12, 'lyd':7, 'loc':13}
669 modTab = {'maj':'major', 'm':'minor', 'min':'minor', 'mix':'mixolydian', 'dor':'dorian', 'phr':'phrygian', 'lyd':'lydian', 'loc':'locrian'}
670 clefMap = { 'alto1':('C','1'), 'alto2':('C','2'), 'alto':('C','3'), 'alto4':('C','4'), 'tenor':('C','4'),
671 'bass3':('F','3'), 'bass':('F','4'), 'treble':('G','2'), 'perc':('percussion',''), 'none':('','')}
672 clefLineMap = {'B':'treble', 'G':'alto1', 'E':'alto2', 'C':'alto', 'A':'tenor', 'F':'bass3', 'D':'bass'}
673 alterTab = {'=':'0', '_':'-1', '__':'-2', '^':'1', '^^':'2'}
674 accTab = {'=':'natural', '_':'flat', '__':'flat-flat', '^':'sharp', '^^':'sharp-sharp'}
675 chordTab = compChordTab ()
676 uSyms = {'~':'roll', 'H':'fermata','L':'>','M':'lowermordent','O':'coda',
677 'P':'uppermordent','S':'segno','T':'trill','u':'upbow','v':'downbow'}
678 pageFmtDef = [1.764,297,210,10,10,10,10] # the MuseScore page formatting defaults for A4
679 creditTab = {'O':'origin', 'A':'area', 'Z':'transcription', 'N':'notes', 'G':'group', 'H':'history', 'R':'rhythm',
680 'B':'book', 'D':'discography', 'F':'fileurl', 'S':'source'}
681
682 def __init__ (s):
683 s.pageFmtCmd = [] # set by command line option -p
684 s.gmwr = 0 # set by command line option -r
685 s.reset ()
686 def reset (s):
687 s.divisions = 120 # xml duration of 1/4 note
688 s.ties = {} # {abc pitch tuple -> alteration} for all open ties
689 s.slurstack = [] # stack of open slur numbers
690 s.slurbeg = 0 # number of slurs to start (when slurs are detected at element-level)
691 s.tmnum = 0 # time modification, numerator
692 s.tmden = 0 # time modification, denominator
693 s.ntup = 0 # number of tuplet notes remaining
694 s.tupnts = [] # all tuplet modifiers with corresp. durations: [(duration, modifier), ...]
695 s.irrtup = 0 # 1 if an irregular tuplet
696 s.ntype = '' # the normal-type of a tuplet (== duration type of a normal tuplet note)
697 s.unitL = (1, 8) # default unit length
698 s.unitLcur = (1, 8) # unit length of current voice
699 s.keyAlts = {} # alterations implied by key
700 s.msreAlts = {} # temporarily alterations
701 s.curVolta = '' # open volta bracket
702 s.slurstack = [] # stack of open slur numbers
703 s.title = '' # title of music
704 s.creator = {} # {creator-type -> creator string}
705 s.credits = {} # {credit-type -> string}
706 s.lyrdash = {} # {lyric number -> 1 if dash between syllables}
707 s.usrSyms = s.uSyms # user defined symbols
708 s.prevNote = None # xml element of previous beamed note to correct beams (start, continue)
709 s.grcbbrk = False # remember any bbrk in a grace sequence
710 s.linebrk = 0 # 1 if next measure should start with a line break
711 s.bardecos = [] # barline decorations (coda, segno) that go into the next measure (MuseScore deficiency!)
712 s.nextdecos = [] # decorations for the next note
713 s.prevmsre = None # the previous measure
714 s.supports_tag = 0 # issue supports-tag in xml file when abc uses explicit linebreaks
715 s.staveDefs = [] # collected %%staves or %%score instructions from score
716 s.staves = [] # staves = [[voice names to be merged into one stave]]
717 s.groups = [] # list of merged part names with interspersed {[ and }]
718 s.grands = [] # [[vid1, vid2, ..], ...] voiceIds to be merged in a grand staff
719 s.gStaffNums = {} # map each voice id in a grand staff to a staff number
720 s.gNstaves = {} # map each voice id in a grand staff to total number of staves
721 s.pageFmtAbc = [] # formatting from abc directives
722 s.mdur = (4,4) # duration of one measure
723 s.gtrans = 0 # octave transposition (by clef)
724 s.midprg = ['', ''] # MIDI channel nr, program nr for the current part
725 s.vid = '' # abc voice id for the current part
726 s.gcue_on = 0 # insert <cue/> tag in each note
727
728 def mkPitch (s, acc, note, oct, lev):
729 nUp = note.upper ()
730 octnum = (4 if nUp == note else 5) + int (oct) + s.gtrans
731 pitch = E.Element ('pitch')
732 addElemT (pitch, 'step', nUp, lev + 1)
733 alter = ''
734 if (note, oct) in s.ties:
735 tied_alter, _, vnum = s.ties [(note,oct)] # vnum = overlay voice number when tie started
736 if vnum == s.overlayVnum: alter = tied_alter # tied note in the same overlay -> same alteration
737 elif acc:
738 s.msreAlts [(nUp, octnum)] = s.alterTab [acc]
739 alter = s.alterTab [acc] # explicit notated alteration
740 elif (nUp, octnum) in s.msreAlts: alter = s.msreAlts [(nUp, octnum)] # temporary alteration
741 elif nUp in s.keyAlts: alter = s.keyAlts [nUp] # alteration implied by the key
742 if alter: addElemT (pitch, 'alter', alter, lev + 1)
743 addElemT (pitch, 'octave', str (octnum), lev + 1)
744 return pitch, alter
745
746 def mkNote (s, n, lev):
747 nnum, nden = n.dur.t # abc dutation of note
748 if nden == 0: nden = 1 # occurs with illegal ABC like: "A2 1". Now interpreted as A2/1
749 num, den = simplify (nnum * s.unitLcur[0], nden * s.unitLcur[1]) # normalised with unit length
750 if den > 64: # limit denominator to 64
751 num = int (round (64 * float (num) / den)) # scale note to num/64
752 num, den = simplify (max ([num, 1]), 64) # smallest num == 1
753 info ('duration too small: rounded to %d/%d' % (num, den))
754 if n.name == 'rest' and ('Z' in n.t or 'X' in n.t):
755 num, den = s.mdur # duration of one measure
756 dvs = (4 * s.divisions * num) / den # divisions is xml-duration of 1/4
757 rdvs = dvs # real duration (will be 0 for chord/grace)
758 num, den = simplify (num, den * 4) # scale by 1/4 for s.typeMap
759 ndot = 0
760 if num == 3: ndot = 1; den = den / 2 # look for dotted notes
761 if num == 7: ndot = 2; den = den / 4
762 nt = E.Element ('note')
763 if getattr (n, 'grace', ''): # a grace note (and possibly a chord note)
764 grace = E.Element ('grace')
765 if s.acciatura: grace.set ('slash', 'yes'); s.acciatura = 0
766 addElem (nt, grace, lev + 1)
767 dvs = rdvs = 0 # no (real) duration for a grace note
768 if den <= 16: den = 32 # not longer than 1/8 for a grace note
769 if s.gcue_on: # insert cue tag
770 cue = E.Element ('cue')
771 addElem (nt, cue, lev + 1)
772 if getattr (n, 'chord', ''): # a chord note
773 chord = E.Element ('chord')
774 addElem (nt, chord, lev + 1)
775 rdvs = 0 # chord notes no real duration
776 if rdvs and s.ntup >= 0: s.ntup -= 1 # count tuplet notes only on non-chord, non grace notes (rdvs > 0)
777 if den not in s.typeMap: # take the nearest smaller legal duration
778 info ('illegal duration %d/%d' % (nnum, nden))
779 den = min (x for x in s.typeMap.keys () if x > den)
780 xmltype = str (s.typeMap [den]) # xml needs the note type in addition to duration
781 acc, step, oct = '', 'C', '0' # abc-notated pitch elements (accidental, pitch step, octave)
782 alter = '' # xml alteration
783 if n.name == 'rest':
784 if 'x' in n.t or 'X' in n.t: nt.set ('print-object', 'no')
785 rest = E.Element ('rest')
786 addElem (nt, rest, lev + 1)
787 else:
788 p = n.pitch.t # get pitch elements from parsed tokens
789 if len (p) == 3: acc, step, oct = p
790 else: step, oct = p
791 pitch, alter = s.mkPitch (acc, step, oct, lev + 1)
792 addElem (nt, pitch, lev + 1)
793 if s.ntup >= 0: # modify duration for tuplet notes
794 dvs = dvs * s.tmden / s.tmnum
795 if dvs: addElemT (nt, 'duration', str (dvs), lev + 1) # skip when dvs == 0, requirement of musicXML
796 inst = E.Element ('instrument', id='I-'+s.vid) # instrument id for midi
797 if s.midprg != ['', '']: addElem (nt, inst, lev + 1) # only add when %%midi was present
798 addElemT (nt, 'voice', '1', lev + 1) # default voice, for merging later
799 addElemT (nt, 'type', xmltype, lev + 1) # add note type
800 for i in range (ndot): # add dots
801 dot = E.Element ('dot')
802 addElem (nt, dot, lev + 1)
803 ptup = (step, oct) # pitch tuple without alteration to check for ties
804 tstop = ptup in s.ties and s.ties[ptup][2] == s.overlayVnum # open tie on this pitch tuple in this overlay
805 if acc and not tstop: addElemT (nt, 'accidental', s.accTab [acc], lev + 1) # only add accidental if note not tied
806 tupnotation = '' # start/stop notation element for tuplets
807 if s.ntup >= 0: # add time modification element for tuplet notes
808 tmod = mkTmod (s.tmnum, s.tmden, lev + 1)
809 addElem (nt, tmod, lev + 1)
810 if s.ntup > 0 and not s.tupnts: tupnotation = 'start'
811 s.tupnts.append ((rdvs, tmod)) # remember all tuplet modifiers with corresp. durations
812 if s.ntup == 0: # last tuplet note (and possible chord notes there after)
813 if rdvs: tupnotation = 'stop' # only insert notation in the real note (rdvs > 0)
814 s.cmpNormType (rdvs, lev + 1) # compute and/or add normal-type elements (-> s.ntype)
815 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
816 if gstaff: addElemT (nt, 'staff', str (gstaff), lev + 1)
817 s.doBeams (n, nt, den, lev + 1)
818 s.doNotations (n, ptup, alter, tupnotation, tstop, nt, lev + 1)
819 if n.objs: s.doLyr (n, nt, lev + 1)
820 return nt
821
822 def cmpNormType (s, rdvs, lev): # compute the normal-type of a tuplet (only needed for Finale)
823 if rdvs: # the last real tuplet note (chord notes can still follow afterwards with rdvs == 0)
824 durs = [dur for dur, tmod in s.tupnts if dur > 0]
825 ndur = sum (durs) / s.tmnum # duration of the normal type
826 s.irrtup = any ((dur != ndur) for dur in durs) # irregular tuplet
827 tix = 16 * s.divisions / ndur # index in typeMap of normal-type duration
828 if tix in s.typeMap:
829 s.ntype = str (s.typeMap [tix]) # the normal-type
830 else: s.irrtup = 0 # give up, no normal type possible
831 if s.irrtup: # only add normal-type for irregular tuplets
832 for dur, tmod in s.tupnts: # add normal-type to all modifiers
833 addElemT (tmod, 'normal-type', s.ntype, lev + 1)
834 s.tupnts = [] # reset the tuplet buffer
835
836 def doNotations (s, n, ptup, alter, tupnotation, tstop, nt, lev):
837 slurs = getattr (n, 'slurs', 0) # slur ends
838 pts = getattr (n, 'pitches', []) # all chord notes available in the first note
839 if pts: # make list of pitches in chord: [(pitch, octave), ..]
840 if type (pts.pitch) == pObj: pts = [pts.pitch] # chord with one note
841 else: pts = [tuple (p.t[-2:]) for p in pts.pitch] # normal chord
842 for pt, (tie_alter, nts, vnum) in s.ties.items (): # scan all open ties and delete illegal ones
843 if vnum != s.overlayVnum: continue # tie belongs to different overlay
844 if pts and pt in pts: continue # pitch tuple of tie exists in chord
845 if getattr (n, 'chord', 0): continue # skip chord notes
846 if pt == ptup: continue # skip correct single note tie
847 if getattr (n, 'grace', 0): continue # skip grace notes
848 info ('tie between different pitches: %s%s converted to slur' % pt)
849 del s.ties [pt] # remove the note from pending ties
850 e = [t for t in nts.findall ('tied') if t.get ('type') == 'start'][0] # get the tie start element
851 e.tag = 'slur' # convert tie into slur
852 slurnum = len (s.slurstack) + 1
853 s.slurstack.append (slurnum)
854 e.set ('number', str (slurnum))
855 if slurs: slurs.t.append (')') # close slur on this note
856 else: slurs = pObj ('slurs', [')'])
857 tstart = getattr (n, 'tie', 0) # start a new tie
858 decos = s.nextdecos # decorations encountered so far
859 ndeco = getattr (n, 'deco', 0) # possible decorations of notes of a chord
860 if ndeco: # add decorations, translate used defined symbols
861 decos += [s.usrSyms.get (d, d).strip ('!+') for d in ndeco.t]
862 s.nextdecos = []
863 if not (tstop or tstart or decos or slurs or s.slurbeg or tupnotation): return nt
864 nots = E.Element ('notations') # notation element needed
865 if tupnotation: # add tuplet type
866 tup = E.Element ('tuplet', type=tupnotation)
867 if tupnotation == 'start': tup.set ('bracket', 'yes')
868 addElem (nots, tup, lev + 1)
869 if tstop: # stop tie
870 del s.ties[ptup] # remove flag
871 tie = E.Element ('tied', type='stop')
872 addElem (nots, tie, lev + 1)
873 if tstart: # start a tie
874 s.ties[ptup] = (alter, nots, s.overlayVnum) # remember pitch tuple to stop tie and apply same alteration
875 tie = E.Element ('tied', type='start')
876 addElem (nots, tie, lev + 1)
877 if decos: # look for slurs and decorations
878 arts = [] # collect articulations
879 for d in decos: # do all slurs and decos
880 if d == '(': s.slurbeg += 1; continue # slurs made in while loop at the end
881 elif d == 'fermata' or d == 'H':
882 ntn = E.Element ('fermata', type='upright')
883 elif d == 'arpeggio':
884 ntn = E.Element ('arpeggiate', number='1')
885 else: arts.append (d); continue
886 addElem (nots, ntn, lev + 1)
887 if arts: # do only note articulations and collect staff annotations in xmldecos
888 rest = s.doArticulations (nots, arts, lev + 1)
889 if rest: info ('unhandled note decorations: %s' % rest)
890 while s.slurbeg > 0:
891 s.slurbeg -= 1
892 slurnum = len (s.slurstack) + 1
893 s.slurstack.append (slurnum)
894 ntn = E.Element ('slur', number='%d' % slurnum, type='start')
895 addElem (nots, ntn, lev + 1)
896 if slurs: # these are only slur endings
897 for d in slurs.t:
898 if not s.slurstack: break # no more open slurs
899 slurnum = s.slurstack.pop ()
900 slur = E.Element ('slur', number='%d' % slurnum, type='stop')
901 addElem (nots, slur, lev + 1)
902 if nots.getchildren() != []: # only add notations if not empty
903 addElem (nt, nots, lev)
904
905 def doArticulations (s, nots, arts, lev):
906 decos = []
907 for a in arts:
908 if a in s.artMap:
909 art = E.Element ('articulations')
910 addElem (nots, art, lev)
911 addElem (art, E.Element (s.artMap[a]), lev + 1)
912 elif a in s.ornMap:
913 orn = E.Element ('ornaments')
914 addElem (nots, orn, lev)
915 addElem (orn, E.Element (s.ornMap[a]), lev + 1)
916 elif a in s.tecMap:
917 tec = E.Element ('technical')
918 addElem (nots, tec, lev)
919 addElem (tec, E.Element (s.tecMap[a]), lev + 1)
920 else: decos.append (a) # return staff annotations
921 return decos
922
923 def doLyr (s, n, nt, lev):
924 for i, lyrobj in enumerate (n.objs):
925 if lyrobj.name != 'syl': continue
926 dash = len (lyrobj.t) == 2
927 if dash:
928 if i in s.lyrdash: type = 'middle'
929 else: type = 'begin'; s.lyrdash [i] = 1
930 else:
931 if i in s.lyrdash: type = 'end'; del s.lyrdash [i]
932 else: type = 'single'
933 lyrel = E.Element ('lyric', number = str (i + 1))
934 addElem (nt, lyrel, lev)
935 addElemT (lyrel, 'syllabic', type, lev + 1)
936 addElemT (lyrel, 'text', lyrobj.t[0].replace ('~',' '), lev + 1)
937
938 def doBeams (s, n, nt, den, lev):
939 if hasattr (n, 'chord') or hasattr (n, 'grace'):
940 s.grcbbrk = s.grcbbrk or n.bbrk.t[0] # remember if there was any bbrk in or before a grace sequence
941 return
942 bbrk = s.grcbbrk or n.bbrk.t[0] or den < 32
943 s.grcbbrk = False
944 if not s.prevNote: pbm = None
945 else: pbm = s.prevNote.find ('beam')
946 bm = E.Element ('beam', number='1')
947 bm.text = 'begin'
948 if pbm != None:
949 if bbrk:
950 if pbm.text == 'begin':
951 s.prevNote.remove (pbm)
952 elif pbm.text == 'continue':
953 pbm.text = 'end'
954 s.prevNote = None
955 else: bm.text = 'continue'
956 if den >= 32 and n.name != 'rest':
957 addElem (nt, bm, lev)
958 s.prevNote = nt
959
960 def stopBeams (s):
961 if not s.prevNote: return
962 pbm = s.prevNote.find ('beam')
963 if pbm != None:
964 if pbm.text == 'begin':
965 s.prevNote.remove (pbm)
966 elif pbm.text == 'continue':
967 pbm.text = 'end'
968 s.prevNote = None
969
970 def staffDecos (s, decos, maat, lev, bardecos=0):
971 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
972 for d in decos:
973 d = s.usrSyms.get (d, d).strip ('!+') # try to replace user defined symbol
974 if d in s.dynaMap:
975 dynel = E.Element ('dynamics')
976 addDirection (maat, dynel, lev, gstaff, [E.Element (d)], 'below', s.gcue_on)
977 elif d in s.wedgeMap: # wedge
978 if ')' in d: type = 'stop'
979 else: type = 'crescendo' if '<' in d or 'crescendo' in d else 'diminuendo'
980 addDirection (maat, E.Element ('wedge', type=type), lev, gstaff)
981 elif d in ['coda', 'segno']:
982 if bardecos: s.bardecos.append (d) # postpone to begin next measure
983 else:
984 text, attr, val = s.capoMap [d]
985 dir = addDirection (maat, E.Element (text), lev, gstaff, placement='above')
986 sound = E.Element ('sound'); sound.set (attr, val)
987 addElem (dir, sound, lev + 1)
988 elif d in s.capoMap:
989 text, attr, val = s.capoMap [d]
990 words = E.Element ('words'); words.text = text
991 dir = addDirection (maat, words, lev, gstaff, placement='above')
992 sound = E.Element ('sound'); sound.set (attr, val)
993 addElem (dir, sound, lev + 1)
994 elif d == '(': s.slurbeg += 1 # start slur on next note
995 else: s.nextdecos.append (d) # keep annotation for the next note
996
997 def doFields (s, maat, fieldmap, lev):
998 def doClef ():
999 clef, gtrans = 0, 0
1000 clefn = re.search (r'alto1|alto2|alto4|alto|tenor|bass3|bass|treble|perc|none', field)
1001 clefm = re.search (r"(?:^m=| m=|middle=)([A-Ga-g])([,']*)", field)
1002 trans_oct2 = re.search (r'octave=([-+]\d)', field)
1003 trans = re.search (r'(?:^t=| t=|transpose=)(-?[\d]+)', field)
1004 trans_oct = re.search (r'([+-^_])(8|15)', field)
1005 cue_onoff = re.search (r'cue=(on|off)', field)
1006 if clefn:
1007 clef = clefn.group ()
1008 if clefm:
1009 note, octstr = clefm.groups ()
1010 nUp = note.upper ()
1011 octnum = (4 if nUp == note else 5) + (len (octstr) if "'" in octstr else -len (octstr))
1012 gtrans = (3 if nUp in 'AFD' else 4) - octnum
1013 if clef not in ['perc', 'none']: clef = s.clefLineMap [nUp]
1014 if clef:
1015 s.gtrans = gtrans # only change global tranposition when a clef is really defined
1016 sign, line = s.clefMap [clef]
1017 if not sign: return
1018 c = E.Element ('clef')
1019 gstaff = s.gStaffNums.get (s.vid, 0) # the current staff number
1020 if gstaff: c.set ('number', str (gstaff)) # only add staff number when defined
1021 addElemT (c, 'sign', sign, lev + 2)
1022 if line: addElemT (c, 'line', line, lev + 2)
1023 if trans_oct:
1024 n = trans_oct.group (1) in '-_' and -1 or 1
1025 if trans_oct.group (2) == '15': n *= 2 # 8 => 1 octave, 15 => 2 octaves
1026 addElemT (c, 'clef-octave-change', str (n), lev + 2) # transpose print out
1027 if trans_oct.group (1) in '+-': s.gtrans += n # also transpose all pitches with one octave
1028 if trans_oct2:
1029 n = int (trans_oct2.group (1))
1030 s.gtrans += n
1031 atts.append ((7, c))
1032 if trans != None: # add transposition in semitones
1033 e = E.Element ('transpose')
1034 addElemT (e, 'chromatic', str (trans.group (1)), lev + 3)
1035 atts.append ((9, e))
1036 if cue_onoff: s.gcue_on = cue_onoff.group (1) == 'on'
1037 atts = [] # collect xml attribute elements [(order-number, xml-element), ..]
1038 for ftype, field in fieldmap.items ():
1039 if not field: # skip empty fields
1040 continue
1041 if ftype == 'Div': # not an abc field, but handled as if
1042 d = E.Element ('divisions')
1043 d.text = field
1044 atts.append ((1, d))
1045 elif ftype == 'gstaff': # make grand staff
1046 e = E.Element ('staves')
1047 e.text = str (field)
1048 atts.append ((4, e))
1049 elif ftype == 'M':
1050 if field == 'none': continue
1051 if field == 'C': field = '4/4'
1052 elif field == 'C|': field = '2/2'
1053 t = E.Element ('time')
1054 if '/' not in field:
1055 info ('M:%s not recognized, 4/4 assumed' % field)
1056 field = '4/4'
1057 beats, btype = field.split ('/')[:2]
1058 try: s.mdur = simplify (eval (beats), int (btype)) # measure duration for Z and X rests (eval allows M:2+3/4)
1059 except:
1060 info ('error in M:%s, 4/4 assumed' % field)
1061 s.mdur = (4,4)
1062 beats, btype = '4','4'
1063 addElemT (t, 'beats', beats, lev + 2)
1064 addElemT (t, 'beat-type', btype, lev + 2)
1065 atts.append ((3, t))
1066 elif ftype == 'K':
1067 accs = ['F','C','G','D','A','E','B'] # == s.sharpness [7:14]
1068 mode = ''
1069 key = re.match (r'\s*([A-G][#b]?)\s*([a-zA-Z]*)', field)
1070 alts = re.search (r'\s((\s?[=^_][A-Ga-g])+)', ' ' + field) # avoid matching middle=G and m=G
1071 if key:
1072 key, mode = key.groups ()
1073 mode = mode.lower ()[:3] # only first three chars, no case
1074 if mode not in s.offTab: mode = 'maj'
1075 fifths = s.sharpness.index (key) - s.offTab [mode]
1076 if fifths >= 0: s.keyAlts = dict (zip (accs[:fifths], fifths * ['1']))
1077 else: s.keyAlts = dict (zip (accs[fifths:], -fifths * ['-1']))
1078 elif field.startswith ('none') or field == '': # the default key
1079 fifths = 0
1080 mode = 'maj'
1081 if alts:
1082 alts = re.findall (r'[=^_][A-Ga-g]', alts.group(1)) # list of explicit alterations
1083 alts = [(x[1], s.alterTab [x[0]]) for x in alts] # [step, alter]
1084 for step, alter in alts: # correct permanent alterations for this key
1085 s.keyAlts [step.upper ()] = alter
1086 k = E.Element ('key')
1087 koctave = []
1088 lowerCaseSteps = [step.upper () for step, alter in alts if step.islower ()]
1089 for step, alter in s.keyAlts.items ():
1090 if alter == '0': # skip neutrals
1091 del s.keyAlts [step.upper ()] # otherwise you get neutral signs on normal notes
1092 continue
1093 addElemT (k, 'key-step', step.upper (), lev + 2)
1094 addElemT (k, 'key-alter', alter, lev + 2)
1095 koctave.append ('5' if step in lowerCaseSteps else '4')
1096 if koctave: # only key signature if not empty
1097 for oct in koctave:
1098 e = E.Element ('key-octave', number=oct)
1099 addElem (k, e, lev + 2)
1100 atts.append ((2, k))
1101 elif mode:
1102 k = E.Element ('key')
1103 addElemT (k, 'fifths', str (fifths), lev + 2)
1104 addElemT (k, 'mode', s.modTab [mode], lev + 2)
1105 atts.append ((2, k))
1106 doClef ()
1107 elif ftype == 'L':
1108 s.unitLcur = map (int, field.split ('/'))
1109 if len (s.unitLcur) == 1 or s.unitLcur[1] not in s.typeMap:
1110 info ('L:%s is not allowed, 1/8 assumed' % field)
1111 s.unitLcur = 1,8
1112 elif ftype == 'V':
1113 doClef ()
1114 elif ftype == 'I':
1115 xs = s.doField_I (ftype, field)
1116 if xs and len (xs) == 1: # when "%%MIDI transpose" matched insert 'attribute/transpose/chromatic'
1117 e = E.Element ('transpose')
1118 addElemT (e, 'chromatic', xs[0], lev + 2) # xs[0] == signed number string given after transpose
1119 atts.append ((9, e))
1120 if xs and len (xs) == 2: # repeated occurrence of [I:MIDI] -> instrument change
1121 midchan, midprog = xs
1122 snd = E.Element ('sound')
1123 mi = E.Element ('midi-instrument', id='I-' + s.vid)
1124 addElem (maat, snd, lev)
1125 addElem (snd, mi, lev + 1)
1126 if midchan: addElemT (mi, 'midi-channel', midchan, lev + 2)
1127 if midprog: addElemT (mi, 'midi-program', str (int (midprog) + 1), lev + 2) # compatible with abc2midi
1128
1129 elif ftype == 'Q':
1130 s.doTempo (maat, field, lev)
1131 elif ftype in 'TCOAZNGHRBDFSU':
1132 info ('**illegal header field in body: %s, content: %s' % (ftype, field))
1133 else:
1134 info ('unhandled field: %s, content: %s' % (ftype, field))
1135
1136 if atts:
1137 att = E.Element ('attributes') # insert sub elements in the order required by musicXML
1138 addElem (maat, att, lev)
1139 for _, att_elem in sorted (atts): # ordering !
1140 addElem (att, att_elem, lev + 1)
1141
1142 def doTempo (s, maat, field, lev):
1143 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
1144 t = re.search (r'(\d)/(\d\d?)\s*=\s*([.\d]+)|([.\d]+)', field)
1145 if not t: return
1146 try:
1147 if t.group (4):
1148 num, den, upm = 1, s.unitLcur[1] , float (t.group (4))
1149 else:
1150 num, den, upm = int (t.group (1)), int (t.group (2)), float (t.group (3))
1151 except: return # float or int conversion failure
1152 if num != 1: info ('in Q: numerator > 1 in %d/%d not supported' % (num, den))
1153 qpm = 4. * num * upm / den
1154 metro = E.Element ('metronome')
1155 u = E.Element ('beat-unit'); u.text = s.typeMap [4 * den]
1156 pm = E.Element ('per-minute'); pm.text = '%.2f' % upm
1157 dir = addDirection (maat, metro, lev, gstaff, [u, pm], placement='above')
1158 sound = E.Element ('sound'); sound.set ('tempo', '%.2f' % qpm)
1159 addElem (dir, sound, lev + 1)
1160
1161 def mkBarline (s, maat, loc, lev, style='', dir='', ending=''):
1162 b = E.Element ('barline', location=loc)
1163 if style:
1164 addElemT (b, 'bar-style', style, lev + 1)
1165 if s.curVolta: # first stop a current volta
1166 end = E.Element ('ending', number=s.curVolta, type='stop')
1167 s.curVolta = ''
1168 if loc == 'left': # stop should always go to a right barline
1169 bp = E.Element ('barline', location='right')
1170 addElem (bp, end, lev + 1)
1171 addElem (s.prevmsre, bp, lev) # prevmsre has no right barline! (ending would have stopped there)
1172 else:
1173 addElem (b, end, lev + 1)
1174 if ending:
1175 ending = ending.replace ('-',',') # MusicXML only accepts comma's
1176 endtxt = ''
1177 if ending.startswith ('"'): # ending is a quoted string
1178 endtxt = ending.strip ('"')
1179 ending = '33' # any number that is not likely to occur elsewhere
1180 end = E.Element ('ending', number=ending, type='start')
1181 if endtxt: end.text = endtxt # text appears in score in stead of number attribute
1182 addElem (b, end, lev + 1)
1183 s.curVolta = ending
1184 if dir:
1185 r = E.Element ('repeat', direction=dir)
1186 addElem (b, r, lev + 1)
1187 addElem (maat, b, lev)
1188
1189 def doChordSym (s, maat, sym, lev):
1190 alterMap = {'#':'1','=':'0','b':'-1'}
1191 rnt = sym.root.t
1192 chord = E.Element ('harmony')
1193 addElem (maat, chord, lev)
1194 root = E.Element ('root')
1195 addElem (chord, root, lev + 1)
1196 addElemT (root, 'root-step', rnt[0], lev + 2)
1197 if len (rnt) == 2: addElemT (root, 'root-alter', alterMap [rnt[1]], lev + 2)
1198 kind = s.chordTab.get (sym.kind.t[0], 'major')
1199 addElemT (chord, 'kind', kind, lev + 1)
1200 degs = getattr (sym, 'degree', '')
1201 if degs:
1202 if type (degs) != types.ListType: degs = [degs]
1203 for deg in degs:
1204 deg = deg.t[0]
1205 if deg[0] == '#': alter = '1'; deg = deg[1:]
1206 elif deg[0] == 'b': alter = '-1'; deg = deg[1:]
1207 else: alter = '0'; deg = deg
1208 degree = E.Element ('degree')
1209 addElem (chord, degree, lev + 1)
1210 addElemT (degree, 'degree-value', deg, lev + 2)
1211 addElemT (degree, 'degree-alter', alter, lev + 2)
1212 addElemT (degree, 'degree-type', 'add', lev + 2)
1213
1214 def mkMeasure (s, i, t, lev, fieldmap={}):
1215 s.msreAlts = {}
1216 s.ntup = -1
1217 s.acciatura = 0 # next grace element gets acciatura attribute
1218 overlay = 0
1219 maat = E.Element ('measure', number = str(i))
1220 if fieldmap: s.doFields (maat, fieldmap, lev + 1)
1221 if s.linebrk: # there was a line break in the previous measure
1222 e = E.Element ('print')
1223 e.set ('new-system', 'yes')
1224 addElem (maat, e, lev + 1)
1225 s.linebrk = 0
1226 if s.bardecos: # output coda and segno attached to the previous right barline
1227 s.staffDecos (s.bardecos, maat, lev + 1)
1228 s.bardecos = []
1229 for it, x in enumerate (t):
1230 if x.name == 'note' or x.name == 'rest':
1231 note = s.mkNote (x, lev + 1)
1232 addElem (maat, note, lev + 1)
1233 elif x.name == 'lbar':
1234 bar = x.t[0]
1235 if bar == '|': pass # skip redundant bar
1236 elif ':' in bar: # forward repeat
1237 volta = x.t[1] if len (x.t) == 2 else ''
1238 s.mkBarline (maat, 'left', lev + 1, style='heavy-light', dir='forward', ending=volta)
1239 else: # bar must be a volta number
1240 s.mkBarline (maat, 'left', lev + 1, ending=bar)
1241 elif x.name == 'rbar':
1242 if hasattr (x, 'deco'): # MuseScore does not support this -> emergency solution
1243 s.staffDecos (x.deco.t, maat, lev + 1, bardecos=1) # coda, segno -> next measure
1244 bar = x.t[0]
1245 if bar == '.|':
1246 s.mkBarline (maat, 'right', lev + 1, style='dotted')
1247 elif ':' in bar: # backward repeat
1248 s.mkBarline (maat, 'right', lev + 1, style='light-heavy', dir='backward')
1249 elif bar == '||':
1250 s.mkBarline (maat, 'right', lev + 1, style='light-light')
1251 elif bar == '[|]' or bar == '[]':
1252 s.mkBarline (maat, 'right', lev + 1, style='none')
1253 elif '[' in bar or ']' in bar:
1254 s.mkBarline (maat, 'right', lev + 1, style='light-heavy')
1255 elif bar[0] == '&': overlay = 1
1256 elif x.name == 'tup':
1257 if len (x.t) == 3: n, into, nts = x.t
1258 else: n, into, nts = x.t[0], 0, 0
1259 if into == 0: into = 3 if n in [2,4,8] else 2
1260 if nts == 0: nts = n
1261 s.tmnum, s.tmden, s.ntup = n, into, nts
1262 elif x.name == 'deco':
1263 s.staffDecos (x.t, maat, lev + 1) # output staff decos, postpone note decos to next note
1264 elif x.name == 'text':
1265 pos, text = x.t[:2]
1266 place = 'above' if pos == '^' else 'below'
1267 words = E.Element ('words')
1268 words.text = text
1269 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
1270 addDirection (maat, words, lev + 1, gstaff, placement=place)
1271 elif x.name == 'inline':
1272 fieldtype, fieldval = x.t[:2]
1273 s.doFields (maat, {fieldtype:fieldval}, lev + 1)
1274 elif x.name == 'accia': s.acciatura = 1
1275 elif x.name == 'linebrk':
1276 s.supports_tag = 1
1277 if it > 0 and t[it -1].name == 'lbar': # we are at start of measure
1278 e = E.Element ('print') # output linebreak now
1279 e.set ('new-system', 'yes')
1280 addElem (maat, e, lev + 1)
1281 else:
1282 s.linebrk = 1 # output linebreak at start of next measure
1283 elif x.name == 'chordsym':
1284 s.doChordSym (maat, x, lev + 1)
1285 s.stopBeams ()
1286 s.prevmsre = maat
1287 return maat, overlay
1288
1289 def mkPart (s, maten, id, lev, attrs, nstaves):
1290 s.slurstack = []
1291 s.unitLcur = s.unitL # set the default unit length at begin of each voice
1292 s.curVolta = ''
1293 s.lyrdash = {}
1294 s.linebrk = 0
1295 s.midprg = ['', ''] # MIDI channel nr, program nr for the current part
1296 s.gcue_on = 0 # reset cue note marker for each new voice
1297 s.gtrans = 0 # reset octave transposition (by clef)
1298 part = E.Element ('part', id=id)
1299 s.overlayVnum = 0 # overlay voice number to relate ties that extend from one overlayed measure to the next
1300 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
1301 attrs_cpy = attrs.copy () # don't change attrs itself in next line
1302 if gstaff == 1: attrs_cpy ['gstaff'] = nstaves # make a grand staff
1303 msre, overlay = s.mkMeasure (1, maten[0], lev + 1, attrs_cpy)
1304 addElem (part, msre, lev + 1)
1305 for i, maat in enumerate (maten[1:]):
1306 s.overlayVnum = s.overlayVnum + 1 if overlay else 0
1307 msre, next_overlay = s.mkMeasure (i+2, maat, lev + 1)
1308 if overlay: mergePartMeasure (part, msre, s.overlayVnum)
1309 else: addElem (part, msre, lev + 1)
1310 overlay = next_overlay
1311 return part
1312
1313 def mkScorePart (s, id, vids_p, partAttr, lev):
1314 naam, subnm, midprg = partAttr [id]
1315 sp = E.Element ('score-part', id='P'+id)
1316 nm = E.Element ('part-name')
1317 nm.text = naam
1318 addElem (sp, nm, lev + 1)
1319 snm = E.Element ('part-abbreviation')
1320 snm.text = subnm
1321 if subnm: addElem (sp, snm, lev + 1) # only add if subname was given
1322 if s.staves: instr_vids = [vids for vids in s.staves if vids[0] == id][0]
1323 else: instr_vids = [id]
1324 inst = []
1325 for id in instr_vids:
1326 if id not in partAttr: continue # error in %%score -> instr_vids may have non existing id's
1327 naam, subnm, midprg = partAttr [id]
1328 midchan, midprog = midprg
1329 if not midchan and not midprog: continue # only add if program nr or channel was given
1330 si = E.Element ('score-instrument', id='I-'+id)
1331 addElemT (si, 'instrument-name', naam, lev + 2)
1332 mi = E.Element ('midi-instrument', id='I-'+id)
1333 if midchan: addElemT (mi, 'midi-channel', midchan, lev + 2)
1334 if midprog: addElemT (mi, 'midi-program', str (int (midprog) + 1), lev + 2) # compatible with abc2midi
1335 inst.append ((si, mi))
1336 for si, mi in inst: addElem (sp, si, lev + 1)
1337 for si, mi in inst: addElem (sp, mi, lev + 1)
1338 return sp, len (inst)
1339
1340 def mkPartlist (s, vids, partAttr, lev):
1341 def addPartGroup (sym, num):
1342 pg = E.Element ('part-group', number=str (num), type='start')
1343 addElem (partlist, pg, lev + 1)
1344 addElemT (pg, 'group-symbol', sym, lev + 2)
1345 addElemT (pg, 'group-barline', 'yes', lev + 2)
1346 partlist = E.Element ('part-list')
1347 g_num = 0 # xml group number
1348 nInstrs = [] # number of instruments in each part
1349 for g in (s.groups or vids): # brace/bracket or abc_voice_id
1350 if g == '[': g_num += 1; addPartGroup ('bracket', g_num)
1351 elif g == '{': g_num += 1; addPartGroup ('brace', g_num)
1352 elif g in '}]':
1353 pg = E.Element ('part-group', number=str (g_num), type='stop')
1354 addElem (partlist, pg, lev + 1)
1355 g_num -= 1
1356 else: # g = abc_voice_id
1357 if g not in vids: continue # error in %%score
1358 sp, nInst = s.mkScorePart (g, vids, partAttr, lev + 1)
1359 addElem (partlist, sp, lev + 1)
1360 nInstrs.append (nInst)
1361 return partlist, nInstrs
1362
1363 def doField_I (s, type, x):
1364 def readPfmt (x, n): # read ABC page formatting constant
1365 if not s.pageFmtAbc: s.pageFmtAbc = s.pageFmtDef # set the default values on first change
1366 ro = re.search (r'[^.\d]*([\d.]+)\s*(cm|in|pt)?', x) # float followed by unit
1367 if ro:
1368 x, unit = ro.groups () # unit == None when not present
1369 u = {'cm':10., 'in':25.4, 'pt':25.4/72} [unit] if unit else 1.
1370 s.pageFmtAbc [n] = float (x) * u # convert ABC values to millimeters
1371 else: info ('error in page format: %s' % x)
1372
1373 if x.startswith ('score') or x.startswith ('staves'):
1374 s.staveDefs += [x] # collect all voice mappings
1375 elif x.startswith ('staffwidth'): info ('skipped I-field: %s' % x)
1376 elif x.startswith ('staff'): # set new staff number of the current voice
1377 r1 = re.search (r'staff *([+-]?)(\d)', x)
1378 if r1:
1379 sign = r1.group (1)
1380 num = int (r1.group (2))
1381 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
1382 if sign: # relative staff number
1383 num = (sign == '-') and gstaff - num or gstaff + num
1384 else: # absolute abc staff number
1385 try: vabc = s.staves [num - 1][0] # vid of (first voice of) abc-staff num
1386 except: vabc = 0; info ('abc staff %s does not exist' % num)
1387 num = s.gStaffNumsOrg.get (vabc, 0) # xml staff number of abc-staff num
1388 if gstaff and num > 0 and num <= s.gNstaves [s.vid]:
1389 s.gStaffNums [s.vid] = num
1390 else: info ('could not relocate to staff: %s' % r1.group ())
1391 else: info ('not a valid staff redirection: %s' % x)
1392 elif x.startswith ('scale'): readPfmt (x, 0)
1393 elif x.startswith ('pageheight'): readPfmt (x, 1)
1394 elif x.startswith ('pagewidth'): readPfmt (x, 2)
1395 elif x.startswith ('leftmargin'): readPfmt (x, 3)
1396 elif x.startswith ('rightmargin'): readPfmt (x, 4)
1397 elif x.startswith ('topmargin'): readPfmt (x, 5)
1398 elif x.startswith ('botmargin'): readPfmt (x, 6)
1399 elif x.startswith ('MIDI'):
1400 r1 = re.search (r'program *(\d*) +(\d+)', x)
1401 r2 = re.search (r'channel\D*(\d+)', x)
1402 if r1: ch, prg = r1.groups () # channel nr or '', program nr
1403 if r2: ch, prg = r2.group (1), '' # channel nr only
1404 if r1 or r2:
1405 if s.midprg[1] == '': # no instrument defined yet
1406 s.midprg[1] = prg
1407 if ch: s.midprg[0] = ch
1408 elif ch and s.midprg[0] == '': # no channel defined yet
1409 s.midprg[0] = ch
1410 else: # repeated midi def -> insert instument change
1411 return [ch, prg]
1412 r = re.search (r'transpose[^-\d]*(-?\d+)', x)
1413 if r: return [r.group (1)]
1414 else: info ('skipped I-field: %s' % x)
1415
1416 def parseStaveDef (s, vdefs):
1417 if not s.staveDefs: return vdefs
1418 for x in s.staveDefs [1:]: info ('%%%%%s dropped, multiple stave mappings not supported' % x)
1419 x = s.staveDefs [0] # only the first %%score is honoured
1420 score = abc_scoredef.parseString (x) [0]
1421 f = lambda x: type (x) == types.UnicodeType and [x] or x
1422 s.staves = map (f, mkStaves (score, vdefs))
1423 s.grands = map (f, mkGrand (score, vdefs))
1424 s.groups = mkGroups (score)
1425 vce_groups = [vids for vids in s.staves if len (vids) > 1] # all voice groups
1426 d = {} # for each voice group: map first voice id -> all merged voice ids
1427 for vgr in vce_groups: d [vgr[0]] = vgr
1428 for gstaff in s.grands: # for all grand staves
1429 if len (gstaff) == 1: continue # skip single parts
1430 for v, stf_num in zip (gstaff, range (1, len (gstaff) + 1)):
1431 for vx in d.get (v, [v]): # allocate staff numbers
1432 s.gStaffNums [vx] = stf_num # to all constituant voices
1433 s.gNstaves [vx] = len (gstaff) # also remember total number of staves
1434 s.gStaffNumsOrg = s.gStaffNums.copy () # keep original allocation for abc -> xml staff map
1435 return vdefs
1436
1437 def voiceNamesAndMaps (s, ps): # get voice names and mappings
1438 vdefs = {}
1439 for vid, vcedef, vce in ps: # vcedef == emtpy of first pObj == voice definition
1440 pname, psubnm = '', '' # part name and abbreviation
1441 if not vcedef: # simple abc without voice definitions
1442 vdefs [vid] = pname, psubnm, ''
1443 else: # abc with voice definitions
1444 if vid != vcedef.t[1]: info ('voice ids unequal: %s (reg-ex) != %s (grammar)' % (vid, vcedef.t[1]))
1445 rn = re.search (r'(?:name|nm)="([^"]*)"', vcedef.t[2])
1446 if rn: pname = rn.group (1)
1447 rn = re.search (r'(?:subname|snm|sname)="([^"]*)"', vcedef.t[2])
1448 if rn: psubnm = rn.group (1)
1449 vdefs [vid] = pname, psubnm, vcedef.t[2]
1450 xs = [pObj.t[1] for maat in vce for pObj in maat if pObj.name == 'inline'] # all inline statements in vce
1451 s.staveDefs += [x for x in xs if x.startswith ('score') or x.startswith ('staves')] # filter %%score and %%staves
1452 return vdefs
1453
1454 def doHeaderField (s, fld, attrmap):
1455 type, value = fld.t[:2]
1456 if not value: # skip empty field
1457 return
1458 if type == 'M':
1459 attrmap [type] = value
1460 elif type == 'L':
1461 try: s.unitL = map (int, fld.t[1].split ('/'))
1462 except:
1463 info ('illegal unit length:%s, 1/8 assumed' % fld.t[1])
1464 s.unitL = 1,8
1465 if len (s.unitL) == 1 or s.unitL[1] not in s.typeMap:
1466 info ('L:%s is not allowed, 1/8 assumed' % fld.t[1])
1467 s.unitL = 1,8
1468 elif type == 'K':
1469 attrmap[type] = value
1470 elif type == 'T':
1471 if s.title: s.title = s.title + '\n' + value
1472 else: s.title = value
1473 elif type == 'C':
1474 s.creator ['composer'] = s.creator.get ('composer', '') + value
1475 elif type == 'Z':
1476 s.creator ['lyricist'] = s.creator.get ('lyricist', '') + value
1477 elif type == 'U':
1478 sym = fld.t[2].strip ('!+')
1479 s.usrSyms [value] = sym
1480 elif type == 'I':
1481 s.doField_I (type, value)
1482 elif type == 'Q':
1483 attrmap[type] = value
1484 elif type in s.creditTab: s.credits [s.creditTab [type]] = value
1485 else:
1486 info ('skipped header: %s' % fld)
1487
1488 def mkIdentification (s, score, lev):
1489 if s.title:
1490 addElemT (score, 'movement-title', s.title, lev + 1)
1491 ident = E.Element ('identification')
1492 addElem (score, ident, lev + 1)
1493 if s.creator:
1494 for ctype, cname in s.creator.items ():
1495 c = E.Element ('creator', type=ctype)
1496 c.text = cname
1497 addElem (ident, c, lev + 2)
1498 encoding = E.Element ('encoding')
1499 addElem (ident, encoding, lev + 2)
1500 encoder = E.Element ('encoder')
1501 encoder.text = 'abc2xml version %d' % VERSION
1502 addElem (encoding, encoder, lev + 3)
1503 if s.supports_tag: # avoids interference of auto-flowing and explicit linebreaks
1504 suports = E.Element ('supports', attribute="new-system", element="print", type="yes", value="yes")
1505 addElem (encoding, suports, lev + 3)
1506 encodingDate = E.Element ('encoding-date')
1507 encodingDate.text = str (datetime.date.today ())
1508 addElem (encoding, encodingDate, lev + 3)
1509
1510 def mkDefaults (s, score, lev):
1511 if s.pageFmtCmd: s.pageFmtAbc = s.pageFmtCmd
1512 if not s.pageFmtAbc: return # do not output the defaults if none is desired
1513 space, h, w, l, r, t, b = s.pageFmtAbc
1514 mils = 4 * space # staff height in millimeters
1515 scale = 40. / mils # tenth's per millimeter
1516 dflts = E.Element ('defaults')
1517 addElem (score, dflts, lev)
1518 scaling = E.Element ('scaling')
1519 addElem (dflts, scaling, lev + 1)
1520 addElemT (scaling, 'millimeters', '%g' % mils, lev + 2)
1521 addElemT (scaling, 'tenths', '40', lev + 2)
1522 layout = E.Element ('page-layout')
1523 addElem (dflts, layout, lev + 1)
1524 addElemT (layout, 'page-height', '%g' % (h * scale), lev + 2)
1525 addElemT (layout, 'page-width', '%g' % (w * scale), lev + 2)
1526 margins = E.Element ('page-margins', type='both')
1527 addElem (layout, margins, lev + 2)
1528 addElemT (margins, 'left-margin', '%g' % (l * scale), lev + 3)
1529 addElemT (margins, 'right-margin', '%g' % (r * scale), lev + 3)
1530 addElemT (margins, 'top-margin', '%g' % (t * scale), lev + 3)
1531 addElemT (margins, 'bottom-margin', '%g' % (b * scale), lev + 3)
1532
1533 def mkCredits (s, score, lev):
1534 if not s.credits: return
1535 for ctype, ctext in s.credits.items ():
1536 credit = E.Element ('credit', page='1')
1537 addElemT (credit, 'credit-type', ctype, lev + 2)
1538 addElemT (credit, 'credit-words', ctext, lev + 2)
1539 addElem (score, credit, lev)
1540
1541 def parse (s, abc_string):
1542 abctext = abc_string if type (abc_string) == types.UnicodeType else decodeInput (abc_string)
1543 abctext = abctext.replace ('[I:staff ','[I:staff') # avoid false beam breaks
1544 s.reset ()
1545 header, voices = splitHeaderVoices (abctext)
1546 ps = []
1547 try:
1548 hs = abc_header.parseString (header) if header else ''
1549 for id, vce_lyr in voices: # vce_lyr = [voice-block] where voice-block = (measures, corresponding lyric lines)
1550 vcelyr = [] # list of measures where measure = list of elements (see syntax)
1551 prevLeftBar = None # previous voice ended with a left-bar symbol (double repeat)
1552 for voice, lyr in vce_lyr:
1553 vce = abc_voice.parseString (voice).asList ()
1554 if not vce: # empty voice, insert an inline field that will be rejected
1555 vce = [[pObj ('inline', ['I', 'empty voice'])]]
1556 if prevLeftBar:
1557 vce[0].insert (0, prevLeftBar) # insert at begin of first measure
1558 prevLeftBar = None
1559 if vce[-1] and vce[-1][-1].name == 'lbar': # last measure ends with an lbar
1560 prevLeftBar = vce[-1][-1]
1561 if len (vce) > 1: # vce should not become empty (-> exception when taking vcelyr [0][0])
1562 del vce[-1] # lbar was the only element in measure vce[-1]
1563 lyr = lyr.strip () # strip leading \n (because we split on '\nw:...')
1564 if lyr: # no lyrics for this measures-lyrics block
1565 lyr = lyr_block.parseString (lyr).asList ()
1566 xs = alignLyr (vce, lyr) # put all syllables into corresponding notes
1567 else: xs = vce
1568 vcelyr += xs
1569 elem1 = vcelyr [0][0] # the first element of the first measure
1570 if elem1.name == 'inline'and elem1.t[0] == 'V': # is a voice definition
1571 voicedef = elem1
1572 del vcelyr [0][0] # do not read voicedef twice
1573 else:
1574 voicedef = ''
1575 ps.append ((id, voicedef, vcelyr))
1576 except ParseException, err:
1577 if err.loc > 40: # limit length of error message, compatible with markInputline
1578 err.pstr = err.pstr [err.loc - 40: err.loc + 40]
1579 err.loc = 40
1580 xs = err.line[err.col-1:]
1581 try: info (err.line.encode ('utf-8'), warn=0) # err.line is a unicode string!!
1582 except: info (err.line.encode ('latin-1'), warn=0)
1583 info ((err.col-1) * '-' + '^', warn=0)
1584 if re.search (r'\[U:[XYZxyz]', xs):
1585 info ('Error: illegal user defined symbol: %s' % xs[1:], warn=0)
1586 elif re.search (r'\[[OAPZNGHRBDFSXTCIU]:', xs):
1587 info ('Error: header-only field %s appears after K:' % xs[1:], warn=0)
1588 else:
1589 info ('Syntax error at column %d' % err.col, warn=0)
1590 raise err
1591
1592 s.unitL = (1, 8)
1593 s.title = ''
1594 s.creator = {} # {creator type -> name string}
1595 s.credits = {} # {credit type -> string}
1596 score = E.Element ('score-partwise')
1597 attrmap = {'Div': str (s.divisions), 'K':'C treble', 'M':'4/4'}
1598 for res in hs:
1599 if res.name == 'field':
1600 s.doHeaderField (res, attrmap)
1601 else:
1602 info ('unexpected header item: %s' % res)
1603
1604 vdefs = s.voiceNamesAndMaps (ps)
1605 vdefs = s.parseStaveDef (vdefs)
1606
1607 lev = 0
1608 vids, parts, partAttr = [], [], {}
1609 for vid, _, vce in ps: # voice id, voice parse tree
1610 pname, psubnm, voicedef = vdefs [vid] # part name
1611 attrmap ['V'] = voicedef # abc text of first voice definition (after V:vid) or empty
1612 pid = 'P%s' % vid # let part id start with an alpha
1613 s.vid = vid # avoid parameter passing, needed in mkNote for instrument id
1614 part = s.mkPart (vce, pid, lev + 1, attrmap, s.gNstaves.get (vid, 0))
1615 if 'Q' in attrmap: del attrmap ['Q'] # header tempo only in first part
1616 parts.append (part)
1617 vids.append (vid)
1618 partAttr [vid] = (pname, psubnm, s.midprg)
1619 parts, vidsnew = mergeParts (parts, vids, s.staves) # merge parts into staves as indicated by %%score
1620 parts, _ = mergeParts (parts, vidsnew, s.grands, 1) # merge grand staves
1621
1622 s.mkIdentification (score, lev)
1623 s.mkDefaults (score, lev + 1)
1624 s.mkCredits (score, lev)
1625
1626 partlist, nInstrs = s.mkPartlist (vids, partAttr, lev + 1)
1627 addElem (score, partlist, lev + 1)
1628 for ip, part in enumerate (parts):
1629 if nInstrs [ip] < 2: # no instrument tag needed for one- or no-instrument parts
1630 removeElems (part, 'measure/note', 'instrument')
1631 addElem (score, part, lev + 1)
1632
1633 return score
1634
1635 def decodeInput (data_string):
1636 try: enc = 'utf-8'; unicode_string = data_string.decode (enc)
1637 except:
1638 try: enc = 'latin-1'; unicode_string = data_string.decode (enc)
1639 except: raise Exception ('data not encoded in utf-8 nor in latin-1')
1640 info ('decoded from %s' % enc)
1641 return unicode_string
1642
1643 xmlVersion = "<?xml version='1.0' encoding='utf-8'?>"
1644 def fixDoctype (elem, enc):
1645 xs = E.tostring (elem, encoding=enc)
1646 ys = xs.split ('\n')
1647 if enc == 'utf-8': ys.insert (0, xmlVersion) # crooked logic of ElementTree lib
1648 ys.insert (1, '<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">')
1649 return '\n'.join (ys)
1650
1651 def xml2mxl (pad, fnm, data): # write xml data to compressed .mxl file
1652 from zipfile import ZipFile, ZIP_DEFLATED
1653 fnmext = fnm + '.xml' # file name with extension, relative to the root within the archive
1654 outfile = os.path.join (pad, fnm + '.mxl')
1655 meta = '%s\n<container><rootfiles>\n' % xmlVersion
1656 meta += '<rootfile full-path="%s" media-type="application/vnd.recordare.musicxml+xml"/>\n' % fnmext
1657 meta += '</rootfiles></container>'
1658 f = ZipFile (outfile, 'w', ZIP_DEFLATED)
1659 f.writestr ('META-INF/container.xml', meta)
1660 f.writestr (fnmext, data)
1661 f.close ()
1662 info ('%s written' % outfile, warn=0)
1663
1664 def convert (pad, fnm, abc_string, mxl):
1665 # these globals should be initialised (as in the __main__ secion) before calling convert
1666 global mxm # optimisation 1: keep instance of MusicXml
1667 global abc_header, abc_voice, lyr_block, abc_scoredef # optimisation 2: keep computed grammars
1668 score = mxm.parse (abc_string)
1669 if pad:
1670 data = fixDoctype (score, 'utf-8')
1671 if not mxl or mxl in ['a', 'add']:
1672 outfnm = os.path.join (pad, fnm + '.xml')
1673 outfile = file (outfnm, 'wb')
1674 outfile.write (data)
1675 outfile.close ()
1676 info ('%s written' % outfnm, warn=0)
1677 if mxl: xml2mxl (pad, fnm, data) # also write a compressed version
1678 else:
1679 outfile = sys.stdout
1680 outfile.write (fixDoctype (score, 'utf-8'))
1681 outfile.write ('\n')
1682
1683 #----------------
1684 # Main Program
1685 #----------------
1686 if __name__ == '__main__':
1687 from optparse import OptionParser
1688 from glob import glob
1689 import time
1690 global mxm # keep instance of MusicXml
1691 global abc_header, abc_voice, lyr_block, abc_scoredef # keep computed grammars
1692 mxm = MusicXml ()
1693
1694 parser = OptionParser (usage='%prog [-h] [-r] [-m SKIP NUM] [-o DIR] [-p PFMT] [-z MODE] <file1> [<file2> ...]', version='version %d' % VERSION)
1695 parser.add_option ("-o", action="store", help="store xml files in DIR", default='', metavar='DIR')
1696 parser.add_option ("-m", action="store", help="skip SKIP tunes, then read at most NUM tunes", nargs=2, type='int', default=(0,1), metavar='SKIP NUM')
1697 parser.add_option ("-p", action="store", help="page formatting in PFMT", default='', metavar='PFMT')
1698 parser.add_option ("-z", "--mxl", dest="mxl", help="store as compressed mxl, MODE = a(dd) or r(eplace)", default='', metavar='MODE')
1699 parser.add_option ("-r", action="store_true", help="show whole measure rests in merged staffs", default=False)
1700 options, args = parser.parse_args ()
1701 if len (args) == 0: parser.error ('no input file given')
1702 pad = options.o
1703 if options.mxl and options.mxl not in ['a','add', 'r', 'replace']:
1704 parser.error ('MODE should be a(dd) or r(eplace), not: %s' % options.mxl)
1705 if pad:
1706 if not os.path.exists (pad): os.mkdir (pad)
1707 if not os.path.isdir (pad): parser.error ('%s is not a directory' % pad)
1708 if options.p: # set page formatting values
1709 try: # space, page-height, -width, margin-left, -right, -top, -bottom
1710 mxm.pageFmtCmd = map (float, options.p.split (','))
1711 if len (mxm.pageFmtCmd) != 7: raise Exception ('-p needs 7 values')
1712 except Exception, err: parser.error (err)
1713 mxm.gmwr = options.r # ugly: needs to be globally accessable
1714
1715 abc_header, abc_voice, lyr_block, abc_scoredef = abc_grammar () # compute grammar only once per file set
1716 fnmext_list = []
1717 for i in args: fnmext_list += glob (i)
1718 if not fnmext_list: parser.error ('none of the input files exist')
1719 t_start = time.time ()
1720 for X, fnmext in enumerate (fnmext_list):
1721 fnm, ext = os.path.splitext (fnmext)
1722 if ext.lower () not in ('.abc'):
1723 info ('skipped input file %s, it should have extension .abc' % fnmext)
1724 continue
1725 if os.path.isdir (fnmext):
1726 info ('skipped directory %s. Only files are accepted' % fnmext)
1727 continue
1728
1729 fobj = open (fnmext, 'rb')
1730 encoded_data = fobj.read ()
1731 fobj.close ()
1732 fragments = encoded_data.split ('X:')
1733 preamble = fragments [0] # tunes can be preceeded by formatting instructions
1734 tunes = fragments[1:]
1735 if not tunes and preamble: tunes, preamble = ['1\n' + preamble], '' # tune without X:
1736 skip, num = options.m # skip tunes, then read at most num tunes
1737 numtunes = min ([len (tunes), num]) # number of tunes to be converted
1738 for itune, tune in enumerate (tunes):
1739 if itune < skip: continue
1740 if itune >= skip + num: break
1741 tune = preamble + 'X:' + tune # restore preamble before each tune
1742 fnmNum = '%s%02d' % (fnm, itune + 1) if numtunes > 1 else fnm
1743 try: # convert string abctext -> file pad/fnmNum.xml
1744 convert (pad, fnmNum, tune, options.mxl)
1745 except ParseException, err: pass # output already printed
1746 except Exception, err: info ('an exception occurred.\n%s' % err)
1747 info ('done in %.2f secs' % (time.time () - t_start))