|
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)) |