comparison abc2xml/abc2xml.py @ 1084:b1dbb76f4eb9 build-default-404

Update abc2xml to latest - Python3 friendly.
author Jim Hague <jim.hague@acm.org>
date Fri, 18 Nov 2022 21:42:55 +0000
parents 4fab69a1027d
children
comparison
equal deleted inserted replaced
1083:b66bc498220d 1084:b1dbb76f4eb9
1 #!/usr/bin/env python
1 # coding=latin-1 2 # coding=latin-1
2 ''' 3 '''
3 Copyright (C) 2012: Willem G. Vree 4 Copyright (C) 2012-2018: Willem G. Vree
4 Contributions: Nils Liberg, Nicolas Froment, Norman Schmidt, Reinier Maliepaard, Martin Tarenskeen 5 Contributions: Nils Liberg, Nicolas Froment, Norman Schmidt, Reinier Maliepaard, Martin Tarenskeen,
6 Paul Villiger, Alexander Scheutzow, Herbert Schneider, David Randolph, Michael Strasser
5 7
6 This program is free software; you can redistribute it and/or modify it under the terms of the 8 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 9 Lesser GNU General Public License as published by the Free Software Foundation;
8 the License, or (at your option) any later version.
9 10
10 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 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 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 See the Lesser GNU General Public License for more details. <http://www.gnu.org/licenses/lgpl.html>.
13 ''' 14 '''
14 15
16 from functools import reduce
15 from pyparsing import Word, OneOrMore, Optional, Literal, NotAny, MatchFirst 17 from pyparsing import Word, OneOrMore, Optional, Literal, NotAny, MatchFirst
16 from pyparsing import Group, oneOf, Suppress, ZeroOrMore, Combine, FollowedBy 18 from pyparsing import Group, oneOf, Suppress, ZeroOrMore, Combine, FollowedBy
17 from pyparsing import srange, CharsNotIn, StringEnd, LineEnd, White, Regex 19 from pyparsing import srange, CharsNotIn, StringEnd, LineEnd, White, Regex
18 from pyparsing import nums, alphas, alphanums, ParseException, Forward 20 from pyparsing import nums, alphas, alphanums, ParseException, Forward
19 try: import xml.etree.cElementTree as E 21 try: import xml.etree.cElementTree as E
20 except: import xml.etree.ElementTree as E 22 except: import xml.etree.ElementTree as E
21 import types, sys, os, re, datetime 23 import types, sys, os, re, datetime
22 24
23 VERSION = 58 25 VERSION = 238
26
27 python3 = sys.version_info[0] > 2
28 lmap = lambda f, xs: list (map (f, xs)) # eager map for python 3
29 if python3:
30 int_type = int
31 list_type = list
32 str_type = str
33 uni_type = str
34 stdin = sys.stdin.buffer # read binary!
35 else:
36 int_type = types.IntType
37 list_type = types.ListType
38 str_type = types.StringTypes
39 uni_type = types.UnicodeType
40 stdin = sys.stdin
24 41
25 def info (s, warn=1): 42 def info (s, warn=1):
26 x = (warn and '-- ' or '') + s 43 x = (warn and '-- ' or '') + s
27 try: sys.stderr.write (x + '\n') 44 try: sys.stderr.write (x + '\n')
28 except: sys.stderr.write (repr (x) + '\n') 45 except: sys.stderr.write (repr (x) + '\n')
29 46
30 def abc_grammar (): # header, voice and lyrics grammar for ABC 47 def abc_grammar (): # header, voice and lyrics grammar for ABC
31 b1 = Word (u"-,'<>\u2019#", exact=1) # catch misplaced chars in chords
32
33 #----------------------------------------------------------------- 48 #-----------------------------------------------------------------
34 # ABC header (fld_text elements are matched later with reg. epr's) 49 # expressions that catch and skip some syntax errors (see corresponding parse expressions)
35 #----------------------------------------------------------------- 50 #-----------------------------------------------------------------
51 b1 = Word (u"-,'<>\u2019#", exact=1) # catch misplaced chars in chords
52 b2 = Regex ('[^H-Wh-w~=]*') # same in user defined symbol definition
53 b3 = Regex ('[^=]*') # same, second part
54
55 #-----------------------------------------------------------------
56 # ABC header (field_str elements are matched later with reg. epr's)
57 #-----------------------------------------------------------------
36 58
37 number = Word (nums).setParseAction (lambda t: int (t[0])) 59 number = Word (nums).setParseAction (lambda t: int (t[0]))
38 field_str = Regex (r'(?:\\.|[^]\\])*') # match anything until end of field, skip escaped \] 60 field_str = Regex (r'[^]]*') # match anything until end of field
39 field_str.setParseAction (lambda t: t[0].strip ()) # and strip spacing 61 field_str.setParseAction (lambda t: t[0].strip ()) # and strip spacing
40 62
41 userdef_symbol = Word (srange ('[H-Wh-w~]'), exact=1) 63 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 64 fieldId = oneOf ('K L M Q P I T C O A Z N G H R B D F S E r Y') # info fields
43 X_field = Literal ('X') + Suppress (':') + number + field_str 65 X_field = Literal ('X') + Suppress (':') + field_str
44 U_field = Literal ('U') + Suppress (':') + userdef_symbol + Suppress ('=') + field_str 66 U_field = Literal ('U') + Suppress (':') + b2 + Optional (userdef_symbol, 'H') + b3 + Suppress ('=') + field_str
45 V_field = Literal ('V') + Suppress (':') + Word (alphanums + '_') + field_str 67 V_field = Literal ('V') + Suppress (':') + Word (alphanums + '_') + field_str
46 inf_fld = fieldId + Suppress (':') + field_str 68 inf_fld = fieldId + Suppress (':') + field_str
47 ifield = Suppress ('[') + (X_field | U_field | V_field | inf_fld) + Suppress (']') 69 ifield = Suppress ('[') + (X_field | U_field | V_field | inf_fld) + Suppress (']')
48 abc_header = OneOrMore (ifield) + StringEnd () 70 abc_header = OneOrMore (ifield) + StringEnd ()
49 71
56 simple_part = voiceId | voice_gr | Suppress ('|') 78 simple_part = voiceId | voice_gr | Suppress ('|')
57 grand_staff = oneOf ('{* {') + OneOrMore (simple_part) + Suppress ('}') 79 grand_staff = oneOf ('{* {') + OneOrMore (simple_part) + Suppress ('}')
58 part = Forward () 80 part = Forward ()
59 part_seq = OneOrMore (part | Suppress ('|')) 81 part_seq = OneOrMore (part | Suppress ('|'))
60 brace_gr = Suppress ('{') + part_seq + Suppress ('}') 82 brace_gr = Suppress ('{') + part_seq + Suppress ('}')
61 bracket_gr = Suppress ('[') + part_seq + Suppress ('\]') # closing brackets are escaped by splitHeaderVoices 83 bracket_gr = Suppress ('[') + part_seq + Suppress (']')
62 part << MatchFirst (simple_part | grand_staff | brace_gr | bracket_gr | Suppress ('|')) 84 part <<= MatchFirst (simple_part | grand_staff | brace_gr | bracket_gr | Suppress ('|'))
63 abc_scoredef = Suppress (oneOf ('staves score')) + OneOrMore (part) 85 abc_scoredef = Suppress (oneOf ('staves score')) + OneOrMore (part)
86
87 #----------------------------------------
88 # ABC lyric lines (white space sensitive)
89 #----------------------------------------
90
91 skip_note = oneOf ('* - ~')
92 extend_note = Literal ('_')
93 measure_end = Literal ('|')
94 syl_str = CharsNotIn ('*~-_| \t\n\\]')
95 syl_chars = Combine (OneOrMore (syl_str | Regex (r'\\.')))
96 white = Word (' \t')
97 syllable = Combine (Optional ('~') + syl_chars + ZeroOrMore (Literal ('~') + syl_chars)) + Optional ('-')
98 lyr_elem = (syllable | skip_note | extend_note | measure_end) + Optional (white).suppress ()
99 lyr_line = Optional (white).suppress () + ZeroOrMore (lyr_elem)
100
101 syllable.setParseAction (lambda t: pObj ('syl', t))
102 skip_note.setParseAction (lambda t: pObj ('skip', t))
103 extend_note.setParseAction (lambda t: pObj ('ext', t))
104 measure_end.setParseAction (lambda t: pObj ('sbar', t))
105 lyr_line_wsp = lyr_line.leaveWhitespace () # parse actions must be set before calling leaveWhitespace
64 106
65 #--------------------------------------------------------------------------------- 107 #---------------------------------------------------------------------------------
66 # ABC voice (not white space sensitive, beams detected in note/rest parse actions) 108 # ABC voice (not white space sensitive, beams detected in note/rest parse actions)
67 #--------------------------------------------------------------------------------- 109 #---------------------------------------------------------------------------------
68 110
69 inline_field = Suppress ('[') + (inf_fld | U_field | V_field) + Suppress (']') 111 inline_field = Suppress ('[') + (inf_fld | U_field | V_field) + Suppress (']')
112 lyr_fld = Suppress ('[') + Suppress ('w') + Suppress (':') + lyr_line_wsp + Suppress (']') # lyric line
113 lyr_blk = OneOrMore (lyr_fld) # verses
114 fld_or_lyr = inline_field | lyr_blk # inline field or block of lyric verses
70 115
71 note_length = Optional (number, 1) + Group (ZeroOrMore ('/')) + Optional (number, 2) 116 note_length = Optional (number, 1) + Group (ZeroOrMore ('/')) + Optional (number, 2)
72 octaveHigh = OneOrMore ("'").setParseAction (lambda t: len(t)) 117 octaveHigh = OneOrMore ("'").setParseAction (lambda t: len(t))
73 octaveLow = OneOrMore (',').setParseAction (lambda t: -len(t)) 118 octaveLow = OneOrMore (',').setParseAction (lambda t: -len(t))
74 octave = octaveHigh | octaveLow 119 octave = octaveHigh | octaveLow
75 120
76 basenote = oneOf ('C D E F G A B c d e f g a b y') # includes spacer for parse efficiency 121 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 ('^^ __ ^ _ =') 122 accidental = oneOf ('^^ __ ^ _ =')
78 rest_sym = oneOf ('x X z Z') 123 rest_sym = oneOf ('x X z Z')
79 slur_beg = oneOf ('( .(') + ~Word (nums) # no tuplet_start 124 slur_beg = oneOf ("( (, (' .( .(, .('") + ~Word (nums) # no tuplet_start
80 slur_ends = OneOrMore (oneOf (') .)')) 125 slur_ends = OneOrMore (oneOf (') .)'))
81 126
82 long_decoration = Combine (oneOf ('! +') + CharsNotIn ('!+ \n') + oneOf ('! +')) 127 long_decoration = Combine (oneOf ('! +') + CharsNotIn ('!+ \n') + oneOf ('! +'))
83 staccato = Literal ('.') + ~Literal ('|') # avoid dotted barline 128 staccato = Literal ('.') + ~Literal ('|') # avoid dotted barline
84 decoration = staccato | userdef_symbol | long_decoration | slur_beg 129 pizzicato = Literal ('!+!') # special case: plus sign is old style deco marker
130 decoration = slur_beg | staccato | userdef_symbol | long_decoration | pizzicato
85 decorations = OneOrMore (decoration) 131 decorations = OneOrMore (decoration)
86 staff_decos = decorations + ~oneOf (': | [|] []')
87 132
88 tie = oneOf ('.- -') 133 tie = oneOf ('.- -')
89 rest = Optional (accidental) + rest_sym + note_length 134 rest = Optional (accidental) + rest_sym + note_length
90 pitch = Optional (accidental) + basenote + Optional (octave, 0) 135 pitch = Optional (accidental) + basenote + Optional (octave, 0)
91 note = pitch + note_length + Optional (tie) + Optional (slur_ends) 136 note = pitch + note_length + Optional (tie) + Optional (slur_ends)
92 chord_note = Optional (decorations) + pitch + note_length + Optional (tie) + Optional (slur_ends) 137 dec_note = Optional (decorations) + pitch + note_length + Optional (tie) + Optional (slur_ends)
93 chord_notes = OneOrMore (chord_note | rest | b1) 138 chord_note = dec_note | rest | b1
94 grace_notes = Forward () 139 grace_notes = Forward ()
95 chord = Suppress ('[') + OneOrMore (chord_notes | grace_notes) + Suppress (']') + note_length + Optional (tie) + Optional (slur_ends) 140 chord = Suppress ('[') + OneOrMore (chord_note | grace_notes) + Suppress (']') + note_length + Optional (tie) + Optional (slur_ends)
96 stem = note | chord | rest 141 stem = note | chord | rest
97 142
98 broken = Combine (OneOrMore ('<') | OneOrMore ('>')) 143 broken = Combine (OneOrMore ('<') | OneOrMore ('>'))
99 144
100 tuplet_num = Suppress ('(') + number 145 tuplet_num = Suppress ('(') + number
102 tuplet_notes = Suppress (':') + Optional (number, 0) 147 tuplet_notes = Suppress (':') + Optional (number, 0)
103 tuplet_start = tuplet_num + Optional (tuplet_into + Optional (tuplet_notes)) 148 tuplet_start = tuplet_num + Optional (tuplet_into + Optional (tuplet_notes))
104 149
105 acciaccatura = Literal ('/') 150 acciaccatura = Literal ('/')
106 grace_stem = Optional (decorations) + stem 151 grace_stem = Optional (decorations) + stem
107 grace_notes << Group (Suppress ('{') + Optional (acciaccatura) + OneOrMore (grace_stem) + Suppress ('}')) 152 grace_notes <<= Group (Suppress ('{') + Optional (acciaccatura) + OneOrMore (grace_stem) + Suppress ('}'))
108 153
109 text_expression = Optional (oneOf ('^ _ < > @'), '^') + Optional (CharsNotIn ('"'), "") 154 text_expression = Optional (oneOf ('^ _ < > @'), '^') + Optional (CharsNotIn ('"'), "")
110 chord_accidental = oneOf ('# b =') 155 chord_accidental = oneOf ('# b =')
111 triad = oneOf ('ma Maj maj M mi min m aug dim o + -') 156 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') 157 seventh = oneOf ('7 ma7 Maj7 M7 maj7 mi7 min7 m7 dim7 o7 -7 aug7 +7 m7b5 mi7b5')
113 sixth = oneOf ('6 ma6 M6 m6 mi6') 158 sixth = oneOf ('6 ma6 M6 mi6 min6 m6')
114 ninth = oneOf ('9 ma9 M9 maj9 Maj9 mi9 m9') 159 ninth = oneOf ('9 ma9 M9 maj9 Maj9 mi9 min9 m9')
115 elevn = oneOf ('11 ma11 M11 maj11 Maj11 mi m11') 160 elevn = oneOf ('11 ma11 M11 maj11 Maj11 mi11 min11 m11')
161 thirt = oneOf ('13 ma13 M13 maj13 Maj13 mi13 min13 m13')
116 suspended = oneOf ('sus sus2 sus4') 162 suspended = oneOf ('sus sus2 sus4')
117 chord_degree = Combine (Optional (chord_accidental) + oneOf ('2 4 5 6 7 9 11 13')) 163 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) 164 chord_kind = Optional (seventh | sixth | ninth | elevn | thirt | triad) + Optional (suspended)
119 chord_root = oneOf ('C D E F G A B') + Optional (chord_accidental) 165 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 166 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) 167 chordsym = chord_root + chord_kind + ZeroOrMore (chord_degree) + Optional (Suppress ('/') + chord_bass)
122 chord_sym = chordsym + Optional (Literal ('(') + CharsNotIn (')') + Literal (')')).suppress () 168 chord_sym = chordsym + Optional (Literal ('(') + CharsNotIn (')') + Literal (')')).suppress ()
123 chord_or_text = Suppress ('"') + (chord_sym ^ text_expression) + Suppress ('"') 169 chord_or_text = Suppress ('"') + (chord_sym ^ text_expression) + Suppress ('"')
130 double_rep = Literal (':') + FollowedBy (':') # otherwise ambiguity with dashed barline 176 double_rep = Literal (':') + FollowedBy (':') # otherwise ambiguity with dashed barline
131 voice_overlay = Combine (OneOrMore ('&')) 177 voice_overlay = Combine (OneOrMore ('&'))
132 bare_volta = FollowedBy (Literal ('[') + Word (nums)) # no barline, but volta follows (volta is parsed in next measure) 178 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 ('| [|') 179 bar_left = (oneOf ('[|: |: [: :') + Optional (volta)) | Optional ('|').suppress () + volta | oneOf ('| [|')
134 bars = ZeroOrMore (':') + ZeroOrMore ('[') + OneOrMore (oneOf ('| ]')) 180 bars = ZeroOrMore (':') + ZeroOrMore ('[') + OneOrMore (oneOf ('| ]'))
135 bar_right = Optional (decorations) + (invisible_barline | double_rep | Combine (bars) | dashed_barline | voice_overlay | bare_volta) 181 bar_right = invisible_barline | double_rep | Combine (bars) | dashed_barline | voice_overlay | bare_volta
136 182
137 errors = ~bar_right + Optional (Word (' \n')) + CharsNotIn (':&|', exact=1) 183 errors = ~bar_right + Optional (Word (' \n')) + CharsNotIn (':&|', exact=1)
138 linebreak = Literal ('$') | ~decorations + Literal ('!') # no need for I:linebreak !!! 184 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 185 element = fld_or_lyr | broken | decorations | 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)) 186 measure = Group (ZeroOrMore (inline_field) + Optional (bar_left) + ZeroOrMore (element) + bar_right + Optional (linebreak) + Optional (lyr_blk))
141 noBarMeasure = Group (ZeroOrMore (inline_field) + Optional (bar_left) + OneOrMore (element) + Optional (linebreak)) 187 noBarMeasure = Group (ZeroOrMore (inline_field) + Optional (bar_left) + OneOrMore (element) + Optional (linebreak) + Optional (lyr_blk))
142 abc_voice = ZeroOrMore (measure) + Optional (noBarMeasure | Group (bar_left)) + ZeroOrMore (inline_field).suppress () + StringEnd () 188 abc_voice = ZeroOrMore (measure) + Optional (noBarMeasure | Group (bar_left)) + ZeroOrMore (inline_field).suppress () + StringEnd ()
143 189
144 #---------------------------------------- 190 #----------------------------------------
145 # ABC lyric lines (white space sensitive) 191 # I:percmap note [step] [midi] [note-head]
146 #---------------------------------------- 192 #----------------------------------------
147 193
148 skip_note = oneOf ('* - ~') 194 white2 = (white | StringEnd ()).suppress ()
149 extend_note = Literal ('_') 195 w3 = Optional (white2)
150 measure_end = Literal ('|') 196 percid = Word (alphanums + '-')
151 syl_chars = CharsNotIn ('*~-_| \t\n') 197 step = basenote + Optional (octave, 0)
152 white = Word (' \t') 198 pitchg = Group (Optional (accidental, '') + step + FollowedBy (white2))
153 syllable = Combine (Optional ('~') + syl_chars + ZeroOrMore (Literal ('~') + syl_chars)) + Optional ('-') 199 stepg = Group (step + FollowedBy (white2)) | Literal ('*')
154 lyr_elem = (syllable | skip_note | extend_note | measure_end) + Optional (white).suppress () 200 midi = (Literal ('*') | number | pitchg | percid)
155 lyr_head = (Literal ('w:') + Optional (white)).suppress () 201 nhd = Optional (Combine (percid + Optional ('+')), '')
156 lyr_line = Group (lyr_head + ZeroOrMore (lyr_elem) + LineEnd ().suppress ()) 202 perc_wsp = Literal ('percmap') + w3 + pitchg + w3 + Optional (stepg, '*') + w3 + Optional (midi, '*') + w3 + nhd
203 abc_percmap = perc_wsp.leaveWhitespace ()
157 204
158 #---------------------------------------------------------------- 205 #----------------------------------------------------------------
159 # Parse actions to convert all relevant results into an abstract 206 # Parse actions to convert all relevant results into an abstract
160 # syntax tree where all tree nodes are instances of pObj 207 # syntax tree where all tree nodes are instances of pObj
161 #---------------------------------------------------------------- 208 #----------------------------------------------------------------
173 chord_kind.setParseAction (lambda t: pObj ('kind', t)) 220 chord_kind.setParseAction (lambda t: pObj ('kind', t))
174 chord_degree.setParseAction (lambda t: pObj ('degree', t)) 221 chord_degree.setParseAction (lambda t: pObj ('degree', t))
175 chord_bass.setParseAction (lambda t: pObj ('bass', t)) 222 chord_bass.setParseAction (lambda t: pObj ('bass', t))
176 text_expression.setParseAction (lambda t: pObj ('text', t)) 223 text_expression.setParseAction (lambda t: pObj ('text', t))
177 inline_field.setParseAction (lambda t: pObj ('inline', t)) 224 inline_field.setParseAction (lambda t: pObj ('inline', t))
178 grace_notes.setParseAction (doGrace) # (lambda t: pObj ('grace', t)) 225 lyr_fld.setParseAction (lambda t: pObj ('lyr_fld', t, 1))
226 lyr_blk.setParseAction (lambda t: pObj ('lyr_blk', t, 1)) # 1 = keep ordered list of lyric lines
227 grace_notes.setParseAction (doGrace)
179 acciaccatura.setParseAction (lambda t: pObj ('accia', t)) 228 acciaccatura.setParseAction (lambda t: pObj ('accia', t))
180 note.setParseAction (noteActn) 229 note.setParseAction (noteActn)
181 chord_note.setParseAction (noteActn)
182 rest.setParseAction (restActn) 230 rest.setParseAction (restActn)
183 decorations.setParseAction (lambda t: pObj ('deco', t)) 231 decorations.setParseAction (lambda t: pObj ('deco', t))
232 pizzicato.setParseAction (lambda t: ['!plus!']) # translate !+!
184 slur_ends.setParseAction (lambda t: pObj ('slurs', t)) 233 slur_ends.setParseAction (lambda t: pObj ('slurs', t))
185 chord.setParseAction (lambda t: pObj ('chord', t)) 234 chord.setParseAction (lambda t: pObj ('chord', t, 1))
235 dec_note.setParseAction (noteActn)
186 tie.setParseAction (lambda t: pObj ('tie', t)) 236 tie.setParseAction (lambda t: pObj ('tie', t))
187 pitch.setParseAction (lambda t: pObj ('pitch', t)) 237 pitch.setParseAction (lambda t: pObj ('pitch', t))
188 bare_volta.setParseAction (lambda t: ['|']) # return barline that user forgot 238 bare_volta.setParseAction (lambda t: ['|']) # return barline that user forgot
189 dashed_barline.setParseAction (lambda t: ['.|']) 239 dashed_barline.setParseAction (lambda t: ['.|'])
190 bar_right.setParseAction (lambda t: pObj ('rbar', t)) 240 bar_right.setParseAction (lambda t: pObj ('rbar', t))
192 broken.setParseAction (lambda t: pObj ('broken', t)) 242 broken.setParseAction (lambda t: pObj ('broken', t))
193 tuplet_start.setParseAction (lambda t: pObj ('tup', t)) 243 tuplet_start.setParseAction (lambda t: pObj ('tup', t))
194 linebreak.setParseAction (lambda t: pObj ('linebrk', t)) 244 linebreak.setParseAction (lambda t: pObj ('linebrk', t))
195 measure.setParseAction (doMaat) 245 measure.setParseAction (doMaat)
196 noBarMeasure.setParseAction (doMaat) 246 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) 247 b1.setParseAction (errorWarn)
248 b2.setParseAction (errorWarn)
249 b3.setParseAction (errorWarn)
202 errors.setParseAction (errorWarn) 250 errors.setParseAction (errorWarn)
203 lyr_block = OneOrMore (lyr_line).leaveWhitespace () # after leaveWhiteSpace no more parse actions can be set!! 251
204 252 return abc_header, abc_voice, abc_scoredef, abc_percmap
205 return abc_header, abc_voice, lyr_block, abc_scoredef
206 253
207 class pObj (object): # every relevant parse result is converted into a pObj 254 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 255 def __init__ (s, name, t, seq=0): # t = list of nested parse results
209 s.name = name # name uniqueliy identifies this pObj 256 s.name = name # name uniqueliy identifies this pObj
210 rest = [] # collect parse results that are not a pObj 257 rest = [] # collect parse results that are not a pObj
226 if nm.startswith ('_'): continue # skip build in attributes 273 if nm.startswith ('_'): continue # skip build in attributes
227 elif nm == 'name': continue # redundant 274 elif nm == 'name': continue # redundant
228 else: 275 else:
229 x = getattr (s, nm) 276 x = getattr (s, nm)
230 if not x: continue # s.t may be empty (list of non-pObj's) 277 if not x: continue # s.t may be empty (list of non-pObj's)
231 if type (x) == types.ListType: r.extend (x) 278 if type (x) == list_type: r.extend (x)
232 else: r.append (x) 279 else: r.append (x)
233 xs = [] 280 xs = []
234 for x in r: # recursively call __repr__ and convert all strings to latin-1 281 for x in r: # recursively call __repr__ and convert all strings to latin-1
235 if isinstance (x, types.StringTypes): 282 if isinstance (x, str_type): xs.append (x) # string -> no recursion
236 try: xs.append (x.encode ('latin-1')) 283 else: xs.append (repr (x)) # pObj -> recursive call
237 except: xs.append (repr (x)) # string -> no recursion
238 else: xs.append (repr (x)) # pObj -> recursive call
239 return '(' + s.name + ' ' +','.join (xs) + ')' 284 return '(' + s.name + ' ' +','.join (xs) + ')'
240 285
241 global prevloc # global to remember previous match position of a note/rest 286 global prevloc # global to remember previous match position of a note/rest
242 prevloc = 0 287 prevloc = 0
243 def detectBeamBreak (line, loc, t): 288 def detectBeamBreak (line, loc, t):
255 300
256 def restActn (line, loc, t): # detect beambreak between previous and current note/rest 301 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 302 detectBeamBreak (line, loc, t) # adds beambreak to parse result t as side effect
258 return pObj ('rest', t) 303 return pObj ('rest', t)
259 304
260 def errorWarn (line, loc, t): # warning for misplaced symbols and skip them 305 def errorWarn (line, loc, t): # warning for misplaced symbols and skip them
306 if not t[0]: return [] # only warn if catched string not empty
261 info ('**misplaced symbol: %s' % t[0], warn=0) 307 info ('**misplaced symbol: %s' % t[0], warn=0)
262 lineCopy = line [:] 308 lineCopy = line [:]
263 if loc > 40: 309 if loc > 40:
264 lineCopy = line [loc - 40: loc + 40] 310 lineCopy = line [loc - 40: loc + 40]
265 loc = 40 311 loc = 40
272 #------------------------------------------------------------- 318 #-------------------------------------------------------------
273 319
274 def simplify (a, b): # divide a and b by their greatest common divisor 320 def simplify (a, b): # divide a and b by their greatest common divisor
275 x, y = a, b 321 x, y = a, b
276 while b: a, b = b, a % b 322 while b: a, b = b, a % b
277 return x / a, y / a 323 return x // a, y // a
278 324
279 def doBroken (prev, brk, x): 325 def doBroken (prev, brk, x):
280 if not prev: info ('error in broken rhythm: %s' % x); return # no changes 326 if not prev: info ('error in broken rhythm: %s' % x); return # no changes
281 nom1, den1 = prev.dur.t # duration of first note/chord 327 nom1, den1 = prev.dur.t # duration of first note/chord
282 nom2, den2 = x.dur.t # duration of second note/chord 328 nom2, den2 = x.dur.t # duration of second note/chord
310 elif x.name == 'broken': 356 elif x.name == 'broken':
311 brk = x.t[0] # remember the broken symbol (=string) 357 brk = x.t[0] # remember the broken symbol (=string)
312 remove.insert (0, i) # and its index, highest index first 358 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 359 for i in remove: del t[i] # delete broken symbols from high to low
314 360
361 def ptc2midi (n): # convert parsed pitch attribute to a midi number
362 pt = getattr (n, 'pitch', '')
363 if pt:
364 p = pt.t
365 if len (p) == 3: acc, step, oct = p
366 else: acc = ''; step, oct = p
367 nUp = step.upper ()
368 oct = (4 if nUp == step else 5) + int (oct)
369 midi = oct * 12 + [0,2,4,5,7,9,11]['CDEFGAB'.index (nUp)] + {'^':1,'_':-1}.get (acc, 0) + 12
370 else: midi = 130 # all non pitch objects first
371 return midi
372
315 def convertChord (t): # convert chord to sequence of notes in musicXml-style 373 def convertChord (t): # convert chord to sequence of notes in musicXml-style
316 ins = [] 374 ins = []
317 for i, x in enumerate (t): 375 for i, x in enumerate (t):
318 if x.name == 'chord': 376 if x.name == 'chord':
319 if hasattr (x, 'rest') and not hasattr (x, 'note'): # chords containing only rests 377 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 378 if type (x.rest) == list_type: x.rest = x.rest[0] # more rests == one rest
321 ins.insert (0, (i, [x.rest])) # just output a single rest, no chord 379 ins.insert (0, (i, [x.rest])) # just output a single rest, no chord
322 continue 380 continue
323 num1, den1 = x.dur.t # chord duration 381 num1, den1 = x.dur.t # chord duration
324 tie = getattr (x, 'tie', None) # chord tie 382 tie = getattr (x, 'tie', None) # chord tie
325 slurs = getattr (x, 'slurs', []) # slur endings 383 slurs = getattr (x, 'slurs', []) # slur endings
326 deco = getattr (x, 'deco', []) # chord decorations 384 if type (x.note) != list_type: x.note = [x.note] # when chord has only one note ...
327 if type (x.note) != types.ListType: x.note = [x.note] # when chord has only one note ... 385 elms = []; j = 0 # sort chord notes, highest first
328 for j, nt in enumerate (x.note): # all notes of the chord 386 nss = sorted (x.objs, key = ptc2midi, reverse=1) if mxm.orderChords else x.objs
329 num2, den2 = nt.dur.t # note duration * chord duration 387 for nt in nss: # all chord elements (note | decorations | rest | grace note)
330 nt.dur.t = simplify (num1 * num2, den1 * den2) 388 if nt.name == 'note':
331 if tie: nt.tie = tie # tie on all chord notes 389 num2, den2 = nt.dur.t # note duration * chord duration
332 if j == 0 and deco: nt.deco = deco # decorations only on first chord note 390 nt.dur.t = simplify (num1 * num2, den1 * den2)
333 if j == 0 and slurs: nt.slurs = slurs # slur endings only on first chord note 391 if tie: nt.tie = tie # tie on all chord notes
334 if j > 0: nt.chord = pObj ('chord', [1]) # label all but first as chord notes 392 if j == 0 and slurs: nt.slurs = slurs # slur endings only on first chord note
335 else: # remember all pitches of the chord in the first note 393 if j > 0: nt.chord = pObj ('chord', [1]) # label all but first as chord notes
336 pitches = [n.pitch for n in x.note] # to implement conversion of erroneous ties to slurs 394 else: # remember all pitches of the chord in the first note
337 nt.pitches = pObj ('pitches', pitches) 395 pitches = [n.pitch for n in x.note] # to implement conversion of erroneous ties to slurs
338 ins.insert (0, (i, x.note)) # high index first 396 nt.pitches = pObj ('pitches', pitches)
397 j += 1
398 if nt.name not in ['dur','tie','slurs','rest']: elms.append (nt)
399 ins.insert (0, (i, elms)) # chord position, [note|decotation|grace note]
339 for i, notes in ins: # insert from high to low 400 for i, notes in ins: # insert from high to low
340 for nt in reversed (notes): 401 for nt in reversed (notes):
341 t.insert (i+1, nt) # insert chord notes after chord 402 t.insert (i+1, nt) # insert chord notes after chord
342 del t[i] # remove chord itself 403 del t[i] # remove chord itself
343 404
353 #-------------------- 414 #--------------------
354 # musicXML generation 415 # musicXML generation
355 #---------------------------------- 416 #----------------------------------
356 417
357 def compChordTab (): # avoid some typing work: returns mapping constant {ABC chordsyms -> musicXML kind} 418 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 () 419 maj, min, aug, dim, dom, ch7, ch6, ch9, ch11, ch13, hd = 'major minor augmented diminished dominant -seventh -sixth -ninth -11th -13th 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]) 420 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 (), 421 seventh = zip ('7 ma7 Maj7 M7 maj7 mi7 min7 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]) 422 [dom, maj+ch7, maj+ch7, maj+ch7, maj+ch7, min+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]) 423 sixth = zip ('6 ma6 M6 mi6 min6 m6'.split (), [maj+ch6, maj+ch6, maj+ch6, min+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]) 424 ninth = zip ('9 ma9 M9 maj9 Maj9 mi9 min9 m9'.split (), [dom+ch9, maj+ch9, maj+ch9, maj+ch9, maj+ch9, min+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]) 425 elevn = zip ('11 ma11 M11 maj11 Maj11 mi11 min11 m11'.split (), [dom+ch11, maj+ch11, maj+ch11, maj+ch11, maj+ch11, min+ch11, min+ch11, min+ch11])
365 return dict (triad + seventh + sixth + ninth + elevn) 426 thirt = zip ('13 ma13 M13 maj13 Maj13 mi13 min13 m13'.split (), [dom+ch13, maj+ch13, maj+ch13, maj+ch13, maj+ch13, min+ch13, min+ch13, min+ch13])
427 sus = zip ('sus sus4 sus2'.split (), ['suspended-fourth', 'suspended-fourth', 'suspended-second'])
428 return dict (list (triad) + list (seventh) + list (sixth) + list (ninth) + list (elevn) + list (thirt) + list (sus))
366 429
367 def addElem (parent, child, level): 430 def addElem (parent, child, level):
368 indent = 2 431 indent = 2
369 chldrn = parent.getchildren () 432 chldrn = list (parent)
370 if chldrn: 433 if chldrn:
371 chldrn[-1].tail += indent * ' ' 434 chldrn[-1].tail += indent * ' '
372 else: 435 else:
373 parent.text = '\n' + level * indent * ' ' 436 parent.text = '\n' + level * indent * ' '
374 parent.append (child) 437 parent.append (child)
376 439
377 def addElemT (parent, tag, text, level): 440 def addElemT (parent, tag, text, level):
378 e = E.Element (tag) 441 e = E.Element (tag)
379 e.text = text 442 e.text = text
380 addElem (parent, e, level) 443 addElem (parent, e, level)
444 return e
381 445
382 def mkTmod (tmnum, tmden, lev): 446 def mkTmod (tmnum, tmden, lev):
383 tmod = E.Element ('time-modification') 447 tmod = E.Element ('time-modification')
384 addElemT (tmod, 'actual-notes', str (tmnum), lev + 1) 448 addElemT (tmod, 'actual-notes', str (tmnum), lev + 1)
385 addElemT (tmod, 'normal-notes', str (tmden), lev + 1) 449 addElemT (tmod, 'normal-notes', str (tmden), lev + 1)
386 return tmod 450 return tmod
387 451
388 def addDirection (parent, elem, lev, gstaff, subelms=[], placement='below', cue_on=0): 452 def addDirection (parent, elems, lev, gstaff, subelms=[], placement='below', cue_on=0):
389 dir = E.Element ('direction', placement=placement) 453 dir = E.Element ('direction', placement=placement)
390 addElem (parent, dir, lev) 454 addElem (parent, dir, lev)
391 typ = E.Element ('direction-type') 455 if type (elems) != list_type: elems = [(elems, subelms)] # ugly hack to provide for multiple direction types
392 addElem (dir, typ, lev + 1) 456 for elem, subelms in elems: # add direction types
393 addElem (typ, elem, lev + 2) 457 typ = E.Element ('direction-type')
394 for subel in subelms: addElem (elem, subel, lev + 3) 458 addElem (dir, typ, lev + 1)
459 addElem (typ, elem, lev + 2)
460 for subel in subelms: addElem (elem, subel, lev + 3)
395 if cue_on: addElem (dir, E.Element ('level', size='cue'), lev + 1) 461 if cue_on: addElem (dir, E.Element ('level', size='cue'), lev + 1)
396 if gstaff: addElemT (dir, 'staff', str (gstaff), lev + 1) 462 if gstaff: addElemT (dir, 'staff', str (gstaff), lev + 1)
397 return dir 463 return dir
398 464
399 def removeElems (root_elem, parent_str, elem_str): 465 def removeElems (root_elem, parent_str, elem_str):
403 469
404 def alignLyr (vce, lyrs): 470 def alignLyr (vce, lyrs):
405 empty_el = pObj ('leeg', '*') 471 empty_el = pObj ('leeg', '*')
406 for k, lyr in enumerate (lyrs): # lyr = one full line of lyrics 472 for k, lyr in enumerate (lyrs): # lyr = one full line of lyrics
407 i = 0 # syl counter 473 i = 0 # syl counter
408 for msre in vce: # reiterate the voice block for each lyrics line 474 for elem in vce: # reiterate the voice block for each lyrics line
409 for elem in msre: 475 if elem.name == 'note' and not (hasattr (elem, 'chord') or hasattr (elem, 'grace')):
410 if elem.name == 'note' and not (hasattr (elem, 'chord') or hasattr (elem, 'grace')): 476 if i >= len (lyr): lr = empty_el
411 if i >= len (lyr): lr = empty_el 477 else: lr = lyr [i]
412 else: lr = lyr [i] 478 lr.t[0] = lr.t[0].replace ('%5d',']')
413 elem.objs.append (lr) 479 elem.objs.append (lr)
414 if lr.name != 'sbar': i += 1 480 if lr.name != 'sbar': i += 1
415 if i < len (lyr) and lyr[i].name == 'sbar': i += 1 481 if elem.name == 'rbar' and i < len (lyr) and lyr[i].name == 'sbar': i += 1
416 return vce 482 return vce
417 483
418 slur_move = re.compile (r'(?<![!+])([}><][<>]?)(\)+)') # (?<!...) means: not preceeded by ... 484 slur_move = re.compile (r'(?<![!+])([}><][<>]?)(\)+)') # (?<!...) means: not preceeded by ...
419 mm_rest = re.compile (r'([XZ])(\d+)') 485 mm_rest = re.compile (r'([XZ])(\d+)')
420 bar_space = re.compile (r'([:|][ |\[\]]+[:|])') # barlines with spaces 486 bar_space = re.compile (r'([:|][ |\[\]]+[:|])') # barlines with spaces
427 x = mm_rest.sub (f, x) 493 x = mm_rest.sub (f, x)
428 x = bar_space.sub (g, x) 494 x = bar_space.sub (g, x)
429 return slur_move.sub (r'\2\1', x) 495 return slur_move.sub (r'\2\1', x)
430 496
431 def splitHeaderVoices (abctext): 497 def splitHeaderVoices (abctext):
498 escField = lambda x: '[' + x.replace (']',r'%5d') + ']' # hope nobody uses %5d in a field
432 r1 = re.compile (r'%.*$') # comments 499 r1 = re.compile (r'%.*$') # comments
433 r2 = re.compile (r'^[A-Z]:.*$') # information field 500 r2 = re.compile (r'^([A-Zw]:.*$)|\[[A-Zw]:[^]]*]$') # information field, including lyrics
434 r3 = re.compile (r'^%%(?=[^%])') # directive: ^%% folowed by not a % 501 r3 = re.compile (r'^%%(?=[^%])') # directive: ^%% folowed by not a %
435 xs, nx = [], 0 502 xs, nx, mcont, fcont = [], 0, 0, 0 # result lines, X-encountered, music continuation, field continuation
503 mln = fln = '' # music line, field line
436 for x in abctext.splitlines (): 504 for x in abctext.splitlines ():
437 x = x.strip () 505 x = x.strip ()
438 if not x and nx == 1: break # end of tune 506 if not x and nx == 1: break # end of tune (empty line)
507 if x.startswith ('X:'):
508 if nx == 1: break # second tune starts without an empty line !!
509 nx = 1 # start first tune
439 x = r3.sub ('I:', x) # replace %% -> I: 510 x = r3.sub ('I:', x) # replace %% -> I:
440 x2 = r1.sub ('', x) # remove comment 511 x2 = r1.sub ('', x) # remove comment
441 while x2.endswith ('*'): x2 = x2[:-1] # remove old syntax for right adjusting 512 while x2.endswith ('*') and not (x2.startswith ('w:') or x2.startswith ('+:') or 'percmap' in x2):
513 x2 = x2[:-1] # remove old syntax for right adjusting
442 if not x2: continue # empty line 514 if not x2: continue # empty line
443 if x2[:2] == 'W:': continue # skip W: lyrics 515 if x2[:2] == 'W:':
444 if x2[:2] == 'w:' and xs[-1][-1] == '\\': 516 field = x2 [2:].strip ()
445 xs[-1] = xs[-1][:-1] # ignore line continuation before lyrics line 517 ftype = mxm.metaMap.get ('W', 'W') # respect the (user defined --meta) mapping of various ABC fields to XML meta data types
446 ro = r2.match (x2) 518 c = mxm.metadata.get (ftype, '')
519 mxm.metadata [ftype] = c + '\n' + field if c else field # concatenate multiple info fields with new line as separator
520 continue # skip W: lyrics
521 if x2[:2] == '+:': # field continuation
522 fln += x2[2:]
523 continue
524 ro = r2.match (x2) # single field on a line
447 if ro: # field -> inline_field, escape all ']' 525 if ro: # field -> inline_field, escape all ']'
448 if x2[-1] == '\\': x2 = x2[:-1] # ignore continuation after field line 526 if fcont: # old style \-info-continuation active
449 x2 = '[' + x2.replace (']',r'\]') + ']' 527 fcont = x2 [-1] == '\\' # possible further \-info-continuation
450 if x2[:2] == '+:': # new style continuation 528 fln += re.sub (r'^.:(.*?)\\*$', r'\1', x2) # add continuation, remove .: and \
451 xs[-1] += x2[2:] 529 continue
452 elif xs and xs[-1][-1] == '\\': # old style continuation 530 if fln: mln += escField (fln)
453 xs[-1] = xs[-1][:-1] + x2 531 if x2.startswith ('['): x2 = x2.strip ('[]')
454 else: # skip lines (except I:) until first X: 532 fcont = x2 [-1] == '\\' # first encounter of old style \-info-continuation
455 if x.startswith ('X:'): 533 fln = x2.rstrip ('\\') # remove continuation from field and inline brackets
456 if nx == 1: break # second tune starts without an empty line !! 534 continue
457 nx = 1 # start of first tune 535 if nx == 1: # x2 is a new music line
458 if nx == 1 or x.startswith ('I:'): 536 fcont = 0 # stop \-continuations (-> only adjacent \-info-continuations are joined)
459 xs.append (x2) 537 if fln:
460 if xs and xs[-1][-1] == '\\': # nothing left to continue with, remove last continuation 538 mln += escField (fln)
461 xs[-1] = xs[-1][:-1] 539 fln = ''
462 540 if mcont:
463 r1 = re.compile (r'\[[A-Z]:(\\.|[^]\\])*\]') # inline field with escaped ']' 541 mcont = x2 [-1] == '\\'
464 r2 = re.compile (r'\[K:') # start of K: field 542 mln += x2.rstrip ('\\')
465 r3 = re.compile (r'\[V:|\[I:MIDI') # start of V: field or midi field 543 else:
466 fields, voices, b = [], [], 0 544 if mln: xs.append (mln); mln = ''
467 for i, x in enumerate (xs): 545 mcont = x2 [-1] == '\\'
468 n = len (r1.sub ('', x)) # remove all inline fields 546 mln = x2.rstrip ('\\')
469 if n > 0: b = 1; break # real abc present -> end of header 547 if not mcont: xs.append (mln); mln = ''
470 if r2.search (x): # start of K: field 548 if fln: mln += escField (fln)
471 fields.append (x) 549 if mln: xs.append (mln)
472 i += 1; b = 1 550
473 break # first K: field -> end of header 551 hs = re.split (r'(\[K:[^]]*\])', xs [0]) # look for end of header K:
474 if r3.search (x): # start of V: field 552 if len (hs) == 1: header = hs[0]; xs [0] = '' # no K: present
475 voices.append (x) 553 else: header = hs [0] + hs [1]; xs [0] = ''.join (hs[2:]) # h[1] is the first K:
476 else: 554 abctext = '\n'.join (xs) # the rest is body text
477 fields.append (x) 555 hfs, vfs = [], []
478 if b: voices += xs[i:] 556 for x in header[1:-1].split (']['):
479 else: voices += [] # tune has only header fields 557 if x[0] == 'V': vfs.append (x) # filter voice- and midi-definitions
480 header = '\n'.join (fields) 558 elif x[:6] == 'I:MIDI': vfs.append (x) # from the header to vfs
481 abctext = '\n'.join (voices) 559 elif x[:9] == 'I:percmap': vfs.append (x) # and also percmap
560 else: hfs.append (x) # all other fields stay in header
561 header = '[' + ']['.join (hfs) + ']' # restore the header
562 abctext = ('[' + ']['.join (vfs) + ']' if vfs else '') + abctext # prepend voice/midi from header before abctext
482 563
483 xs = abctext.split ('[V:') 564 xs = abctext.split ('[V:')
484 if len (xs) == 1: abctext = '[V:1]' + abctext # abc has no voice defs at all 565 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 566 elif re.sub (r'\[[A-Z]:[^]]*\]', '', xs[0]).strip (): # remove inline fields from starting text, if any
486 abctext = '[V:1]' + abctext # abc with voices has no V: at start 567 abctext = '[V:1]' + abctext # abc with voices has no V: at start
487 568
488 r1 = re.compile (r'\[V:\s*(\S*)[ \]]') # get voice id from V: field (skip spaces betwee V: and ID) 569 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]} 570 vmap = {} # {voice id -> [voice abc string]}
490 vorder = {} # mark document order of voices 571 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) 572 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 ...')) 573 if len (xs) == 1: raise ValueError ('bugs ...')
493 else: 574 else:
494 header += xs[0] # xs[0] = text between K: and first V:, normally empty, but we put it in the header 575 pm = re.findall (r'\[P:.\]', xs[0]) # all P:-marks after K: but before first V:
576 if pm: xs[2] = ''.join (pm) + xs[2] # prepend P:-marks to the text of the first voice
577 header += re.sub (r'\[P:.\]', '', xs[0]) # clear all P:-marks from text between K: and first V: and put text in the header
495 i = 1 578 i = 1
496 while i < len (xs): # xs = ['', V-field, voice abc, V-field, voice abc, ...] 579 while i < len (xs): # xs = ['', V-field, voice abc, V-field, voice abc, ...]
497 vce, abc = xs[i:i+2] 580 vce, abc = xs[i:i+2]
498 id = r1.search (vce).group (1) # get voice ID from V-field 581 id = r1.search (vce).group (1) # get voice ID from V-field
582 if not id: id, vce = '1', '[V:1]' # voice def has no ID
499 vmap[id] = vmap.get (id, []) + [vce, abc] # collect abc-text for each voice id (include V-fields) 583 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 584 if id not in vorder: vorder [id] = i # store document order of first occurrence of voice id
501 i += 2 585 i += 2
502 voices = [] 586 voices = []
503 ixs = sorted ([(i, id) for id, i in vorder.items ()]) # restore document order of voices 587 ixs = sorted ([(i, id) for id, i in vorder.items ()]) # restore document order of voices
504 for i, id in ixs: 588 for i, id in ixs:
505 voice = ''.join (vmap [id]) # all abc of one voice 589 voice = ''.join (vmap [id]) # all abc of one voice
506 xs = re.split (r'((?:\nw:[^\n]*)+)', voice) # split voice into voice-lyrics blocks 590 voice = fixSlurs (voice) # put slurs right after the notes
507 if len (xs) == 1: # no lyrics 591 voices.append ((id, voice))
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 592 return header, voices
519 593
520 def mergeMeasure (m1, m2, slur_offset, voice_offset, is_grand=0): 594 def mergeMeasure (m1, m2, slur_offset, voice_offset, rOpt, is_grand=0, is_overlay=0):
521 slurs = m2.findall ('note/notations/slur') 595 slurs = m2.findall ('note/notations/slur')
522 for slr in slurs: 596 for slr in slurs:
523 slrnum = int (slr.get ('number')) + slur_offset 597 slrnum = int (slr.get ('number')) + slur_offset
524 slr.set ('number', str (slrnum)) # make unique slurnums in m2 598 slr.set ('number', str (slrnum)) # make unique slurnums in m2
525 vs = m2.findall ('note/voice') # set all voice number elements in m2 599 vs = m2.findall ('note/voice') # set all voice number elements in m2
532 el.set ('number', str (n + lnum_max)) 606 el.set ('number', str (n + lnum_max))
533 ns = m1.findall ('note') # determine the total duration of m1, subtract all backups 607 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 608 dur1 = sum (int (n.find ('duration').text) for n in ns
535 if n.find ('grace') == None and n.find ('chord') == None) 609 if n.find ('grace') == None and n.find ('chord') == None)
536 dur1 -= sum (int (b.text) for b in m1.findall ('backup/duration')) 610 dur1 -= sum (int (b.text) for b in m1.findall ('backup/duration'))
537 nns, es = 0, [] # nns = number of real notes in m2 611 repbar, nns, es = 0, 0, [] # nns = number of real notes in m2
538 for e in m2.getchildren (): # scan all elements of m2 612 for e in list (m2): # scan all elements of m2
539 if e.tag == 'attributes': 613 if e.tag == 'attributes':
540 if not is_grand: continue # no attribute merging for normal voices 614 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 615 else: nns += 1 # but we do merge (clef) attributes for a grand staff
542 if e.tag == 'print': continue 616 if e.tag == 'print': continue
543 if e.tag == 'note' and (mxm.gmwr or e.find ('rest') == None): nns += 1 617 if e.tag == 'note' and (rOpt or e.find ('rest') == None): nns += 1
618 if e.tag == 'barline' and e.find ('repeat') != None: repbar = e;
544 es.append (e) # buffer elements to be merged 619 es.append (e) # buffer elements to be merged
545 if nns > 0: # only merge if m2 contains any real notes 620 if nns > 0: # only merge if m2 contains any real notes
546 if dur1 > 0: # only insert backup if duration of m1 > 0 621 if dur1 > 0: # only insert backup if duration of m1 > 0
547 b = E.Element ('backup') 622 b = E.Element ('backup')
548 addElem (m1, b, level=3) 623 addElem (m1, b, level=3)
549 addElemT (b, 'duration', str (dur1), level=4) 624 addElemT (b, 'duration', str (dur1), level=4)
550 for e in es: addElem (m1, e, level=3) # merge buffered elements of m2 625 for e in es: addElem (m1, e, level=3) # merge buffered elements of m2
551 626 elif is_overlay and repbar: addElem (m1, repbar, level=3) # merge repeat in empty overlay
552 def mergePartList (parts, is_grand=0): # merge parts, make grand staff when is_grand true 627
628 def mergePartList (parts, rOpt, is_grand=0): # merge parts, make grand staff when is_grand true
553 629
554 def delAttrs (part): # for the time being we only keep clef attributes 630 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')] 631 xs = [(m, e) for m in part.findall ('measure') for e in m.findall ('attributes')]
556 for m, e in xs: 632 for m, e in xs:
557 for c in e.getchildren (): 633 for c in list (e):
558 if c.tag == 'clef': continue # keep clef attribute 634 if c.tag == 'clef': continue # keep clef attribute
635 if c.tag == 'staff-details': continue # keep staff-details attribute
559 e.remove (c) # delete all other attrinutes for higher staff numbers 636 e.remove (c) # delete all other attrinutes for higher staff numbers
560 if len (e.getchildren ()) == 0: m.remove (e) # remove empty attributes element 637 if len (list (e)) == 0: m.remove (e) # remove empty attributes element
561 638
562 p1 = parts[0] 639 p1 = parts[0]
563 for p2 in parts[1:]: 640 for p2 in parts[1:]:
564 if is_grand: delAttrs (p2) # delete all attributes except clef 641 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 642 for i in range (len (p1) + 1, len (p2) + 1): # second part longer than first one
568 slurs = p1.findall ('measure/note/notations/slur') # find highest slur num in first part 645 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]) 646 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 647 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 648 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 649 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 650 mergeMeasure (p1[im], m2, slur_max, vnum_max, rOpt, is_grand) # may change slur numbers in p1
574 return p1 651 return p1
575 652
576 def mergeParts (parts, vids, staves, is_grand=0): 653 def mergeParts (parts, vids, staves, rOpt, is_grand=0):
577 if not staves: return parts, vids # no voice mapping 654 if not staves: return parts, vids # no voice mapping
578 partsnew, vidsnew = [], [] 655 partsnew, vidsnew = [], []
579 for voice_ids in staves: 656 for voice_ids in staves:
580 pixs = [] 657 pixs = []
581 for vid in voice_ids: 658 for vid in voice_ids:
582 if vid in vids: pixs.append (vids.index (vid)) 659 if vid in vids: pixs.append (vids.index (vid))
583 else: info ('score partname %s does not exist' % vid) 660 else: info ('score partname %s does not exist' % vid)
584 if pixs: 661 if pixs:
585 xparts = [parts[pix] for pix in pixs] 662 xparts = [parts[pix] for pix in pixs]
586 if len (xparts) > 1: mergedpart = mergePartList (xparts, is_grand) 663 if len (xparts) > 1: mergedpart = mergePartList (xparts, rOpt, is_grand)
587 else: mergedpart = xparts [0] 664 else: mergedpart = xparts [0]
588 partsnew.append (mergedpart) 665 partsnew.append (mergedpart)
589 vidsnew.append (vids [pixs[0]]) 666 vidsnew.append (vids [pixs[0]])
590 return partsnew, vidsnew 667 return partsnew, vidsnew
591 668
592 def mergePartMeasure (part, msre, ovrlaynum): # merge msre into last measure of part, only for overlays 669 def mergePartMeasure (part, msre, ovrlaynum, rOpt): # merge msre into last measure of part, only for overlays
593 slurs = part.findall ('measure/note/notations/slur') # find highest slur num in part 670 slur_offset = 0; # slur numbers determined by the slurstack size (as in a single voice)
594 slur_max = max ([int (slr.get ('number')) for slr in slurs] + [0]) 671 last_msre = list (part)[-1] # last measure in part
595 last_msre = part.getchildren ()[-1] # last measure in part 672 mergeMeasure (last_msre, msre, slur_offset, ovrlaynum, rOpt, is_overlay=1) # voice offset = s.overlayVNum
596 mergeMeasure (last_msre, msre, slur_max, ovrlaynum) # voice offset = s.overlayVNum 673
674 def pushSlur (boogStapel, stem):
675 if stem not in boogStapel: boogStapel [stem] = [] # initialize slurstack for stem
676 boognum = sum (map (len, boogStapel.values ())) + 1 # number of open slurs in all (overlay) voices
677 boogStapel [stem].append (boognum)
678 return boognum
597 679
598 def setFristVoiceNameFromGroup (vids, vdefs): # vids = [vid], vdef = {vid -> (name, subname, voicedef)} 680 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 681 vids = [v for v in vids if v in vdefs] # only consider defined voices
600 if not vids: return vdefs 682 if not vids: return vdefs
601 vid0 = vids [0] # first vid of the group 683 vid0 = vids [0] # first vid of the group
652 else: xs.extend (mkGroups (x) + ['}']) # x.name == 'grand' == rejected grand staff 734 else: xs.extend (mkGroups (x) + ['}']) # x.name == 'grand' == rejected grand staff
653 else: 735 else:
654 xs.append (p.t[0]) 736 xs.append (p.t[0])
655 return xs 737 return xs
656 738
739 def stepTrans (step, soct, clef): # [A-G] (1...8)
740 if clef.startswith ('bass'):
741 nm7 = 'C,D,E,F,G,A,B'.split (',')
742 n = 14 + nm7.index (step) - 12 # two octaves extra to avoid negative numbers
743 step, soct = nm7 [n % 7], soct + n // 7 - 2 # subtract two octaves again
744 return step, soct
745
746 def reduceMids (parts, vidsnew, midiInst): # remove redundant instruments from a part
747 for pid, part in zip (vidsnew, parts):
748 mids, repls, has_perc = {}, {}, 0
749 for ipid, ivid, ch, prg, vol, pan in sorted (list (midiInst.values ())):
750 if ipid != pid: continue # only instruments from part pid
751 if ch == '10': has_perc = 1; continue # only consider non percussion instruments
752 instId, inst = 'I%s-%s' % (ipid, ivid), (ch, prg)
753 if inst in mids: # midi instrument already defined in this part
754 repls [instId] = mids [inst] # remember to replace instId by inst (see below)
755 del midiInst [instId] # instId is redundant
756 else: mids [inst] = instId # collect unique instruments in this part
757 if len (mids) < 2 and not has_perc: # only one instrument used -> no instrument tags needed in notes
758 removeElems (part, 'measure/note', 'instrument') # no instrument tag needed for one- or no-instrument parts
759 else:
760 for e in part.findall ('measure/note/instrument'):
761 id = e.get ('id') # replace all redundant instrument Id's
762 if id in repls: e.set ('id', repls [id])
763
764 class stringAlloc:
765 def __init__ (s):
766 s.snaarVrij = [] # [[(t1, t2) ...] for each string ]
767 s.snaarIx = [] # index in snaarVrij for each string
768 s.curstaff = -1 # staff being allocated
769 def beginZoek (s): # reset snaarIx at start of each voice
770 s.snaarIx = []
771 for i in range (len (s.snaarVrij)): s.snaarIx.append (0)
772 def setlines (s, stflines, stfnum):
773 if stfnum != s.curstaff: # initialize for new staff
774 s.curstaff = stfnum
775 s.snaarVrij = []
776 for i in range (stflines): s.snaarVrij.append ([])
777 s.beginZoek ()
778 def isVrij (s, snaar, t1, t2): # see if string snaar is free between t1 and t2
779 xs = s.snaarVrij [snaar]
780 for i in range (s.snaarIx [snaar], len (xs)):
781 tb, te = xs [i]
782 if t1 >= te: continue # te_prev < t1 <= te
783 if t1 >= tb: s.snaarIx [snaar] = i; return 0 # tb <= t1 < te
784 if t2 > tb: s.snaarIx [snaar] = i; return 0 # t1 < tb < t2
785 s.snaarIx [snaar] = i; # remember position for next call
786 xs.insert (i, (t1,t2)) # te_prev < t1 < t2 < tb
787 return 1
788 xs.append ((t1,t2))
789 s.snaarIx [snaar] = len (xs) - 1
790 return 1
791 def bezet (s, snaar, t1, t2): # force allocation of note (t1,t2) on string snaar
792 xs = s.snaarVrij [snaar]
793 for i, (tb, te) in enumerate (xs):
794 if t1 >= te: continue # te_prev < t1 <= te
795 xs.insert (i, (t1, t2))
796 return
797 xs.append ((t1,t2))
798
657 class MusicXml: 799 class MusicXml:
658 typeMap = {1:'long', 2:'breve', 4:'whole', 8:'half', 16:'quarter', 32:'eighth', 64:'16th', 128:'32nd', 256:'64th'} 800 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} 801 dynaMap = {'p':1,'pp':1,'ppp':1,'pppp':1,'f':1,'ff':1,'fff':1,'ffff':1,'mp':1,'mf':1,'sfz':1}
802 tempoMap = {'larghissimo':40, 'moderato':104, 'adagissimo':44, 'allegretto':112, 'lentissimo':48, 'allegro':120, 'largo':56,
803 'vivace':168, 'adagio':59, 'vivo':180, 'lento':62, 'presto':192, 'larghetto':66, 'allegrissimo':208, 'adagietto':76,
804 'vivacissimo':220, 'andante':88, 'prestissimo':240, 'andantino':96}
660 wedgeMap = {'>(':1, '>)':1, '<(':1,'<)':1,'crescendo(':1,'crescendo)':1,'diminuendo(':1,'diminuendo)':1} 805 wedgeMap = {'>(':1, '>)':1, '<(':1,'<)':1,'crescendo(':1,'crescendo)':1,'diminuendo(':1,'diminuendo)':1}
661 artMap = {'.':'staccato','>':'accent','accent':'accent','wedge':'staccatissimo','tenuto':'tenuto'} 806 artMap = {'.':'staccato','>':'accent','accent':'accent','wedge':'staccatissimo','tenuto':'tenuto',
807 'breath':'breath-mark','marcato':'strong-accent','^':'strong-accent','slide':'scoop'}
662 ornMap = {'trill':'trill-mark','T':'trill-mark','turn':'turn','uppermordent':'inverted-mordent','lowermordent':'mordent', 808 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'} 809 'pralltriller':'inverted-mordent','mordent':'mordent','turn':'turn','invertedturn':'inverted-turn'}
664 tecMap = {'upbow':'up-bow', 'downbow':'down-bow'} 810 tecMap = {'upbow':'up-bow', 'downbow':'down-bow', 'plus':'stopped','open':'open-string','snap':'snap-pizzicato',
811 'thumb':'thumb-position'}
665 capoMap = {'fine':('Fine','fine','yes'), 'D.S.':('D.S.','dalsegno','segno'), 'D.C.':('D.C.','dacapo','yes'),'dacapo':('D.C.','dacapo','yes'), 812 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')} 813 '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#'] 814 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} 815 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'} 816 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'), 817 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':('','')} 818 'bass3':('F','3'), 'bass':('F','4'), 'treble':('G','2'), 'perc':('percussion',''), 'none':('',''), 'tab':('TAB','5')}
672 clefLineMap = {'B':'treble', 'G':'alto1', 'E':'alto2', 'C':'alto', 'A':'tenor', 'F':'bass3', 'D':'bass'} 819 clefLineMap = {'B':'treble', 'G':'alto1', 'E':'alto2', 'C':'alto', 'A':'tenor', 'F':'bass3', 'D':'bass'}
673 alterTab = {'=':'0', '_':'-1', '__':'-2', '^':'1', '^^':'2'} 820 alterTab = {'=':'0', '_':'-1', '__':'-2', '^':'1', '^^':'2'}
674 accTab = {'=':'natural', '_':'flat', '__':'flat-flat', '^':'sharp', '^^':'sharp-sharp'} 821 accTab = {'=':'natural', '_':'flat', '__':'flat-flat', '^':'sharp', '^^':'sharp-sharp'}
675 chordTab = compChordTab () 822 chordTab = compChordTab ()
676 uSyms = {'~':'roll', 'H':'fermata','L':'>','M':'lowermordent','O':'coda', 823 uSyms = {'~':'roll', 'H':'fermata','L':'>','M':'lowermordent','O':'coda',
677 'P':'uppermordent','S':'segno','T':'trill','u':'upbow','v':'downbow'} 824 '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 825 pageFmtDef = [0.75,297,210,18,18,10,10] # the abcm2ps page formatting defaults for A4
679 creditTab = {'O':'origin', 'A':'area', 'Z':'transcription', 'N':'notes', 'G':'group', 'H':'history', 'R':'rhythm', 826 metaTab = {'O':'origin', 'A':'area', 'Z':'transcription', 'N':'notes', 'G':'group', 'H':'history', 'R':'rhythm',
680 'B':'book', 'D':'discography', 'F':'fileurl', 'S':'source'} 827 'B':'book', 'D':'discography', 'F':'fileurl', 'S':'source', 'P':'partmap', 'W':'lyrics'}
828 metaMap = {'C':'composer'} # mapping of composer is fixed
829 metaTypes = {'composer':1,'lyricist':1,'poet':1,'arranger':1,'translator':1, 'rights':1} # valid MusicXML meta data types
830 tuningDef = 'E2,A2,D3,G3,B3,E4'.split (',') # default string tuning (guitar)
681 831
682 def __init__ (s): 832 def __init__ (s):
683 s.pageFmtCmd = [] # set by command line option -p 833 s.pageFmtCmd = [] # set by command line option -p
684 s.gmwr = 0 # set by command line option -r
685 s.reset () 834 s.reset ()
686 def reset (s): 835 def reset (s, fOpt=False):
687 s.divisions = 120 # xml duration of 1/4 note 836 s.divisions = 2520 # xml duration of 1/4 note, 2^3 * 3^2 * 5 * 7 => 5,7,9 tuplets
688 s.ties = {} # {abc pitch tuple -> alteration} for all open ties 837 s.ties = {} # {abc pitch tuple -> alteration} for all open ties
689 s.slurstack = [] # stack of open slur numbers 838 s.slurstack = {} # stack of open slur numbers per (overlay) voice
690 s.slurbeg = 0 # number of slurs to start (when slurs are detected at element-level) 839 s.slurbeg = [] # type of slurs to start (when slurs are detected at element-level)
691 s.tmnum = 0 # time modification, numerator 840 s.tmnum = 0 # time modification, numerator
692 s.tmden = 0 # time modification, denominator 841 s.tmden = 0 # time modification, denominator
693 s.ntup = 0 # number of tuplet notes remaining 842 s.ntup = 0 # number of tuplet notes remaining
843 s.trem = 0 # number of bars for tremolo
844 s.intrem = 0 # mark tremolo sequence (for duration doubling)
694 s.tupnts = [] # all tuplet modifiers with corresp. durations: [(duration, modifier), ...] 845 s.tupnts = [] # all tuplet modifiers with corresp. durations: [(duration, modifier), ...]
695 s.irrtup = 0 # 1 if an irregular tuplet 846 s.irrtup = 0 # 1 if an irregular tuplet
696 s.ntype = '' # the normal-type of a tuplet (== duration type of a normal tuplet note) 847 s.ntype = '' # the normal-type of a tuplet (== duration type of a normal tuplet note)
697 s.unitL = (1, 8) # default unit length 848 s.unitL = (1, 8) # default unit length
698 s.unitLcur = (1, 8) # unit length of current voice 849 s.unitLcur = (1, 8) # unit length of current voice
699 s.keyAlts = {} # alterations implied by key 850 s.keyAlts = {} # alterations implied by key
700 s.msreAlts = {} # temporarily alterations 851 s.msreAlts = {} # temporarily alterations
701 s.curVolta = '' # open volta bracket 852 s.curVolta = '' # open volta bracket
702 s.slurstack = [] # stack of open slur numbers
703 s.title = '' # title of music 853 s.title = '' # title of music
704 s.creator = {} # {creator-type -> creator string} 854 s.creator = {} # {creator-type -> creator string}
705 s.credits = {} # {credit-type -> string} 855 s.metadata = {} # {metadata-type -> string}
706 s.lyrdash = {} # {lyric number -> 1 if dash between syllables} 856 s.lyrdash = {} # {lyric number -> 1 if dash between syllables}
707 s.usrSyms = s.uSyms # user defined symbols 857 s.usrSyms = s.uSyms # user defined symbols
708 s.prevNote = None # xml element of previous beamed note to correct beams (start, continue) 858 s.prevNote = None # xml element of previous beamed note to correct beams (start, continue)
859 s.prevLyric = {} # xml element of previous lyric to add/correct extend type (start, continue)
709 s.grcbbrk = False # remember any bbrk in a grace sequence 860 s.grcbbrk = False # remember any bbrk in a grace sequence
710 s.linebrk = 0 # 1 if next measure should start with a line break 861 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 862 s.nextdecos = [] # decorations for the next note
713 s.prevmsre = None # the previous measure 863 s.prevmsre = None # the previous measure
714 s.supports_tag = 0 # issue supports-tag in xml file when abc uses explicit linebreaks 864 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 865 s.staveDefs = [] # collected %%staves or %%score instructions from score
716 s.staves = [] # staves = [[voice names to be merged into one stave]] 866 s.staves = [] # staves = [[voice names to be merged into one stave]]
719 s.gStaffNums = {} # map each voice id in a grand staff to a staff number 869 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 870 s.gNstaves = {} # map each voice id in a grand staff to total number of staves
721 s.pageFmtAbc = [] # formatting from abc directives 871 s.pageFmtAbc = [] # formatting from abc directives
722 s.mdur = (4,4) # duration of one measure 872 s.mdur = (4,4) # duration of one measure
723 s.gtrans = 0 # octave transposition (by clef) 873 s.gtrans = 0 # octave transposition (by clef)
724 s.midprg = ['', ''] # MIDI channel nr, program nr for the current part 874 s.midprg = ['', '', '', ''] # MIDI channel nr, program nr, volume, panning for the current part
725 s.vid = '' # abc voice id for the current part 875 s.vid = '' # abc voice id for the current voice
876 s.pid = '' # xml part id for the current voice
726 s.gcue_on = 0 # insert <cue/> tag in each note 877 s.gcue_on = 0 # insert <cue/> tag in each note
878 s.percVoice = 0 # 1 if percussion enabled
879 s.percMap = {} # (part-id, abc_pitch, xml-octave) -> (abc staff step, midi note number, xml notehead)
880 s.pMapFound = 0 # at least one I:percmap has been found
881 s.vcepid = {} # voice_id -> part_id
882 s.midiInst = {} # inst_id -> (part_id, voice_id, channel, midi_number), remember instruments used
883 s.capo = 0 # fret position of the capodastro
884 s.tunmid = [] # midi numbers of strings
885 s.tunTup = [] # ordered midi numbers of strings [(midi_num, string_num), ...] (midi_num from high to low)
886 s.fOpt = fOpt # force string/fret allocations for tab staves
887 s.orderChords = 0 # order notes in a chord
888 s.chordDecos = {} # decos that should be distributed to all chord notes for xml
889 ch10 = 'acoustic-bass-drum,35;bass-drum-1,36;side-stick,37;acoustic-snare,38;hand-clap,39;electric-snare,40;low-floor-tom,41;closed-hi-hat,42;high-floor-tom,43;pedal-hi-hat,44;low-tom,45;open-hi-hat,46;low-mid-tom,47;hi-mid-tom,48;crash-cymbal-1,49;high-tom,50;ride-cymbal-1,51;chinese-cymbal,52;ride-bell,53;tambourine,54;splash-cymbal,55;cowbell,56;crash-cymbal-2,57;vibraslap,58;ride-cymbal-2,59;hi-bongo,60;low-bongo,61;mute-hi-conga,62;open-hi-conga,63;low-conga,64;high-timbale,65;low-timbale,66;high-agogo,67;low-agogo,68;cabasa,69;maracas,70;short-whistle,71;long-whistle,72;short-guiro,73;long-guiro,74;claves,75;hi-wood-block,76;low-wood-block,77;mute-cuica,78;open-cuica,79;mute-triangle,80;open-triangle,81'
890 s.percsnd = [x.split (',') for x in ch10.split (';')] # {name -> midi number} of standard channel 10 sound names
891 s.gTime = (0,0) # (XML begin time, XML end time) in divisions
892 s.tabStaff = '' # == pid (part ID) for a tab staff
727 893
728 def mkPitch (s, acc, note, oct, lev): 894 def mkPitch (s, acc, note, oct, lev):
895 if s.percVoice: # percussion map switched off by perc=off (see doClef)
896 octq = int (oct) + s.gtrans # honour the octave= transposition when querying percmap
897 tup = s.percMap.get ((s.pid, acc+note, octq), s.percMap.get (('', acc+note, octq), 0))
898 if tup: step, soct, midi, notehead = tup
899 else: step, soct = note, octq
900 octnum = (4 if step.upper() == step else 5) + int (soct)
901 if not tup: # add percussion map for unmapped notes in this part
902 midi = str (octnum * 12 + [0,2,4,5,7,9,11]['CDEFGAB'.index (step.upper())] + {'^':1,'_':-1}.get (acc, 0) + 12)
903 notehead = {'^':'x', '_':'circle-x'}.get (acc, 'normal')
904 if s.pMapFound: info ('no I:percmap for: %s%s in part %s, voice %s' % (acc+note, -oct*',' if oct<0 else oct*"'", s.pid, s.vid))
905 s.percMap [(s.pid, acc+note, octq)] = (note, octq, midi, notehead)
906 else: # correct step value for clef
907 step, octnum = stepTrans (step.upper (), octnum, s.curClef)
908 pitch = E.Element ('unpitched')
909 addElemT (pitch, 'display-step', step.upper (), lev + 1)
910 addElemT (pitch, 'display-octave', str (octnum), lev + 1)
911 return pitch, '', midi, notehead
729 nUp = note.upper () 912 nUp = note.upper ()
730 octnum = (4 if nUp == note else 5) + int (oct) + s.gtrans 913 octnum = (4 if nUp == note else 5) + int (oct) + s.gtrans
731 pitch = E.Element ('pitch') 914 pitch = E.Element ('pitch')
732 addElemT (pitch, 'step', nUp, lev + 1) 915 addElemT (pitch, 'step', nUp, lev + 1)
733 alter = '' 916 alter = ''
734 if (note, oct) in s.ties: 917 if (note, oct) in s.ties:
735 tied_alter, _, vnum = s.ties [(note,oct)] # vnum = overlay voice number when tie started 918 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 919 if vnum == s.overlayVnum: alter = tied_alter # tied note in the same overlay -> same alteration
737 elif acc: 920 elif acc:
738 s.msreAlts [(nUp, octnum)] = s.alterTab [acc] 921 s.msreAlts [(nUp, octnum)] = s.alterTab [acc]
739 alter = s.alterTab [acc] # explicit notated alteration 922 alter = s.alterTab [acc] # explicit notated alteration
740 elif (nUp, octnum) in s.msreAlts: alter = s.msreAlts [(nUp, octnum)] # temporary alteration 923 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 924 elif nUp in s.keyAlts: alter = s.keyAlts [nUp] # alteration implied by the key
742 if alter: addElemT (pitch, 'alter', alter, lev + 1) 925 if alter: addElemT (pitch, 'alter', alter, lev + 1)
743 addElemT (pitch, 'octave', str (octnum), lev + 1) 926 addElemT (pitch, 'octave', str (octnum), lev + 1)
744 return pitch, alter 927 return pitch, alter, '', ''
928
929 def getNoteDecos (s, n):
930 decos = s.nextdecos # decorations encountered so far
931 ndeco = getattr (n, 'deco', 0) # possible decorations of notes of a chord
932 if ndeco: # add decorations, translate used defined symbols
933 decos += [s.usrSyms.get (d, d).strip ('!+') for d in ndeco.t]
934 s.nextdecos = []
935 if s.tabStaff == s.pid and s.fOpt and n.name != 'rest': # force fret/string allocation if explicit string decoration is missing
936 if [d for d in decos if d in '0123456789'] == []: decos.append ('0')
937 return decos
745 938
746 def mkNote (s, n, lev): 939 def mkNote (s, n, lev):
940 isgrace = getattr (n, 'grace', '')
941 ischord = getattr (n, 'chord', '')
942 if s.ntup >= 0 and not isgrace and not ischord:
943 s.ntup -= 1 # count tuplet notes only on non-chord, non grace notes
944 if s.ntup == -1 and s.trem <= 0:
945 s.intrem = 0 # tremolo pair ends at first note that is not a new tremolo pair (s.trem > 0)
747 nnum, nden = n.dur.t # abc dutation of note 946 nnum, nden = n.dur.t # abc dutation of note
947 if s.intrem: nnum += nnum # double duration of tremolo duplets
748 if nden == 0: nden = 1 # occurs with illegal ABC like: "A2 1". Now interpreted as A2/1 948 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 949 num, den = simplify (nnum * s.unitLcur[0], nden * s.unitLcur[1]) # normalised with unit length
750 if den > 64: # limit denominator to 64 950 if den > 64: # limit denominator to 64
751 num = int (round (64 * float (num) / den)) # scale note to num/64 951 num = int (round (64 * float (num) / den)) # scale note to num/64
752 num, den = simplify (max ([num, 1]), 64) # smallest num == 1 952 num, den = simplify (max ([num, 1]), 64) # smallest num == 1
753 info ('duration too small: rounded to %d/%d' % (num, den)) 953 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): 954 if n.name == 'rest' and ('Z' in n.t or 'X' in n.t):
755 num, den = s.mdur # duration of one measure 955 num, den = s.mdur # duration of one measure
756 dvs = (4 * s.divisions * num) / den # divisions is xml-duration of 1/4 956 noMsrRest = not (n.name == 'rest' and (num, den) == s.mdur) # not a measure rest
957 dvs = (4 * s.divisions * num) // den # divisions is xml-duration of 1/4
757 rdvs = dvs # real duration (will be 0 for chord/grace) 958 rdvs = dvs # real duration (will be 0 for chord/grace)
758 num, den = simplify (num, den * 4) # scale by 1/4 for s.typeMap 959 num, den = simplify (num, den * 4) # scale by 1/4 for s.typeMap
759 ndot = 0 960 ndot = 0
760 if num == 3: ndot = 1; den = den / 2 # look for dotted notes 961 if num == 3 and noMsrRest: ndot = 1; den = den // 2 # look for dotted notes
761 if num == 7: ndot = 2; den = den / 4 962 if num == 7 and noMsrRest: ndot = 2; den = den // 4
762 nt = E.Element ('note') 963 nt = E.Element ('note')
763 if getattr (n, 'grace', ''): # a grace note (and possibly a chord note) 964 if isgrace: # a grace note (and possibly a chord note)
764 grace = E.Element ('grace') 965 grace = E.Element ('grace')
765 if s.acciatura: grace.set ('slash', 'yes'); s.acciatura = 0 966 if s.acciatura: grace.set ('slash', 'yes'); s.acciatura = 0
766 addElem (nt, grace, lev + 1) 967 addElem (nt, grace, lev + 1)
767 dvs = rdvs = 0 # no (real) duration for a grace note 968 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 969 if den <= 16: den = 32 # not longer than 1/8 for a grace note
769 if s.gcue_on: # insert cue tag 970 if s.gcue_on: # insert cue tag
770 cue = E.Element ('cue') 971 cue = E.Element ('cue')
771 addElem (nt, cue, lev + 1) 972 addElem (nt, cue, lev + 1)
772 if getattr (n, 'chord', ''): # a chord note 973 if ischord: # a chord note
773 chord = E.Element ('chord') 974 chord = E.Element ('chord')
774 addElem (nt, chord, lev + 1) 975 addElem (nt, chord, lev + 1)
775 rdvs = 0 # chord notes no real duration 976 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 977 if den not in s.typeMap: # take the nearest smaller legal duration
778 info ('illegal duration %d/%d' % (nnum, nden)) 978 info ('illegal duration %d/%d' % (nnum, nden))
779 den = min (x for x in s.typeMap.keys () if x > den) 979 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 980 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) 981 acc, step, oct = '', 'C', '0' # abc-notated pitch elements (accidental, pitch step, octave)
782 alter = '' # xml alteration 982 alter, midi, notehead = '', '', '' # xml alteration
783 if n.name == 'rest': 983 if n.name == 'rest':
784 if 'x' in n.t or 'X' in n.t: nt.set ('print-object', 'no') 984 if 'x' in n.t or 'X' in n.t: nt.set ('print-object', 'no')
785 rest = E.Element ('rest') 985 rest = E.Element ('rest')
986 if not noMsrRest: rest.set ('measure', 'yes')
786 addElem (nt, rest, lev + 1) 987 addElem (nt, rest, lev + 1)
787 else: 988 else:
788 p = n.pitch.t # get pitch elements from parsed tokens 989 p = n.pitch.t # get pitch elements from parsed tokens
789 if len (p) == 3: acc, step, oct = p 990 if len (p) == 3: acc, step, oct = p
790 else: step, oct = p 991 else: step, oct = p
791 pitch, alter = s.mkPitch (acc, step, oct, lev + 1) 992 pitch, alter, midi, notehead = s.mkPitch (acc, step, oct, lev + 1)
993 if midi: acc = '' # erase accidental for percussion notes
792 addElem (nt, pitch, lev + 1) 994 addElem (nt, pitch, lev + 1)
793 if s.ntup >= 0: # modify duration for tuplet notes 995 if s.ntup >= 0: # modify duration for tuplet notes
794 dvs = dvs * s.tmden / s.tmnum 996 dvs = dvs * s.tmden // s.tmnum
795 if dvs: addElemT (nt, 'duration', str (dvs), lev + 1) # skip when dvs == 0, requirement of musicXML 997 if dvs:
796 inst = E.Element ('instrument', id='I-'+s.vid) # instrument id for midi 998 addElemT (nt, 'duration', str (dvs), lev + 1) # skip when dvs == 0, requirement of musicXML
797 if s.midprg != ['', '']: addElem (nt, inst, lev + 1) # only add when %%midi was present 999 if not ischord: s.gTime = s.gTime [1], s.gTime [1] + dvs
1000 ptup = (step, oct) # pitch tuple without alteration to check for ties
1001 tstop = ptup in s.ties and s.ties[ptup][2] == s.overlayVnum # open tie on this pitch tuple in this overlay
1002 if tstop:
1003 tie = E.Element ('tie', type='stop')
1004 addElem (nt, tie, lev + 1)
1005 if getattr (n, 'tie', 0):
1006 tie = E.Element ('tie', type='start')
1007 addElem (nt, tie, lev + 1)
1008 if (s.midprg != ['', '', '', ''] or midi) and n.name != 'rest': # only add when %%midi was present or percussion
1009 instId = 'I%s-%s' % (s.pid, 'X' + midi if midi else s.vid)
1010 chan, midi = ('10', midi) if midi else s.midprg [:2]
1011 inst = E.Element ('instrument', id=instId) # instrument id for midi
1012 addElem (nt, inst, lev + 1)
1013 if instId not in s.midiInst: s.midiInst [instId] = (s.pid, s.vid, chan, midi, s.midprg [2], s.midprg [3]) # for instrument list in mkScorePart
798 addElemT (nt, 'voice', '1', lev + 1) # default voice, for merging later 1014 addElemT (nt, 'voice', '1', lev + 1) # default voice, for merging later
799 addElemT (nt, 'type', xmltype, lev + 1) # add note type 1015 if noMsrRest: addElemT (nt, 'type', xmltype, lev + 1) # add note type if not a measure rest
800 for i in range (ndot): # add dots 1016 for i in range (ndot): # add dots
801 dot = E.Element ('dot') 1017 dot = E.Element ('dot')
802 addElem (nt, dot, lev + 1) 1018 addElem (nt, dot, lev + 1)
803 ptup = (step, oct) # pitch tuple without alteration to check for ties 1019 decos = s.getNoteDecos (n) # get decorations for this note
804 tstop = ptup in s.ties and s.ties[ptup][2] == s.overlayVnum # open tie on this pitch tuple in this overlay 1020 if acc and not tstop: # only add accidental if note not tied
805 if acc and not tstop: addElemT (nt, 'accidental', s.accTab [acc], lev + 1) # only add accidental if note not tied 1021 e = E.Element ('accidental')
1022 if 'courtesy' in decos:
1023 e.set ('parentheses', 'yes')
1024 decos.remove ('courtesy')
1025 e.text = s.accTab [acc]
1026 addElem (nt, e, lev + 1)
806 tupnotation = '' # start/stop notation element for tuplets 1027 tupnotation = '' # start/stop notation element for tuplets
807 if s.ntup >= 0: # add time modification element for tuplet notes 1028 if s.ntup >= 0: # add time modification element for tuplet notes
808 tmod = mkTmod (s.tmnum, s.tmden, lev + 1) 1029 tmod = mkTmod (s.tmnum, s.tmden, lev + 1)
809 addElem (nt, tmod, lev + 1) 1030 addElem (nt, tmod, lev + 1)
810 if s.ntup > 0 and not s.tupnts: tupnotation = 'start' 1031 if s.ntup > 0 and not s.tupnts: tupnotation = 'start'
811 s.tupnts.append ((rdvs, tmod)) # remember all tuplet modifiers with corresp. durations 1032 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) 1033 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) 1034 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) 1035 s.cmpNormType (rdvs, lev + 1) # compute and/or add normal-type elements (-> s.ntype)
1036 hasStem = 1
1037 if not ischord: s.chordDecos = {} # clear on non chord note
1038 if 'stemless' in decos or (s.nostems and n.name != 'rest') or 'stemless' in s.chordDecos:
1039 hasStem = 0
1040 addElemT (nt, 'stem', 'none', lev + 1)
1041 if 'stemless' in decos: decos.remove ('stemless') # do not handle in doNotations
1042 if hasattr (n, 'pitches'): s.chordDecos ['stemless'] = 1 # set on first chord note
1043 if notehead:
1044 nh = addElemT (nt, 'notehead', re.sub (r'[+-]$', '', notehead), lev + 1)
1045 if notehead[-1] in '+-': nh.set ('filled', 'yes' if notehead[-1] == '+' else 'no')
815 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice 1046 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
816 if gstaff: addElemT (nt, 'staff', str (gstaff), lev + 1) 1047 if gstaff: addElemT (nt, 'staff', str (gstaff), lev + 1)
817 s.doBeams (n, nt, den, lev + 1) 1048 if hasStem: s.doBeams (n, nt, den, lev + 1) # no stems -> no beams in a tab staff
818 s.doNotations (n, ptup, alter, tupnotation, tstop, nt, lev + 1) 1049 s.doNotations (n, decos, ptup, alter, tupnotation, tstop, nt, lev + 1)
819 if n.objs: s.doLyr (n, nt, lev + 1) 1050 if n.objs: s.doLyr (n, nt, lev + 1)
1051 else: s.prevLyric = {} # clear on note without lyrics
820 return nt 1052 return nt
821 1053
822 def cmpNormType (s, rdvs, lev): # compute the normal-type of a tuplet (only needed for Finale) 1054 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) 1055 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] 1056 durs = [dur for dur, tmod in s.tupnts if dur > 0]
825 ndur = sum (durs) / s.tmnum # duration of the normal type 1057 ndur = sum (durs) // s.tmnum # duration of the normal type
826 s.irrtup = any ((dur != ndur) for dur in durs) # irregular tuplet 1058 s.irrtup = any ((dur != ndur) for dur in durs) # irregular tuplet
827 tix = 16 * s.divisions / ndur # index in typeMap of normal-type duration 1059 tix = 16 * s.divisions // ndur # index in typeMap of normal-type duration
828 if tix in s.typeMap: 1060 if tix in s.typeMap:
829 s.ntype = str (s.typeMap [tix]) # the normal-type 1061 s.ntype = str (s.typeMap [tix]) # the normal-type
830 else: s.irrtup = 0 # give up, no normal type possible 1062 else: s.irrtup = 0 # give up, no normal type possible
831 if s.irrtup: # only add normal-type for irregular tuplets 1063 if s.irrtup: # only add normal-type for irregular tuplets
832 for dur, tmod in s.tupnts: # add normal-type to all modifiers 1064 for dur, tmod in s.tupnts: # add normal-type to all modifiers
833 addElemT (tmod, 'normal-type', s.ntype, lev + 1) 1065 addElemT (tmod, 'normal-type', s.ntype, lev + 1)
834 s.tupnts = [] # reset the tuplet buffer 1066 s.tupnts = [] # reset the tuplet buffer
835 1067
836 def doNotations (s, n, ptup, alter, tupnotation, tstop, nt, lev): 1068 def doNotations (s, n, decos, ptup, alter, tupnotation, tstop, nt, lev):
837 slurs = getattr (n, 'slurs', 0) # slur ends 1069 slurs = getattr (n, 'slurs', 0) # slur ends
838 pts = getattr (n, 'pitches', []) # all chord notes available in the first note 1070 pts = getattr (n, 'pitches', []) # all chord notes available in the first note
1071 ov = s.overlayVnum # current overlay voice number (0 for the main voice)
839 if pts: # make list of pitches in chord: [(pitch, octave), ..] 1072 if pts: # make list of pitches in chord: [(pitch, octave), ..]
840 if type (pts.pitch) == pObj: pts = [pts.pitch] # chord with one note 1073 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 1074 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 1075 for pt, (tie_alter, nts, vnum, ntelm) in sorted (list (s.ties.items ())): # scan all open ties and delete illegal ones
843 if vnum != s.overlayVnum: continue # tie belongs to different overlay 1076 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 1077 if pts and pt in pts: continue # pitch tuple of tie exists in chord
845 if getattr (n, 'chord', 0): continue # skip chord notes 1078 if getattr (n, 'chord', 0): continue # skip chord notes
846 if pt == ptup: continue # skip correct single note tie 1079 if pt == ptup: continue # skip correct single note tie
847 if getattr (n, 'grace', 0): continue # skip grace notes 1080 if getattr (n, 'grace', 0): continue # skip grace notes
848 info ('tie between different pitches: %s%s converted to slur' % pt) 1081 info ('tie between different pitches: %s%s converted to slur' % pt)
849 del s.ties [pt] # remove the note from pending ties 1082 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 1083 e = [t for t in ntelm.findall ('tie') if t.get ('type') == 'start'][0] # get the tie start element
1084 ntelm.remove (e) # delete start tie element
1085 e = [t for t in nts.findall ('tied') if t.get ('type') == 'start'][0] # get the tied start element
851 e.tag = 'slur' # convert tie into slur 1086 e.tag = 'slur' # convert tie into slur
852 slurnum = len (s.slurstack) + 1 1087 slurnum = pushSlur (s.slurstack, ov)
853 s.slurstack.append (slurnum)
854 e.set ('number', str (slurnum)) 1088 e.set ('number', str (slurnum))
855 if slurs: slurs.t.append (')') # close slur on this note 1089 if slurs: slurs.t.append (')') # close slur on this note
856 else: slurs = pObj ('slurs', [')']) 1090 else: slurs = pObj ('slurs', [')'])
857 tstart = getattr (n, 'tie', 0) # start a new tie 1091 tstart = getattr (n, 'tie', 0) # start a new tie
858 decos = s.nextdecos # decorations encountered so far 1092 if not (tstop or tstart or decos or slurs or s.slurbeg or tupnotation or s.trem): return nt
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 1093 nots = E.Element ('notations') # notation element needed
865 if tupnotation: # add tuplet type 1094 if s.trem: # +/- => tuple tremolo sequence / single note tremolo
1095 if s.trem < 0: tupnotation = 'single'; s.trem = -s.trem
1096 if not tupnotation: return # only add notation at first or last note of a tremolo sequence
1097 orn = E.Element ('ornaments')
1098 trm = E.Element ('tremolo', type=tupnotation) # type = start, stop or single
1099 trm.text = str (s.trem) # the number of bars in a tremolo note
1100 addElem (nots, orn, lev + 1)
1101 addElem (orn, trm, lev + 2)
1102 if tupnotation == 'stop' or tupnotation == 'single': s.trem = 0
1103 elif tupnotation: # add tuplet type
866 tup = E.Element ('tuplet', type=tupnotation) 1104 tup = E.Element ('tuplet', type=tupnotation)
867 if tupnotation == 'start': tup.set ('bracket', 'yes') 1105 if tupnotation == 'start': tup.set ('bracket', 'yes')
868 addElem (nots, tup, lev + 1) 1106 addElem (nots, tup, lev + 1)
869 if tstop: # stop tie 1107 if tstop: # stop tie
870 del s.ties[ptup] # remove flag 1108 del s.ties[ptup] # remove flag
871 tie = E.Element ('tied', type='stop') 1109 tie = E.Element ('tied', type='stop')
872 addElem (nots, tie, lev + 1) 1110 addElem (nots, tie, lev + 1)
873 if tstart: # start a tie 1111 if tstart: # start a tie
874 s.ties[ptup] = (alter, nots, s.overlayVnum) # remember pitch tuple to stop tie and apply same alteration 1112 s.ties[ptup] = (alter, nots, s.overlayVnum, nt) # remember pitch tuple to stop tie and apply same alteration
875 tie = E.Element ('tied', type='start') 1113 tie = E.Element ('tied', type='start')
1114 if tstart.t[0] == '.-': tie.set ('line-type', 'dotted')
876 addElem (nots, tie, lev + 1) 1115 addElem (nots, tie, lev + 1)
877 if decos: # look for slurs and decorations 1116 if decos: # look for slurs and decorations
1117 slurMap = { '(':1, '.(':1, '(,':1, "('":1, '.(,':1, ".('":1 }
878 arts = [] # collect articulations 1118 arts = [] # collect articulations
879 for d in decos: # do all slurs and decos 1119 for d in decos: # do all slurs and decos
880 if d == '(': s.slurbeg += 1; continue # slurs made in while loop at the end 1120 if d in slurMap: s.slurbeg.append (d); continue # slurs made in while loop at the end
881 elif d == 'fermata' or d == 'H': 1121 elif d == 'fermata' or d == 'H':
882 ntn = E.Element ('fermata', type='upright') 1122 ntn = E.Element ('fermata', type='upright')
883 elif d == 'arpeggio': 1123 elif d == 'arpeggio':
884 ntn = E.Element ('arpeggiate', number='1') 1124 ntn = E.Element ('arpeggiate', number='1')
1125 elif d in ['~(', '~)']:
1126 if d[1] == '(': tp = 'start'; s.glisnum += 1; gn = s.glisnum
1127 else: tp = 'stop'; gn = s.glisnum; s.glisnum -= 1
1128 if s.glisnum < 0: s.glisnum = 0; continue # stop without previous start
1129 ntn = E.Element ('glissando', {'line-type':'wavy', 'number':'%d' % gn, 'type':tp})
1130 elif d in ['-(', '-)']:
1131 if d[1] == '(': tp = 'start'; s.slidenum += 1; gn = s.slidenum
1132 else: tp = 'stop'; gn = s.slidenum; s.slidenum -= 1
1133 if s.slidenum < 0: s.slidenum = 0; continue # stop without previous start
1134 ntn = E.Element ('slide', {'line-type':'solid', 'number':'%d' % gn, 'type':tp})
885 else: arts.append (d); continue 1135 else: arts.append (d); continue
886 addElem (nots, ntn, lev + 1) 1136 addElem (nots, ntn, lev + 1)
887 if arts: # do only note articulations and collect staff annotations in xmldecos 1137 if arts: # do only note articulations and collect staff annotations in xmldecos
888 rest = s.doArticulations (nots, arts, lev + 1) 1138 rest = s.doArticulations (nt, nots, arts, lev + 1)
889 if rest: info ('unhandled note decorations: %s' % rest) 1139 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 1140 if slurs: # these are only slur endings
897 for d in slurs.t: 1141 for d in slurs.t: # slurs to be closed on this note
898 if not s.slurstack: break # no more open slurs 1142 if not s.slurstack.get (ov, 0): break # no more open old slurs for this (overlay) voice
899 slurnum = s.slurstack.pop () 1143 slurnum = s.slurstack [ov].pop ()
900 slur = E.Element ('slur', number='%d' % slurnum, type='stop') 1144 slur = E.Element ('slur', number='%d' % slurnum, type='stop')
901 addElem (nots, slur, lev + 1) 1145 addElem (nots, slur, lev + 1)
902 if nots.getchildren() != []: # only add notations if not empty 1146 while s.slurbeg: # create slurs beginning on this note
1147 stp = s.slurbeg.pop (0)
1148 slurnum = pushSlur (s.slurstack, ov)
1149 ntn = E.Element ('slur', number='%d' % slurnum, type='start')
1150 if '.' in stp: ntn.set ('line-type', 'dotted')
1151 if ',' in stp: ntn.set ('placement', 'below')
1152 if "'" in stp: ntn.set ('placement', 'above')
1153 addElem (nots, ntn, lev + 1)
1154 if list (nots) != []: # only add notations if not empty
903 addElem (nt, nots, lev) 1155 addElem (nt, nots, lev)
904 1156
905 def doArticulations (s, nots, arts, lev): 1157 def doArticulations (s, nt, nots, arts, lev):
906 decos = [] 1158 decos = []
907 for a in arts: 1159 for a in arts:
908 if a in s.artMap: 1160 if a in s.artMap:
909 art = E.Element ('articulations') 1161 art = E.Element ('articulations')
910 addElem (nots, art, lev) 1162 addElem (nots, art, lev)
911 addElem (art, E.Element (s.artMap[a]), lev + 1) 1163 addElem (art, E.Element (s.artMap[a]), lev + 1)
912 elif a in s.ornMap: 1164 elif a in s.ornMap:
913 orn = E.Element ('ornaments') 1165 orn = E.Element ('ornaments')
914 addElem (nots, orn, lev) 1166 addElem (nots, orn, lev)
915 addElem (orn, E.Element (s.ornMap[a]), lev + 1) 1167 addElem (orn, E.Element (s.ornMap[a]), lev + 1)
1168 elif a in ['trill(','trill)']:
1169 orn = E.Element ('ornaments')
1170 addElem (nots, orn, lev)
1171 type = 'start' if a.endswith ('(') else 'stop'
1172 if type == 'start': addElem (orn, E.Element ('trill-mark'), lev + 1)
1173 addElem (orn, E.Element ('wavy-line', type=type), lev + 1)
916 elif a in s.tecMap: 1174 elif a in s.tecMap:
917 tec = E.Element ('technical') 1175 tec = E.Element ('technical')
918 addElem (nots, tec, lev) 1176 addElem (nots, tec, lev)
919 addElem (tec, E.Element (s.tecMap[a]), lev + 1) 1177 addElem (tec, E.Element (s.tecMap[a]), lev + 1)
1178 elif a in '0123456':
1179 tec = E.Element ('technical')
1180 addElem (nots, tec, lev)
1181 if s.tabStaff == s.pid: # current voice belongs to a tabStaff
1182 alt = int (nt.findtext ('pitch/alter') or 0) # find midi number of current note
1183 step = nt.findtext ('pitch/step')
1184 oct = int (nt.findtext ('pitch/octave'))
1185 midi = oct * 12 + [0,2,4,5,7,9,11]['CDEFGAB'.index (step)] + alt + 12
1186 if a == '0': # no string annotation: find one
1187 firstFit = ''
1188 for smid, istr in s.tunTup: # midi numbers of open strings from high to low
1189 if midi >= smid: # highest open string where this note can be played
1190 isvrij = s.strAlloc.isVrij (istr - 1, s.gTime [0], s.gTime [1])
1191 a = str (istr) # string number
1192 if not firstFit: firstFit = a
1193 if isvrij: break
1194 if not isvrij: # no free string, take the first fit (lowest fret)
1195 a = firstFit
1196 s.strAlloc.bezet (int (a) - 1, s.gTime [0], s.gTime [1])
1197 else: # force annotated string number
1198 s.strAlloc.bezet (int (a) - 1, s.gTime [0], s.gTime [1])
1199 bmidi = s.tunmid [int (a) - 1] # midi number of allocated string (with capodastro)
1200 fret = midi - bmidi # fret position (respecting capodastro)
1201 if fret < 25 and fret >= 0:
1202 addElemT (tec, 'fret', str (fret), lev + 1)
1203 else:
1204 altp = 'b' if alt == -1 else '#' if alt == 1 else ''
1205 info ('fret %d out of range, note %s%d on string %s' % (fret, step+altp, oct, a))
1206 addElemT (tec, 'string', a, lev + 1)
1207 else:
1208 addElemT (tec, 'fingering', a, lev + 1)
920 else: decos.append (a) # return staff annotations 1209 else: decos.append (a) # return staff annotations
921 return decos 1210 return decos
922 1211
923 def doLyr (s, n, nt, lev): 1212 def doLyr (s, n, nt, lev):
924 for i, lyrobj in enumerate (n.objs): 1213 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)) 1214 lyrel = E.Element ('lyric', number = str (i + 1))
1215 if lyrobj.name == 'syl':
1216 dash = len (lyrobj.t) == 2
1217 if dash:
1218 if i in s.lyrdash: type = 'middle'
1219 else: type = 'begin'; s.lyrdash [i] = 1
1220 else:
1221 if i in s.lyrdash: type = 'end'; del s.lyrdash [i]
1222 else: type = 'single'
1223 addElemT (lyrel, 'syllabic', type, lev + 1)
1224 txt = lyrobj.t[0] # the syllabe
1225 txt = re.sub (r'(?<!\\)~', ' ', txt) # replace ~ by space when not escaped (preceded by \)
1226 txt = re.sub (r'\\(.)', r'\1', txt) # replace all escaped characters by themselves (for the time being)
1227 addElemT (lyrel, 'text', txt, lev + 1)
1228 elif lyrobj.name == 'ext' and i in s.prevLyric:
1229 pext = s.prevLyric [i].find ('extend') # identify previous extend
1230 if pext == None:
1231 ext = E.Element ('extend', type = 'start')
1232 addElem (s.prevLyric [i], ext, lev + 1)
1233 elif pext.get('type') == 'stop': # subsequent extend: stop -> continue
1234 pext.set ('type', 'continue')
1235 ext = E.Element ('extend', type = 'stop') # always stop on current extend
1236 addElem (lyrel, ext, lev + 1)
1237 elif lyrobj.name == 'ext': info ('lyric extend error'); continue
1238 else: continue # skip other lyric elements or errors
934 addElem (nt, lyrel, lev) 1239 addElem (nt, lyrel, lev)
935 addElemT (lyrel, 'syllabic', type, lev + 1) 1240 s.prevLyric [i] = lyrel # for extension (melisma) on the next note
936 addElemT (lyrel, 'text', lyrobj.t[0].replace ('~',' '), lev + 1)
937 1241
938 def doBeams (s, n, nt, den, lev): 1242 def doBeams (s, n, nt, den, lev):
939 if hasattr (n, 'chord') or hasattr (n, 'grace'): 1243 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 1244 s.grcbbrk = s.grcbbrk or n.bbrk.t[0] # remember if there was any bbrk in or before a grace sequence
941 return 1245 return
965 s.prevNote.remove (pbm) 1269 s.prevNote.remove (pbm)
966 elif pbm.text == 'continue': 1270 elif pbm.text == 'continue':
967 pbm.text = 'end' 1271 pbm.text = 'end'
968 s.prevNote = None 1272 s.prevNote = None
969 1273
970 def staffDecos (s, decos, maat, lev, bardecos=0): 1274 def staffDecos (s, decos, maat, lev):
971 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice 1275 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
972 for d in decos: 1276 for d in decos:
973 d = s.usrSyms.get (d, d).strip ('!+') # try to replace user defined symbol 1277 d = s.usrSyms.get (d, d).strip ('!+') # try to replace user defined symbol
974 if d in s.dynaMap: 1278 if d in s.dynaMap:
975 dynel = E.Element ('dynamics') 1279 dynel = E.Element ('dynamics')
976 addDirection (maat, dynel, lev, gstaff, [E.Element (d)], 'below', s.gcue_on) 1280 addDirection (maat, dynel, lev, gstaff, [E.Element (d)], 'below', s.gcue_on)
977 elif d in s.wedgeMap: # wedge 1281 elif d in s.wedgeMap: # wedge
978 if ')' in d: type = 'stop' 1282 if ')' in d: type = 'stop'
979 else: type = 'crescendo' if '<' in d or 'crescendo' in d else 'diminuendo' 1283 else: type = 'crescendo' if '<' in d or 'crescendo' in d else 'diminuendo'
980 addDirection (maat, E.Element ('wedge', type=type), lev, gstaff) 1284 addDirection (maat, E.Element ('wedge', type=type), lev, gstaff)
1285 elif d.startswith ('8v'):
1286 if 'a' in d: type, plce = 'down', 'above'
1287 else: type, plce = 'up', 'below'
1288 if ')' in d: type = 'stop'
1289 addDirection (maat, E.Element ('octave-shift', type=type, size='8'), lev, gstaff, placement=plce)
1290 elif d in (['ped','ped-up']):
1291 type = 'stop' if d.endswith ('up') else 'start'
1292 addDirection (maat, E.Element ('pedal', type=type), lev, gstaff)
981 elif d in ['coda', 'segno']: 1293 elif d in ['coda', 'segno']:
982 if bardecos: s.bardecos.append (d) # postpone to begin next measure 1294 text, attr, val = s.capoMap [d]
983 else: 1295 dir = addDirection (maat, E.Element (text), lev, gstaff, placement='above')
984 text, attr, val = s.capoMap [d] 1296 sound = E.Element ('sound'); sound.set (attr, val)
985 dir = addDirection (maat, E.Element (text), lev, gstaff, placement='above') 1297 addElem (dir, sound, lev + 1)
986 sound = E.Element ('sound'); sound.set (attr, val)
987 addElem (dir, sound, lev + 1)
988 elif d in s.capoMap: 1298 elif d in s.capoMap:
989 text, attr, val = s.capoMap [d] 1299 text, attr, val = s.capoMap [d]
990 words = E.Element ('words'); words.text = text 1300 words = E.Element ('words'); words.text = text
991 dir = addDirection (maat, words, lev, gstaff, placement='above') 1301 dir = addDirection (maat, words, lev, gstaff, placement='above')
992 sound = E.Element ('sound'); sound.set (attr, val) 1302 sound = E.Element ('sound'); sound.set (attr, val)
993 addElem (dir, sound, lev + 1) 1303 addElem (dir, sound, lev + 1)
994 elif d == '(': s.slurbeg += 1 # start slur on next note 1304 elif d == '(' or d == '.(': s.slurbeg.append (d) # start slur on next note
1305 elif d in ['/-','//-','///-','////-']: # duplet tremolo sequence
1306 s.tmnum, s.tmden, s.ntup, s.trem, s.intrem = 2, 1, 2, len (d) - 1, 1
1307 elif d in ['/','//','///']: s.trem = - len (d) # single note tremolo
995 else: s.nextdecos.append (d) # keep annotation for the next note 1308 else: s.nextdecos.append (d) # keep annotation for the next note
996 1309
997 def doFields (s, maat, fieldmap, lev): 1310 def doFields (s, maat, fieldmap, lev):
998 def doClef (): 1311 def instDir (midelm, midnum, dirtxt):
1312 instId = 'I%s-%s' % (s.pid, s.vid)
1313 words = E.Element ('words'); words.text = dirtxt % midnum
1314 snd = E.Element ('sound')
1315 mi = E.Element ('midi-instrument', id=instId)
1316 dir = addDirection (maat, words, lev, gstaff, placement='above')
1317 addElem (dir, snd, lev + 1)
1318 addElem (snd, mi, lev + 2)
1319 addElemT (mi, midelm, midnum, lev + 3)
1320 def addTrans (n):
1321 e = E.Element ('transpose')
1322 addElemT (e, 'chromatic', n, lev + 2) # n == signed number string given after transpose
1323 atts.append ((9, e))
1324 def doClef (field):
1325 if re.search (r'perc|map', field): # percussion clef or new style perc=on or map=perc
1326 r = re.search (r'(perc|map)\s*=\s*(\S*)', field)
1327 s.percVoice = 0 if r and r.group (2) not in ['on','true','perc'] else 1
1328 field = re.sub (r'(perc|map)\s*=\s*(\S*)', '', field) # erase the perc= for proper clef matching
999 clef, gtrans = 0, 0 1329 clef, gtrans = 0, 0
1000 clefn = re.search (r'alto1|alto2|alto4|alto|tenor|bass3|bass|treble|perc|none', field) 1330 clefn = re.search (r'alto1|alto2|alto4|alto|tenor|bass3|bass|treble|perc|none|tab', field)
1001 clefm = re.search (r"(?:^m=| m=|middle=)([A-Ga-g])([,']*)", field) 1331 clefm = re.search (r"(?:^m=| m=|middle=)([A-Ga-g])([,']*)", field)
1002 trans_oct2 = re.search (r'octave=([-+]\d)', field) 1332 trans_oct2 = re.search (r'octave=([-+]?\d)', field)
1003 trans = re.search (r'(?:^t=| t=|transpose=)(-?[\d]+)', field) 1333 trans = re.search (r'(?:^t=| t=|transpose=)(-?[\d]+)', field)
1004 trans_oct = re.search (r'([+-^_])(8|15)', field) 1334 trans_oct = re.search (r'([+-^_])(8|15)', field)
1005 cue_onoff = re.search (r'cue=(on|off)', field) 1335 cue_onoff = re.search (r'cue=(on|off)', field)
1336 strings = re.search (r"strings=(\S+)", field)
1337 stafflines = re.search (r'stafflines=\s*(\d)', field)
1338 capo = re.search (r'capo=(\d+)', field)
1006 if clefn: 1339 if clefn:
1007 clef = clefn.group () 1340 clef = clefn.group ()
1008 if clefm: 1341 if clefm:
1009 note, octstr = clefm.groups () 1342 note, octstr = clefm.groups ()
1010 nUp = note.upper () 1343 nUp = note.upper ()
1011 octnum = (4 if nUp == note else 5) + (len (octstr) if "'" in octstr else -len (octstr)) 1344 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 1345 gtrans = (3 if nUp in 'AFD' else 4) - octnum
1013 if clef not in ['perc', 'none']: clef = s.clefLineMap [nUp] 1346 if clef not in ['perc', 'none']: clef = s.clefLineMap [nUp]
1014 if clef: 1347 if clef:
1015 s.gtrans = gtrans # only change global tranposition when a clef is really defined 1348 s.gtrans = gtrans # only change global tranposition when a clef is really defined
1349 if clef != 'none': s.curClef = clef # keep track of current abc clef (for percmap)
1016 sign, line = s.clefMap [clef] 1350 sign, line = s.clefMap [clef]
1017 if not sign: return 1351 if not sign: return
1018 c = E.Element ('clef') 1352 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 1353 if gstaff: c.set ('number', str (gstaff)) # only add staff number when defined
1021 addElemT (c, 'sign', sign, lev + 2) 1354 addElemT (c, 'sign', sign, lev + 2)
1022 if line: addElemT (c, 'line', line, lev + 2) 1355 if line: addElemT (c, 'line', line, lev + 2)
1023 if trans_oct: 1356 if trans_oct:
1024 n = trans_oct.group (1) in '-_' and -1 or 1 1357 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 1358 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 1359 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 1360 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)) 1361 atts.append ((7, c))
1362 if trans_oct2: # octave= can also be in a K: field
1363 n = int (trans_oct2.group (1))
1364 s.gtrans = gtrans + n
1032 if trans != None: # add transposition in semitones 1365 if trans != None: # add transposition in semitones
1033 e = E.Element ('transpose') 1366 e = E.Element ('transpose')
1034 addElemT (e, 'chromatic', str (trans.group (1)), lev + 3) 1367 addElemT (e, 'chromatic', str (trans.group (1)), lev + 3)
1035 atts.append ((9, e)) 1368 atts.append ((9, e))
1036 if cue_onoff: s.gcue_on = cue_onoff.group (1) == 'on' 1369 if cue_onoff: s.gcue_on = cue_onoff.group (1) == 'on'
1370 nlines = 0
1371 if clef == 'tab':
1372 s.tabStaff = s.pid
1373 if capo: s.capo = int (capo.group (1))
1374 if strings: s.tuning = strings.group (1).split (',')
1375 s.tunmid = [int (boct) * 12 + [0,2,4,5,7,9,11]['CDEFGAB'.index (bstep)] + 12 + s.capo for bstep, boct in s.tuning]
1376 s.tunTup = sorted (zip (s.tunmid, range (len (s.tunmid), 0, -1)), reverse=1)
1377 s.tunmid.reverse ()
1378 nlines = str (len (s.tuning))
1379 s.strAlloc.setlines (len (s.tuning), s.pid)
1380 s.nostems = 'nostems' in field # tab clef without stems
1381 s.diafret = 'diafret' in field # tab with diatonic fretting
1382 if stafflines or nlines:
1383 e = E.Element ('staff-details')
1384 if gstaff: e.set ('number', str (gstaff)) # only add staff number when defined
1385 if not nlines: nlines = stafflines.group (1)
1386 addElemT (e, 'staff-lines', nlines, lev + 2)
1387 if clef == 'tab':
1388 for line, t in enumerate (s.tuning):
1389 st = E.Element ('staff-tuning', line=str(line+1))
1390 addElemT (st, 'tuning-step', t[0], lev + 3)
1391 addElemT (st, 'tuning-octave', t[1], lev + 3)
1392 addElem (e, st, lev + 2)
1393 if s.capo: addElemT (e, 'capo', str (s.capo), lev + 2)
1394 atts.append ((8, e))
1395 s.diafret = 0 # chromatic fretting is default
1037 atts = [] # collect xml attribute elements [(order-number, xml-element), ..] 1396 atts = [] # collect xml attribute elements [(order-number, xml-element), ..]
1397 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
1038 for ftype, field in fieldmap.items (): 1398 for ftype, field in fieldmap.items ():
1039 if not field: # skip empty fields 1399 if not field: # skip empty fields
1040 continue 1400 continue
1041 if ftype == 'Div': # not an abc field, but handled as if 1401 if ftype == 'Div': # not an abc field, but handled as if
1042 d = E.Element ('divisions') 1402 d = E.Element ('divisions')
1084 for step, alter in alts: # correct permanent alterations for this key 1444 for step, alter in alts: # correct permanent alterations for this key
1085 s.keyAlts [step.upper ()] = alter 1445 s.keyAlts [step.upper ()] = alter
1086 k = E.Element ('key') 1446 k = E.Element ('key')
1087 koctave = [] 1447 koctave = []
1088 lowerCaseSteps = [step.upper () for step, alter in alts if step.islower ()] 1448 lowerCaseSteps = [step.upper () for step, alter in alts if step.islower ()]
1089 for step, alter in s.keyAlts.items (): 1449 for step, alter in sorted (list (s.keyAlts.items ())):
1090 if alter == '0': # skip neutrals 1450 if alter == '0': # skip neutrals
1091 del s.keyAlts [step.upper ()] # otherwise you get neutral signs on normal notes 1451 del s.keyAlts [step.upper ()] # otherwise you get neutral signs on normal notes
1092 continue 1452 continue
1093 addElemT (k, 'key-step', step.upper (), lev + 2) 1453 addElemT (k, 'key-step', step.upper (), lev + 2)
1094 addElemT (k, 'key-alter', alter, lev + 2) 1454 addElemT (k, 'key-alter', alter, lev + 2)
1101 elif mode: 1461 elif mode:
1102 k = E.Element ('key') 1462 k = E.Element ('key')
1103 addElemT (k, 'fifths', str (fifths), lev + 2) 1463 addElemT (k, 'fifths', str (fifths), lev + 2)
1104 addElemT (k, 'mode', s.modTab [mode], lev + 2) 1464 addElemT (k, 'mode', s.modTab [mode], lev + 2)
1105 atts.append ((2, k)) 1465 atts.append ((2, k))
1106 doClef () 1466 doClef (field)
1107 elif ftype == 'L': 1467 elif ftype == 'L':
1108 s.unitLcur = map (int, field.split ('/')) 1468 try: s.unitLcur = lmap (int, field.split ('/'))
1469 except: s.unitLcur = (1,8)
1109 if len (s.unitLcur) == 1 or s.unitLcur[1] not in s.typeMap: 1470 if len (s.unitLcur) == 1 or s.unitLcur[1] not in s.typeMap:
1110 info ('L:%s is not allowed, 1/8 assumed' % field) 1471 info ('L:%s is not allowed, 1/8 assumed' % field)
1111 s.unitLcur = 1,8 1472 s.unitLcur = 1,8
1112 elif ftype == 'V': 1473 elif ftype == 'V':
1113 doClef () 1474 doClef (field)
1114 elif ftype == 'I': 1475 elif ftype == 'I':
1115 xs = s.doField_I (ftype, field) 1476 s.doField_I (ftype, field, instDir, addTrans)
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': 1477 elif ftype == 'Q':
1130 s.doTempo (maat, field, lev) 1478 s.doTempo (maat, field, lev)
1479 elif ftype == 'P': # ad hoc translation of P: into a staff text direction
1480 words = E.Element ('rehearsal')
1481 words.set ('font-weight', 'bold')
1482 words.text = field
1483 addDirection (maat, words, lev, gstaff, placement='above')
1131 elif ftype in 'TCOAZNGHRBDFSU': 1484 elif ftype in 'TCOAZNGHRBDFSU':
1132 info ('**illegal header field in body: %s, content: %s' % (ftype, field)) 1485 info ('**illegal header field in body: %s, content: %s' % (ftype, field))
1133 else: 1486 else:
1134 info ('unhandled field: %s, content: %s' % (ftype, field)) 1487 info ('unhandled field: %s, content: %s' % (ftype, field))
1135 1488
1136 if atts: 1489 if atts:
1137 att = E.Element ('attributes') # insert sub elements in the order required by musicXML 1490 att = E.Element ('attributes') # insert sub elements in the order required by musicXML
1138 addElem (maat, att, lev) 1491 addElem (maat, att, lev)
1139 for _, att_elem in sorted (atts): # ordering ! 1492 for _, att_elem in sorted (atts, key=lambda x: x[0]): # ordering !
1140 addElem (att, att_elem, lev + 1) 1493 addElem (att, att_elem, lev + 1)
1494 if s.diafret:
1495 other = E.Element ('other-direction'); other.text = 'diatonic fretting'
1496 addDirection (maat, other, lev, 0)
1141 1497
1142 def doTempo (s, maat, field, lev): 1498 def doTempo (s, maat, field, lev):
1143 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice 1499 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) 1500 t = re.search (r'(\d)/(\d\d?)\s*=\s*(\d[.\d]*)|(\d[.\d]*)', field)
1145 if not t: return 1501 rtxt = re.search (r'"([^"]*)"', field) # look for text in Q: field
1146 try: 1502 if not t and not rtxt: return
1147 if t.group (4): 1503 elems = [] # [(element, sub-elements)] will be added as direction-types
1148 num, den, upm = 1, s.unitLcur[1] , float (t.group (4)) 1504 if rtxt:
1149 else: 1505 num, den, upm = 1, 4, s.tempoMap.get (rtxt.group (1).lower ().strip (), 120)
1150 num, den, upm = int (t.group (1)), int (t.group (2)), float (t.group (3)) 1506 words = E.Element ('words'); words.text = rtxt.group (1)
1151 except: return # float or int conversion failure 1507 elems.append ((words, []))
1152 if num != 1: info ('in Q: numerator > 1 in %d/%d not supported' % (num, den)) 1508 if t:
1509 try:
1510 if t.group (4): num, den, upm = 1, s.unitLcur[1] , float (t.group (4)) # old syntax Q:120
1511 else: num, den, upm = int (t.group (1)), int (t.group (2)), float (t.group (3))
1512 except: info ('conversion error: %s' % field); return
1513 num, den = simplify (num, den);
1514 dotted, den_not = (1, den // 2) if num == 3 else (0, den)
1515 metro = E.Element ('metronome')
1516 u = E.Element ('beat-unit'); u.text = s.typeMap.get (4 * den_not, 'quarter')
1517 pm = E.Element ('per-minute'); pm.text = ('%.2f' % upm).rstrip ('0').rstrip ('.')
1518 subelms = [u, E.Element ('beat-unit-dot'), pm] if dotted else [u, pm]
1519 elems.append ((metro, subelms))
1520 dir = addDirection (maat, elems, lev, gstaff, [], placement='above')
1521 if num != 1 and num != 3: info ('in Q: numerator in %d/%d not supported' % (num, den))
1153 qpm = 4. * num * upm / den 1522 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) 1523 sound = E.Element ('sound'); sound.set ('tempo', '%.2f' % qpm)
1159 addElem (dir, sound, lev + 1) 1524 addElem (dir, sound, lev + 1)
1160 1525
1161 def mkBarline (s, maat, loc, lev, style='', dir='', ending=''): 1526 def mkBarline (s, maat, loc, lev, style='', dir='', ending=''):
1162 b = E.Element ('barline', location=loc) 1527 b = E.Element ('barline', location=loc)
1193 addElem (maat, chord, lev) 1558 addElem (maat, chord, lev)
1194 root = E.Element ('root') 1559 root = E.Element ('root')
1195 addElem (chord, root, lev + 1) 1560 addElem (chord, root, lev + 1)
1196 addElemT (root, 'root-step', rnt[0], lev + 2) 1561 addElemT (root, 'root-step', rnt[0], lev + 2)
1197 if len (rnt) == 2: addElemT (root, 'root-alter', alterMap [rnt[1]], lev + 2) 1562 if len (rnt) == 2: addElemT (root, 'root-alter', alterMap [rnt[1]], lev + 2)
1198 kind = s.chordTab.get (sym.kind.t[0], 'major') 1563 kind = s.chordTab.get (sym.kind.t[0], 'major') if sym.kind.t else 'major'
1199 addElemT (chord, 'kind', kind, lev + 1) 1564 addElemT (chord, 'kind', kind, lev + 1)
1565 if hasattr (sym, 'bass'):
1566 bnt = sym.bass.t
1567 bass = E.Element ('bass')
1568 addElem (chord, bass, lev + 1)
1569 addElemT (bass, 'bass-step', bnt[0], lev + 2)
1570 if len (bnt) == 2: addElemT (bass, 'bass-alter', alterMap [bnt[1]], lev + 2)
1200 degs = getattr (sym, 'degree', '') 1571 degs = getattr (sym, 'degree', '')
1201 if degs: 1572 if degs:
1202 if type (degs) != types.ListType: degs = [degs] 1573 if type (degs) != list_type: degs = [degs]
1203 for deg in degs: 1574 for deg in degs:
1204 deg = deg.t[0] 1575 deg = deg.t[0]
1205 if deg[0] == '#': alter = '1'; deg = deg[1:] 1576 if deg[0] == '#': alter = '1'; deg = deg[1:]
1206 elif deg[0] == 'b': alter = '-1'; deg = deg[1:] 1577 elif deg[0] == 'b': alter = '-1'; deg = deg[1:]
1207 else: alter = '0'; deg = deg 1578 else: alter = '0'; deg = deg
1211 addElemT (degree, 'degree-alter', alter, lev + 2) 1582 addElemT (degree, 'degree-alter', alter, lev + 2)
1212 addElemT (degree, 'degree-type', 'add', lev + 2) 1583 addElemT (degree, 'degree-type', 'add', lev + 2)
1213 1584
1214 def mkMeasure (s, i, t, lev, fieldmap={}): 1585 def mkMeasure (s, i, t, lev, fieldmap={}):
1215 s.msreAlts = {} 1586 s.msreAlts = {}
1216 s.ntup = -1 1587 s.ntup, s.trem, s.intrem = -1, 0, 0
1217 s.acciatura = 0 # next grace element gets acciatura attribute 1588 s.acciatura = 0 # next grace element gets acciatura attribute
1218 overlay = 0 1589 overlay = 0
1219 maat = E.Element ('measure', number = str(i)) 1590 maat = E.Element ('measure', number = str(i))
1220 if fieldmap: s.doFields (maat, fieldmap, lev + 1) 1591 if fieldmap: s.doFields (maat, fieldmap, lev + 1)
1221 if s.linebrk: # there was a line break in the previous measure 1592 if s.linebrk: # there was a line break in the previous measure
1222 e = E.Element ('print') 1593 e = E.Element ('print')
1223 e.set ('new-system', 'yes') 1594 e.set ('new-system', 'yes')
1224 addElem (maat, e, lev + 1) 1595 addElem (maat, e, lev + 1)
1225 s.linebrk = 0 1596 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): 1597 for it, x in enumerate (t):
1230 if x.name == 'note' or x.name == 'rest': 1598 if x.name == 'note' or x.name == 'rest':
1599 if x.dur.t[0] == 0: # a leading zero was used for stemmless in abcm2ps, we only support !stemless!
1600 x.dur.t = tuple ([1, x.dur.t[1]])
1231 note = s.mkNote (x, lev + 1) 1601 note = s.mkNote (x, lev + 1)
1232 addElem (maat, note, lev + 1) 1602 addElem (maat, note, lev + 1)
1233 elif x.name == 'lbar': 1603 elif x.name == 'lbar':
1234 bar = x.t[0] 1604 bar = x.t[0]
1235 if bar == '|': pass # skip redundant bar 1605 if bar == '|' or bar == '[|': pass # skip redundant bar
1236 elif ':' in bar: # forward repeat 1606 elif ':' in bar: # forward repeat
1237 volta = x.t[1] if len (x.t) == 2 else '' 1607 volta = x.t[1] if len (x.t) == 2 else ''
1238 s.mkBarline (maat, 'left', lev + 1, style='heavy-light', dir='forward', ending=volta) 1608 s.mkBarline (maat, 'left', lev + 1, style='heavy-light', dir='forward', ending=volta)
1239 else: # bar must be a volta number 1609 else: # bar must be a volta number
1240 s.mkBarline (maat, 'left', lev + 1, ending=bar) 1610 s.mkBarline (maat, 'left', lev + 1, ending=bar)
1241 elif x.name == 'rbar': 1611 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] 1612 bar = x.t[0]
1245 if bar == '.|': 1613 if bar == '.|':
1246 s.mkBarline (maat, 'right', lev + 1, style='dotted') 1614 s.mkBarline (maat, 'right', lev + 1, style='dotted')
1247 elif ':' in bar: # backward repeat 1615 elif ':' in bar: # backward repeat
1248 s.mkBarline (maat, 'right', lev + 1, style='light-heavy', dir='backward') 1616 s.mkBarline (maat, 'right', lev + 1, style='light-heavy', dir='backward')
1252 s.mkBarline (maat, 'right', lev + 1, style='none') 1620 s.mkBarline (maat, 'right', lev + 1, style='none')
1253 elif '[' in bar or ']' in bar: 1621 elif '[' in bar or ']' in bar:
1254 s.mkBarline (maat, 'right', lev + 1, style='light-heavy') 1622 s.mkBarline (maat, 'right', lev + 1, style='light-heavy')
1255 elif bar[0] == '&': overlay = 1 1623 elif bar[0] == '&': overlay = 1
1256 elif x.name == 'tup': 1624 elif x.name == 'tup':
1257 if len (x.t) == 3: n, into, nts = x.t 1625 if len (x.t) == 3: n, into, nts = x.t
1258 else: n, into, nts = x.t[0], 0, 0 1626 elif len (x.t) == 2: n, into, nts = x.t + [0]
1627 else: n, into, nts = x.t[0], 0, 0
1259 if into == 0: into = 3 if n in [2,4,8] else 2 1628 if into == 0: into = 3 if n in [2,4,8] else 2
1260 if nts == 0: nts = n 1629 if nts == 0: nts = n
1261 s.tmnum, s.tmden, s.ntup = n, into, nts 1630 s.tmnum, s.tmden, s.ntup = n, into, nts
1262 elif x.name == 'deco': 1631 elif x.name == 'deco':
1263 s.staffDecos (x.t, maat, lev + 1) # output staff decos, postpone note decos to next note 1632 s.staffDecos (x.t, maat, lev + 1) # output staff decos, postpone note decos to next note
1267 words = E.Element ('words') 1636 words = E.Element ('words')
1268 words.text = text 1637 words.text = text
1269 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice 1638 gstaff = s.gStaffNums.get (s.vid, 0) # staff number of the current voice
1270 addDirection (maat, words, lev + 1, gstaff, placement=place) 1639 addDirection (maat, words, lev + 1, gstaff, placement=place)
1271 elif x.name == 'inline': 1640 elif x.name == 'inline':
1272 fieldtype, fieldval = x.t[:2] 1641 fieldtype, fieldval = x.t[0], ' '.join (x.t[1:])
1273 s.doFields (maat, {fieldtype:fieldval}, lev + 1) 1642 s.doFields (maat, {fieldtype:fieldval}, lev + 1)
1274 elif x.name == 'accia': s.acciatura = 1 1643 elif x.name == 'accia': s.acciatura = 1
1275 elif x.name == 'linebrk': 1644 elif x.name == 'linebrk':
1276 s.supports_tag = 1 1645 s.supports_tag = 1
1277 if it > 0 and t[it -1].name == 'lbar': # we are at start of measure 1646 if it > 0 and t[it -1].name == 'lbar': # we are at start of measure
1284 s.doChordSym (maat, x, lev + 1) 1653 s.doChordSym (maat, x, lev + 1)
1285 s.stopBeams () 1654 s.stopBeams ()
1286 s.prevmsre = maat 1655 s.prevmsre = maat
1287 return maat, overlay 1656 return maat, overlay
1288 1657
1289 def mkPart (s, maten, id, lev, attrs, nstaves): 1658 def mkPart (s, maten, id, lev, attrs, nstaves, rOpt):
1290 s.slurstack = [] 1659 s.slurstack = {}
1660 s.glisnum = 0; # xml number attribute for glissandos
1661 s.slidenum = 0; # xml number attribute for slides
1291 s.unitLcur = s.unitL # set the default unit length at begin of each voice 1662 s.unitLcur = s.unitL # set the default unit length at begin of each voice
1292 s.curVolta = '' 1663 s.curVolta = ''
1293 s.lyrdash = {} 1664 s.lyrdash = {}
1294 s.linebrk = 0 1665 s.linebrk = 0
1295 s.midprg = ['', ''] # MIDI channel nr, program nr for the current part 1666 s.midprg = ['', '', '', ''] # MIDI channel nr, program nr, volume, panning for the current part
1296 s.gcue_on = 0 # reset cue note marker for each new voice 1667 s.gcue_on = 0 # reset cue note marker for each new voice
1297 s.gtrans = 0 # reset octave transposition (by clef) 1668 s.gtrans = 0 # reset octave transposition (by clef)
1669 s.percVoice = 0 # 1 if percussion clef encountered
1670 s.curClef = '' # current abc clef (for percmap)
1671 s.nostems = 0 # for the tab clef
1672 s.tuning = s.tuningDef # reset string tuning to default
1298 part = E.Element ('part', id=id) 1673 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 1674 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 1675 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 1676 attrs_cpy = attrs.copy () # don't change attrs itself in next line
1302 if gstaff == 1: attrs_cpy ['gstaff'] = nstaves # make a grand staff 1677 if gstaff == 1: attrs_cpy ['gstaff'] = nstaves # make a grand staff
1678 if 'perc' in attrs_cpy.get ('V', ''): del attrs_cpy ['K'] # remove key from percussion voice
1303 msre, overlay = s.mkMeasure (1, maten[0], lev + 1, attrs_cpy) 1679 msre, overlay = s.mkMeasure (1, maten[0], lev + 1, attrs_cpy)
1304 addElem (part, msre, lev + 1) 1680 addElem (part, msre, lev + 1)
1305 for i, maat in enumerate (maten[1:]): 1681 for i, maat in enumerate (maten[1:]):
1306 s.overlayVnum = s.overlayVnum + 1 if overlay else 0 1682 s.overlayVnum = s.overlayVnum + 1 if overlay else 0
1307 msre, next_overlay = s.mkMeasure (i+2, maat, lev + 1) 1683 msre, next_overlay = s.mkMeasure (i+2, maat, lev + 1)
1308 if overlay: mergePartMeasure (part, msre, s.overlayVnum) 1684 if overlay: mergePartMeasure (part, msre, s.overlayVnum, rOpt)
1309 else: addElem (part, msre, lev + 1) 1685 else: addElem (part, msre, lev + 1)
1310 overlay = next_overlay 1686 overlay = next_overlay
1311 return part 1687 return part
1312 1688
1313 def mkScorePart (s, id, vids_p, partAttr, lev): 1689 def mkScorePart (s, id, vids_p, partAttr, lev):
1690 def mkInst (instId, vid, midchan, midprog, midnot, vol, pan, lev):
1691 si = E.Element ('score-instrument', id=instId)
1692 pnm = partAttr.get (vid, [''])[0] # part name if present
1693 addElemT (si, 'instrument-name', pnm or 'dummy', lev + 2) # MuseScore needs a name
1694 mi = E.Element ('midi-instrument', id=instId)
1695 if midchan: addElemT (mi, 'midi-channel', midchan, lev + 2)
1696 if midprog: addElemT (mi, 'midi-program', str (int (midprog) + 1), lev + 2) # compatible with abc2midi
1697 if midnot: addElemT (mi, 'midi-unpitched', str (int (midnot) + 1), lev + 2)
1698 if vol: addElemT (mi, 'volume', '%.2f' % (int (vol) / 1.27), lev + 2)
1699 if pan: addElemT (mi, 'pan', '%.2f' % (int (pan) / 127. * 180 - 90), lev + 2)
1700 return (si, mi)
1314 naam, subnm, midprg = partAttr [id] 1701 naam, subnm, midprg = partAttr [id]
1315 sp = E.Element ('score-part', id='P'+id) 1702 sp = E.Element ('score-part', id='P'+id)
1316 nm = E.Element ('part-name') 1703 nm = E.Element ('part-name')
1317 nm.text = naam 1704 nm.text = naam
1318 addElem (sp, nm, lev + 1) 1705 addElem (sp, nm, lev + 1)
1319 snm = E.Element ('part-abbreviation') 1706 snm = E.Element ('part-abbreviation')
1320 snm.text = subnm 1707 snm.text = subnm
1321 if subnm: addElem (sp, snm, lev + 1) # only add if subname was given 1708 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 = [] 1709 inst = []
1325 for id in instr_vids: 1710 for instId, (pid, vid, chan, midprg, vol, pan) in sorted (s.midiInst.items ()):
1326 if id not in partAttr: continue # error in %%score -> instr_vids may have non existing id's 1711 midprg, midnot = ('0', midprg) if chan == '10' else (midprg, '')
1327 naam, subnm, midprg = partAttr [id] 1712 if pid == id: inst.append (mkInst (instId, vid, chan, midprg, midnot, vol, pan, lev))
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) 1713 for si, mi in inst: addElem (sp, si, lev + 1)
1337 for si, mi in inst: addElem (sp, mi, lev + 1) 1714 for si, mi in inst: addElem (sp, mi, lev + 1)
1338 return sp, len (inst) 1715 return sp
1339 1716
1340 def mkPartlist (s, vids, partAttr, lev): 1717 def mkPartlist (s, vids, partAttr, lev):
1341 def addPartGroup (sym, num): 1718 def addPartGroup (sym, num):
1342 pg = E.Element ('part-group', number=str (num), type='start') 1719 pg = E.Element ('part-group', number=str (num), type='start')
1343 addElem (partlist, pg, lev + 1) 1720 addElem (partlist, pg, lev + 1)
1344 addElemT (pg, 'group-symbol', sym, lev + 2) 1721 addElemT (pg, 'group-symbol', sym, lev + 2)
1345 addElemT (pg, 'group-barline', 'yes', lev + 2) 1722 addElemT (pg, 'group-barline', 'yes', lev + 2)
1346 partlist = E.Element ('part-list') 1723 partlist = E.Element ('part-list')
1347 g_num = 0 # xml group number 1724 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 1725 for g in (s.groups or vids): # brace/bracket or abc_voice_id
1350 if g == '[': g_num += 1; addPartGroup ('bracket', g_num) 1726 if g == '[': g_num += 1; addPartGroup ('bracket', g_num)
1351 elif g == '{': g_num += 1; addPartGroup ('brace', g_num) 1727 elif g == '{': g_num += 1; addPartGroup ('brace', g_num)
1352 elif g in '}]': 1728 elif g in '}]':
1353 pg = E.Element ('part-group', number=str (g_num), type='stop') 1729 pg = E.Element ('part-group', number=str (g_num), type='stop')
1354 addElem (partlist, pg, lev + 1) 1730 addElem (partlist, pg, lev + 1)
1355 g_num -= 1 1731 g_num -= 1
1356 else: # g = abc_voice_id 1732 else: # g = abc_voice_id
1357 if g not in vids: continue # error in %%score 1733 if g not in vids: continue # error in %%score
1358 sp, nInst = s.mkScorePart (g, vids, partAttr, lev + 1) 1734 sp = s.mkScorePart (g, vids, partAttr, lev + 1)
1359 addElem (partlist, sp, lev + 1) 1735 addElem (partlist, sp, lev + 1)
1360 nInstrs.append (nInst) 1736 return partlist
1361 return partlist, nInstrs 1737
1362 1738 def doField_I (s, type, x, instDir, addTrans):
1363 def doField_I (s, type, x): 1739 def instChange (midchan, midprog): # instDir -> doFields
1740 if midchan and midchan != s.midprg [0]: instDir ('midi-channel', midchan, 'chan: %s')
1741 if midprog and midprog != s.midprg [1]: instDir ('midi-program', str (int (midprog) + 1), 'prog: %s')
1364 def readPfmt (x, n): # read ABC page formatting constant 1742 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 1743 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 1744 ro = re.search (r'[^.\d]*([\d.]+)\s*(cm|in|pt)?', x) # float followed by unit
1367 if ro: 1745 if ro:
1368 x, unit = ro.groups () # unit == None when not present 1746 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. 1747 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 1748 s.pageFmtAbc [n] = float (x) * u # convert ABC values to millimeters
1371 else: info ('error in page format: %s' % x) 1749 else: info ('error in page format: %s' % x)
1372 1750 def readPercMap (x): # parse I:percmap <abc_note> <step> <MIDI> <notehead>
1751 def getMidNum (sndnm): # find midi number of GM drum sound name
1752 pnms = sndnm.split ('-') # sound name parts (from I:percmap)
1753 ps = s.percsnd [:] # copy of the instruments
1754 _f = lambda ip, xs, pnm: ip < len (xs) and xs[ip].find (pnm) > -1 # part xs[ip] and pnm match
1755 for ip, pnm in enumerate (pnms): # match all percmap sound name parts
1756 ps = [(nm, mnum) for nm, mnum in ps if _f (ip, nm.split ('-'), pnm) ] # filter instruments
1757 if len (ps) <= 1: break # no match or one instrument left
1758 if len (ps) == 0: info ('drum sound: %s not found' % sndnm); return '38'
1759 return ps [0][1] # midi number of (first) instrument found
1760 def midiVal (acc, step, oct): # abc note -> midi note number
1761 oct = (4 if step.upper() == step else 5) + int (oct)
1762 return oct * 12 + [0,2,4,5,7,9,11]['CDEFGAB'.index (step.upper())] + {'^':1,'_':-1,'=':0}.get (acc, 0) + 12
1763 p0, p1, p2, p3, p4 = abc_percmap.parseString (x).asList () # percmap, abc-note, display-step, midi, note-head
1764 acc, astep, aoct = p1
1765 nstep, noct = (astep, aoct) if p2 == '*' else p2
1766 if p3 == '*': midi = str (midiVal (acc, astep, aoct))
1767 elif isinstance (p3, list_type): midi = str (midiVal (p3[0], p3[1], p3[2]))
1768 elif isinstance (p3, int_type): midi = str (p3)
1769 else: midi = getMidNum (p3.lower ())
1770 head = re.sub (r'(.)-([^x])', r'\1 \2', p4) # convert abc note head names to xml
1771 s.percMap [(s.pid, acc + astep, aoct)] = (nstep, noct, midi, head)
1373 if x.startswith ('score') or x.startswith ('staves'): 1772 if x.startswith ('score') or x.startswith ('staves'):
1374 s.staveDefs += [x] # collect all voice mappings 1773 s.staveDefs += [x] # collect all voice mappings
1375 elif x.startswith ('staffwidth'): info ('skipped I-field: %s' % x) 1774 elif x.startswith ('staffwidth'): info ('skipped I-field: %s' % x)
1376 elif x.startswith ('staff'): # set new staff number of the current voice 1775 elif x.startswith ('staff'): # set new staff number of the current voice
1377 r1 = re.search (r'staff *([+-]?)(\d)', x) 1776 r1 = re.search (r'staff *([+-]?)(\d)', x)
1394 elif x.startswith ('pagewidth'): readPfmt (x, 2) 1793 elif x.startswith ('pagewidth'): readPfmt (x, 2)
1395 elif x.startswith ('leftmargin'): readPfmt (x, 3) 1794 elif x.startswith ('leftmargin'): readPfmt (x, 3)
1396 elif x.startswith ('rightmargin'): readPfmt (x, 4) 1795 elif x.startswith ('rightmargin'): readPfmt (x, 4)
1397 elif x.startswith ('topmargin'): readPfmt (x, 5) 1796 elif x.startswith ('topmargin'): readPfmt (x, 5)
1398 elif x.startswith ('botmargin'): readPfmt (x, 6) 1797 elif x.startswith ('botmargin'): readPfmt (x, 6)
1399 elif x.startswith ('MIDI'): 1798 elif x.startswith ('MIDI') or x.startswith ('midi'):
1400 r1 = re.search (r'program *(\d*) +(\d+)', x) 1799 r1 = re.search (r'program *(\d*) +(\d+)', x)
1401 r2 = re.search (r'channel\D*(\d+)', x) 1800 r2 = re.search (r'channel *(\d+)', x)
1402 if r1: ch, prg = r1.groups () # channel nr or '', program nr 1801 r3 = re.search (r"drummap\s+([_=^]*)([A-Ga-g])([,']*)\s+(\d+)", x)
1403 if r2: ch, prg = r2.group (1), '' # channel nr only 1802 r4 = re.search (r'control *(\d+) +(\d+)', x)
1404 if r1 or r2: 1803 ch_nw, prg_nw, vol_nw, pan_nw = '', '', '', ''
1405 if s.midprg[1] == '': # no instrument defined yet 1804 if r1: ch_nw, prg_nw = r1.groups () # channel nr or '', program nr
1406 s.midprg[1] = prg 1805 if r2: ch_nw = r2.group (1) # channel nr only
1407 if ch: s.midprg[0] = ch 1806 if r4:
1408 elif ch and s.midprg[0] == '': # no channel defined yet 1807 cnum, cval = r4.groups () # controller number, controller value
1409 s.midprg[0] = ch 1808 if cnum == '7': vol_nw = cval
1410 else: # repeated midi def -> insert instument change 1809 if cnum == '10': pan_nw = cval
1411 return [ch, prg] 1810 if r1 or r2 or r4:
1811 ch = ch_nw or s.midprg [0]
1812 prg = prg_nw or s.midprg [1]
1813 vol = vol_nw or s.midprg [2]
1814 pan = pan_nw or s.midprg [3]
1815 instId = 'I%s-%s' % (s.pid, s.vid) # only look for real instruments, no percussion
1816 if instId in s.midiInst: instChange (ch, prg) # instChance -> doFields
1817 s.midprg = [ch, prg, vol, pan] # mknote: new instrument -> s.midiInst
1818 if r3: # translate drummap to percmap
1819 acc, step, oct, midi = r3.groups ()
1820 oct = -len (oct) if ',' in x else len (oct)
1821 notehead = 'x' if acc == '^' else 'circle-x' if acc == '_' else 'normal'
1822 s.percMap [(s.pid, acc + step, oct)] = (step, oct, midi, notehead)
1412 r = re.search (r'transpose[^-\d]*(-?\d+)', x) 1823 r = re.search (r'transpose[^-\d]*(-?\d+)', x)
1413 if r: return [r.group (1)] 1824 if r: addTrans (r.group (1)) # addTrans -> doFields
1825 elif x.startswith ('percmap'): readPercMap (x); s.pMapFound = 1
1414 else: info ('skipped I-field: %s' % x) 1826 else: info ('skipped I-field: %s' % x)
1415 1827
1416 def parseStaveDef (s, vdefs): 1828 def parseStaveDef (s, vdefs):
1829 for vid in vdefs: s.vcepid [vid] = vid # default: each voice becomes an xml part
1417 if not s.staveDefs: return vdefs 1830 if not s.staveDefs: return vdefs
1418 for x in s.staveDefs [1:]: info ('%%%%%s dropped, multiple stave mappings not supported' % x) 1831 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 1832 x = s.staveDefs [0] # only the first %%score is honoured
1420 score = abc_scoredef.parseString (x) [0] 1833 score = abc_scoredef.parseString (x) [0]
1421 f = lambda x: type (x) == types.UnicodeType and [x] or x 1834 f = lambda x: type (x) == uni_type and [x] or x
1422 s.staves = map (f, mkStaves (score, vdefs)) 1835 s.staves = lmap (f, mkStaves (score, vdefs)) # [[vid] for each staff]
1423 s.grands = map (f, mkGrand (score, vdefs)) 1836 s.grands = lmap (f, mkGrand (score, vdefs)) # [staff-id], staff-id == [vid][0]
1424 s.groups = mkGroups (score) 1837 s.groups = mkGroups (score)
1425 vce_groups = [vids for vids in s.staves if len (vids) > 1] # all voice groups 1838 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 1839 d = {} # for each voice group: map first voice id -> all merged voice ids
1427 for vgr in vce_groups: d [vgr[0]] = vgr 1840 for vgr in vce_groups: d [vgr[0]] = vgr
1428 for gstaff in s.grands: # for all grand staves 1841 for gstaff in s.grands: # for all grand staves
1430 for v, stf_num in zip (gstaff, range (1, len (gstaff) + 1)): 1843 for v, stf_num in zip (gstaff, range (1, len (gstaff) + 1)):
1431 for vx in d.get (v, [v]): # allocate staff numbers 1844 for vx in d.get (v, [v]): # allocate staff numbers
1432 s.gStaffNums [vx] = stf_num # to all constituant voices 1845 s.gStaffNums [vx] = stf_num # to all constituant voices
1433 s.gNstaves [vx] = len (gstaff) # also remember total number of staves 1846 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 1847 s.gStaffNumsOrg = s.gStaffNums.copy () # keep original allocation for abc -> xml staff map
1848 for xmlpart in s.grands:
1849 pid = xmlpart [0] # part id == first staff id == first voice id
1850 vces = [v for stf in xmlpart for v in d.get (stf, [stf])]
1851 for v in vces: s.vcepid [v] = pid
1435 return vdefs 1852 return vdefs
1436 1853
1437 def voiceNamesAndMaps (s, ps): # get voice names and mappings 1854 def voiceNamesAndMaps (s, ps): # get voice names and mappings
1438 vdefs = {} 1855 vdefs = {}
1439 for vid, vcedef, vce in ps: # vcedef == emtpy of first pObj == voice definition 1856 for vid, vcedef, vce in ps: # vcedef == emtpy or first pObj == voice definition
1440 pname, psubnm = '', '' # part name and abbreviation 1857 pname, psubnm = '', '' # part name and abbreviation
1441 if not vcedef: # simple abc without voice definitions 1858 if not vcedef: # simple abc without voice definitions
1442 vdefs [vid] = pname, psubnm, '' 1859 vdefs [vid] = pname, psubnm, ''
1443 else: # abc with voice definitions 1860 else: # abc with voice definitions
1444 if vid != vcedef.t[1]: info ('voice ids unequal: %s (reg-ex) != %s (grammar)' % (vid, vcedef.t[1])) 1861 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]) 1862 rn = re.search (r'(?:name|nm)="([^"]*)"', vcedef.t[2])
1446 if rn: pname = rn.group (1) 1863 if rn: pname = rn.group (1)
1447 rn = re.search (r'(?:subname|snm|sname)="([^"]*)"', vcedef.t[2]) 1864 rn = re.search (r'(?:subname|snm|sname)="([^"]*)"', vcedef.t[2])
1448 if rn: psubnm = rn.group (1) 1865 if rn: psubnm = rn.group (1)
1866 vcedef.t[2] = vcedef.t[2].replace ('"%s"' % pname, '""').replace ('"%s"' % psubnm, '""') # clear voice name to avoid false clef matches later on
1449 vdefs [vid] = pname, psubnm, vcedef.t[2] 1867 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 1868 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 1869 s.staveDefs += [x.replace ('%5d',']') for x in xs if x.startswith ('score') or x.startswith ('staves')] # filter %%score and %%staves
1452 return vdefs 1870 return vdefs
1453 1871
1454 def doHeaderField (s, fld, attrmap): 1872 def doHeaderField (s, fld, attrmap):
1455 type, value = fld.t[:2] 1873 type, value = fld.t[0], fld.t[1].replace ('%5d',']') # restore closing brackets (see splitHeaderVoices)
1456 if not value: # skip empty field 1874 if not value: # skip empty field
1457 return 1875 return
1458 if type == 'M': 1876 if type == 'M':
1459 attrmap [type] = value 1877 attrmap [type] = value
1460 elif type == 'L': 1878 elif type == 'L':
1461 try: s.unitL = map (int, fld.t[1].split ('/')) 1879 try: s.unitL = lmap (int, fld.t[1].split ('/'))
1462 except: 1880 except:
1463 info ('illegal unit length:%s, 1/8 assumed' % fld.t[1]) 1881 info ('illegal unit length:%s, 1/8 assumed' % fld.t[1])
1464 s.unitL = 1,8 1882 s.unitL = 1,8
1465 if len (s.unitL) == 1 or s.unitL[1] not in s.typeMap: 1883 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]) 1884 info ('L:%s is not allowed, 1/8 assumed' % fld.t[1])
1467 s.unitL = 1,8 1885 s.unitL = 1,8
1468 elif type == 'K': 1886 elif type == 'K':
1469 attrmap[type] = value 1887 attrmap[type] = value
1470 elif type == 'T': 1888 elif type == 'T':
1471 if s.title: s.title = s.title + '\n' + value 1889 s.title = s.title + '\n' + value if s.title else 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': 1890 elif type == 'U':
1478 sym = fld.t[2].strip ('!+') 1891 sym = fld.t[2].strip ('!+')
1479 s.usrSyms [value] = sym 1892 s.usrSyms [value] = sym
1480 elif type == 'I': 1893 elif type == 'I':
1481 s.doField_I (type, value) 1894 s.doField_I (type, value, lambda x,y,z:0, lambda x:0)
1482 elif type == 'Q': 1895 elif type == 'Q':
1483 attrmap[type] = value 1896 attrmap[type] = value
1484 elif type in s.creditTab: s.credits [s.creditTab [type]] = value 1897 elif type in 'CRZNOAGHBDFSP': # part maps are treated as meta data
1898 type = s.metaMap.get (type, type) # respect the (user defined --meta) mapping of various ABC fields to XML meta data types
1899 c = s.metadata.get (type, '')
1900 s.metadata [type] = c + '\n' + value if c else value # concatenate multiple info fields with new line as separator
1485 else: 1901 else:
1486 info ('skipped header: %s' % fld) 1902 info ('skipped header: %s' % fld)
1487 1903
1488 def mkIdentification (s, score, lev): 1904 def mkIdentification (s, score, lev):
1489 if s.title: 1905 if s.title:
1490 addElemT (score, 'movement-title', s.title, lev + 1) 1906 xs = s.title.split ('\n') # the first T: line goes to work-title
1907 ys = '\n'.join (xs [1:]) # place subsequent T: lines into work-number
1908 w = E.Element ('work')
1909 addElem (score, w, lev + 1)
1910 if ys: addElemT (w, 'work-number', ys, lev + 2)
1911 addElemT (w, 'work-title', xs[0], lev + 2)
1491 ident = E.Element ('identification') 1912 ident = E.Element ('identification')
1492 addElem (score, ident, lev + 1) 1913 addElem (score, ident, lev + 1)
1493 if s.creator: 1914 for mtype, mval in s.metadata.items ():
1494 for ctype, cname in s.creator.items (): 1915 if mtype in s.metaTypes and mtype != 'rights': # all metaTypes are MusicXML creator types
1495 c = E.Element ('creator', type=ctype) 1916 c = E.Element ('creator', type=mtype)
1496 c.text = cname 1917 c.text = mval
1497 addElem (ident, c, lev + 2) 1918 addElem (ident, c, lev + 2)
1919 if 'rights' in s.metadata:
1920 c = addElemT (ident, 'rights', s.metadata ['rights'], lev + 2)
1498 encoding = E.Element ('encoding') 1921 encoding = E.Element ('encoding')
1499 addElem (ident, encoding, lev + 2) 1922 addElem (ident, encoding, lev + 2)
1500 encoder = E.Element ('encoder') 1923 encoder = E.Element ('encoder')
1501 encoder.text = 'abc2xml version %d' % VERSION 1924 encoder.text = 'abc2xml version %d' % VERSION
1502 addElem (encoding, encoder, lev + 3) 1925 addElem (encoding, encoder, lev + 3)
1504 suports = E.Element ('supports', attribute="new-system", element="print", type="yes", value="yes") 1927 suports = E.Element ('supports', attribute="new-system", element="print", type="yes", value="yes")
1505 addElem (encoding, suports, lev + 3) 1928 addElem (encoding, suports, lev + 3)
1506 encodingDate = E.Element ('encoding-date') 1929 encodingDate = E.Element ('encoding-date')
1507 encodingDate.text = str (datetime.date.today ()) 1930 encodingDate.text = str (datetime.date.today ())
1508 addElem (encoding, encodingDate, lev + 3) 1931 addElem (encoding, encodingDate, lev + 3)
1932 s.addMeta (ident, lev + 2)
1509 1933
1510 def mkDefaults (s, score, lev): 1934 def mkDefaults (s, score, lev):
1511 if s.pageFmtCmd: s.pageFmtAbc = s.pageFmtCmd 1935 if s.pageFmtCmd: s.pageFmtAbc = s.pageFmtCmd
1512 if not s.pageFmtAbc: return # do not output the defaults if none is desired 1936 if not s.pageFmtAbc: return # do not output the defaults if none is desired
1513 space, h, w, l, r, t, b = s.pageFmtAbc 1937 abcScale, h, w, l, r, t, b = s.pageFmtAbc
1938 space = abcScale * 2.117 # 2.117 = 6pt = space between staff lines for scale = 1.0 in abcm2ps
1514 mils = 4 * space # staff height in millimeters 1939 mils = 4 * space # staff height in millimeters
1515 scale = 40. / mils # tenth's per millimeter 1940 scale = 40. / mils # tenth's per millimeter
1516 dflts = E.Element ('defaults') 1941 dflts = E.Element ('defaults')
1517 addElem (score, dflts, lev) 1942 addElem (score, dflts, lev)
1518 scaling = E.Element ('scaling') 1943 scaling = E.Element ('scaling')
1528 addElemT (margins, 'left-margin', '%g' % (l * scale), lev + 3) 1953 addElemT (margins, 'left-margin', '%g' % (l * scale), lev + 3)
1529 addElemT (margins, 'right-margin', '%g' % (r * scale), lev + 3) 1954 addElemT (margins, 'right-margin', '%g' % (r * scale), lev + 3)
1530 addElemT (margins, 'top-margin', '%g' % (t * scale), lev + 3) 1955 addElemT (margins, 'top-margin', '%g' % (t * scale), lev + 3)
1531 addElemT (margins, 'bottom-margin', '%g' % (b * scale), lev + 3) 1956 addElemT (margins, 'bottom-margin', '%g' % (b * scale), lev + 3)
1532 1957
1533 def mkCredits (s, score, lev): 1958 def addMeta (s, parent, lev):
1534 if not s.credits: return 1959 misc = E.Element ('miscellaneous')
1535 for ctype, ctext in s.credits.items (): 1960 mf = 0
1536 credit = E.Element ('credit', page='1') 1961 for mtype, mval in sorted (s.metadata.items ()):
1537 addElemT (credit, 'credit-type', ctype, lev + 2) 1962 if mtype == 'S':
1538 addElemT (credit, 'credit-words', ctext, lev + 2) 1963 addElemT (parent, 'source', mval, lev)
1539 addElem (score, credit, lev) 1964 elif mtype in s.metaTypes: continue # mapped meta data has already been output (in creator elements)
1540 1965 else:
1541 def parse (s, abc_string): 1966 mf = E.Element ('miscellaneous-field', name=s.metaTab [mtype])
1542 abctext = abc_string if type (abc_string) == types.UnicodeType else decodeInput (abc_string) 1967 mf.text = mval
1543 abctext = abctext.replace ('[I:staff ','[I:staff') # avoid false beam breaks 1968 addElem (misc, mf, lev + 1)
1544 s.reset () 1969 if mf != 0: addElem (parent, misc, lev)
1970
1971 def parse (s, abc_string, rOpt=False, bOpt=False, fOpt=False):
1972 abctext = abc_string.replace ('[I:staff ','[I:staff') # avoid false beam breaks
1973 s.reset (fOpt)
1545 header, voices = splitHeaderVoices (abctext) 1974 header, voices = splitHeaderVoices (abctext)
1546 ps = [] 1975 ps = []
1547 try: 1976 try:
1977 lbrk_insert = 0 if re.search (r'I:linebreak\s*([!$]|none)|I:continueall\s*(1|true)', header) else bOpt
1548 hs = abc_header.parseString (header) if header else '' 1978 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) 1979 for id, voice in voices:
1550 vcelyr = [] # list of measures where measure = list of elements (see syntax) 1980 if lbrk_insert: # insert linebreak at EOL
1981 r1 = re.compile (r'\[[wA-Z]:[^]]*\]') # inline field
1982 has_abc = lambda x: r1.sub ('', x).strip () # empty if line only contains inline fields
1983 voice = '\n'.join ([balk.rstrip ('$!') + '$' if has_abc (balk) else balk for balk in voice.splitlines ()])
1551 prevLeftBar = None # previous voice ended with a left-bar symbol (double repeat) 1984 prevLeftBar = None # previous voice ended with a left-bar symbol (double repeat)
1552 for voice, lyr in vce_lyr: 1985 s.orderChords = s.fOpt and ('tab' in voice [:200] or [x for x in hs if x.t[0] == 'K' and 'tab' in x.t[1]])
1553 vce = abc_voice.parseString (voice).asList () 1986 vce = abc_voice.parseString (voice).asList ()
1554 if not vce: # empty voice, insert an inline field that will be rejected 1987 lyr_notes = [] # remember notes between lyric blocks
1555 vce = [[pObj ('inline', ['I', 'empty voice'])]] 1988 for m in vce: # all measures
1556 if prevLeftBar: 1989 for e in m: # all abc-elements
1557 vce[0].insert (0, prevLeftBar) # insert at begin of first measure 1990 if e.name == 'lyr_blk': # -> e.objs is list of lyric lines
1558 prevLeftBar = None 1991 lyr = [line.objs for line in e.objs] # line.objs is listof syllables
1559 if vce[-1] and vce[-1][-1].name == 'lbar': # last measure ends with an lbar 1992 alignLyr (lyr_notes, lyr) # put all syllables into corresponding notes
1560 prevLeftBar = vce[-1][-1] 1993 lyr_notes = []
1561 if len (vce) > 1: # vce should not become empty (-> exception when taking vcelyr [0][0]) 1994 else:
1562 del vce[-1] # lbar was the only element in measure vce[-1] 1995 lyr_notes.append (e)
1563 lyr = lyr.strip () # strip leading \n (because we split on '\nw:...') 1996 if not vce: # empty voice, insert an inline field that will be rejected
1564 if lyr: # no lyrics for this measures-lyrics block 1997 vce = [[pObj ('inline', ['I', 'empty voice'])]]
1565 lyr = lyr_block.parseString (lyr).asList () 1998 if prevLeftBar:
1566 xs = alignLyr (vce, lyr) # put all syllables into corresponding notes 1999 vce[0].insert (0, prevLeftBar) # insert at begin of first measure
1567 else: xs = vce 2000 prevLeftBar = None
1568 vcelyr += xs 2001 if vce[-1] and vce[-1][-1].name == 'lbar': # last measure ends with an lbar
2002 prevLeftBar = vce[-1][-1]
2003 if len (vce) > 1: # vce should not become empty (-> exception when taking vcelyr [0][0])
2004 del vce[-1] # lbar was the only element in measure vce[-1]
2005 vcelyr = vce
1569 elem1 = vcelyr [0][0] # the first element of the first measure 2006 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 2007 if elem1.name == 'inline'and elem1.t[0] == 'V': # is a voice definition
1571 voicedef = elem1 2008 voicedef = elem1
1572 del vcelyr [0][0] # do not read voicedef twice 2009 del vcelyr [0][0] # do not read voicedef twice
1573 else: 2010 else:
1574 voicedef = '' 2011 voicedef = ''
1575 ps.append ((id, voicedef, vcelyr)) 2012 ps.append ((id, voicedef, vcelyr))
1576 except ParseException, err: 2013 except ParseException as err:
1577 if err.loc > 40: # limit length of error message, compatible with markInputline 2014 if err.loc > 40: # limit length of error message, compatible with markInputline
1578 err.pstr = err.pstr [err.loc - 40: err.loc + 40] 2015 err.pstr = err.pstr [err.loc - 40: err.loc + 40]
1579 err.loc = 40 2016 err.loc = 40
1580 xs = err.line[err.col-1:] 2017 xs = err.line[err.col-1:]
1581 try: info (err.line.encode ('utf-8'), warn=0) # err.line is a unicode string!! 2018 info (err.line, warn=0)
1582 except: info (err.line.encode ('latin-1'), warn=0)
1583 info ((err.col-1) * '-' + '^', warn=0) 2019 info ((err.col-1) * '-' + '^', warn=0)
1584 if re.search (r'\[U:[XYZxyz]', xs): 2020 if re.search (r'\[U:', xs):
1585 info ('Error: illegal user defined symbol: %s' % xs[1:], warn=0) 2021 info ('Error: illegal user defined symbol: %s' % xs[1:], warn=0)
1586 elif re.search (r'\[[OAPZNGHRBDFSXTCIU]:', xs): 2022 elif re.search (r'\[[OAPZNGHRBDFSXTCIU]:', xs):
1587 info ('Error: header-only field %s appears after K:' % xs[1:], warn=0) 2023 info ('Error: header-only field %s appears after K:' % xs[1:], warn=0)
1588 else: 2024 else:
1589 info ('Syntax error at column %d' % err.col, warn=0) 2025 info ('Syntax error at column %d' % err.col, warn=0)
1590 raise err 2026 raise
1591 2027
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') 2028 score = E.Element ('score-partwise')
1597 attrmap = {'Div': str (s.divisions), 'K':'C treble', 'M':'4/4'} 2029 attrmap = {'Div': str (s.divisions), 'K':'C treble', 'M':'4/4'}
1598 for res in hs: 2030 for res in hs:
1599 if res.name == 'field': 2031 if res.name == 'field':
1600 s.doHeaderField (res, attrmap) 2032 s.doHeaderField (res, attrmap)
1604 vdefs = s.voiceNamesAndMaps (ps) 2036 vdefs = s.voiceNamesAndMaps (ps)
1605 vdefs = s.parseStaveDef (vdefs) 2037 vdefs = s.parseStaveDef (vdefs)
1606 2038
1607 lev = 0 2039 lev = 0
1608 vids, parts, partAttr = [], [], {} 2040 vids, parts, partAttr = [], [], {}
2041 s.strAlloc = stringAlloc ()
1609 for vid, _, vce in ps: # voice id, voice parse tree 2042 for vid, _, vce in ps: # voice id, voice parse tree
1610 pname, psubnm, voicedef = vdefs [vid] # part name 2043 pname, psubnm, voicedef = vdefs [vid] # part name
1611 attrmap ['V'] = voicedef # abc text of first voice definition (after V:vid) or empty 2044 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 2045 pid = 'P%s' % vid # let part id start with an alpha
1613 s.vid = vid # avoid parameter passing, needed in mkNote for instrument id 2046 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)) 2047 s.pid = s.vcepid [s.vid] # xml part-id for the current voice
2048 s.gTime = (0, 0) # reset time
2049 s.strAlloc.beginZoek () # reset search index
2050 part = s.mkPart (vce, pid, lev + 1, attrmap, s.gNstaves.get (vid, 0), rOpt)
1615 if 'Q' in attrmap: del attrmap ['Q'] # header tempo only in first part 2051 if 'Q' in attrmap: del attrmap ['Q'] # header tempo only in first part
1616 parts.append (part) 2052 parts.append (part)
1617 vids.append (vid) 2053 vids.append (vid)
1618 partAttr [vid] = (pname, psubnm, s.midprg) 2054 partAttr [vid] = (pname, psubnm, s.midprg)
1619 parts, vidsnew = mergeParts (parts, vids, s.staves) # merge parts into staves as indicated by %%score 2055 if s.midprg != ['', '', '', ''] and not s.percVoice: # when a part has only rests
1620 parts, _ = mergeParts (parts, vidsnew, s.grands, 1) # merge grand staves 2056 instId = 'I%s-%s' % (s.pid, s.vid)
2057 if instId not in s.midiInst: s.midiInst [instId] = (s.pid, s.vid, s.midprg [0], s.midprg [1], s.midprg [2], s.midprg [3])
2058 parts, vidsnew = mergeParts (parts, vids, s.staves, rOpt) # merge parts into staves as indicated by %%score
2059 parts, vidsnew = mergeParts (parts, vidsnew, s.grands, rOpt, 1) # merge grand staves
2060 reduceMids (parts, vidsnew, s.midiInst)
1621 2061
1622 s.mkIdentification (score, lev) 2062 s.mkIdentification (score, lev)
1623 s.mkDefaults (score, lev + 1) 2063 s.mkDefaults (score, lev + 1)
1624 s.mkCredits (score, lev) 2064
1625 2065 partlist = s.mkPartlist (vids, partAttr, lev + 1)
1626 partlist, nInstrs = s.mkPartlist (vids, partAttr, lev + 1)
1627 addElem (score, partlist, lev + 1) 2066 addElem (score, partlist, lev + 1)
1628 for ip, part in enumerate (parts): 2067 for ip, part in enumerate (parts): addElem (score, part, lev + 1)
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 2068
1633 return score 2069 return score
2070
1634 2071
1635 def decodeInput (data_string): 2072 def decodeInput (data_string):
1636 try: enc = 'utf-8'; unicode_string = data_string.decode (enc) 2073 try: enc = 'utf-8'; unicode_string = data_string.decode (enc)
1637 except: 2074 except:
1638 try: enc = 'latin-1'; unicode_string = data_string.decode (enc) 2075 try: enc = 'latin-1'; unicode_string = data_string.decode (enc)
1639 except: raise Exception ('data not encoded in utf-8 nor in latin-1') 2076 except: raise ValueError ('data not encoded in utf-8 nor in latin-1')
1640 info ('decoded from %s' % enc) 2077 info ('decoded from %s' % enc)
1641 return unicode_string 2078 return unicode_string
1642 2079
2080 def ggd (a, b): # greatest common divisor
2081 return a if b == 0 else ggd (b, a % b)
2082
1643 xmlVersion = "<?xml version='1.0' encoding='utf-8'?>" 2083 xmlVersion = "<?xml version='1.0' encoding='utf-8'?>"
1644 def fixDoctype (elem, enc): 2084 def fixDoctype (elem):
1645 xs = E.tostring (elem, encoding=enc) 2085 if python3: xs = E.tostring (elem, encoding='unicode') # writing to file will auto-encode to utf-8
2086 else: xs = E.tostring (elem, encoding='utf-8') # keep the string utf-8 encoded for writing to file
1646 ys = xs.split ('\n') 2087 ys = xs.split ('\n')
1647 if enc == 'utf-8': ys.insert (0, xmlVersion) # crooked logic of ElementTree lib 2088 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">') 2089 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) 2090 return '\n'.join (ys)
1650 2091
1651 def xml2mxl (pad, fnm, data): # write xml data to compressed .mxl file 2092 def xml2mxl (pad, fnm, data): # write xml data to compressed .mxl file
1652 from zipfile import ZipFile, ZIP_DEFLATED 2093 from zipfile import ZipFile, ZIP_DEFLATED
1659 f.writestr ('META-INF/container.xml', meta) 2100 f.writestr ('META-INF/container.xml', meta)
1660 f.writestr (fnmext, data) 2101 f.writestr (fnmext, data)
1661 f.close () 2102 f.close ()
1662 info ('%s written' % outfile, warn=0) 2103 info ('%s written' % outfile, warn=0)
1663 2104
1664 def convert (pad, fnm, abc_string, mxl): 2105 def convert (pad, fnm, abc_string, mxl, rOpt=False, tOpt=False, bOpt=False, fOpt=False): # not used, backwards compatibility
1665 # these globals should be initialised (as in the __main__ secion) before calling convert 2106 score = mxm.parse (abc_string, rOpt, bOpt, fOpt)
1666 global mxm # optimisation 1: keep instance of MusicXml 2107 writefile (pad, fnm, '', score, mxl, tOpt)
1667 global abc_header, abc_voice, lyr_block, abc_scoredef # optimisation 2: keep computed grammars 2108
1668 score = mxm.parse (abc_string) 2109 def writefile (pad, fnm, fnmNum, xmldoc, mxlOpt, tOpt=False):
2110 ipad, ifnm = os.path.split (fnm) # base name of input path is
2111 if tOpt:
2112 x = xmldoc.findtext ('work/work-title', 'no_title')
2113 ifnm = x.replace (',','_').replace ("'",'_').replace ('?','_')
2114 else:
2115 ifnm += fnmNum
2116 xmlstr = fixDoctype (xmldoc)
1669 if pad: 2117 if pad:
1670 data = fixDoctype (score, 'utf-8') 2118 if not mxlOpt or mxlOpt in ['a', 'add']:
1671 if not mxl or mxl in ['a', 'add']: 2119 outfnm = os.path.join (pad, ifnm + '.xml') # joined with path from -o option
1672 outfnm = os.path.join (pad, fnm + '.xml') 2120 outfile = open (outfnm, 'w')
1673 outfile = file (outfnm, 'wb') 2121 outfile.write (xmlstr)
1674 outfile.write (data)
1675 outfile.close () 2122 outfile.close ()
1676 info ('%s written' % outfnm, warn=0) 2123 info ('%s written' % outfnm, warn=0)
1677 if mxl: xml2mxl (pad, fnm, data) # also write a compressed version 2124 if mxlOpt: xml2mxl (pad, ifnm, xmlstr) # also write a compressed version
1678 else: 2125 else:
1679 outfile = sys.stdout 2126 outfile = sys.stdout
1680 outfile.write (fixDoctype (score, 'utf-8')) 2127 outfile.write (xmlstr)
1681 outfile.write ('\n') 2128 outfile.write ('\n')
1682 2129
2130 def readfile (fnmext, errmsg='read error: '):
2131 try:
2132 if fnmext == '-.abc': fobj = stdin # see python2/3 differences
2133 else: fobj = open (fnmext, 'rb')
2134 encoded_data = fobj.read ()
2135 fobj.close ()
2136 return encoded_data if type (encoded_data) == uni_type else decodeInput (encoded_data)
2137 except Exception as e:
2138 info (errmsg + repr (e) + ' ' + fnmext)
2139 return None
2140
2141 def expand_abc_include (abctxt):
2142 ys = []
2143 for x in abctxt.splitlines ():
2144 if x.startswith ('%%abc-include') or x.startswith ('I:abc-include'):
2145 x = readfile (x[13:].strip (), 'include error: ')
2146 if x != None: ys.append (x)
2147 return '\n'.join (ys)
2148
2149 abc_header, abc_voice, abc_scoredef, abc_percmap = abc_grammar () # compute grammars only once
2150 mxm = MusicXml () # same for instance of MusicXml
2151
2152 def getXmlScores (abc_string, skip=0, num=1, rOpt=False, bOpt=False, fOpt=False): # not used, backwards compatibility
2153 return [fixDoctype (xml_doc) for xml_doc in
2154 getXmlDocs (abc_string, skip=0, num=1, rOpt=False, bOpt=False, fOpt=False)]
2155
2156 def getXmlDocs (abc_string, skip=0, num=1, rOpt=False, bOpt=False, fOpt=False): # added by David Randolph
2157 xml_docs = []
2158 abctext = expand_abc_include (abc_string)
2159 fragments = re.split ('^\s*X:', abctext, flags=re.M)
2160 preamble = fragments [0] # tunes can be preceeded by formatting instructions
2161 tunes = fragments[1:]
2162 if not tunes and preamble: tunes, preamble = ['1\n' + preamble], '' # tune without X:
2163 for itune, tune in enumerate (tunes):
2164 if itune < skip: continue # skip tunes, then read at most num tunes
2165 if itune >= skip + num: break
2166 tune = preamble + 'X:' + tune # restore preamble before each tune
2167 try: # convert string abctext -> file pad/fnmNum.xml
2168 score = mxm.parse (tune, rOpt, bOpt, fOpt)
2169 ds = list (score.iter ('duration')) # need to iterate twice
2170 ss = [int (d.text) for d in ds]
2171 deler = reduce (ggd, ss + [21]) # greatest common divisor of all durations
2172 for i, d in enumerate (ds): d.text = str (ss [i] // deler)
2173 for d in score.iter ('divisions'): d.text = str (int (d.text) // deler)
2174 xml_docs.append (score)
2175 except ParseException:
2176 pass # output already printed
2177 except Exception as err:
2178 info ('an exception occurred.\n%s' % err)
2179 return xml_docs
2180
1683 #---------------- 2181 #----------------
1684 # Main Program 2182 # Main Program
1685 #---------------- 2183 #----------------
1686 if __name__ == '__main__': 2184 if __name__ == '__main__':
1687 from optparse import OptionParser 2185 from optparse import OptionParser
1688 from glob import glob 2186 from glob import glob
1689 import time 2187 import time
1690 global mxm # keep instance of MusicXml 2188
1691 global abc_header, abc_voice, lyr_block, abc_scoredef # keep computed grammars 2189 parser = OptionParser (usage='%prog [-h] [-r] [-t] [-b] [-m SKIP NUM] [-o DIR] [-p PFMT] [-z MODE] [--meta MAP] <file1> [<file2> ...]', version='version %d' % VERSION)
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') 2190 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') 2191 parser.add_option ("-m", action="store", help="skip SKIP (0) tunes, then read at most NUM (1) 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') 2192 parser.add_option ("-p", action="store", help="pageformat PFMT (mm) = scale (0.75), pageheight (297), pagewidth (210), leftmargin (18), rightmargin (18), topmargin (10), botmargin (10)", 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') 2193 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) 2194 parser.add_option ("-r", action="store_true", help="show whole measure rests in merged staffs", default=False)
2195 parser.add_option ("-t", action="store_true", help="use tune title as file name", default=False)
2196 parser.add_option ("-b", action="store_true", help="line break at EOL", default=False)
2197 parser.add_option ("--meta", action="store", help="map infofields to XML metadata, MAP = R:poet,Z:lyricist,N:...", default='', metavar='MAP')
2198 parser.add_option ("-f", action="store_true", help="force string/fret allocations for tab staves", default=False)
1700 options, args = parser.parse_args () 2199 options, args = parser.parse_args ()
1701 if len (args) == 0: parser.error ('no input file given') 2200 if len (args) == 0: parser.error ('no input file given')
1702 pad = options.o 2201 pad = options.o
1703 if options.mxl and options.mxl not in ['a','add', 'r', 'replace']: 2202 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) 2203 parser.error ('MODE should be a(dd) or r(eplace), not: %s' % options.mxl)
1705 if pad: 2204 if pad:
1706 if not os.path.exists (pad): os.mkdir (pad) 2205 if not os.path.exists (pad): os.mkdir (pad)
1707 if not os.path.isdir (pad): parser.error ('%s is not a directory' % pad) 2206 if not os.path.isdir (pad): parser.error ('%s is not a directory' % pad)
1708 if options.p: # set page formatting values 2207 if options.p: # set page formatting values
1709 try: # space, page-height, -width, margin-left, -right, -top, -bottom 2208 try: # space, page-height, -width, margin-left, -right, -top, -bottom
1710 mxm.pageFmtCmd = map (float, options.p.split (',')) 2209 mxm.pageFmtCmd = lmap (float, options.p.split (','))
1711 if len (mxm.pageFmtCmd) != 7: raise Exception ('-p needs 7 values') 2210 if len (mxm.pageFmtCmd) != 7: raise ValueError ('-p needs 7 values')
1712 except Exception, err: parser.error (err) 2211 except Exception as err: parser.error (err)
1713 mxm.gmwr = options.r # ugly: needs to be globally accessable 2212 for x in options.meta.split (','):
1714 2213 if not x: continue
1715 abc_header, abc_voice, lyr_block, abc_scoredef = abc_grammar () # compute grammar only once per file set 2214 try: field, tag = x.split (':')
2215 except: parser.error ('--meta: %s cannot be split on colon' % x)
2216 if field not in 'OAZNGHRBDFSPW': parser.error ('--meta: field %s is no valid ABC field' % field)
2217 if tag not in mxm.metaTypes: parser.error ('--meta: tag %s is no valid XML creator type' % tag)
2218 mxm.metaMap [field] = tag
1716 fnmext_list = [] 2219 fnmext_list = []
1717 for i in args: fnmext_list += glob (i) 2220 for i in args:
2221 if i == '-': fnmext_list.append ('-.abc') # represents standard input
2222 else: fnmext_list += glob (i)
1718 if not fnmext_list: parser.error ('none of the input files exist') 2223 if not fnmext_list: parser.error ('none of the input files exist')
1719 t_start = time.time () 2224 t_start = time.time ()
1720 for X, fnmext in enumerate (fnmext_list): 2225 for fnmext in fnmext_list:
1721 fnm, ext = os.path.splitext (fnmext) 2226 fnm, ext = os.path.splitext (fnmext)
1722 if ext.lower () not in ('.abc'): 2227 if ext.lower () not in ('.abc'):
1723 info ('skipped input file %s, it should have extension .abc' % fnmext) 2228 info ('skipped input file %s, it should have extension .abc' % fnmext)
1724 continue 2229 continue
1725 if os.path.isdir (fnmext): 2230 if os.path.isdir (fnmext):
1726 info ('skipped directory %s. Only files are accepted' % fnmext) 2231 info ('skipped directory %s. Only files are accepted' % fnmext)
1727 continue 2232 continue
1728 2233 abctext = readfile (fnmext)
1729 fobj = open (fnmext, 'rb') 2234 skip, num = options.m
1730 encoded_data = fobj.read () 2235 xml_docs = getXmlDocs (abctext, skip, num, options.r, options.b, options.f)
1731 fobj.close () 2236 for itune, xmldoc in enumerate (xml_docs):
1732 fragments = encoded_data.split ('X:') 2237 fnmNum = '%02d' % (itune + 1) if len (xml_docs) > 1 else ''
1733 preamble = fragments [0] # tunes can be preceeded by formatting instructions 2238 writefile (pad, fnm, fnmNum, xmldoc, options.mxl, options.t)
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)) 2239 info ('done in %.2f secs' % (time.time () - t_start))