1
|
1 |
#!/usr/bin/env python
|
|
2 |
# coding: latin-1
|
|
3 |
''' Maakt een .b40 file uit een .xml file (MusicXML).
|
|
4 |
muziek = [parts], part = [maten], maat = [voices]
|
|
5 |
voice = [[dur, tijd, noot1, noot2, ....]]
|
|
6 |
per part: len (voices) = maxvoice
|
|
7 |
vceCnt telt het aantal stemmen in het hele stuk (d.w.z. over meerdere parts)
|
|
8 |
In deze versie worden eerst alle noten in één part archter elkaar gezet en dan pas worden
|
|
9 |
gebonden noten samengevoegd. Dit in verband met het terugzetten van de tijd door backup, waardoor
|
|
10 |
eem gebonden noot tussen twee stemmen verloren gaat als voor de backup (naar de andere stem)
|
|
11 |
de noot nog een keer voorkomt.
|
|
12 |
'''
|
|
13 |
import operator, sys, os
|
|
14 |
import xml.etree.ElementTree as E
|
|
15 |
|
|
16 |
b40List = ['Cbb','Cb','C','C#','C##','-','Dbb','Db','D','D#','D##','-','Ebb','Eb','E','E#','E##','Fbb','Fb','F',
|
|
17 |
'F#','F##','-','Gbb','Gb','G','G#','G##','-','Abb','Ab','A','A#','A##','-','Bbb','Bb','B','B#','B##']
|
|
18 |
b40Dict = dict (zip (b40List, range (len (b40List))))
|
|
19 |
|
|
20 |
gtikkenPerKwart = 384 # ticks per quarter note
|
|
21 |
gC5 = False # central C == C5 if gC5 else C4
|
|
22 |
|
|
23 |
def ntB40 (p, o, alt):
|
|
24 |
if alt:
|
|
25 |
alt = int (alt)
|
|
26 |
while alt > 0: p += '#'; alt -= 1
|
|
27 |
while alt < 0: p += 'b'; alt += 1
|
|
28 |
b40 = o * 40 + b40Dict[p] + 1
|
|
29 |
return str (b40)
|
|
30 |
|
|
31 |
def doNote (n):
|
|
32 |
global tijd, maxVoice
|
|
33 |
chord = n.find ('chord') != None
|
|
34 |
p = n.findtext ('pitch/step')
|
|
35 |
alt = n.findtext ('pitch/alter')
|
|
36 |
o = n.findtext ('pitch/octave')
|
|
37 |
r = n.find ('rest')
|
|
38 |
dur = int (n.findtext ('duration'))
|
|
39 |
v = int (n.findtext ('voice')) - 1
|
|
40 |
if v > maxVoice: maxVoice = v
|
|
41 |
if r != None: noot = 'z'
|
|
42 |
else: noot = ntB40 (p, int (o) + (1 if gC5 else 0), alt)
|
|
43 |
tie = n.find ('tie')
|
|
44 |
if tie != None and tie.get ('type') == 'stop': noot = '-' + noot
|
|
45 |
if chord: addChord (v, noot)
|
|
46 |
else: appendNote (v, dur, noot)
|
|
47 |
|
|
48 |
def doAttr (e):
|
|
49 |
global durUnit
|
|
50 |
kmaj = ['Cb','Gb','Dd','Ab','Eb','Bb','F','C','G','D','A', 'E', 'B', 'F#','C#']
|
|
51 |
kmin = ['Ab','Eb','Bb','F', 'C', 'G', 'D','A','E','B','F#','C#','G#','D#','A#']
|
|
52 |
dvstxt = e.findtext ('divisions')
|
|
53 |
if dvstxt: # niet alle attribuut knopen hebben een divisions (b.v. key change)
|
|
54 |
#~ print 'divisions per kwart', int (dvstxt)
|
|
55 |
durUnit = int (dvstxt)
|
|
56 |
key = ''
|
|
57 |
f = e.findtext ('key/fifths')
|
|
58 |
m = e.findtext ('key/mode')
|
|
59 |
if m == 'major': key = kmaj [7 + int (f)]
|
|
60 |
if m == 'minor': key = kmin [7 + int (f)] + 'min'
|
|
61 |
if key:
|
|
62 |
for v in range (nVoices): appendNote (v, 0, 'K:%s\n' % key)
|
|
63 |
beats = e.findtext ('time/beats')
|
|
64 |
if beats:
|
|
65 |
met = beats + '/' + e.findtext ('time/beat-type')
|
|
66 |
for v in range (nVoices): appendNote (v, 0, 'M:%s\n' % met)
|
|
67 |
|
|
68 |
def appendNote (v, dur, noot):
|
|
69 |
global tijd
|
|
70 |
t = vtimes [v]
|
|
71 |
if tijd > t: voices [v].append ([tijd - t, t, 'z'])
|
|
72 |
if tijd < t: raise 'kenniet'
|
|
73 |
voices [v].append ([dur, tijd, noot])
|
|
74 |
tijd += int (dur)
|
|
75 |
vtimes[v] = tijd
|
|
76 |
|
|
77 |
def addChord (v, noot):
|
|
78 |
t = voices[v][-1]
|
|
79 |
voices[v][-1] = t + [noot]
|
|
80 |
|
|
81 |
def addBar ():
|
|
82 |
global voices
|
|
83 |
for v in range (nVoices): appendNote (v, 0, ' |\n')
|
|
84 |
gMaten.append (voices)
|
|
85 |
voices = [[] for i in range (nVoices)]
|
|
86 |
|
|
87 |
def outMaat (i, mbuf, ns):
|
|
88 |
for nx in mbuf:
|
|
89 |
dur = nx[0] * gTikkenPerKwart / durUnit
|
|
90 |
tijd = nx[1] * gTikkenPerKwart / durUnit
|
|
91 |
if not dur: continue # alleen echte noten
|
|
92 |
for noot in nx[2:]: # de noot of noten in chord
|
|
93 |
if 'z' in noot: continue # skip rusten
|
|
94 |
ns.append ((tijd, noot, dur))
|
|
95 |
|
|
96 |
def remTies (ns):
|
|
97 |
global noten
|
|
98 |
tieBufDur = {} # tijdelijk vasthouden van noten voor evt. volgende tie
|
|
99 |
tieBufTijd = {} # twee aparte buffers voor duur en tijd
|
|
100 |
ns.sort ()
|
|
101 |
for tijd, noot, dur in ns: # nu nog voor gebonden noten checken
|
|
102 |
if noot.startswith ('-'): tieBufDur [noot[1:]] += dur
|
|
103 |
else:
|
|
104 |
if noot in tieBufDur: # bij de volgdende niet gebonden noot vorige uitvoeren
|
|
105 |
noten.append ((tieBufTijd [noot], noot, tieBufDur[noot]))
|
|
106 |
tieBufTijd [noot] = tijd # alle noten vast houden voor evt. volgende tie
|
|
107 |
tieBufDur [noot] = dur
|
|
108 |
for noot, dur in tieBufDur.iteritems (): # alle vastgehouden noten nu uitvoeren
|
|
109 |
noten.append ((tieBufTijd[noot], noot, dur))
|
|
110 |
|
|
111 |
def outVoices (vceCnt): # output alle stemmen in een part, nummering begint bij vceCnt
|
|
112 |
global tijd, gMaten, voices, vtimes, tupcnt, maxVoice, noten
|
|
113 |
ns = []
|
|
114 |
for maat in gMaten: # aantal gebruikte voices in gMaten = maxVoice + 1
|
|
115 |
mbuf = reduce (operator.concat, maat[:maxVoice + 1]) # plak de gebruikte voices aanelkaar
|
|
116 |
outMaat (vceCnt, mbuf, ns) # alle stemmen in deze maat => ns, en nummer telkens vanaf vceCnt
|
|
117 |
remTies (ns)
|
|
118 |
vceCnt += maxVoice + 1 # de volgende part start met aansluitend hogere voice nummers
|
|
119 |
gMaten = []
|
|
120 |
voices = [[] for i in range (nVoices)]
|
|
121 |
vtimes = nVoices * [0]
|
|
122 |
tijd = 0
|
|
123 |
tupcnt = 0
|
|
124 |
maxVoice = 0
|
|
125 |
return vceCnt
|
|
126 |
|
|
127 |
def xml2b40 (fnmext, tpk=384, C5=1): # base name, ticks per quarter, central C == C5
|
|
128 |
global nVoices, gMaten, voices, vtimes, tijd, tupcnt, maxVoice, vceCnt, noten, gTikkenPerKwart, gC5
|
|
129 |
gTikkenPerKwart = tpk
|
|
130 |
gC5 = C5
|
|
131 |
nVoices = 10
|
|
132 |
gMaten = []
|
|
133 |
voices = [[] for i in range (nVoices)]
|
|
134 |
vtimes = nVoices * [0]
|
|
135 |
tijd = 0
|
|
136 |
tupcnt = 0
|
|
137 |
maxVoice = 0
|
|
138 |
vceCnt = 1
|
|
139 |
noten = []
|
|
140 |
|
|
141 |
e = E.parse (fnmext)
|
|
142 |
parts = e.findall ('part')
|
|
143 |
noten = [] # de uitvoer van alle noten
|
|
144 |
for p in parts:
|
|
145 |
maten = p.findall ('measure')
|
|
146 |
for maat in maten:
|
|
147 |
es = maat.getchildren ()
|
|
148 |
for e in es:
|
|
149 |
if e.tag == 'note': doNote (e)
|
|
150 |
elif e.tag == 'attributes': doAttr (e)
|
|
151 |
elif e.tag == 'backup':
|
|
152 |
dt = int (e.findtext ('duration'))
|
|
153 |
tijd -= dt
|
|
154 |
elif e.tag == 'forward':
|
|
155 |
dt = int (e.findtext ('duration'))
|
|
156 |
tijd += dt
|
|
157 |
else: pass
|
|
158 |
addBar ()
|
|
159 |
vceCnt = outVoices (vceCnt)
|
|
160 |
offnoten = []
|
|
161 |
for tijd, noot, dur in noten:
|
|
162 |
noot = int (noot) # straks numeriek ordenen i.v.m. vergelijken oude b40 files
|
|
163 |
offnoten.append ((tijd, noot))
|
|
164 |
offnoten.append ((tijd + dur, -noot))
|
|
165 |
noten = offnoten
|
|
166 |
noten.sort ()
|
|
167 |
return noten
|
|
168 |
|
|
169 |
|
|
170 |
if __name__ == '__main__':
|
|
171 |
from optparse import OptionParser
|
|
172 |
parser = OptionParser (usage='%prog [-h] [-g TPQ] [--C5] <file1>')
|
|
173 |
parser.add_option ("-t", action="store", type="int", help="ticks per quarternote", default=120, metavar='TPQ')
|
|
174 |
parser.add_option ("--C5", action="store_true", help="central C is C5", default=False)
|
|
175 |
parser.add_option ("-w", action="store_true", help="write .b40 file", default=False)
|
|
176 |
options, args = parser.parse_args ()
|
|
177 |
if len (args) < 1: parser.error ('file argument needed')
|
|
178 |
fnmext = args [0]
|
|
179 |
fnm, ext = os.path.splitext (fnmext)
|
|
180 |
if ext != '.xml': parser.error ('.xml file needed file needed')
|
|
181 |
if not os.path.exists (fnmext): parser.error ('%s does not exist' % fnmext)
|
|
182 |
xml2b40 (fnmext, options.t, options.C5)
|
|
183 |
if options.w:
|
|
184 |
g = file (fnm + '.b40', 'w')
|
|
185 |
for tijd, noot in noten: g.write ('%d %s\n' % (tijd, noot))
|
|
186 |
g.close ()
|
|
187 |
print fnm + '.b40 written'
|
|
188 |
else:
|
|
189 |
for tijd, noot in noten: print tijd, noot
|