annotate dsp/chromagram/ConstantQ.cpp @ 298:255e431ae3d4

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