annotate dsp/chromagram/ConstantQ.cpp @ 129:6ec45e85ed81 kissfft

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