22
22
except : import xml .etree .ElementTree as E
23
23
import types , sys , os , re , datetime
24
24
25
- VERSION = 233
25
+ VERSION = 245
26
26
27
27
python3 = sys .version_info [0 ] > 2
28
28
lmap = lambda f , xs : list (map (f , xs )) # eager map for python 3
31
31
list_type = list
32
32
str_type = str
33
33
uni_type = str
34
+ stdin = sys .stdin .buffer if sys .stdin else None # read binary if stdin available!
34
35
else :
35
36
int_type = types .IntType
36
37
list_type = types .ListType
37
38
str_type = types .StringTypes
38
39
uni_type = types .UnicodeType
40
+ stdin = sys .stdin
39
41
42
+ info_list = [] # diagnostic messages
40
43
def info (s , warn = 1 ):
41
44
x = (warn and '-- ' or '' ) + s
42
- try : sys .stderr .write (x + '\n ' )
43
- except : sys .stderr .write (repr (x ) + '\n ' )
45
+ info_list .append (x + '\n ' ) # collect messages
46
+ if __name__ == '__main__' : # only write to stdout when called as main progeam
47
+ try : sys .stderr .write (x + '\n ' )
48
+ except : sys .stderr .write (repr (x ) + '\n ' )
49
+
50
+ def getInfo (): # get string of diagnostic messages, then clear messages
51
+ global info_list
52
+ xs = '' .join (info_list )
53
+ info_list = []
54
+ return xs
44
55
45
56
def abc_grammar (): # header, voice and lyrics grammar for ABC
46
57
#-----------------------------------------------------------------
@@ -86,13 +97,13 @@ def abc_grammar (): # header, voice and lyrics grammar for ABC
86
97
# ABC lyric lines (white space sensitive)
87
98
#----------------------------------------
88
99
89
- skip_note = oneOf ('* - ~ ' )
100
+ skip_note = oneOf ('* -' )
90
101
extend_note = Literal ('_' )
91
102
measure_end = Literal ('|' )
92
- syl_str = CharsNotIn ('*~ -_| \t \n \\ ]' )
103
+ syl_str = CharsNotIn ('*-_| \t \n \\ ]' )
93
104
syl_chars = Combine (OneOrMore (syl_str | Regex (r'\\.' )))
94
105
white = Word (' \t ' )
95
- syllable = Combine ( Optional ( '~' ) + syl_chars + ZeroOrMore ( Literal ( '~' ) + syl_chars )) + Optional ('-' )
106
+ syllable = syl_chars + Optional ('-' )
96
107
lyr_elem = (syllable | skip_note | extend_note | measure_end ) + Optional (white ).suppress ()
97
108
lyr_line = Optional (white ).suppress () + ZeroOrMore (lyr_elem )
98
109
@@ -589,7 +600,7 @@ def splitHeaderVoices (abctext):
589
600
voices .append ((id , voice ))
590
601
return header , voices
591
602
592
- def mergeMeasure (m1 , m2 , slur_offset , voice_offset , rOpt , is_grand = 0 ):
603
+ def mergeMeasure (m1 , m2 , slur_offset , voice_offset , rOpt , is_grand = 0 , is_overlay = 0 ):
593
604
slurs = m2 .findall ('note/notations/slur' )
594
605
for slr in slurs :
595
606
slrnum = int (slr .get ('number' )) + slur_offset
@@ -606,20 +617,22 @@ def mergeMeasure (m1, m2, slur_offset, voice_offset, rOpt, is_grand=0):
606
617
dur1 = sum (int (n .find ('duration' ).text ) for n in ns
607
618
if n .find ('grace' ) == None and n .find ('chord' ) == None )
608
619
dur1 -= sum (int (b .text ) for b in m1 .findall ('backup/duration' ))
609
- nns , es = 0 , [] # nns = number of real notes in m2
620
+ repbar , nns , es = 0 , 0 , [] # nns = number of real notes in m2
610
621
for e in list (m2 ): # scan all elements of m2
611
622
if e .tag == 'attributes' :
612
623
if not is_grand : continue # no attribute merging for normal voices
613
- else : nns += 1 # but we do merge (clef) attributes for a grand staff
624
+ else : nns += 1 # but we do merge (clef) attributes for a grand staff
614
625
if e .tag == 'print' : continue
615
626
if e .tag == 'note' and (rOpt or e .find ('rest' ) == None ): nns += 1
627
+ if e .tag == 'barline' and e .find ('repeat' ) != None : repbar = e ;
616
628
es .append (e ) # buffer elements to be merged
617
629
if nns > 0 : # only merge if m2 contains any real notes
618
630
if dur1 > 0 : # only insert backup if duration of m1 > 0
619
631
b = E .Element ('backup' )
620
632
addElem (m1 , b , level = 3 )
621
633
addElemT (b , 'duration' , str (dur1 ), level = 4 )
622
634
for e in es : addElem (m1 , e , level = 3 ) # merge buffered elements of m2
635
+ elif is_overlay and repbar : addElem (m1 , repbar , level = 3 ) # merge repeat in empty overlay
623
636
624
637
def mergePartList (parts , rOpt , is_grand = 0 ): # merge parts, make grand staff when is_grand true
625
638
@@ -662,11 +675,16 @@ def mergeParts (parts, vids, staves, rOpt, is_grand=0):
662
675
vidsnew .append (vids [pixs [0 ]])
663
676
return partsnew , vidsnew
664
677
665
- def mergePartMeasure (part , msre , ovrlaynum , rOpt ): # merge msre into last measure of part, only for overlays
666
- slurs = part .findall ('measure/note/notations/slur' ) # find highest slur num in part
667
- slur_max = max ([int (slr .get ('number' )) for slr in slurs ] + [0 ])
678
+ def mergePartMeasure (part , msre , ovrlaynum , rOpt ): # merge msre into last measure of part, only for overlays
679
+ slur_offset = 0 ; # slur numbers determined by the slurstack size (as in a single voice)
668
680
last_msre = list (part )[- 1 ] # last measure in part
669
- mergeMeasure (last_msre , msre , slur_max , ovrlaynum , rOpt ) # voice offset = s.overlayVNum
681
+ mergeMeasure (last_msre , msre , slur_offset , ovrlaynum , rOpt , is_overlay = 1 ) # voice offset = s.overlayVNum
682
+
683
+ def pushSlur (boogStapel , stem ):
684
+ if stem not in boogStapel : boogStapel [stem ] = [] # initialize slurstack for stem
685
+ boognum = sum (map (len , boogStapel .values ())) + 1 # number of open slurs in all (overlay) voices
686
+ boogStapel [stem ].append (boognum )
687
+ return boognum
670
688
671
689
def setFristVoiceNameFromGroup (vids , vdefs ): # vids = [vid], vdef = {vid -> (name, subname, voicedef)}
672
690
vids = [v for v in vids if v in vdefs ] # only consider defined voices
@@ -826,7 +844,7 @@ def __init__ (s):
826
844
def reset (s , fOpt = False ):
827
845
s .divisions = 2520 # xml duration of 1/4 note, 2^3 * 3^2 * 5 * 7 => 5,7,9 tuplets
828
846
s .ties = {} # {abc pitch tuple -> alteration} for all open ties
829
- s .slurstack = [] # stack of open slur numbers
847
+ s .slurstack = {} # stack of open slur numbers per (overlay) voice
830
848
s .slurbeg = [] # type of slurs to start (when slurs are detected at element-level)
831
849
s .tmnum = 0 # time modification, numerator
832
850
s .tmden = 0 # time modification, denominator
@@ -1059,6 +1077,7 @@ def cmpNormType (s, rdvs, lev): # compute the normal-type of a tuplet (only need
1059
1077
def doNotations (s , n , decos , ptup , alter , tupnotation , tstop , nt , lev ):
1060
1078
slurs = getattr (n , 'slurs' , 0 ) # slur ends
1061
1079
pts = getattr (n , 'pitches' , []) # all chord notes available in the first note
1080
+ ov = s .overlayVnum # current overlay voice number (0 for the main voice)
1062
1081
if pts : # make list of pitches in chord: [(pitch, octave), ..]
1063
1082
if type (pts .pitch ) == pObj : pts = [pts .pitch ] # chord with one note
1064
1083
else : pts = [tuple (p .t [- 2 :]) for p in pts .pitch ] # normal chord
@@ -1074,8 +1093,7 @@ def doNotations (s, n, decos, ptup, alter, tupnotation, tstop, nt, lev):
1074
1093
ntelm .remove (e ) # delete start tie element
1075
1094
e = [t for t in nts .findall ('tied' ) if t .get ('type' ) == 'start' ][0 ] # get the tied start element
1076
1095
e .tag = 'slur' # convert tie into slur
1077
- slurnum = len (s .slurstack ) + 1
1078
- s .slurstack .append (slurnum )
1096
+ slurnum = pushSlur (s .slurstack , ov )
1079
1097
e .set ('number' , str (slurnum ))
1080
1098
if slurs : slurs .t .append (')' ) # close slur on this note
1081
1099
else : slurs = pObj ('slurs' , [')' ])
@@ -1130,14 +1148,13 @@ def doNotations (s, n, decos, ptup, alter, tupnotation, tstop, nt, lev):
1130
1148
if rest : info ('unhandled note decorations: %s' % rest )
1131
1149
if slurs : # these are only slur endings
1132
1150
for d in slurs .t : # slurs to be closed on this note
1133
- if not s .slurstack : break # no more open old slurs
1134
- slurnum = s .slurstack .pop ()
1151
+ if not s .slurstack . get ( ov , 0 ) : break # no more open old slurs for this (overlay) voice
1152
+ slurnum = s .slurstack [ ov ] .pop ()
1135
1153
slur = E .Element ('slur' , number = '%d' % slurnum , type = 'stop' )
1136
1154
addElem (nots , slur , lev + 1 )
1137
1155
while s .slurbeg : # create slurs beginning on this note
1138
1156
stp = s .slurbeg .pop (0 )
1139
- slurnum = len (s .slurstack ) + 1
1140
- s .slurstack .append (slurnum )
1157
+ slurnum = pushSlur (s .slurstack , ov )
1141
1158
ntn = E .Element ('slur' , number = '%d' % slurnum , type = 'start' )
1142
1159
if '.' in stp : ntn .set ('line-type' , 'dotted' )
1143
1160
if ',' in stp : ntn .set ('placement' , 'below' )
@@ -1297,6 +1314,7 @@ def staffDecos (s, decos, maat, lev):
1297
1314
elif d in ['/-' ,'//-' ,'///-' ,'////-' ]: # duplet tremolo sequence
1298
1315
s .tmnum , s .tmden , s .ntup , s .trem , s .intrem = 2 , 1 , 2 , len (d ) - 1 , 1
1299
1316
elif d in ['/' ,'//' ,'///' ]: s .trem = - len (d ) # single note tremolo
1317
+ elif d == 'rbstop' : s .rbStop = 1 ; # sluit een open volta aan het eind van de maat
1300
1318
else : s .nextdecos .append (d ) # keep annotation for the next note
1301
1319
1302
1320
def doFields (s , maat , fieldmap , lev ):
@@ -1578,6 +1596,7 @@ def mkMeasure (s, i, t, lev, fieldmap={}):
1578
1596
s .msreAlts = {}
1579
1597
s .ntup , s .trem , s .intrem = - 1 , 0 , 0
1580
1598
s .acciatura = 0 # next grace element gets acciatura attribute
1599
+ s .rbStop = 0 # sluit een open volta aan het eind van de maat
1581
1600
overlay = 0
1582
1601
maat = E .Element ('measure' , number = str (i ))
1583
1602
if fieldmap : s .doFields (maat , fieldmap , lev + 1 )
@@ -1612,6 +1631,8 @@ def mkMeasure (s, i, t, lev, fieldmap={}):
1612
1631
s .mkBarline (maat , 'right' , lev + 1 , style = 'none' )
1613
1632
elif '[' in bar or ']' in bar :
1614
1633
s .mkBarline (maat , 'right' , lev + 1 , style = 'light-heavy' )
1634
+ elif bar == '|' and s .rbStop : # normale barline hoeft niet, behalve om een volta te stoppen
1635
+ s .mkBarline (maat , 'right' , lev + 1 , style = 'regular' )
1615
1636
elif bar [0 ] == '&' : overlay = 1
1616
1637
elif x .name == 'tup' :
1617
1638
if len (x .t ) == 3 : n , into , nts = x .t
@@ -1648,7 +1669,7 @@ def mkMeasure (s, i, t, lev, fieldmap={}):
1648
1669
return maat , overlay
1649
1670
1650
1671
def mkPart (s , maten , id , lev , attrs , nstaves , rOpt ):
1651
- s .slurstack = []
1672
+ s .slurstack = {}
1652
1673
s .glisnum = 0 ; # xml number attribute for glissandos
1653
1674
s .slidenum = 0 ; # xml number attribute for slides
1654
1675
s .unitLcur = s .unitL # set the default unit length at begin of each voice
@@ -2121,7 +2142,8 @@ def writefile (pad, fnm, fnmNum, xmldoc, mxlOpt, tOpt=False):
2121
2142
2122
2143
def readfile (fnmext , errmsg = 'read error: ' ):
2123
2144
try :
2124
- fobj = open (fnmext , 'rb' )
2145
+ if fnmext == '-.abc' : fobj = stdin # see python2/3 differences
2146
+ else : fobj = open (fnmext , 'rb' )
2125
2147
encoded_data = fobj .read ()
2126
2148
fobj .close ()
2127
2149
return encoded_data if type (encoded_data ) == uni_type else decodeInput (encoded_data )
@@ -2147,7 +2169,7 @@ def getXmlScores (abc_string, skip=0, num=1, rOpt=False, bOpt=False, fOpt=False)
2147
2169
def getXmlDocs (abc_string , skip = 0 , num = 1 , rOpt = False , bOpt = False , fOpt = False ): # added by David Randolph
2148
2170
xml_docs = []
2149
2171
abctext = expand_abc_include (abc_string )
2150
- fragments = re .split ('^\s*X:' , abctext , flags = re .M )
2172
+ fragments = re .split (r '^\s*X:' , abctext , flags = re .M )
2151
2173
preamble = fragments [0 ] # tunes can be preceeded by formatting instructions
2152
2174
tunes = fragments [1 :]
2153
2175
if not tunes and preamble : tunes , preamble = ['1\n ' + preamble ], '' # tune without X:
@@ -2208,10 +2230,12 @@ def getXmlDocs (abc_string, skip=0, num=1, rOpt=False, bOpt=False, fOpt=False):
2208
2230
if tag not in mxm .metaTypes : parser .error ('--meta: tag %s is no valid XML creator type' % tag )
2209
2231
mxm .metaMap [field ] = tag
2210
2232
fnmext_list = []
2211
- for i in args : fnmext_list += glob (i )
2233
+ for i in args :
2234
+ if i == '-' : fnmext_list .append ('-.abc' ) # represents standard input
2235
+ else : fnmext_list += glob (i )
2212
2236
if not fnmext_list : parser .error ('none of the input files exist' )
2213
2237
t_start = time .time ()
2214
- for X , fnmext in enumerate ( fnmext_list ) :
2238
+ for fnmext in fnmext_list :
2215
2239
fnm , ext = os .path .splitext (fnmext )
2216
2240
if ext .lower () not in ('.abc' ):
2217
2241
info ('skipped input file %s, it should have extension .abc' % fnmext )
0 commit comments