#!/usr/bin/env python
# coding: latin-1
''' Maakt een .b40 file uit een .xml file (MusicXML).
muziek = [parts], part = [maten], maat = [voices]
voice = [[dur, tijd, noot1, noot2, ....]]
per part: len (voices) = maxvoice
vceCnt telt het aantal stemmen in het hele stuk (d.w.z. over meerdere parts)
In deze versie worden eerst alle noten in één part archter elkaar gezet en dan pas worden
gebonden noten samengevoegd. Dit in verband met het terugzetten van de tijd door backup, waardoor
eem gebonden noot tussen twee stemmen verloren gaat als voor de backup (naar de andere stem)
de noot nog een keer voorkomt.
'''
import operator, sys, os
import xml.etree.ElementTree as E
b40List = ['Cbb','Cb','C','C#','C##','-','Dbb','Db','D','D#','D##','-','Ebb','Eb','E','E#','E##','Fbb','Fb','F',
'F#','F##','-','Gbb','Gb','G','G#','G##','-','Abb','Ab','A','A#','A##','-','Bbb','Bb','B','B#','B##']
b40Dict = dict (zip (b40List, range (len (b40List))))
gtikkenPerKwart = 384 # ticks per quarter note
gC5 = False # central C == C5 if gC5 else C4
def ntB40 (p, o, alt):
if alt:
alt = int (alt)
while alt > 0: p += '#'; alt -= 1
while alt < 0: p += 'b'; alt += 1
b40 = o * 40 + b40Dict[p] + 1
return str (b40)
def doNote (n):
global tijd, maxVoice
chord = n.find ('chord') != None
p = n.findtext ('pitch/step')
alt = n.findtext ('pitch/alter')
o = n.findtext ('pitch/octave')
r = n.find ('rest')
dur = int (n.findtext ('duration'))
v = int (n.findtext ('voice')) - 1
if v > maxVoice: maxVoice = v
if r != None: noot = 'z'
else: noot = ntB40 (p, int (o) + (1 if gC5 else 0), alt)
tie = n.find ('tie')
if tie != None and tie.get ('type') == 'stop': noot = '-' + noot
if chord: addChord (v, noot)
else: appendNote (v, dur, noot)
def doAttr (e):
global durUnit
kmaj = ['Cb','Gb','Dd','Ab','Eb','Bb','F','C','G','D','A', 'E', 'B', 'F#','C#']
kmin = ['Ab','Eb','Bb','F', 'C', 'G', 'D','A','E','B','F#','C#','G#','D#','A#']
dvstxt = e.findtext ('divisions')
if dvstxt: # niet alle attribuut knopen hebben een divisions (b.v. key change)
#~ print 'divisions per kwart', int (dvstxt)
durUnit = int (dvstxt)
key = ''
f = e.findtext ('key/fifths')
m = e.findtext ('key/mode')
if m == 'major': key = kmaj [7 + int (f)]
if m == 'minor': key = kmin [7 + int (f)] + 'min'
if key:
for v in range (nVoices): appendNote (v, 0, 'K:%s\n' % key)
beats = e.findtext ('time/beats')
if beats:
met = beats + '/' + e.findtext ('time/beat-type')
for v in range (nVoices): appendNote (v, 0, 'M:%s\n' % met)
def appendNote (v, dur, noot):
global tijd
t = vtimes [v]
if tijd > t: voices [v].append ([tijd - t, t, 'z'])
if tijd < t: raise 'kenniet'
voices [v].append ([dur, tijd, noot])
tijd += int (dur)
vtimes[v] = tijd
def addChord (v, noot):
t = voices[v][-1]
voices[v][-1] = t + [noot]
def addBar ():
global voices
for v in range (nVoices): appendNote (v, 0, ' |\n')
gMaten.append (voices)
voices = [[] for i in range (nVoices)]
def outMaat (i, mbuf, ns):
for nx in mbuf:
dur = nx[0] * gTikkenPerKwart / durUnit
tijd = nx[1] * gTikkenPerKwart / durUnit
if not dur: continue # alleen echte noten
for noot in nx[2:]: # de noot of noten in chord
if 'z' in noot: continue # skip rusten
ns.append ((tijd, noot, dur))
def remTies (ns):
global noten
tieBufDur = {} # tijdelijk vasthouden van noten voor evt. volgende tie
tieBufTijd = {} # twee aparte buffers voor duur en tijd
ns.sort ()
for tijd, noot, dur in ns: # nu nog voor gebonden noten checken
if noot.startswith ('-'): tieBufDur [noot[1:]] += dur
else:
if noot in tieBufDur: # bij de volgdende niet gebonden noot vorige uitvoeren
noten.append ((tieBufTijd [noot], noot, tieBufDur[noot]))
tieBufTijd [noot] = tijd # alle noten vast houden voor evt. volgende tie
tieBufDur [noot] = dur
for noot, dur in tieBufDur.iteritems (): # alle vastgehouden noten nu uitvoeren
noten.append ((tieBufTijd[noot], noot, dur))
def outVoices (vceCnt): # output alle stemmen in een part, nummering begint bij vceCnt
global tijd, gMaten, voices, vtimes, tupcnt, maxVoice, noten
ns = []
for maat in gMaten: # aantal gebruikte voices in gMaten = maxVoice + 1
mbuf = reduce (operator.concat, maat[:maxVoice + 1]) # plak de gebruikte voices aanelkaar
outMaat (vceCnt, mbuf, ns) # alle stemmen in deze maat => ns, en nummer telkens vanaf vceCnt
remTies (ns)
vceCnt += maxVoice + 1 # de volgende part start met aansluitend hogere voice nummers
gMaten = []
voices = [[] for i in range (nVoices)]
vtimes = nVoices * [0]
tijd = 0
tupcnt = 0
maxVoice = 0
return vceCnt
def xml2b40 (fnmext, tpk=384, C5=1): # base name, ticks per quarter, central C == C5
global nVoices, gMaten, voices, vtimes, tijd, tupcnt, maxVoice, vceCnt, noten, gTikkenPerKwart, gC5
gTikkenPerKwart = tpk
gC5 = C5
nVoices = 10
gMaten = []
voices = [[] for i in range (nVoices)]
vtimes = nVoices * [0]
tijd = 0
tupcnt = 0
maxVoice = 0
vceCnt = 1
noten = []
e = E.parse (fnmext)
parts = e.findall ('part')
noten = [] # de uitvoer van alle noten
for p in parts:
maten = p.findall ('measure')
for maat in maten:
es = maat.getchildren ()
for e in es:
if e.tag == 'note': doNote (e)
elif e.tag == 'attributes': doAttr (e)
elif e.tag == 'backup':
dt = int (e.findtext ('duration'))
tijd -= dt
elif e.tag == 'forward':
dt = int (e.findtext ('duration'))
tijd += dt
else: pass
addBar ()
vceCnt = outVoices (vceCnt)
offnoten = []
for tijd, noot, dur in noten:
noot = int (noot) # straks numeriek ordenen i.v.m. vergelijken oude b40 files
offnoten.append ((tijd, noot))
offnoten.append ((tijd + dur, -noot))
noten = offnoten
noten.sort ()
return noten
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser (usage='%prog [-h] [-g TPQ] [--C5] <file1>')
parser.add_option ("-t", action="store", type="int", help="ticks per quarternote", default=120, metavar='TPQ')
parser.add_option ("--C5", action="store_true", help="central C is C5", default=False)
parser.add_option ("-w", action="store_true", help="write .b40 file", default=False)
options, args = parser.parse_args ()
if len (args) < 1: parser.error ('file argument needed')
fnmext = args [0]
fnm, ext = os.path.splitext (fnmext)
if ext != '.xml': parser.error ('.xml file needed file needed')
if not os.path.exists (fnmext): parser.error ('%s does not exist' % fnmext)
xml2b40 (fnmext, options.t, options.C5)
if options.w:
g = file (fnm + '.b40', 'w')
for tijd, noot in noten: g.write ('%d %s\n' % (tijd, noot))
g.close ()
print fnm + '.b40 written'
else:
for tijd, noot in noten: print tijd, noot