source: gs3-extensions/web-audio/trunk/js-dsp/component/wf-chroma-transform.js@ 28388

Last change on this file since 28388 was 28388, checked in by davidb, 11 years ago

Set of JS, CSS, PNG etc web resources to support a mixture of audio player/document display capabilities

File size: 11.2 KB
Line 
1
2var WFChromaTransform = function(transformMode,frameBufferLength,channels,samplerate,totalNumFrames) {
3
4 this.getset = {};
5
6 this.transformMode = transformMode;
7
8 this.frameBufferLength = frameBufferLength;
9 this.channels = channels;
10 this.samplerate = samplerate;
11
12 this.totalNumFrames = totalNumFrames; // **** Not currently used
13
14 this.defaults();
15
16 var fftWindowSize = Math.floor(this.frameBufferLength/channels); // check this is a power of 2??
17
18 this.initCQT(fftWindowSize, fftWindowSize/2);
19
20 this.fft = new FFTCustom(fftWindowSize, this.samplerate);
21
22 this.chromaFeaturesOut = new Float32Array(this.numChromaBins); // Init once here, space is reused per calculation
23 this.filterBankOut = new Float32Array(this.cqt_rec.cqtN); // ditto above
24
25 this.chromaFeaturesSequence = new Array();
26 this.filterBankSequence = new Array();
27
28 this.maxval = { freqSpectrum: 0.0, freqPowerSpectrum: 0.0, filterBank: 0.0, chromaFeatures: 0.0 };
29
30}
31
32WFChromaTransform.inherits(Component);
33
34//----------------------------- OVERVIEW -----------------------------------------------------
35
36WFChromaTransform.staticfield('Component', {
37
38 creator: "David Bainbridge",
39 description: "Overview: To be written ...",
40 name: "Chromagram",
41 tags: "signal process, chromagram",
42 dependency: [ "dsp.js" ]
43});
44
45//------------------------------ INPUTS ------------------------------------------------------
46
47WFChromaTransform.staticfield('ComponentInputArray', [
48/*
49 {
50 name: "signal",
51 description: "A Signal object with \"fileLocation\" metadata, indicating the "
52 + "location of the corresponding audio file."
53 }
54*/
55 {
56 name: "inputFrames",
57 description: "Frames of mono-audio input"
58 }
59
60]);
61
62
63//------------------------------ PROPERTIES --------------------------------------------------
64
65WFChromaTransform.staticfield('ComponentPropertyArray', [
66
67]);
68
69
70//------------------------------ OUTPUTS -----------------------------------------------------
71
72WFChromaTransform.staticfield('ComponentOutputArray', [
73
74 {
75 name: "output",
76 description: "Chroma bins"
77 }
78]);
79
80
81
82
83WFChromaTransform.prototype.reset = function()
84{
85 this.maxval.freqSpectrum = 0.0;
86 this.maxval.freqPowerSpectrum = 0.0;
87 this.maxval.filterBank = 0.0;
88 this.maxval.chromaFeatures = 0.0;
89}
90
91WFChromaTransform.prototype.setTransformMode = function(transformMode) {
92 this.transformMode = transformMode;
93}
94
95WFChromaTransform.prototype.getTransformMode = function() {
96 return this.transformMode;
97}
98
99WFChromaTransform.prototype.setMaxVal = function(v)
100{
101 this.maxval[this.transformMode] = v;
102}
103
104
105WFChromaTransform.prototype.getMaxVal = function()
106{
107 var maxVal = this.maxval[this.transformMode];
108
109 return maxVal;
110}
111
112WFChromaTransform.prototype.getChromaFeaturesSequence = function() {
113 return this.chromaFeaturesSequence;
114}
115
116WFChromaTransform.prototype.getFilterBankSequence = function() {
117 return this.filterBankSequence;
118}
119
120
121// ******
122// ******
123
124
125
126WFChromaTransform.prototype.defaults = function() {
127
128 this.constantQ = 12;
129 this.numOctaves = 1;
130 this.numChromaBins = this.constantQ * this.numOctaves;
131
132 this.loEdge = 55.0 * Math.pow(2.0, 2.5/12.0); // low C minus quater tone
133 this.hiEdge = 8000.0;
134
135};
136
137
138WFChromaTransform.prototype.initializeCallback = function(ccp) {
139
140
141 this.progress = 0;
142 this.ssm = null;
143}
144
145
146//
147// init Constant Q transform (http://en.wikipedia.org/wiki/Constant_Q_transform)
148//
149WFChromaTransform.prototype.initCQT = function(fftN, outN) {
150
151 // Linear to log frequency map for FFT
152
153 // BPO = bands per octave
154
155 var info_mess = "chroma-transform: { ";
156 info_mess += "constantQ: " + this.constantQ;
157 info_mess += ", fftN = " + fftN;
158 info_mess += ", outN = " + outN;
159 info_mess += ", sameplerate = " + this.samplerate;
160 info_mess += ", loEdge = " + this.loEdge + " Hz";
161 info_mess += ", hiEdge = " + this.hiEdge + " Hz";
162 info_mess += " }";
163
164 console.info(info_mess);
165
166 var i,j;
167
168 var BPO = this.constantQ;
169 var fratio = Math.pow(2.0,1.0/BPO);// Constant-Q bandwidth
170
171 var cqtN = Math.floor(Math.log(this.hiEdge/this.loEdge)/Math.log(fratio));
172 if(cqtN<1) {
173 console.error("cqtN not positive definite");
174 }
175
176 if (_dspdebug) {
177 console.log("debug: cqtN = " + cqtN);
178 }
179
180 var fftfrqs = new Float32Array(outN); // Actual number of real FFT coefficients
181 var logfrqs = new Float32Array(cqtN); // Number of constant-Q spectral bins
182 var logfbws = new Float32Array(cqtN); // Bandwidths of constant-Q bins
183
184 var CQT = new Float32Array(cqtN*outN); // The transformation matrix
185
186 var mxnorm = new Float32Array(cqtN); // Normalization coefficients
187 var N = fftN; // N used as a (double)
188
189 for(i=0; i < outN; i++) {
190 fftfrqs[i] = i * this.samplerate / N;
191 }
192
193 for(i=0; i<cqtN; i++){
194 logfrqs[i] = this.loEdge * Math.exp(Math.log(2.0)*i/BPO);
195 logfbws[i] = Math.max(logfrqs[i] * (fratio - 1.0), this.samplerate / N);
196 }
197
198 var ovfctr = 0.5475; // Norm constant so CQT'*CQT close to 1.0
199 var tmp,tmp2;
200 var ptr;
201
202 // Build the constant-Q transform (CQT)
203 for(i=0,ptr=0; i<cqtN; i++){
204 mxnorm[i]=0.0;
205 tmp2=1.0/(ovfctr*logfbws[i]);
206 for(j=0 ; j < outN; j++, ptr++){
207 tmp=(logfrqs[i] - fftfrqs[j])*tmp2;
208 tmp=Math.exp(-0.5*tmp*tmp);
209 CQT[ptr]=tmp; // row major transform
210 mxnorm[i]+=tmp*tmp;
211 }
212 mxnorm[i]=2.0*Math.sqrt(mxnorm[i]);
213 }
214
215 // Normalize transform matrix for identity inverse
216 for(i=0,ptr=0; i<cqtN; i++) {
217 tmp = 1.0/mxnorm[i];
218 for(j=0; j<outN; j++,ptr++) {
219 CQT[ptr] *= tmp;
220 }
221 }
222
223 var cqt_rec = { CQT: CQT, cqtN: cqtN, outN: outN };
224
225 if (_dspdebug) {
226 var CQT = cqt_rec.CQT;
227 var cqtN = cqt_rec.cqtN;
228 var outN = cqt_rec.outN;
229
230 //for(i=0,ptr=0; i<cqtN; i++){
231 for(i=0,ptr=0; i<cqtN; i+=10){
232 ptr = i*outN;
233 var line = "";
234 //for(j=0; j<outN; j++,ptr++) {
235 for(j=0; j<outN; j+=20,ptr+=20) {
236 line += CQT[ptr] + " ";
237 }
238 console.log(line);
239 }
240 }
241
242 // store it in object
243 this.cqt_rec = cqt_rec;
244
245};
246
247
248WFChromaTransform.prototype.computeFilterBank = function(fftSpectrumIn,cqtOut)
249{
250 var CQT = this.cqt_rec.CQT;
251 var cqtN = this.cqt_rec.cqtN;
252 var outN = this.cqt_rec.outN;
253
254 // matrix product of CQT * fftSpectrumIn
255
256 var a=cqtN;
257 var i=0;
258
259 while (a--) {
260 cqtOut[i] = 0.0;
261
262 var ptr2 = i * outN;
263 var ptr3 = 0;
264
265 var b = outN;
266 while (b--) {
267
268 cqtOut[i] += CQT[ptr2] * fftSpectrumIn[ptr3];
269
270 ptr2++;
271 ptr3++;
272 }
273
274 if (cqtOut[i]>this.maxval.filterBank) {
275 this.maxval.filterBank = cqtOut[i];
276 }
277
278 i++;
279 }
280}
281
282WFChromaTransform.prototype.computeChroma = function(cqtIn, chromaOut) {
283
284 // compute CHROMA
285
286 var cqtN = cqtIn.length;
287
288 var log10 = Math.log(10);
289
290 var a = this.numChromaBins;
291
292 var i=0;
293
294 while(a--){
295 var c = 1;
296
297 // folder higher up octaves into the first one
298 var ptr1_val = 0;
299
300 //for(var b=this.constantQ+i; b<cqtN; b+=this.constantQ){
301 for(var b=i; b<cqtN; b+=this.numChromaBins){
302 ptr1_val += cqtIn[b];
303 c++;
304 }
305 ptr1_val /= c; // Normalize by number of bands summed
306
307 // ***** can generate negative values ???
308 //ptr1_val = Math.log( ptr1_val ) / log10;
309
310 chromaOut[i] = ptr1_val;
311
312 if (chromaOut[i]>this.maxval.chromaFeatures) {
313 this.maxval.chromaFeatures = chromaOut[i];
314 }
315
316 i++;
317 }
318}
319
320WFChromaTransform.prototype.computeChromaAlt = function(spectrumIn,chromaOut) {
321
322
323 var CB = chromaOut.length; // num chroma bins
324
325 for (var i=0; i<CB; i++) {
326 chromaOut[i] = 0.0;
327 }
328
329 var CR = 440/32.0;
330 var fs = this.samplerate;
331 var fL = (this.frameBufferLength/this.channels); // **** mod by me
332
333 var log2 = Math.log(2);
334
335 for (var k=this.loEdge; k<=this.hiEdge; k++) {
336 var ind = Math.round(CB*Math.log((k)*fs/fL/CR)/log2);
337 var mod_ind = ind%CB;
338 chromaOut[mod_ind] += spectrumIn[k];
339
340 if (chromaOut[mod_ind]>this.maxval.chromaFeatures) {
341 this.maxval.chromaFeatures = chromaOut[mod_ind];
342 }
343
344 }
345
346}
347
348
349WFChromaTransform.prototype.computeTransform = function(filterBankFrame) {
350
351
352 //this.fft.forward(monoAudioFrame);
353
354 //this.computeFilterBank(this.fft.powerSpectrum,this.filterBankOut);
355
356 //var filterBankCopy = new Float32Array(this.filterBankOut); // need a copy of the array (not just a reference)
357 //this.filterBankSequence.push(filterBankCopy);
358
359 this.computeChroma(this.filterBankOut,this.chromaFeaturesOut);
360 //this.computeChromaAlt(this.fft.powerSpectrum,this.chromaFeaturesOut);
361
362 this.computeChroma(filterBankFrame,this.chromaFeaturesOut);
363
364 var chromaFeaturesCopy = new Float32Array(this.chromaFeaturesOut); // need a copy of the array (not just a reference)
365 this.chromaFeaturesSequence.push(chromaFeaturesCopy);
366}
367
368
369
370WFChromaTransform.prototype.executeCallBack = function(cc) {
371
372 // typeof(cc) => ComponentContext
373
374 // get the "[D" input
375
376 //console.info("*** WFChromaTransform::executeCallBack(): away to request 'inputFrames'");
377
378 var frame = cc.getDataComponentFromInput("inputFrames");
379/*
380 var mess = "";
381 for (var i=0; i<frame.length; i+=200) {
382 mess += frame[i] + ", ";
383 }
384 console.log("**** WF Chroma Trans: data samples = " + mess);
385*/
386
387 this.computeTransform(frame);
388}
389
390
391// *********
392// *********
393
394
395WFChromaTransform.prototype.dotProduct = function(v1,v2) {
396 var vlen = v1.length;
397
398 var d = 0;
399
400 for (var i=0; i<vlen; i++) {
401 d = d + (v1[i]*v2[i]);
402 }
403
404 return d;
405}
406
407WFChromaTransform.prototype.vectorLength = function(v) {
408 var vlen = v.length;
409
410 var l = 0.0;
411
412 for (var i=0; i<vlen; i++) {
413 l = l + (v[i]*v[i]);
414 }
415
416 l = Math.sqrt(l);
417
418 return l;
419}
420
421
422WFChromaTransform.prototype.computeSelfSimilarityMatrix = function(chromaFeaturesSequence) {
423
424 var num_frames = chromaFeaturesSequence.length;
425
426 var norm_lengths = new Float32Array(num_frames);
427
428 this.csm = new Array(num_frames) ; // chroma (self) similarity matrix
429 for (var i=0; i<num_frames; i++) {
430 this.csm[i] = new Float32Array(num_frames);
431 this.csm[i][i]=1.0;
432
433 norm_lengths[i] = this.vectorLength(chromaFeaturesSequence[i]);
434 }
435
436
437 var total_calcs = num_frames*num_frames;
438 var c = 0;
439 this.progress = 0;
440
441 var i = 0;
442
443 (function(outerThis) {
444 //for (var i=0; i<num_frames-1; i++) {
445
446
447 for (var j=i+1; j<num_frames; j++) {
448
449 outerThis.progress = Math.round((100.0*c)/total_calcs);
450
451 outerThis.csm[i][j] = outerThis.dotProduct(chromaFeaturesSequence[i],chromaFeaturesSequence[j])/(norm_lengths[i]*norm_lengths[j]);
452
453 outerThis.csm[j][i] = outerThis.csm[i][j];
454 c+=2;
455 }
456 i++;
457
458 if (i<num_frames-1) {
459 setTimeout(arguments.callee,0,outerThis);
460 }
461 else {
462 outerThis.progress = 100;
463 }
464 })(this);
465
466
467}
Note: See TracBrowser for help on using the repository browser.