Mercurial > hg > qm-dsp
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 } |