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