source: gs3-extensions/web-audio/trunk/js-dsp/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: 9.3 KB
Line 
1
2var ChromaTransform = function(transformMode,frameBufferLength,channels,samplerate,totalNumFrames) {
3
4 this.frameBufferLength = frameBufferLength;
5 this.channels = channels;
6 this.samplerate = samplerate;
7
8 // **** Not currently used
9 this.totalNumFrames = totalNumFrames;
10
11 this.constantQ = 12;
12 this.numOctaves = 1;
13 this.numChromaBins = this.constantQ * this.numOctaves;
14
15 this.loEdge = 55.0 * Math.pow(2.0, 2.5/12.0); // low C minus quater tone
16 this.hiEdge = 8000.0;
17
18 this.transformMode = transformMode;
19
20 var fftWindowSize = Math.floor(frameBufferLength/channels); // check this is a power of 2??
21
22 this.initCQT(fftWindowSize, fftWindowSize/2);
23
24 this.fft = new FFTCustom(fftWindowSize, samplerate);
25
26 this.chromaFeaturesOut = new Float32Array(this.numChromaBins); // Init once here, space is reused per calculation
27 this.filterBankOut = new Float32Array(this.cqt_rec.cqtN); // ditto above
28
29 this.chromaFeaturesSequence = new Array();
30 this.filterBankSequence = new Array();
31
32 this.maxval = { freqSpectrum: 0.0, freqPowerSpectrum: 0.0, filterBank: 0.0, chromaFeatures: 0.0 };
33
34 this.progress = 0;
35 this.ssm = null;
36
37};
38
39ChromaTransform.prototype.reset = function()
40{
41 this.maxval.freqSpectrum = 0.0;
42 this.maxval.freqPowerSpectrum = 0.0;
43 this.maxval.filterBank = 0.0;
44 this.maxval.chromaFeatures = 0.0;
45}
46
47ChromaTransform.prototype.setTransformMode = function(transformMode) {
48 this.transformMode = transformMode;
49}
50
51ChromaTransform.prototype.getTransformMode = function() {
52 return this.transformMode;
53}
54
55ChromaTransform.prototype.setMaxVal = function(v)
56{
57 this.maxval[this.transformMode] = v;
58}
59
60
61ChromaTransform.prototype.getMaxVal = function()
62{
63 var maxVal = this.maxval[this.transformMode];
64
65 return maxVal;
66}
67
68ChromaTransform.prototype.getChromaFeaturesSequence = function() {
69 return this.chromaFeaturesSequence;
70}
71
72ChromaTransform.prototype.getFilterBankSequence = function() {
73 return this.filterBankSequence;
74}
75
76//
77// init Constant Q transform (http://en.wikipedia.org/wiki/Constant_Q_transform)
78//
79ChromaTransform.prototype.initCQT = function(fftN, outN) {
80
81 // Linear to log frequency map for FFT
82
83 // BPO = bands per octave
84
85 var info_mess = "chroma-transform: { ";
86 info_mess += "constantQ: " + this.constantQ;
87 info_mess += ", fftN = " + fftN;
88 info_mess += ", outN = " + outN;
89 info_mess += ", sameplerate = " + this.samplerate;
90 info_mess += ", loEdge = " + this.loEdge + " Hz";
91 info_mess += ", hiEdge = " + this.hiEdge + " Hz";
92 info_mess += " }";
93
94 console.info(info_mess);
95
96 var i,j;
97
98 var BPO = this.constantQ;
99 var fratio = Math.pow(2.0,1.0/BPO);// Constant-Q bandwidth
100
101 var cqtN = Math.floor(Math.log(this.hiEdge/this.loEdge)/Math.log(fratio));
102 if(cqtN<1) {
103 console.error("cqtN not positive definite");
104 }
105
106 if (_debug) {
107 console.log("debug: cqtN = " + cqtN);
108 }
109
110 var fftfrqs = new Float32Array(outN); // Actual number of real FFT coefficients
111 var logfrqs = new Float32Array(cqtN); // Number of constant-Q spectral bins
112 var logfbws = new Float32Array(cqtN); // Bandwidths of constant-Q bins
113
114 var CQT = new Float32Array(cqtN*outN); // The transformation matrix
115
116 var mxnorm = new Float32Array(cqtN); // Normalization coefficients
117 var N = fftN; // N used as a (double)
118
119 for(i=0; i < outN; i++) {
120 fftfrqs[i] = i * this.samplerate / N;
121 }
122
123 for(i=0; i<cqtN; i++){
124 logfrqs[i] = this.loEdge * Math.exp(Math.log(2.0)*i/BPO);
125 logfbws[i] = Math.max(logfrqs[i] * (fratio - 1.0), this.samplerate / N);
126 }
127
128 var ovfctr = 0.5475; // Norm constant so CQT'*CQT close to 1.0
129 var tmp,tmp2;
130 var ptr;
131
132 // Build the constant-Q transform (CQT)
133 for(i=0,ptr=0; i<cqtN; i++){
134 mxnorm[i]=0.0;
135 tmp2=1.0/(ovfctr*logfbws[i]);
136 for(j=0 ; j < outN; j++, ptr++){
137 tmp=(logfrqs[i] - fftfrqs[j])*tmp2;
138 tmp=Math.exp(-0.5*tmp*tmp);
139 CQT[ptr]=tmp; // row major transform
140 mxnorm[i]+=tmp*tmp;
141 }
142 mxnorm[i]=2.0*Math.sqrt(mxnorm[i]);
143 }
144
145 // Normalize transform matrix for identity inverse
146 for(i=0,ptr=0; i<cqtN; i++) {
147 tmp = 1.0/mxnorm[i];
148 for(j=0; j<outN; j++,ptr++) {
149 CQT[ptr] *= tmp;
150 }
151 }
152
153 var cqt_rec = { CQT: CQT, cqtN: cqtN, outN: outN };
154
155 if (_debug) {
156 var CQT = cqt_rec.CQT;
157 var cqtN = cqt_rec.cqtN;
158 var outN = cqt_rec.outN;
159
160 //for(i=0,ptr=0; i<cqtN; i++){
161 for(i=0,ptr=0; i<cqtN; i+=10){
162 ptr = i*outN;
163 var line = "";
164 //for(j=0; j<outN; j++,ptr++) {
165 for(j=0; j<outN; j+=20,ptr+=20) {
166 line += CQT[ptr] + " ";
167 }
168 console.log(line);
169 }
170 }
171
172 // store it in object
173 this.cqt_rec = cqt_rec;
174
175};
176
177
178ChromaTransform.prototype.computeFilterBank = function(fftSpectrumIn,cqtOut)
179{
180 var CQT = this.cqt_rec.CQT;
181 var cqtN = this.cqt_rec.cqtN;
182 var outN = this.cqt_rec.outN;
183
184 // matrix product of CQT * fftSpectrumIn
185
186 var a=cqtN;
187 var i=0;
188
189 while (a--) {
190 cqtOut[i] = 0.0;
191
192 var ptr2 = i * outN;
193 var ptr3 = 0;
194
195 var b = outN;
196 while (b--) {
197
198 cqtOut[i] += CQT[ptr2] * fftSpectrumIn[ptr3];
199
200 ptr2++;
201 ptr3++;
202 }
203
204 if (cqtOut[i]>this.maxval.filterBank) {
205 this.maxval.filterBank = cqtOut[i];
206 }
207
208 i++;
209 }
210}
211
212ChromaTransform.prototype.computeChroma = function(cqtIn, chromaOut) {
213
214 // compute CHROMA
215
216 var cqtN = cqtIn.length;
217
218 var log10 = Math.log(10);
219
220 var a = this.numChromaBins;
221
222 var i=0;
223
224 while(a--){
225 var c = 1;
226
227 // folder higher up octaves into the first one
228 var ptr1_val = 0;
229
230 //for(var b=this.constantQ+i; b<cqtN; b+=this.constantQ){
231 for(var b=i; b<cqtN; b+=this.numChromaBins){
232 ptr1_val += cqtIn[b];
233 c++;
234 }
235 ptr1_val /= c; // Normalize by number of bands summed
236
237 // ***** can generate negative values ???
238 //ptr1_val = Math.log( ptr1_val ) / log10;
239
240 chromaOut[i] = ptr1_val;
241
242 if (chromaOut[i]>this.maxval.chromaFeatures) {
243 this.maxval.chromaFeatures = chromaOut[i];
244 }
245
246 i++;
247 }
248}
249
250ChromaTransform.prototype.computeChromaAlt = function(spectrumIn,chromaOut) {
251
252
253 var CB = chromaOut.length; // num chroma bins
254
255 for (var i=0; i<CB; i++) {
256 chromaOut[i] = 0.0;
257 }
258
259 var CR = 440/32.0;
260 var fs = this.samplerate;
261 var fL = (this.frameBufferLength/this.channels); // **** mod by me
262
263 var log2 = Math.log(2);
264
265 for (var k=this.loEdge; k<=this.hiEdge; k++) {
266 var ind = Math.round(CB*Math.log((k)*fs/fL/CR)/log2);
267 var mod_ind = ind%CB;
268 chromaOut[mod_ind] += spectrumIn[k];
269
270 if (chromaOut[mod_ind]>this.maxval.chromaFeatures) {
271 this.maxval.chromaFeatures = chromaOut[mod_ind];
272 }
273
274 }
275
276}
277
278
279ChromaTransform.prototype.computeTransform = function(signal) {
280
281 this.fft.forward(signal);
282
283 this.computeFilterBank(this.fft.powerSpectrum,this.filterBankOut);
284
285 var filterBankCopy = new Float32Array(this.filterBankOut); // need a copy of the array (not just a reference)
286 this.filterBankSequence.push(filterBankCopy);
287
288 if (_debug) {
289 var line = "";
290 for (i=0; i<this.filterBankOut.length; i+=20) {
291 line += this.filterBankOut[i] + " ";
292 }
293
294 console.log("filter bank: " + line);
295 }
296
297
298 this.computeChroma(this.filterBankOut,this.chromaFeaturesOut);
299
300 //this.computeChromaAlt(this.fft.powerSpectrum,this.chromaFeaturesOut);
301
302 var chromaFeaturesCopy = new Float32Array(this.chromaFeaturesOut); // need a copy of the array (not just a reference)
303 this.chromaFeaturesSequence.push(chromaFeaturesCopy);
304}
305
306
307
308ChromaTransform.prototype.dotProduct = function(v1,v2) {
309 var vlen = v1.length;
310
311 var d = 0;
312
313 for (var i=0; i<vlen; i++) {
314 d = d + (v1[i]*v2[i]);
315 }
316
317 return d;
318}
319
320ChromaTransform.prototype.vectorLength = function(v) {
321 var vlen = v.length;
322
323 var l = 0.0;
324
325 for (var i=0; i<vlen; i++) {
326 l = l + (v[i]*v[i]);
327 }
328
329 l = Math.sqrt(l);
330
331 return l;
332}
333
334
335ChromaTransform.prototype.computeSelfSimilarityMatrix = function(chromaFeaturesSequence) {
336
337 var num_frames = chromaFeaturesSequence.length;
338
339 var norm_lengths = new Float32Array(num_frames);
340
341 this.csm = new Array(num_frames) ; // chroma (self) similarity matrix
342 for (var i=0; i<num_frames; i++) {
343 this.csm[i] = new Float32Array(num_frames);
344 this.csm[i][i]=1.0;
345
346 norm_lengths[i] = this.vectorLength(chromaFeaturesSequence[i]);
347 }
348
349
350 var total_calcs = num_frames*num_frames;
351 var c = 0;
352 this.progress = 0;
353
354 var i = 0;
355
356 (function(outerThis) {
357 //for (var i=0; i<num_frames-1; i++) {
358
359
360 for (var j=i+1; j<num_frames; j++) {
361
362 outerThis.progress = Math.round((100.0*c)/total_calcs);
363
364 outerThis.csm[i][j] = outerThis.dotProduct(chromaFeaturesSequence[i],chromaFeaturesSequence[j])/(norm_lengths[i]*norm_lengths[j]);
365
366 outerThis.csm[j][i] = outerThis.csm[i][j];
367 c+=2;
368 }
369 i++;
370
371 if (i<num_frames-1) {
372 setTimeout(arguments.callee,0,outerThis);
373 }
374 else {
375 outerThis.progress = 100;
376 }
377 })(this);
378
379
380}
Note: See TracBrowser for help on using the repository browser.