xml2b40.py
changeset 1 7fd6cac1a69d
child 6 193999e56a90
equal deleted inserted replaced
0:4896b49e870a 1:7fd6cac1a69d
       
     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