comparison dsp/chromagram/ConstantQ.cpp @ 495:1bea13b8f951

Style fixes in constant-Q: avoid unsigned, reuse our Window class, fix comments
author Chris Cannam <cannam@all-day-breakfast.com>
date Fri, 31 May 2019 18:25:31 +0100
parents 5998ee1042d3
children 0d3a001e63c7
comparison
equal deleted inserted replaced
494:3f649fbb1172 495:1bea13b8f951
12 COPYING included with this distribution for more information. 12 COPYING included with this distribution for more information.
13 */ 13 */
14 14
15 #include "ConstantQ.h" 15 #include "ConstantQ.h"
16 #include "dsp/transforms/FFT.h" 16 #include "dsp/transforms/FFT.h"
17 #include "base/Window.h"
17 18
18 #include <iostream> 19 #include <iostream>
19 20
20 //--------------------------------------------------------------------------- 21 //----------------------------------------------------------------------------
21 // nextpow2 returns the smallest integer n such that 2^n >= x. 22
22 static double nextpow2(double x) { 23 ConstantQ::ConstantQ( CQConfig config ) :
23 double y = ceil(log(x)/log(2.0)); 24 m_sparseKernel(0)
24 return(y); 25 {
26 initialise(config);
27 }
28
29 ConstantQ::~ConstantQ()
30 {
31 deInitialise();
25 } 32 }
26 33
27 static double squaredModule(const double & xx, const double & yy) { 34 static double squaredModule(const double & xx, const double & yy) {
28 return xx*xx + yy*yy; 35 return xx*xx + yy*yy;
29 } 36 }
30 37
31 //----------------------------------------------------------------------------
32
33 ConstantQ::ConstantQ( CQConfig Config ) :
34 m_sparseKernel(0)
35 {
36 initialise( Config );
37 }
38
39 ConstantQ::~ConstantQ()
40 {
41 deInitialise();
42 }
43
44 //----------------------------------------------------------------------------
45 void ConstantQ::sparsekernel() 38 void ConstantQ::sparsekernel()
46 { 39 {
47 // std::cerr << "ConstantQ: initialising sparse kernel, uK = " << m_uK << ", FFTLength = " << m_FFTLength << "...";
48
49 SparseKernel *sk = new SparseKernel(); 40 SparseKernel *sk = new SparseKernel();
50 41
51 //generates spectral kernel matrix (upside down?) 42 double* windowRe = new double [ m_FFTLength ];
52 // initialise temporal kernel with zeros, twice length to deal w. complex numbers 43 double* windowIm = new double [ m_FFTLength ];
53 44 double* transfWindowRe = new double [ m_FFTLength ];
54 double* hammingWindowRe = new double [ m_FFTLength ]; 45 double* transfWindowIm = new double [ m_FFTLength ];
55 double* hammingWindowIm = new double [ m_FFTLength ]; 46
56 double* transfHammingWindowRe = new double [ m_FFTLength ]; 47 // for each bin value K, calculate temporal kernel, take its fft
57 double* transfHammingWindowIm = new double [ m_FFTLength ]; 48 // to calculate the spectral kernel then threshold it to make it
58 49 // sparse and add it to the sparse kernels matrix
59 for (unsigned u=0; u < m_FFTLength; u++) { 50
60 hammingWindowRe[u] = 0;
61 hammingWindowIm[u] = 0;
62 }
63
64 // Here, fftleng*2 is a guess of the number of sparse cells in the matrix
65 // The matrix K x fftlength but the non-zero cells are an antialiased
66 // square root function. So mostly is a line, with some grey point.
67 sk->is.reserve( m_FFTLength*2 );
68 sk->js.reserve( m_FFTLength*2 );
69 sk->real.reserve( m_FFTLength*2 );
70 sk->imag.reserve( m_FFTLength*2 );
71
72 // for each bin value K, calculate temporal kernel, take its fft to
73 //calculate the spectral kernel then threshold it to make it sparse and
74 //add it to the sparse kernels matrix
75 double squareThreshold = m_CQThresh * m_CQThresh; 51 double squareThreshold = m_CQThresh * m_CQThresh;
76 52
77 FFT m_FFT(m_FFTLength); 53 FFT fft(m_FFTLength);
78 54
79 for (unsigned k = m_uK; k--; ) { 55 for (int j = m_uK; j >= 0; --j) {
80 for (unsigned u=0; u < m_FFTLength; u++) { 56
81 hammingWindowRe[u] = 0; 57 for (int i = 0; i < m_FFTLength; ++i) {
82 hammingWindowIm[u] = 0; 58 windowRe[i] = 0;
83 } 59 windowIm[i] = 0;
84 60 }
85 // Computing a hamming window 61
86 const unsigned hammingLength = (int) ceil( m_dQ * m_FS / ( m_FMin * pow(2,((double)(k))/(double)m_BPO))); 62 // Compute a complex sinusoid windowed with a hamming window
87 63 // of the right length
88 // cerr << "k = " << k << ", q = " << m_dQ << ", m_FMin = " << m_FMin << ", hammingLength = " << hammingLength << " (rounded up from " << (m_dQ * m_FS / ( m_FMin * pow(2,((double)(k))/(double)m_BPO))) << ")" << endl; 64
89 65 int windowLength = (int)ceil
90 66 (m_dQ * m_FS / (m_FMin * pow(2, (double)j / (double)m_BPO)));
91 unsigned origin = m_FFTLength/2 - hammingLength/2; 67
92 68 int origin = m_FFTLength/2 - windowLength/2;
93 for (unsigned i=0; i<hammingLength; i++) { 69
94 const double angle = 2*M_PI*m_dQ*i/hammingLength; 70 for (int i = 0; i < windowLength; ++i) {
95 const double real = cos(angle); 71 double angle = (2.0 * M_PI * m_dQ * i) / windowLength;
96 const double imag = sin(angle); 72 windowRe[origin + i] = cos(angle);
97 const double absol = hamming(hammingLength, i)/hammingLength; 73 windowIm[origin + i] = sin(angle);
98 hammingWindowRe[ origin + i ] = absol*real; 74 }
99 hammingWindowIm[ origin + i ] = absol*imag; 75
100 } 76 // Shape with hamming window
101 77 Window<double> hamming(HammingWindow, windowLength);
102 for (unsigned i = 0; i < m_FFTLength/2; ++i) { 78 hamming.cut(windowRe + origin);
103 double temp = hammingWindowRe[i]; 79 hamming.cut(windowIm + origin);
104 hammingWindowRe[i] = hammingWindowRe[i + m_FFTLength/2]; 80
105 hammingWindowRe[i + m_FFTLength/2] = temp; 81 // Scale
106 temp = hammingWindowIm[i]; 82 for (int i = 0; i < windowLength; ++i) {
107 hammingWindowIm[i] = hammingWindowIm[i + m_FFTLength/2]; 83 windowRe[origin + i] /= windowLength;
108 hammingWindowIm[i + m_FFTLength/2] = temp; 84 }
85 for (int i = 0; i < windowLength; ++i) {
86 windowIm[origin + i] /= windowLength;
87 }
88
89 // Input is expected to have been fftshifted, so do the
90 // same to the input to the fft that contains the kernel
91 for (int i = 0; i < m_FFTLength/2; ++i) {
92 double temp = windowRe[i];
93 windowRe[i] = windowRe[i + m_FFTLength/2];
94 windowRe[i + m_FFTLength/2] = temp;
95 }
96 for (int i = 0; i < m_FFTLength/2; ++i) {
97 double temp = windowIm[i];
98 windowIm[i] = windowIm[i + m_FFTLength/2];
99 windowIm[i + m_FFTLength/2] = temp;
109 } 100 }
110 101
111 //do fft of hammingWindow 102 fft.process(false, windowRe, windowIm, transfWindowRe, transfWindowIm);
112 m_FFT.process( 0, hammingWindowRe, hammingWindowIm, transfHammingWindowRe, transfHammingWindowIm ); 103
113 104 // convert to sparse form
114 for (unsigned j=0; j<( m_FFTLength ); j++) { 105 for (int i = 0; i < m_FFTLength; i++) {
106
115 // perform thresholding 107 // perform thresholding
116 const double squaredBin = squaredModule( transfHammingWindowRe[ j ], transfHammingWindowIm[ j ]); 108 double mag = squaredModule(transfWindowRe[i], transfWindowIm[i]);
117 if (squaredBin <= squareThreshold) continue; 109 if (mag <= squareThreshold) continue;
118 110
119 // Insert non-zero position indexes 111 // Insert non-zero position indexes
120 sk->is.push_back(j); 112 sk->is.push_back(i);
121 sk->js.push_back(k); 113 sk->js.push_back(j);
122 114
123 // take conjugate, normalise and add to array sparkernel 115 // take conjugate, normalise and add to array for sparse kernel
124 sk->real.push_back( transfHammingWindowRe[ j ]/m_FFTLength); 116 sk->real.push_back( transfWindowRe[i] / m_FFTLength);
125 sk->imag.push_back(-transfHammingWindowIm[ j ]/m_FFTLength); 117 sk->imag.push_back(-transfWindowIm[i] / m_FFTLength);
126 } 118 }
127 } 119 }
128 120
129 delete [] hammingWindowRe; 121 delete [] windowRe;
130 delete [] hammingWindowIm; 122 delete [] windowIm;
131 delete [] transfHammingWindowRe; 123 delete [] transfWindowRe;
132 delete [] transfHammingWindowIm; 124 delete [] transfWindowIm;
133 125
134 // std::cerr << "done\n -> is: " << sk->is.size() << ", js: " << sk->js.size() << ", reals: " << sk->real.size() << ", imags: " << sk->imag.size() << std::endl;
135
136 m_sparseKernel = sk; 126 m_sparseKernel = sk;
137 return; 127 }
128
129 void ConstantQ::initialise( CQConfig Config )
130 {
131 m_FS = Config.FS; // Sample rate
132 m_FMin = Config.min; // Minimum frequency
133 m_FMax = Config.max; // Maximum frequency
134 m_BPO = Config.BPO; // Bins per octave
135 m_CQThresh = Config.CQThresh; // Threshold for sparse kernel generation
136
137 // Q value for filter bank
138 m_dQ = 1/(pow(2,(1/(double)m_BPO))-1);
139
140 // No. of constant Q bins
141 m_uK = (int)ceil(m_BPO * log(m_FMax/m_FMin)/log(2.0));
142
143 // Length of fft required for this Constant Q filter bank
144 m_FFTLength = MathUtilities::nextPowerOfTwo(int(ceil(m_dQ * m_FS/m_FMin)));
145
146 // Hop from one frame to next
147 m_hop = m_FFTLength / 8;
148
149 // allocate memory for cqdata
150 m_CQdata = new double [2*m_uK];
151 }
152
153 void ConstantQ::deInitialise()
154 {
155 delete [] m_CQdata;
156 delete m_sparseKernel;
138 } 157 }
139 158
140 //----------------------------------------------------------------------------- 159 //-----------------------------------------------------------------------------
141 double* ConstantQ::process( const double* fftdata ) 160 double* ConstantQ::process( const double* fftdata )
142 { 161 {
145 return m_CQdata; 164 return m_CQdata;
146 } 165 }
147 166
148 SparseKernel *sk = m_sparseKernel; 167 SparseKernel *sk = m_sparseKernel;
149 168
150 for (unsigned row=0; row<2*m_uK; row++) { 169 for (int row=0; row < 2 * m_uK; row++) {
151 m_CQdata[ row ] = 0; 170 m_CQdata[ row ] = 0;
152 m_CQdata[ row+1 ] = 0; 171 m_CQdata[ row+1 ] = 0;
153 } 172 }
154 const unsigned *fftbin = &(sk->is[0]); 173 const int *fftbin = &(sk->is[0]);
155 const unsigned *cqbin = &(sk->js[0]); 174 const int *cqbin = &(sk->js[0]);
156 const double *real = &(sk->real[0]); 175 const double *real = &(sk->real[0]);
157 const double *imag = &(sk->imag[0]); 176 const double *imag = &(sk->imag[0]);
158 const unsigned int sparseCells = sk->real.size(); 177 const int sparseCells = int(sk->real.size());
159 178
160 for (unsigned i = 0; i<sparseCells; i++) { 179 for (int i = 0; i < sparseCells; i++) {
161 const unsigned row = cqbin[i]; 180 const int row = cqbin[i];
162 const unsigned col = fftbin[i]; 181 const int col = fftbin[i];
163 if (col == 0) continue; 182 if (col == 0) continue;
164 const double & r1 = real[i]; 183 const double & r1 = real[i];
165 const double & i1 = imag[i]; 184 const double & i1 = imag[i];
166 const double & r2 = fftdata[ (2*m_FFTLength) - 2*col - 2 ]; 185 const double & r2 = fftdata[ (2*m_FFTLength) - 2*col - 2 ];
167 const double & i2 = fftdata[ (2*m_FFTLength) - 2*col - 2 + 1 ]; 186 const double & i2 = fftdata[ (2*m_FFTLength) - 2*col - 2 + 1 ];
168 // add the multiplication 187 // add the multiplication
169 m_CQdata[ 2*row ] += (r1*r2 - i1*i2); 188 m_CQdata[ 2*row ] += (r1*r2 - i1*i2);
170 m_CQdata[ 2*row+1] += (r1*i2 + i1*r2); 189 m_CQdata[ 2*row+1] += (r1*i2 + i1*r2);
171 } 190 }
172 191
173 return m_CQdata; 192 return m_CQdata;
174 } 193 }
175 194
176
177 void ConstantQ::initialise( CQConfig Config )
178 {
179 m_FS = Config.FS;
180 m_FMin = Config.min; // min freq
181 m_FMax = Config.max; // max freq
182 m_BPO = Config.BPO; // bins per octave
183 m_CQThresh = Config.CQThresh;// ConstantQ threshold for kernel generation
184
185 m_dQ = 1/(pow(2,(1/(double)m_BPO))-1); // Work out Q value for Filter bank
186 m_uK = (unsigned int) ceil(m_BPO * log(m_FMax/m_FMin)/log(2.0)); // No. of constant Q bins
187
188 // std::cerr << "ConstantQ::initialise: rate = " << m_FS << ", fmin = " << m_FMin << ", fmax = " << m_FMax << ", bpo = " << m_BPO << ", K = " << m_uK << ", Q = " << m_dQ << std::endl;
189
190 // work out length of fft required for this constant Q Filter bank
191 m_FFTLength = (int) pow(2, nextpow2(ceil( m_dQ*m_FS/m_FMin )));
192
193 m_hop = m_FFTLength/8;
194
195 // std::cerr << "ConstantQ::initialise: -> fft length = " << m_FFTLength << ", hop = " << m_hop << std::endl;
196
197 // allocate memory for cqdata
198 m_CQdata = new double [2*m_uK];
199 }
200
201 void ConstantQ::deInitialise()
202 {
203 delete [] m_CQdata;
204 delete m_sparseKernel;
205 }
206
207 void ConstantQ::process(const double *FFTRe, const double* FFTIm, 195 void ConstantQ::process(const double *FFTRe, const double* FFTIm,
208 double *CQRe, double *CQIm) 196 double *CQRe, double *CQIm)
209 { 197 {
210 if (!m_sparseKernel) { 198 if (!m_sparseKernel) {
211 std::cerr << "ERROR: ConstantQ::process: Sparse kernel has not been initialised" << std::endl; 199 std::cerr << "ERROR: ConstantQ::process: Sparse kernel has not been initialised" << std::endl;
212 return; 200 return;
213 } 201 }
214 202
215 SparseKernel *sk = m_sparseKernel; 203 SparseKernel *sk = m_sparseKernel;
216 204
217 for (unsigned row=0; row<m_uK; row++) { 205 for (int row = 0; row < m_uK; row++) {
218 CQRe[ row ] = 0; 206 CQRe[ row ] = 0;
219 CQIm[ row ] = 0; 207 CQIm[ row ] = 0;
220 } 208 }
221 209
222 const unsigned *fftbin = &(sk->is[0]); 210 const int *fftbin = &(sk->is[0]);
223 const unsigned *cqbin = &(sk->js[0]); 211 const int *cqbin = &(sk->js[0]);
224 const double *real = &(sk->real[0]); 212 const double *real = &(sk->real[0]);
225 const double *imag = &(sk->imag[0]); 213 const double *imag = &(sk->imag[0]);
226 const unsigned int sparseCells = sk->real.size(); 214 const int sparseCells = int(sk->real.size());
227 215
228 for (unsigned i = 0; i<sparseCells; i++) { 216 for (int i = 0; i<sparseCells; i++) {
229 const unsigned row = cqbin[i]; 217 const int row = cqbin[i];
230 const unsigned col = fftbin[i]; 218 const int col = fftbin[i];
231 if (col == 0) continue; 219 if (col == 0) continue;
232 const double & r1 = real[i]; 220 const double & r1 = real[i];
233 const double & i1 = imag[i]; 221 const double & i1 = imag[i];
234 const double & r2 = FFTRe[ m_FFTLength - col ]; 222 const double & r2 = FFTRe[ m_FFTLength - col ];
235 const double & i2 = FFTIm[ m_FFTLength - col ]; 223 const double & i2 = FFTIm[ m_FFTLength - col ];
236 // add the multiplication 224 // add the multiplication
237 CQRe[ row ] += (r1*r2 - i1*i2); 225 CQRe[ row ] += (r1*r2 - i1*i2);
238 CQIm[ row ] += (r1*i2 + i1*r2); 226 CQIm[ row ] += (r1*i2 + i1*r2);
239 } 227 }
240 } 228 }