fazekasgy@9
|
1 '''PySpectralFeatures.py - Example plugin demonstrates'''
|
fazekasgy@9
|
2 '''how to use the NumPy array interface and write Matlab style code.'''
|
fazekasgy@9
|
3
|
fazekasgy@9
|
4 from numpy import *
|
fazekasgy@9
|
5
|
fazekasgy@9
|
6 class PySpectralFeatures:
|
fazekasgy@9
|
7
|
cannam@24
|
8 def __init__(self,inputSampleRate):
|
cannam@24
|
9 self.m_inputSampleRate = inputSampleRate
|
fazekasgy@9
|
10 self.m_stepSize = 0
|
fazekasgy@9
|
11 self.m_blockSize = 0
|
fazekasgy@9
|
12 self.m_channels = 0
|
fazekasgy@26
|
13 self.threshold = 0.05
|
fazekasgy@9
|
14 self.r = 2.0
|
fazekasgy@26
|
15 return None
|
fazekasgy@9
|
16
|
cannam@24
|
17 def initialise(self,channels,stepSize,blockSize):
|
fazekasgy@9
|
18 self.m_channels = channels
|
fazekasgy@9
|
19 self.m_stepSize = stepSize
|
fazekasgy@9
|
20 self.m_blockSize = blockSize
|
fazekasgy@9
|
21 self.prevMag = zeros((blockSize/2)-1)
|
fazekasgy@9
|
22 self.prevMag[0] = 1
|
fazekasgy@9
|
23 return True
|
fazekasgy@26
|
24
|
fazekasgy@26
|
25 def reset(self):
|
fazekasgy@26
|
26 # reset any initial conditions
|
fazekasgy@26
|
27 self.prevMag = zeros((self.m_blockSize/2)-1)
|
fazekasgy@26
|
28 self.prevMag[0] = 1
|
fazekasgy@26
|
29 return None
|
fazekasgy@9
|
30
|
fazekasgy@9
|
31 def getMaker(self):
|
fazekasgy@9
|
32 return 'VamPy Example Plugins'
|
fazekasgy@9
|
33
|
fazekasgy@9
|
34 def getName(self):
|
fazekasgy@9
|
35 return 'VamPy Spectral Features'
|
fazekasgy@9
|
36
|
fazekasgy@9
|
37 def getIdentifier(self):
|
fazekasgy@9
|
38 return 'vampy-sf2'
|
fazekasgy@9
|
39
|
fazekasgy@9
|
40 def getDescription(self):
|
fazekasgy@9
|
41 return 'A collection of low-level spectral descriptors.'
|
fazekasgy@9
|
42
|
fazekasgy@9
|
43 def getMaxChannelCount(self):
|
fazekasgy@9
|
44 return 1
|
fazekasgy@9
|
45
|
fazekasgy@9
|
46 def getInputDomain(self):
|
fazekasgy@9
|
47 return 'FrequencyDomain'
|
fazekasgy@9
|
48
|
fazekasgy@9
|
49 def getOutputDescriptors(self):
|
fazekasgy@9
|
50
|
fazekasgy@9
|
51 #descriptors are python dictionaries
|
fazekasgy@9
|
52 #Generic values are the same for all
|
fazekasgy@9
|
53 Generic={
|
fazekasgy@9
|
54 'hasFixedBinCount':True,
|
fazekasgy@9
|
55 'binCount':1,
|
fazekasgy@9
|
56 'hasKnownExtents':False,
|
fazekasgy@9
|
57 'isQuantized':False,
|
fazekasgy@9
|
58 'sampleType':'OneSamplePerStep'
|
fazekasgy@9
|
59 }
|
fazekasgy@9
|
60
|
fazekasgy@9
|
61 #Spectral centroid etc...
|
fazekasgy@9
|
62 SC=Generic.copy()
|
fazekasgy@9
|
63 SC.update({
|
fazekasgy@9
|
64 'identifier':'vampy-sc',
|
fazekasgy@9
|
65 'name':'Spectral Centroid',
|
fazekasgy@9
|
66 'description':'Spectral Centroid (Brightness)',
|
fazekasgy@9
|
67 'unit':'Hz'
|
fazekasgy@9
|
68 })
|
fazekasgy@9
|
69
|
fazekasgy@9
|
70 SCF=Generic.copy()
|
fazekasgy@9
|
71 SCF.update({
|
fazekasgy@9
|
72 'identifier':'vampy-scf',
|
fazekasgy@9
|
73 'name':'Spectral Crest Factor',
|
fazekasgy@9
|
74 'description':'Spectral Crest (Tonality)',
|
fazekasgy@9
|
75 'unit':'v'
|
fazekasgy@9
|
76 })
|
fazekasgy@9
|
77
|
fazekasgy@9
|
78 BW=Generic.copy()
|
fazekasgy@9
|
79 BW.update({
|
fazekasgy@9
|
80 'identifier':'vampy-bw',
|
fazekasgy@9
|
81 'name':'Band Width',
|
fazekasgy@9
|
82 'description':'Spectral Band Width',
|
fazekasgy@9
|
83 'unit':'Hz',
|
fazekasgy@9
|
84 })
|
fazekasgy@9
|
85
|
fazekasgy@9
|
86 SE=Generic.copy()
|
fazekasgy@9
|
87 SE.update({
|
fazekasgy@9
|
88 'identifier':'vampy-se',
|
fazekasgy@9
|
89 'name':'Shannon Entropy',
|
fazekasgy@9
|
90 'description':'Shannon Entropy',
|
fazekasgy@9
|
91 'unit':'',
|
fazekasgy@9
|
92 })
|
fazekasgy@9
|
93
|
fazekasgy@9
|
94 RE=Generic.copy()
|
fazekasgy@9
|
95 RE.update({
|
fazekasgy@9
|
96 'identifier':'vampy-re',
|
fazekasgy@9
|
97 'name':'Renyi Entropy',
|
fazekasgy@9
|
98 'description':'Renyi Entropy',
|
fazekasgy@9
|
99 'unit':'',
|
fazekasgy@9
|
100 })
|
fazekasgy@9
|
101
|
fazekasgy@9
|
102 KL=Generic.copy()
|
fazekasgy@9
|
103 KL.update({
|
fazekasgy@9
|
104 'identifier':'vampy-kl',
|
fazekasgy@9
|
105 'name':'Kullback Leibler divergence',
|
fazekasgy@9
|
106 'description':'KL divergence between successive spectra',
|
fazekasgy@9
|
107 'unit':'',
|
fazekasgy@9
|
108 })
|
fazekasgy@9
|
109
|
fazekasgy@9
|
110 #return a list of dictionaries
|
fazekasgy@9
|
111 return [SC,SCF,BW,SE,RE,KL]
|
fazekasgy@9
|
112
|
fazekasgy@9
|
113 def getParameterDescriptors(self):
|
fazekasgy@9
|
114 threshold={
|
fazekasgy@9
|
115 'identifier':'threshold',
|
fazekasgy@9
|
116 'name':'Noise threshold: ',
|
fazekasgy@26
|
117 'description':'Noise threshold',
|
fazekasgy@9
|
118 'unit':'v',
|
fazekasgy@9
|
119 'minValue':0.0,
|
fazekasgy@9
|
120 'maxValue':0.5,
|
fazekasgy@9
|
121 'defaultValue':0.05,
|
fazekasgy@9
|
122 'isQuantized':False
|
fazekasgy@9
|
123 }
|
fazekasgy@9
|
124
|
fazekasgy@9
|
125 renyicoeff={
|
fazekasgy@9
|
126 'identifier':'r',
|
fazekasgy@9
|
127 'name':'Renyi entropy coeff: ',
|
fazekasgy@26
|
128 'description':'Renyi entropy coeff',
|
fazekasgy@9
|
129 'unit':'',
|
fazekasgy@9
|
130 'minValue':0.0,
|
fazekasgy@9
|
131 'maxValue':10.0,
|
fazekasgy@26
|
132 'defaultValue':2.0,
|
fazekasgy@9
|
133 'isQuantized':False
|
fazekasgy@9
|
134 }
|
fazekasgy@9
|
135
|
fazekasgy@9
|
136 return [threshold,renyicoeff]
|
fazekasgy@9
|
137
|
fazekasgy@9
|
138 def setParameter(self,paramid,newval):
|
fazekasgy@9
|
139 if paramid == 'threshold' :
|
fazekasgy@9
|
140 self.threshold = newval
|
fazekasgy@9
|
141 if paramid == 'r' :
|
fazekasgy@9
|
142 self.r == newval
|
fazekasgy@9
|
143 return
|
fazekasgy@9
|
144
|
fazekasgy@9
|
145 def getParameter(self,paramid):
|
fazekasgy@9
|
146 if paramid == 'threshold' :
|
fazekasgy@9
|
147 return self.threshold
|
fazekasgy@9
|
148 if paramid == 'r':
|
fazekasgy@9
|
149 return float(self.r)
|
fazekasgy@9
|
150 else:
|
fazekasgy@9
|
151 return 0.0
|
fazekasgy@9
|
152
|
fazekasgy@9
|
153 def processN(self,membuffer,samplecount):
|
fazekasgy@9
|
154 fftsize = self.m_blockSize
|
fazekasgy@9
|
155 sampleRate = self.m_inputSampleRate
|
fazekasgy@9
|
156
|
fazekasgy@9
|
157 #for time domain plugins use the following line:
|
fazekasgy@9
|
158 #audioSamples = frombuffer(membuffer[0],float32)
|
fazekasgy@26
|
159 #for frequency domain plugins use the following line:
|
fazekasgy@9
|
160 complexSpectrum = frombuffer(membuffer[0],complex64,-1,8)
|
fazekasgy@26
|
161 #meaning of the parameters above:
|
fazekasgy@26
|
162 #-1: do until the end, skip DC 2*32bit / 8bit = 8byte
|
fazekasgy@9
|
163 magnitudeSpectrum = abs(complexSpectrum) / (fftsize/2)
|
fazekasgy@9
|
164 tpower = sum(magnitudeSpectrum)
|
fazekasgy@9
|
165 #phaseSpectrum = angle(complexSpectrum)
|
fazekasgy@9
|
166
|
fazekasgy@9
|
167 freq = array(range(1,len(complexSpectrum)+1)) \
|
fazekasgy@9
|
168 * sampleRate / fftsize
|
fazekasgy@9
|
169
|
fazekasgy@9
|
170 centroid = 0.0
|
fazekasgy@9
|
171 crest = 0.0
|
fazekasgy@9
|
172 bw = 0.0
|
fazekasgy@9
|
173 shannon = 0.0
|
fazekasgy@9
|
174 renyi = 0.0
|
fazekasgy@9
|
175 KLdiv = 0.0
|
fazekasgy@9
|
176 exp=1.0 / (fftsize/2)
|
fazekasgy@9
|
177
|
fazekasgy@9
|
178 #declare outputs
|
fazekasgy@9
|
179 output0=[]
|
fazekasgy@9
|
180 output1=[]
|
fazekasgy@9
|
181 output2=[]
|
fazekasgy@9
|
182 output3=[]
|
fazekasgy@9
|
183 output4=[]
|
fazekasgy@9
|
184 output5=[]
|
fazekasgy@9
|
185
|
fazekasgy@9
|
186 if tpower > self.threshold :
|
fazekasgy@9
|
187
|
fazekasgy@9
|
188 centroid = sum(freq * magnitudeSpectrum) / tpower
|
fazekasgy@9
|
189 crest = max(magnitudeSpectrum) / tpower
|
fazekasgy@9
|
190 bw = sum( abs(freq - centroid) * magnitudeSpectrum ) / tpower
|
fazekasgy@9
|
191 normMag = magnitudeSpectrum / tpower #make it sum to 1
|
fazekasgy@9
|
192 shannon = - sum( normMag * log2(normMag) )
|
fazekasgy@26
|
193 renyi = (1/1-self.r) * log10( sum( power(normMag,self.r)))
|
fazekasgy@9
|
194 KLdiv = sum( normMag * log2(normMag / self.prevMag) )
|
fazekasgy@9
|
195 self.prevMag = normMag
|
fazekasgy@9
|
196
|
fazekasgy@9
|
197 output0.append({
|
fazekasgy@9
|
198 'hasTimestamp':False,
|
fazekasgy@9
|
199 'values':[float(centroid)],
|
fazekasgy@9
|
200 #'label':str(centroid)
|
fazekasgy@9
|
201 })
|
fazekasgy@9
|
202
|
fazekasgy@9
|
203 output1.append({
|
fazekasgy@9
|
204 'hasTimestamp':False,
|
fazekasgy@9
|
205 'values':[float(crest)],
|
fazekasgy@9
|
206 #'label':str(crest)
|
fazekasgy@9
|
207 })
|
fazekasgy@9
|
208
|
fazekasgy@9
|
209 output2.append({
|
fazekasgy@9
|
210 'hasTimestamp':False,
|
fazekasgy@9
|
211 'values':[float(bw)],
|
fazekasgy@9
|
212 #'label':str(bw)
|
fazekasgy@9
|
213 })
|
fazekasgy@9
|
214
|
fazekasgy@9
|
215 output3.append({
|
fazekasgy@9
|
216 'hasTimestamp':False,
|
fazekasgy@9
|
217 'values':[float(shannon)],
|
fazekasgy@9
|
218 #'label':str(shannon)
|
fazekasgy@9
|
219 })
|
fazekasgy@9
|
220
|
fazekasgy@9
|
221 output4.append({
|
fazekasgy@9
|
222 'hasTimestamp':False,
|
fazekasgy@9
|
223 'values':[float(renyi)],
|
fazekasgy@9
|
224 #'label':str(renyi)
|
fazekasgy@9
|
225 })
|
fazekasgy@9
|
226
|
fazekasgy@9
|
227 output5.append({
|
fazekasgy@9
|
228 'hasTimestamp':False,
|
fazekasgy@9
|
229 'values':[float(KLdiv)], #strictly must be a list
|
fazekasgy@9
|
230 #'label':str(renyi)
|
fazekasgy@9
|
231 })
|
fazekasgy@9
|
232
|
fazekasgy@9
|
233 #return a LIST of list of dictionaries
|
fazekasgy@9
|
234 return [output0,output1,output2,output3,output4,output5]
|
fazekasgy@26
|
235
|