1 |
|
---|
2 | var WFFilterBank = function(frameBufferLength,channels,samplerate,totalNumFrames) {
|
---|
3 |
|
---|
4 | this.frameBufferLength = frameBufferLength;
|
---|
5 | this.channels = channels;
|
---|
6 | this.samplerate = samplerate;
|
---|
7 |
|
---|
8 | this.totalNumFrames = totalNumFrames; // **** Not currently used
|
---|
9 |
|
---|
10 | this.defaults();
|
---|
11 |
|
---|
12 | var fftWindowSize = Math.floor(this.frameBufferLength/channels); // check this is a power of 2??
|
---|
13 |
|
---|
14 | this.initCQT(fftWindowSize, fftWindowSize/2);
|
---|
15 |
|
---|
16 | //this.fft = new FFTCustom(fftWindowSize, this.samplerate);
|
---|
17 |
|
---|
18 | this.filterBankOut = new Float32Array(this.cqt_rec.cqtN); // ditto above
|
---|
19 |
|
---|
20 | this.filterBankSequence = new Array();
|
---|
21 |
|
---|
22 | this.maxval = 0.0;
|
---|
23 |
|
---|
24 | }
|
---|
25 |
|
---|
26 | WFFilterBank.inherits(Component);
|
---|
27 |
|
---|
28 | //----------------------------- OVERVIEW -----------------------------------------------------
|
---|
29 |
|
---|
30 | WFFilterBank.staticfield('Component', {
|
---|
31 |
|
---|
32 | creator: "David Bainbridge",
|
---|
33 | description: "Overview: Computes a frequency filter bank based on frequency spectrum input",
|
---|
34 | name: "filterBank",
|
---|
35 | tags: "signal process, filter-bank",
|
---|
36 | dependency: [ "dsp.js" ]
|
---|
37 | });
|
---|
38 |
|
---|
39 | //------------------------------ INPUTS ------------------------------------------------------
|
---|
40 |
|
---|
41 | WFFilterBank.staticfield('ComponentInputArray', [
|
---|
42 | {
|
---|
43 | name: "freqSpectrumInputFrames",
|
---|
44 | description: "Frames of (mono) freqency spectrum input"
|
---|
45 | }
|
---|
46 |
|
---|
47 | ]);
|
---|
48 |
|
---|
49 |
|
---|
50 | //------------------------------ PROPERTIES --------------------------------------------------
|
---|
51 |
|
---|
52 | WFFilterBank.staticfield('ComponentPropertyArray', [
|
---|
53 | ]);
|
---|
54 |
|
---|
55 |
|
---|
56 | //------------------------------ OUTPUTS -----------------------------------------------------
|
---|
57 |
|
---|
58 | WFFilterBank.staticfield('ComponentOutputArray', [
|
---|
59 | {
|
---|
60 | name: "filterBankFrames",
|
---|
61 | description: "Per frame filter bank"
|
---|
62 | }
|
---|
63 | ]);
|
---|
64 |
|
---|
65 |
|
---|
66 |
|
---|
67 | WFFilterBank.prototype.reset = function()
|
---|
68 | {
|
---|
69 | this.maxval = 0.0;
|
---|
70 | }
|
---|
71 |
|
---|
72 |
|
---|
73 | WFFilterBank.prototype.setMaxVal = function(v)
|
---|
74 | {
|
---|
75 | this.maxval = v;
|
---|
76 | }
|
---|
77 |
|
---|
78 |
|
---|
79 | WFFilterBank.prototype.getMaxVal = function()
|
---|
80 | {
|
---|
81 | return this.maxval;
|
---|
82 | }
|
---|
83 |
|
---|
84 | WFFilterBank.prototype.getFilterBankSequence = function() {
|
---|
85 | return this.filterBankSequence;
|
---|
86 | }
|
---|
87 |
|
---|
88 |
|
---|
89 | // ******
|
---|
90 | // ******
|
---|
91 |
|
---|
92 |
|
---|
93 |
|
---|
94 | WFFilterBank.prototype.defaults = function() {
|
---|
95 |
|
---|
96 | this.constantQ = 12;
|
---|
97 |
|
---|
98 | this.loEdge = 55.0 * Math.pow(2.0, 2.5/12.0); // low C minus quater tone
|
---|
99 | this.hiEdge = 8000.0;
|
---|
100 | };
|
---|
101 |
|
---|
102 |
|
---|
103 | WFFilterBank.prototype.initializeCallback = function(ccp) {
|
---|
104 |
|
---|
105 | }
|
---|
106 |
|
---|
107 |
|
---|
108 | //
|
---|
109 | // init Constant Q transform (http://en.wikipedia.org/wiki/Constant_Q_transform)
|
---|
110 | //
|
---|
111 | WFFilterBank.prototype.initCQT = function(fftN, outN) {
|
---|
112 |
|
---|
113 | // Linear to log frequency map for FFT
|
---|
114 |
|
---|
115 | // BPO = bands per octave
|
---|
116 |
|
---|
117 | var info_mess = "filter-bank: { ";
|
---|
118 | info_mess += "constantQ: " + this.constantQ;
|
---|
119 | info_mess += ", fftN = " + fftN;
|
---|
120 | info_mess += ", outN = " + outN;
|
---|
121 | info_mess += ", sameplerate = " + this.samplerate;
|
---|
122 | info_mess += ", loEdge = " + this.loEdge + " Hz";
|
---|
123 | info_mess += ", hiEdge = " + this.hiEdge + " Hz";
|
---|
124 | info_mess += " }";
|
---|
125 |
|
---|
126 | console.info(info_mess);
|
---|
127 |
|
---|
128 | var i,j;
|
---|
129 |
|
---|
130 | var BPO = this.constantQ;
|
---|
131 | var fratio = Math.pow(2.0,1.0/BPO);// Constant-Q bandwidth
|
---|
132 |
|
---|
133 | var cqtN = Math.floor(Math.log(this.hiEdge/this.loEdge)/Math.log(fratio));
|
---|
134 | if(cqtN<1) {
|
---|
135 | console.error("cqtN not positive definite");
|
---|
136 | }
|
---|
137 |
|
---|
138 | if (_dspdebug) {
|
---|
139 | console.log("debug: cqtN = " + cqtN);
|
---|
140 | }
|
---|
141 |
|
---|
142 | var fftfrqs = new Float32Array(outN); // Actual number of real FFT coefficients
|
---|
143 | var logfrqs = new Float32Array(cqtN); // Number of constant-Q spectral bins
|
---|
144 | var logfbws = new Float32Array(cqtN); // Bandwidths of constant-Q bins
|
---|
145 |
|
---|
146 | var CQT = new Float32Array(cqtN*outN); // The transformation matrix
|
---|
147 |
|
---|
148 | var mxnorm = new Float32Array(cqtN); // Normalization coefficients
|
---|
149 | var N = fftN; // N used as a (double)
|
---|
150 |
|
---|
151 | for(i=0; i < outN; i++) {
|
---|
152 | fftfrqs[i] = i * this.samplerate / N;
|
---|
153 | }
|
---|
154 |
|
---|
155 | for(i=0; i<cqtN; i++){
|
---|
156 | logfrqs[i] = this.loEdge * Math.exp(Math.log(2.0)*i/BPO);
|
---|
157 | logfbws[i] = Math.max(logfrqs[i] * (fratio - 1.0), this.samplerate / N);
|
---|
158 | }
|
---|
159 |
|
---|
160 | var ovfctr = 0.5475; // Norm constant so CQT'*CQT close to 1.0
|
---|
161 | var tmp,tmp2;
|
---|
162 | var ptr;
|
---|
163 |
|
---|
164 | // Build the constant-Q transform (CQT)
|
---|
165 | for(i=0,ptr=0; i<cqtN; i++){
|
---|
166 | mxnorm[i]=0.0;
|
---|
167 | tmp2=1.0/(ovfctr*logfbws[i]);
|
---|
168 | for(j=0 ; j < outN; j++, ptr++){
|
---|
169 | tmp=(logfrqs[i] - fftfrqs[j])*tmp2;
|
---|
170 | tmp=Math.exp(-0.5*tmp*tmp);
|
---|
171 | CQT[ptr]=tmp; // row major transform
|
---|
172 | mxnorm[i]+=tmp*tmp;
|
---|
173 | }
|
---|
174 | mxnorm[i]=2.0*Math.sqrt(mxnorm[i]);
|
---|
175 | }
|
---|
176 |
|
---|
177 | // Normalize transform matrix for identity inverse
|
---|
178 | for(i=0,ptr=0; i<cqtN; i++) {
|
---|
179 | tmp = 1.0/mxnorm[i];
|
---|
180 | for(j=0; j<outN; j++,ptr++) {
|
---|
181 | CQT[ptr] *= tmp;
|
---|
182 | }
|
---|
183 | }
|
---|
184 |
|
---|
185 | var cqt_rec = { CQT: CQT, cqtN: cqtN, outN: outN };
|
---|
186 |
|
---|
187 | if (_dspdebug) {
|
---|
188 | var CQT = cqt_rec.CQT;
|
---|
189 | var cqtN = cqt_rec.cqtN;
|
---|
190 | var outN = cqt_rec.outN;
|
---|
191 |
|
---|
192 | //for(i=0,ptr=0; i<cqtN; i++){
|
---|
193 | for(i=0,ptr=0; i<cqtN; i+=10){
|
---|
194 | ptr = i*outN;
|
---|
195 | var line = "";
|
---|
196 | //for(j=0; j<outN; j++,ptr++) {
|
---|
197 | for(j=0; j<outN; j+=20,ptr+=20) {
|
---|
198 | line += CQT[ptr] + " ";
|
---|
199 | }
|
---|
200 | console.log(line);
|
---|
201 | }
|
---|
202 | }
|
---|
203 |
|
---|
204 | // store it in object
|
---|
205 | this.cqt_rec = cqt_rec;
|
---|
206 |
|
---|
207 | };
|
---|
208 |
|
---|
209 |
|
---|
210 | WFFilterBank.prototype.computeFilterBank = function(fftSpectrumIn,cqtOut)
|
---|
211 | {
|
---|
212 | var CQT = this.cqt_rec.CQT;
|
---|
213 | var cqtN = this.cqt_rec.cqtN;
|
---|
214 | var outN = this.cqt_rec.outN;
|
---|
215 |
|
---|
216 | // matrix product of CQT * fftSpectrumIn
|
---|
217 |
|
---|
218 | var a=cqtN;
|
---|
219 | var i=0;
|
---|
220 |
|
---|
221 | while (a--) {
|
---|
222 | cqtOut[i] = 0.0;
|
---|
223 |
|
---|
224 | var ptr2 = i * outN;
|
---|
225 | var ptr3 = 0;
|
---|
226 |
|
---|
227 | var b = outN;
|
---|
228 | while (b--) {
|
---|
229 |
|
---|
230 | cqtOut[i] += CQT[ptr2] * fftSpectrumIn[ptr3];
|
---|
231 |
|
---|
232 | ptr2++;
|
---|
233 | ptr3++;
|
---|
234 | }
|
---|
235 |
|
---|
236 | if (cqtOut[i]>this.maxval) {
|
---|
237 | this.maxval = cqtOut[i];
|
---|
238 | }
|
---|
239 |
|
---|
240 | i++;
|
---|
241 | }
|
---|
242 | }
|
---|
243 |
|
---|
244 | /*
|
---|
245 | WFFilterBank.prototype.computeTransform = function(signal) {
|
---|
246 |
|
---|
247 | // executeCallback
|
---|
248 | // need to get signal from 'cc' rather than passed in directly as param
|
---|
249 |
|
---|
250 | this.fft.forward(signal);
|
---|
251 |
|
---|
252 | this.computeFilterBank(this.fft.powerSpectrum,this.filterBankOut);
|
---|
253 |
|
---|
254 | var filterBankCopy = new Float32Array(this.filterBankOut); // need a copy of the array (not just a reference)
|
---|
255 | this.filterBankSequence.push(filterBankCopy);
|
---|
256 |
|
---|
257 | if (_dspdebug) {
|
---|
258 | var line = "";
|
---|
259 | for (i=0; i<this.filterBankOut.length; i+=20) {
|
---|
260 | line += this.filterBankOut[i] + " ";
|
---|
261 | }
|
---|
262 |
|
---|
263 | console.log("filter bank: " + line);
|
---|
264 | }
|
---|
265 |
|
---|
266 | }
|
---|
267 |
|
---|
268 | */
|
---|
269 |
|
---|
270 |
|
---|
271 | WFFilterBank.prototype.executeCallBack = function(cc) {
|
---|
272 |
|
---|
273 | // typeof(cc) => ComponentContext
|
---|
274 |
|
---|
275 | // get the "[D" input
|
---|
276 |
|
---|
277 | //console.info("*** WFFilterBank::executeCallBack(): away to request 'inputFrames'");
|
---|
278 |
|
---|
279 | var frame = cc.getDataComponentFromInput("freqSpectrumInputFrames");
|
---|
280 |
|
---|
281 | // this.fft.forward(frame);
|
---|
282 |
|
---|
283 | // this.computeFilterBank(this.fft.powerSpectrum,this.filterBankOut);
|
---|
284 |
|
---|
285 | this.computeFilterBank(frame,this.filterBankOut);
|
---|
286 |
|
---|
287 | var filterBankCopy = new Float32Array(this.filterBankOut); // need a copy of the array (not just a reference)
|
---|
288 | this.filterBankSequence.push(filterBankCopy);
|
---|
289 |
|
---|
290 | cc.pushDataComponentToOutput("filterBankFrames",this.filterBankOut);
|
---|
291 |
|
---|
292 | }
|
---|
293 |
|
---|