segLibB40.py
changeset 1 7fd6cac1a69d
child 6 193999e56a90
equal deleted inserted replaced
0:4896b49e870a 1:7fd6cac1a69d
       
     1 '''B40 versie
       
     2 bevat B40lib, b40ana en b40krumm
       
     3 zie ook b40krumm.py voor uitleg van b40 keyalgoritme.
       
     4 '''
       
     5 import re, math
       
     6 
       
     7 major = [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88]
       
     8 minor = [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]
       
     9 base_templates = [(0,12,23),(0,12,23,34),(0,11,23),(0,11,22,33),(0,11,22,34),(0,11,22)]
       
    10 base_names = ['maj','dom7','min','fdim','hdim','dim']
       
    11 
       
    12 def avg (xs):
       
    13     return sum (xs) / len (xs)
       
    14 
       
    15 tab = [1,10,11,0,1,2,3,0,1,2,3,4,1,2,3,4,5,6,3,4,5,6,7,4,5,6,7,8,9,6,7,8,9,10,11,8,9,10,11,0]
       
    16 acs = [2,-2,-1,0,1,2,0,-2,-1,0,1,2,0,-2,-1,0,1,2,-2,-1,0,1,2,0,-2,-1,0,1,2,0,-2,-1,0,1,2,0,-2,-1,0,1]
       
    17 def b40pcHst (b40Hst):
       
    18     pcHst = [0.0 for i in range (12)]
       
    19     nacc = 0
       
    20     for ix, b40 in enumerate (b40Hst):
       
    21         if b40 == 0: continue
       
    22         pcHst [tab [ix]] += float (b40) # soms valt een Dbb op een C b.v. ex14, maat 5, 3e tel
       
    23         nacc += acs [ix] * b40
       
    24     return pcHst, nacc
       
    25 
       
    26 # de b40 nummers van de 12 pitches [3,[4,8],9,[10,14],15,20,[21,25],26,[27,31],32,[33,37],38]
       
    27 b12b40 = [3,4,9,10,15,20,21,26,27,32,33,38]
       
    28 dkeus = {}.fromkeys ([1,3,6,8,10], 1)
       
    29 
       
    30 def keyCor (b40Hst):
       
    31     pcHst, nacc = b40pcHst (b40Hst)
       
    32     pcHst [0] += 0.0001
       
    33     pc_mean = avg (pcHst)
       
    34     maj_mean = avg (major)
       
    35     min_mean = avg (minor)
       
    36     maj_dif_sq = sum ([(m - maj_mean)**2 for m in major])
       
    37     min_dif_sq = sum ([(m - min_mean)**2 for m in minor])
       
    38     pc_dif_sq  = sum ([(m -  pc_mean)**2 for m in pcHst])
       
    39     maj_noemer = math.sqrt (maj_dif_sq * pc_dif_sq)
       
    40     min_noemer = math.sqrt (min_dif_sq * pc_dif_sq)
       
    41 
       
    42     cor_maj = []; cor_min = []
       
    43     for i in range (12):
       
    44         maj_cor = min_cor = 0
       
    45         for j in range (12):
       
    46             k = (i + j) % 12
       
    47             maj_cor += (major[j] - maj_mean) * (pcHst[k] - pc_mean)
       
    48             min_cor += (minor[j] - min_mean) * (pcHst[k] - pc_mean)
       
    49         cor_maj.append (maj_cor / maj_noemer)
       
    50         cor_min.append (min_cor / min_noemer)
       
    51 
       
    52     ks = [(c,i,0) for i,c in enumerate (cor_maj)] + [(c,i,1) for i,c in enumerate (cor_min)]
       
    53     ks.sort ()
       
    54     ks.reverse ()
       
    55     ksb40 = []
       
    56     for c,b12,mnr in ks:
       
    57         b40 = b12b40 [b12]
       
    58         if b12 in dkeus and nacc < 0: # er zijn meer mollen dan kruisen
       
    59             b40 += 4                  # C# => Db (4 => 8) etc.
       
    60         ksb40.append ((c, b40, mnr))
       
    61     return ksb40
       
    62 
       
    63 # the note names on the line of fifth in order of sharpness. The sharpness of the notes is:
       
    64 # -13 (Fbb), -12, -11, ... 0 (Bb), 1, 2 (=C) , ... 21 (B##)
       
    65 Lof = ['Fbb','Cbb','Gbb','Dbb','Abb','Ebb','Bbb','Fb','Cb','Gb','Db','Ab','Eb','Bb',
       
    66        'F','C','G','D','A','E','B','F#','C#','G#','D#','A#','E#','B#',
       
    67        'F##','C##','G##','D##','A##','E##','B##']
       
    68        
       
    69 # returns the base40 number of a note (between 1 and 40) given the sharpness (between -13 and 21)
       
    70 def loftobase40 (lof):
       
    71     return (lof + 12) * 23 % 40 + 1
       
    72 
       
    73 # returns the sharpness of a note given the base40 number
       
    74 def base40tolof (b40):
       
    75     return (b40 * 7 - 1) % 40 - 18
       
    76 
       
    77 # returns the name of a b40 note number
       
    78 def b40nm (b40):
       
    79     if b40 in [6,12,23,29,35]: name = '' # no note on these positions
       
    80     else:
       
    81         ix = base40tolof (b40) # the sharpness
       
    82         name = Lof [ix+13]     # +13 -> index in the line of fifth table
       
    83     return name
       
    84 
       
    85 rc = re.compile (r'([-A-GX][b#]*)(.*)')
       
    86 def splitCh (chnm):
       
    87     ro = rc.match (chnm)
       
    88     if not ro: raise '%s matcht niet' % chnm
       
    89     return ro.group (1), ro.group (2)
       
    90 
       
    91 def mkTemplates ():
       
    92     global templates, accNames, freqTab
       
    93     templates = []
       
    94     accNames = []
       
    95     freqTab = [] # template index -> frequentie index = (5..0), 5 meest voorkomend type, 0 minst voorkomend
       
    96     for sh in range (-6,15):   # sharpness of 'Fb','Cb', Gb, ... F, C, G, ... D#, A#, E#, B#
       
    97         b40 = loftobase40 (sh) # base40 number = roots of chords
       
    98         for it, t in enumerate (base_templates):
       
    99             tx = tuple( [(ival + b40) % 40 for ival in t] )      # the b40 notes of the chord
       
   100             ex = tuple( [n for n in range (40) if not n in tx] ) # the notes notes not in the chord
       
   101             templates.append ((tx, ex))
       
   102             accNames.append (b40nm (b40) + base_names[it])
       
   103     for acc in accNames:
       
   104         chroot, type = splitCh (acc)
       
   105         freqIx = len (base_names) - base_names.index (type) # hoogste index voor meestvoorkomende chordtype
       
   106         freqTab.append (freqIx)
       
   107 
       
   108 def score (iseg, jseg):
       
   109     w = getSegment (iseg, jseg)
       
   110     scores = []
       
   111     nseg = jseg - iseg + 1
       
   112     for it, (tx, ex) in enumerate (templates):
       
   113         notes = [w[i] for i in tx]
       
   114         fout = [w[i] for i in ex]
       
   115         mis = sum ([1 for n in notes if n == 0])
       
   116         s = sum (notes) - sum (fout) - mis
       
   117         #~ ps = [n for n in notes if n > 0]  # noot histogram van segment
       
   118         #~ if ps and min (ps) < nseg * 0.3: s -= 2 # niet gewogen (oud), bevoordeelt s01 t.o.v. s0 + s1
       
   119         #~ if ps and min (ps) * 4 < nseg: s -= int (round (0.1 * nseg)) # nieuw, gewogen
       
   120         rootw = notes[0] # root weight voor tie-break
       
   121         acc = accNames [it]
       
   122         freqIx = freqTab [it]
       
   123         scores.append ((s, rootw, freqIx, acc))
       
   124     scores.sort ()
       
   125     scores.reverse () # hoogste score, hoogste root-weight, hoogste freqIx komt bovenaan
       
   126     highest, _, _, acc = scores[0]
       
   127     return highest, acc, scores[:5] # score, acc-name
       
   128 
       
   129 def readEvents (events, resolution=2): # list of [time, +/- midi note number]
       
   130     #~ events.sort ()
       
   131     tgroep = events[0][0]
       
   132     groep = []
       
   133     merged = []
       
   134     for t, p in events:
       
   135         if t - tgroep < resolution:
       
   136             groep.append (p)
       
   137             tgroep = t
       
   138         else:
       
   139             merged.append ((tgroep, groep))
       
   140             tgroep = t
       
   141             groep = [p]
       
   142     merged.append ((tgroep, groep))
       
   143     klinkt = []
       
   144     kgroep = []
       
   145     for t, g in merged:
       
   146         for p in g:
       
   147             if p > 0: klinkt.append (p)
       
   148             elif -p in klinkt: klinkt.remove (-p)
       
   149             else: print 'unmatched off-message'
       
   150         kgroep.append ((t, klinkt[:]))
       
   151     return kgroep
       
   152 
       
   153 def mkWeights (kgroep):
       
   154     global weights
       
   155     weights = [[] for i in range (len (kgroep))]
       
   156     for ix, (t, g) in enumerate (kgroep):
       
   157         w = 40 * [0]
       
   158         for n in g: w [n % 40] += 1
       
   159         weights [ix] = w
       
   160     return weights
       
   161 
       
   162 def getSegment (i, j):
       
   163     wtot = 40 * [0]
       
   164     for ws in weights [i:j+1]:
       
   165         for i, w in enumerate (ws):
       
   166             wtot [i] += w
       
   167     return wtot
       
   168 
       
   169 def analyseK (kgroep, debug=0):
       
   170     if debug: print 'aantal segmenten', len (kgroep)
       
   171 
       
   172     #~ mkWeights (kgroep)
       
   173     #~ mkTemplates ()
       
   174 
       
   175     j0, j1, j2 = 0,1,2
       
   176     s0, acc0, xs0 = score (j0, j0)
       
   177     s1, acc1, xs1 = score (j1, j1)
       
   178     s01, acc01, xs01 = score (j0, j1)
       
   179     segs = []
       
   180     while j1 < len (weights):
       
   181         s2, acc2, xs2 = score (j2, j2)
       
   182         s12, acc12, xs12 = score (j1, j2)
       
   183         s012, acc012, xs012 = score (j0, j2)
       
   184         left = max ([s0+s12, s0+s1+s2])
       
   185         right = max ([s012, s01+s2])
       
   186         if left <= right:
       
   187             j1, j2 = j2, j2 + 1
       
   188             s0, acc0, xs0 = s01, acc01, xs01
       
   189             s01, acc01, xs01 = s012, acc012, xs012
       
   190         else:
       
   191             segs.append ((j0,j1,s0,acc0,xs0))
       
   192             if debug: print j0, j1, s0, acc0
       
   193             j0, j1, j2 = j1, j2, j2 + 1
       
   194             s0, acc0, xs0 = s1, acc1, xs1
       
   195             s01, acc01, xs01 = s12, acc12, xs12
       
   196         s1, acc1, xs1 = s2, acc2, xs2
       
   197     if j0 < len (weights):
       
   198         segs.append ((j0,j1,s0,acc0,xs0))
       
   199         if debug: print j0, j1, s0, acc0
       
   200 
       
   201     return segs, kgroep
       
   202 
       
   203 if __name__ == '__main__':
       
   204     f = open ('mids/bwv539p.b40')
       
   205     xs = f.readlines ()
       
   206     f.close ()
       
   207     def toint (xs): return map (lambda x: int (x), xs)
       
   208     events = map (lambda x: toint (x.strip().split()), xs)
       
   209     kgroep = readEvents (events)
       
   210     mkWeights (kgroep)
       
   211     mkTemplates ()
       
   212     segs, kgroep = analyseK (kgroep, debug=0)
       
   213     print '%d segs, %d groepen' % (len (segs), len (kgroep))
       
   214     for iseg, jseg, score, acc, rest in segs:
       
   215         print '---', acc, score
       
   216         for t, ns in kgroep [iseg:jseg]:
       
   217             print t, ', '.join (map (b40nm, ns))