annotate dsp/chromagram/ConstantQ.cpp @ 73:dcb555b90924

* Key detector: when returning key strengths, use the peak value of the three underlying chromagram correlations (from 36-bin chromagram) corresponding to each key, instead of the mean. Rationale: This is the same method as used when returning the key value, and it's nice to have the same results in both returned value and plot. The peak performed better than the sum with a simple test set of triads, so it seems reasonable to change the plot to match the key output rather than the other way around. * FFT: kiss_fftr returns only the non-conjugate bins, synthesise the rest rather than leaving them (perhaps dangerously) undefined. Fixes an uninitialised data error in chromagram that could cause garbage results from key detector. * Constant Q: remove precalculated values again, I reckon they're not proving such a good tradeoff.
author cannam
date Fri, 05 Jun 2009 15:12:39 +0000
parents 6cb2b3cd5356
children e5907ae6de17
rev   line source
cannam@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@0 2 /*
cannam@0 3 QM DSP Library
cannam@0 4
cannam@0 5 Centre for Digital Music, Queen Mary, University of London.
cannam@0 6 This file copyright 2005-2006 Christian Landone.
cannam@0 7 All rights reserved.
cannam@0 8 */
cannam@0 9
cannam@0 10 #include "ConstantQ.h"
cannam@0 11 #include "dsp/transforms/FFT.h"
cannam@0 12
cannam@20 13 #include <iostream>
cannam@20 14
cannam@73 15 #ifdef NOT_DEFINED
cannam@73 16 // see note in CQprecalc
cannam@73 17
cannam@51 18 #include "CQprecalc.cpp"
cannam@51 19
cannam@51 20 static bool push_precalculated(int uk, int fftlength,
cannam@51 21 std::vector<unsigned> &is,
cannam@51 22 std::vector<unsigned> &js,
cannam@51 23 std::vector<double> &real,
cannam@51 24 std::vector<double> &imag)
cannam@51 25 {
cannam@51 26 if (uk == 76 && fftlength == 16384) {
cannam@51 27 push_76_16384(is, js, real, imag);
cannam@51 28 return true;
cannam@51 29 }
cannam@51 30 if (uk == 144 && fftlength == 4096) {
cannam@51 31 push_144_4096(is, js, real, imag);
cannam@51 32 return true;
cannam@51 33 }
cannam@51 34 if (uk == 65 && fftlength == 2048) {
cannam@51 35 push_65_2048(is, js, real, imag);
cannam@51 36 return true;
cannam@51 37 }
cannam@51 38 if (uk == 84 && fftlength == 65536) {
cannam@51 39 push_84_65536(is, js, real, imag);
cannam@51 40 return true;
cannam@51 41 }
cannam@51 42 return false;
cannam@51 43 }
cannam@73 44 #endif
cannam@51 45
cannam@0 46 //---------------------------------------------------------------------------
cannam@0 47 // nextpow2 returns the smallest integer n such that 2^n >= x.
cannam@0 48 static double nextpow2(double x) {
cannam@0 49 double y = ceil(log(x)/log(2.0));
cannam@0 50 return(y);
cannam@0 51 }
cannam@0 52
cannam@0 53 static double squaredModule(const double & xx, const double & yy) {
cannam@0 54 return xx*xx + yy*yy;
cannam@0 55 }
cannam@0 56
cannam@0 57 //----------------------------------------------------------------------------
cannam@0 58
cannam@51 59 ConstantQ::ConstantQ( CQConfig Config ) :
cannam@51 60 m_sparseKernel(0)
cannam@0 61 {
cannam@0 62 initialise( Config );
cannam@0 63 }
cannam@0 64
cannam@0 65 ConstantQ::~ConstantQ()
cannam@0 66 {
cannam@0 67 deInitialise();
cannam@0 68 }
cannam@0 69
cannam@0 70 //----------------------------------------------------------------------------
cannam@0 71 void ConstantQ::sparsekernel()
cannam@0 72 {
cannam@51 73 // std::cerr << "ConstantQ: initialising sparse kernel, uK = " << m_uK << ", FFTLength = " << m_FFTLength << "...";
cannam@51 74
cannam@51 75 SparseKernel *sk = new SparseKernel();
cannam@51 76
cannam@73 77 #ifdef NOT_DEFINED
cannam@51 78 if (push_precalculated(m_uK, m_FFTLength,
cannam@51 79 sk->is, sk->js, sk->real, sk->imag)) {
cannam@73 80 // std::cerr << "using precalculated kernel" << std::endl;
cannam@51 81 m_sparseKernel = sk;
cannam@51 82 return;
cannam@51 83 }
cannam@73 84 #endif
cannam@73 85
cannam@0 86 //generates spectral kernel matrix (upside down?)
cannam@0 87 // initialise temporal kernel with zeros, twice length to deal w. complex numbers
cannam@0 88
cannam@0 89 double* hammingWindowRe = new double [ m_FFTLength ];
cannam@0 90 double* hammingWindowIm = new double [ m_FFTLength ];
cannam@0 91 double* transfHammingWindowRe = new double [ m_FFTLength ];
cannam@0 92 double* transfHammingWindowIm = new double [ m_FFTLength ];
cannam@0 93
cannam@0 94 for (unsigned u=0; u < m_FFTLength; u++)
cannam@0 95 {
cannam@0 96 hammingWindowRe[u] = 0;
cannam@0 97 hammingWindowIm[u] = 0;
cannam@0 98 }
cannam@0 99
cannam@0 100 // Here, fftleng*2 is a guess of the number of sparse cells in the matrix
cannam@0 101 // The matrix K x fftlength but the non-zero cells are an antialiased
cannam@0 102 // square root function. So mostly is a line, with some grey point.
cannam@51 103 sk->is.reserve( m_FFTLength*2 );
cannam@51 104 sk->js.reserve( m_FFTLength*2 );
cannam@51 105 sk->real.reserve( m_FFTLength*2 );
cannam@51 106 sk->imag.reserve( m_FFTLength*2 );
cannam@0 107
cannam@0 108 // for each bin value K, calculate temporal kernel, take its fft to
cannam@0 109 //calculate the spectral kernel then threshold it to make it sparse and
cannam@0 110 //add it to the sparse kernels matrix
cannam@0 111 double squareThreshold = m_CQThresh * m_CQThresh;
cannam@0 112
cannam@64 113 FFT m_FFT(m_FFTLength);
cannam@0 114
cannam@0 115 for (unsigned k = m_uK; k--; )
cannam@0 116 {
cannam@3 117 for (unsigned u=0; u < m_FFTLength; u++)
cannam@3 118 {
cannam@3 119 hammingWindowRe[u] = 0;
cannam@3 120 hammingWindowIm[u] = 0;
cannam@3 121 }
cannam@3 122
cannam@0 123 // Computing a hamming window
cannam@0 124 const unsigned hammingLength = (int) ceil( m_dQ * m_FS / ( m_FMin * pow(2,((double)(k))/(double)m_BPO)));
cannam@3 125
cannam@3 126 unsigned origin = m_FFTLength/2 - hammingLength/2;
cannam@3 127
cannam@0 128 for (unsigned i=0; i<hammingLength; i++)
cannam@0 129 {
cannam@0 130 const double angle = 2*PI*m_dQ*i/hammingLength;
cannam@0 131 const double real = cos(angle);
cannam@0 132 const double imag = sin(angle);
cannam@0 133 const double absol = hamming(hammingLength, i)/hammingLength;
cannam@3 134 hammingWindowRe[ origin + i ] = absol*real;
cannam@3 135 hammingWindowIm[ origin + i ] = absol*imag;
cannam@0 136 }
cannam@0 137
cannam@3 138 for (unsigned i = 0; i < m_FFTLength/2; ++i) {
cannam@3 139 double temp = hammingWindowRe[i];
cannam@3 140 hammingWindowRe[i] = hammingWindowRe[i + m_FFTLength/2];
cannam@3 141 hammingWindowRe[i + m_FFTLength/2] = temp;
cannam@3 142 temp = hammingWindowIm[i];
cannam@3 143 hammingWindowIm[i] = hammingWindowIm[i + m_FFTLength/2];
cannam@3 144 hammingWindowIm[i + m_FFTLength/2] = temp;
cannam@3 145 }
cannam@3 146
cannam@0 147 //do fft of hammingWindow
cannam@64 148 m_FFT.process( 0, hammingWindowRe, hammingWindowIm, transfHammingWindowRe, transfHammingWindowIm );
cannam@0 149
cannam@0 150
cannam@0 151 for (unsigned j=0; j<( m_FFTLength ); j++)
cannam@0 152 {
cannam@0 153 // perform thresholding
cannam@0 154 const double squaredBin = squaredModule( transfHammingWindowRe[ j ], transfHammingWindowIm[ j ]);
cannam@0 155 if (squaredBin <= squareThreshold) continue;
cannam@0 156
cannam@0 157 // Insert non-zero position indexes, doubled because they are floats
cannam@51 158 sk->is.push_back(j);
cannam@51 159 sk->js.push_back(k);
cannam@0 160
cannam@0 161 // take conjugate, normalise and add to array sparkernel
cannam@51 162 sk->real.push_back( transfHammingWindowRe[ j ]/m_FFTLength);
cannam@51 163 sk->imag.push_back(-transfHammingWindowIm[ j ]/m_FFTLength);
cannam@0 164 }
cannam@0 165
cannam@0 166 }
cannam@0 167
cannam@0 168 delete [] hammingWindowRe;
cannam@0 169 delete [] hammingWindowIm;
cannam@0 170 delete [] transfHammingWindowRe;
cannam@0 171 delete [] transfHammingWindowIm;
cannam@0 172
cannam@51 173 /*
cannam@51 174 using std::cout;
cannam@51 175 using std::endl;
cannam@51 176
cannam@51 177 cout.precision(28);
cannam@51 178
cannam@51 179 int n = sk->is.size();
cannam@51 180 int w = 8;
cannam@51 181 cout << "static unsigned int sk_i_" << m_uK << "_" << m_FFTLength << "[" << n << "] = {" << endl;
cannam@51 182 for (int i = 0; i < n; ++i) {
cannam@51 183 if (i % w == 0) cout << " ";
cannam@51 184 cout << sk->is[i];
cannam@51 185 if (i + 1 < n) cout << ", ";
cannam@51 186 if (i % w == w-1) cout << endl;
cannam@51 187 };
cannam@51 188 if (n % w != 0) cout << endl;
cannam@51 189 cout << "};" << endl;
cannam@51 190
cannam@51 191 n = sk->js.size();
cannam@51 192 cout << "static unsigned int sk_j_" << m_uK << "_" << m_FFTLength << "[" << n << "] = {" << endl;
cannam@51 193 for (int i = 0; i < n; ++i) {
cannam@51 194 if (i % w == 0) cout << " ";
cannam@51 195 cout << sk->js[i];
cannam@51 196 if (i + 1 < n) cout << ", ";
cannam@51 197 if (i % w == w-1) cout << endl;
cannam@51 198 };
cannam@51 199 if (n % w != 0) cout << endl;
cannam@51 200 cout << "};" << endl;
cannam@51 201
cannam@51 202 w = 2;
cannam@51 203 n = sk->real.size();
cannam@51 204 cout << "static double sk_real_" << m_uK << "_" << m_FFTLength << "[" << n << "] = {" << endl;
cannam@51 205 for (int i = 0; i < n; ++i) {
cannam@51 206 if (i % w == 0) cout << " ";
cannam@51 207 cout << sk->real[i];
cannam@51 208 if (i + 1 < n) cout << ", ";
cannam@51 209 if (i % w == w-1) cout << endl;
cannam@51 210 };
cannam@51 211 if (n % w != 0) cout << endl;
cannam@51 212 cout << "};" << endl;
cannam@51 213
cannam@51 214 n = sk->imag.size();
cannam@51 215 cout << "static double sk_imag_" << m_uK << "_" << m_FFTLength << "[" << n << "] = {" << endl;
cannam@51 216 for (int i = 0; i < n; ++i) {
cannam@51 217 if (i % w == 0) cout << " ";
cannam@51 218 cout << sk->imag[i];
cannam@51 219 if (i + 1 < n) cout << ", ";
cannam@51 220 if (i % w == w-1) cout << endl;
cannam@51 221 };
cannam@51 222 if (n % w != 0) cout << endl;
cannam@51 223 cout << "};" << endl;
cannam@51 224
cannam@51 225 cout << "static void push_" << m_uK << "_" << m_FFTLength << "(vector<unsigned int> &is, vector<unsigned int> &js, vector<double> &real, vector<double> &imag)" << endl;
cannam@51 226 cout << "{\n is.reserve(" << n << ");\n";
cannam@51 227 cout << " js.reserve(" << n << ");\n";
cannam@51 228 cout << " real.reserve(" << n << ");\n";
cannam@51 229 cout << " imag.reserve(" << n << ");\n";
cannam@51 230 cout << " for (int i = 0; i < " << n << "; ++i) {" << endl;
cannam@51 231 cout << " is.push_back(sk_i_" << m_uK << "_" << m_FFTLength << "[i]);" << endl;
cannam@51 232 cout << " js.push_back(sk_j_" << m_uK << "_" << m_FFTLength << "[i]);" << endl;
cannam@51 233 cout << " real.push_back(sk_real_" << m_uK << "_" << m_FFTLength << "[i]);" << endl;
cannam@51 234 cout << " imag.push_back(sk_imag_" << m_uK << "_" << m_FFTLength << "[i]);" << endl;
cannam@51 235 cout << " }" << endl;
cannam@51 236 cout << "}" << endl;
cannam@51 237 */
cannam@51 238 // std::cerr << "done\n -> is: " << sk->is.size() << ", js: " << sk->js.size() << ", reals: " << sk->real.size() << ", imags: " << sk->imag.size() << std::endl;
cannam@51 239
cannam@51 240 m_sparseKernel = sk;
cannam@51 241 return;
cannam@0 242 }
cannam@0 243
cannam@0 244 //-----------------------------------------------------------------------------
cannam@32 245 double* ConstantQ::process( const double* fftdata )
cannam@0 246 {
cannam@51 247 if (!m_sparseKernel) {
cannam@51 248 std::cerr << "ERROR: ConstantQ::process: Sparse kernel has not been initialised" << std::endl;
cannam@51 249 return m_CQdata;
cannam@51 250 }
cannam@51 251
cannam@51 252 SparseKernel *sk = m_sparseKernel;
cannam@51 253
cannam@0 254 for (unsigned row=0; row<2*m_uK; row++)
cannam@0 255 {
cannam@0 256 m_CQdata[ row ] = 0;
cannam@0 257 m_CQdata[ row+1 ] = 0;
cannam@0 258 }
cannam@51 259 const unsigned *fftbin = &(sk->is[0]);
cannam@51 260 const unsigned *cqbin = &(sk->js[0]);
cannam@51 261 const double *real = &(sk->real[0]);
cannam@51 262 const double *imag = &(sk->imag[0]);
cannam@51 263 const unsigned int sparseCells = sk->real.size();
cannam@0 264
cannam@0 265 for (unsigned i = 0; i<sparseCells; i++)
cannam@0 266 {
cannam@0 267 const unsigned row = cqbin[i];
cannam@0 268 const unsigned col = fftbin[i];
cannam@0 269 const double & r1 = real[i];
cannam@0 270 const double & i1 = imag[i];
cannam@38 271 const double & r2 = fftdata[ (2*m_FFTLength) - 2*col - 2 ];
cannam@38 272 const double & i2 = fftdata[ (2*m_FFTLength) - 2*col - 2 + 1 ];
cannam@0 273 // add the multiplication
cannam@0 274 m_CQdata[ 2*row ] += (r1*r2 - i1*i2);
cannam@0 275 m_CQdata[ 2*row+1] += (r1*i2 + i1*r2);
cannam@0 276 }
cannam@0 277
cannam@0 278 return m_CQdata;
cannam@0 279 }
cannam@0 280
cannam@0 281
cannam@0 282 void ConstantQ::initialise( CQConfig Config )
cannam@0 283 {
cannam@0 284 m_FS = Config.FS;
cannam@0 285 m_FMin = Config.min; // min freq
cannam@0 286 m_FMax = Config.max; // max freq
cannam@0 287 m_BPO = Config.BPO; // bins per octave
cannam@0 288 m_CQThresh = Config.CQThresh;// ConstantQ threshold for kernel generation
cannam@0 289
cannam@0 290 m_dQ = 1/(pow(2,(1/(double)m_BPO))-1); // Work out Q value for Filter bank
cannam@0 291 m_uK = (unsigned int) ceil(m_BPO * log(m_FMax/m_FMin)/log(2.0)); // No. of constant Q bins
cannam@0 292
cannam@24 293 // std::cerr << "ConstantQ::initialise: rate = " << m_FS << ", fmin = " << m_FMin << ", fmax = " << m_FMax << ", bpo = " << m_BPO << ", K = " << m_uK << ", Q = " << m_dQ << std::endl;
cannam@20 294
cannam@0 295 // work out length of fft required for this constant Q Filter bank
cannam@0 296 m_FFTLength = (int) pow(2, nextpow2(ceil( m_dQ*m_FS/m_FMin )));
cannam@0 297
cannam@0 298 m_hop = m_FFTLength/8; // <------ hop size is window length divided by 32
cannam@0 299
cannam@24 300 // std::cerr << "ConstantQ::initialise: -> fft length = " << m_FFTLength << ", hop = " << m_hop << std::endl;
cannam@20 301
cannam@0 302 // allocate memory for cqdata
cannam@0 303 m_CQdata = new double [2*m_uK];
cannam@0 304 }
cannam@0 305
cannam@0 306 void ConstantQ::deInitialise()
cannam@0 307 {
cannam@0 308 delete [] m_CQdata;
cannam@51 309 delete m_sparseKernel;
cannam@0 310 }
cannam@0 311
cannam@32 312 void ConstantQ::process(const double *FFTRe, const double* FFTIm,
cannam@32 313 double *CQRe, double *CQIm)
cannam@0 314 {
cannam@51 315 if (!m_sparseKernel) {
cannam@51 316 std::cerr << "ERROR: ConstantQ::process: Sparse kernel has not been initialised" << std::endl;
cannam@51 317 return;
cannam@51 318 }
cannam@51 319
cannam@51 320 SparseKernel *sk = m_sparseKernel;
cannam@51 321
cannam@0 322 for (unsigned row=0; row<m_uK; row++)
cannam@0 323 {
cannam@0 324 CQRe[ row ] = 0;
cannam@0 325 CQIm[ row ] = 0;
cannam@0 326 }
cannam@0 327
cannam@51 328 const unsigned *fftbin = &(sk->is[0]);
cannam@51 329 const unsigned *cqbin = &(sk->js[0]);
cannam@51 330 const double *real = &(sk->real[0]);
cannam@51 331 const double *imag = &(sk->imag[0]);
cannam@51 332 const unsigned int sparseCells = sk->real.size();
cannam@0 333
cannam@0 334 for (unsigned i = 0; i<sparseCells; i++)
cannam@0 335 {
cannam@0 336 const unsigned row = cqbin[i];
cannam@0 337 const unsigned col = fftbin[i];
cannam@0 338 const double & r1 = real[i];
cannam@0 339 const double & i1 = imag[i];
cannam@38 340 const double & r2 = FFTRe[ m_FFTLength - col - 1 ];
cannam@38 341 const double & i2 = FFTIm[ m_FFTLength - col - 1 ];
cannam@0 342 // add the multiplication
cannam@0 343 CQRe[ row ] += (r1*r2 - i1*i2);
cannam@0 344 CQIm[ row ] += (r1*i2 + i1*r2);
cannam@0 345 }
cannam@0 346 }