Mercurial > hg > constant-q-cpp
comparison src/CQInverse.cpp @ 116:6deec2a51d13
Moving to standalone library layout
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Thu, 15 May 2014 12:04:00 +0100 |
parents | |
children | 2375457f2876 |
comparison
equal
deleted
inserted
replaced
115:93be4aa255e5 | 116:6deec2a51d13 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 /* | |
3 Constant-Q library | |
4 Copyright (c) 2013-2014 Queen Mary, University of London | |
5 | |
6 Permission is hereby granted, free of charge, to any person | |
7 obtaining a copy of this software and associated documentation | |
8 files (the "Software"), to deal in the Software without | |
9 restriction, including without limitation the rights to use, copy, | |
10 modify, merge, publish, distribute, sublicense, and/or sell copies | |
11 of the Software, and to permit persons to whom the Software is | |
12 furnished to do so, subject to the following conditions: | |
13 | |
14 The above copyright notice and this permission notice shall be | |
15 included in all copies or substantial portions of the Software. | |
16 | |
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
21 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
22 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
24 | |
25 Except as contained in this notice, the names of the Centre for | |
26 Digital Music; Queen Mary, University of London; and Chris Cannam | |
27 shall not be used in advertising or otherwise to promote the sale, | |
28 use or other dealings in this Software without prior written | |
29 authorization. | |
30 */ | |
31 | |
32 #include "CQInverse.h" | |
33 | |
34 #include "dsp/rateconversion/Resampler.h" | |
35 #include "maths/MathUtilities.h" | |
36 #include "dsp/transforms/FFT.h" | |
37 | |
38 #include <algorithm> | |
39 #include <iostream> | |
40 #include <stdexcept> | |
41 | |
42 using std::vector; | |
43 using std::cerr; | |
44 using std::endl; | |
45 | |
46 //#define DEBUG_CQ 1 | |
47 | |
48 CQInverse::CQInverse(double sampleRate, | |
49 double minFreq, | |
50 double maxFreq, | |
51 int binsPerOctave) : | |
52 m_sampleRate(sampleRate), | |
53 m_maxFrequency(maxFreq), | |
54 m_minFrequency(minFreq), | |
55 m_binsPerOctave(binsPerOctave), | |
56 m_fft(0) | |
57 { | |
58 if (minFreq <= 0.0 || maxFreq <= 0.0) { | |
59 throw std::invalid_argument("Frequency extents must be positive"); | |
60 } | |
61 | |
62 initialise(); | |
63 } | |
64 | |
65 CQInverse::~CQInverse() | |
66 { | |
67 delete m_fft; | |
68 for (int i = 0; i < (int)m_upsamplers.size(); ++i) { | |
69 delete m_upsamplers[i]; | |
70 } | |
71 delete m_kernel; | |
72 } | |
73 | |
74 double | |
75 CQInverse::getMinFrequency() const | |
76 { | |
77 return m_p.minFrequency / pow(2.0, m_octaves - 1); | |
78 } | |
79 | |
80 double | |
81 CQInverse::getBinFrequency(int bin) const | |
82 { | |
83 return getMinFrequency() * pow(2, (double(bin) / getBinsPerOctave())); | |
84 } | |
85 | |
86 void | |
87 CQInverse::initialise() | |
88 { | |
89 m_octaves = int(ceil(log2(m_maxFrequency / m_minFrequency))); | |
90 m_kernel = new CQKernel(m_sampleRate, m_maxFrequency, m_binsPerOctave); | |
91 m_p = m_kernel->getProperties(); | |
92 | |
93 // Use exact powers of two for resampling rates. They don't have | |
94 // to be related to our actual samplerate: the resampler only | |
95 // cares about the ratio, but it only accepts integer source and | |
96 // target rates, and if we start from the actual samplerate we | |
97 // risk getting non-integer rates for lower octaves | |
98 | |
99 int sourceRate = pow(2, m_octaves); | |
100 vector<int> latencies; | |
101 | |
102 // top octave, no resampling | |
103 latencies.push_back(0); | |
104 m_upsamplers.push_back(0); | |
105 | |
106 for (int i = 1; i < m_octaves; ++i) { | |
107 | |
108 int factor = pow(2, i); | |
109 | |
110 Resampler *r = new Resampler | |
111 (sourceRate / factor, sourceRate, 50, 0.05); | |
112 | |
113 #ifdef DEBUG_CQ | |
114 cerr << "inverse: octave " << i << ": resample from " << sourceRate/factor << " to " << sourceRate << endl; | |
115 #endif | |
116 | |
117 // See ConstantQ.cpp for discussion on latency -- output | |
118 // latency here is at target rate which, this way around, is | |
119 // what we want | |
120 | |
121 latencies.push_back(r->getLatency()); | |
122 m_upsamplers.push_back(r); | |
123 } | |
124 | |
125 // additionally we will have fftHop latency at individual octave | |
126 // rate (before upsampling) for the overlap-add in each octave | |
127 for (int i = 0; i < m_octaves; ++i) { | |
128 latencies[i] += m_p.fftHop * pow(2, i); | |
129 } | |
130 | |
131 // Now reverse the drop adjustment made in ConstantQ to align the | |
132 // atom centres across different octaves (but this time at output | |
133 // sample rate) | |
134 | |
135 int emptyHops = m_p.firstCentre / m_p.atomSpacing; | |
136 | |
137 vector<int> pushes; | |
138 for (int i = 0; i < m_octaves; ++i) { | |
139 int factor = pow(2, i); | |
140 int pushHops = emptyHops * pow(2, m_octaves - i - 1) - emptyHops; | |
141 int push = ((pushHops * m_p.fftHop) * factor) / m_p.atomsPerFrame; | |
142 pushes.push_back(push); | |
143 } | |
144 | |
145 int maxLatLessPush = 0; | |
146 for (int i = 0; i < m_octaves; ++i) { | |
147 int latLessPush = latencies[i] - pushes[i]; | |
148 if (latLessPush > maxLatLessPush) maxLatLessPush = latLessPush; | |
149 } | |
150 | |
151 int totalLatency = maxLatLessPush + 10; | |
152 if (totalLatency < 0) totalLatency = 0; | |
153 | |
154 m_outputLatency = totalLatency + m_p.firstCentre * pow(2, m_octaves-1); | |
155 | |
156 #ifdef DEBUG_CQ | |
157 cerr << "totalLatency = " << totalLatency << ", m_outputLatency = " << m_outputLatency << endl; | |
158 #endif | |
159 | |
160 for (int i = 0; i < m_octaves; ++i) { | |
161 | |
162 // Calculate the difference between the total latency applied | |
163 // across all octaves, and the existing latency due to the | |
164 // upsampler for this octave. | |
165 | |
166 int latencyPadding = totalLatency - latencies[i] + pushes[i]; | |
167 | |
168 #ifdef DEBUG_CQ | |
169 cerr << "octave " << i << ": push " << pushes[i] << ", resampler latency inc overlap space " << latencies[i] << ", latencyPadding = " << latencyPadding << " (/factor = " << latencyPadding / pow(2, i) << ")" << endl; | |
170 #endif | |
171 | |
172 m_buffers.push_back(RealSequence(latencyPadding, 0.0)); | |
173 } | |
174 | |
175 for (int i = 0; i < m_octaves; ++i) { | |
176 // Fixed-size buffer for IFFT overlap-add | |
177 m_olaBufs.push_back(RealSequence(m_p.fftSize, 0.0)); | |
178 } | |
179 | |
180 m_fft = new FFTReal(m_p.fftSize); | |
181 } | |
182 | |
183 CQInverse::RealSequence | |
184 CQInverse::process(const ComplexBlock &block) | |
185 { | |
186 // The input data is of the form produced by ConstantQ::process -- | |
187 // an unknown number N of columns of varying height. We assert | |
188 // that N is a multiple of atomsPerFrame * 2^(octaves-1), as must | |
189 // be the case for data that came directly from our ConstantQ | |
190 // implementation. | |
191 | |
192 int widthProvided = block.size(); | |
193 | |
194 if (widthProvided == 0) { | |
195 return drawFromBuffers(); | |
196 } | |
197 | |
198 int blockWidth = m_p.atomsPerFrame * int(pow(2, m_octaves - 1)); | |
199 | |
200 if (widthProvided % blockWidth != 0) { | |
201 cerr << "ERROR: CQInverse::process: Input block size (" | |
202 << widthProvided | |
203 << ") must be a multiple of processing block width " | |
204 << "(atoms-per-frame * 2^(octaves-1) = " | |
205 << m_p.atomsPerFrame << " * 2^(" << m_octaves << "-1) = " | |
206 << blockWidth << ")" << endl; | |
207 throw std::invalid_argument | |
208 ("Input block size must be a multiple of processing block width"); | |
209 } | |
210 | |
211 // Procedure: | |
212 // | |
213 // 1. Slice the list of columns into a set of lists of columns, | |
214 // one per octave, each of width N / (2^octave-1) and height | |
215 // binsPerOctave, containing the values present in that octave | |
216 // | |
217 // 2. Group each octave list by atomsPerFrame columns at a time, | |
218 // and stack these so as to achieve a list, for each octave, of | |
219 // taller columns of height binsPerOctave * atomsPerFrame | |
220 // | |
221 // 3. For each taller column, take the product with the inverse CQ | |
222 // kernel (which is the conjugate of the forward kernel) and | |
223 // perform an inverse FFT | |
224 // | |
225 // 4. Overlap-add each octave's resynthesised blocks (unwindowed) | |
226 // | |
227 // 5. Resample each octave's overlap-add stream to the original | |
228 // rate | |
229 // | |
230 // 6. Sum the resampled streams and return | |
231 | |
232 for (int i = 0; i < m_octaves; ++i) { | |
233 | |
234 // Step 1 | |
235 | |
236 ComplexBlock oct; | |
237 | |
238 for (int j = 0; j < widthProvided; ++j) { | |
239 int h = block[j].size(); | |
240 if (h < m_binsPerOctave * (i+1)) { | |
241 continue; | |
242 } | |
243 ComplexColumn col(block[j].begin() + m_binsPerOctave * i, | |
244 block[j].begin() + m_binsPerOctave * (i+1)); | |
245 oct.push_back(col); | |
246 } | |
247 | |
248 // Steps 2, 3, 4, 5 | |
249 processOctave(i, oct); | |
250 } | |
251 | |
252 // Step 6 | |
253 return drawFromBuffers(); | |
254 } | |
255 | |
256 CQInverse::RealSequence | |
257 CQInverse::drawFromBuffers() | |
258 { | |
259 // 6. Sum the resampled streams and return | |
260 | |
261 int available = 0; | |
262 | |
263 for (int i = 0; i < m_octaves; ++i) { | |
264 if (i == 0 || int(m_buffers[i].size()) < available) { | |
265 available = m_buffers[i].size(); | |
266 } | |
267 } | |
268 | |
269 RealSequence result(available, 0); | |
270 | |
271 if (available == 0) { | |
272 return result; | |
273 } | |
274 | |
275 for (int i = 0; i < m_octaves; ++i) { | |
276 for (int j = 0; j < available; ++j) { | |
277 result[j] += m_buffers[i][j]; | |
278 } | |
279 m_buffers[i] = RealSequence(m_buffers[i].begin() + available, | |
280 m_buffers[i].end()); | |
281 } | |
282 | |
283 return result; | |
284 } | |
285 | |
286 CQInverse::RealSequence | |
287 CQInverse::getRemainingOutput() | |
288 { | |
289 for (int j = 0; j < m_octaves; ++j) { | |
290 int factor = pow(2, j); | |
291 int latency = (j > 0 ? m_upsamplers[j]->getLatency() : 0) / factor; | |
292 for (int i = 0; i < (latency + m_p.fftSize) / m_p.fftHop; ++i) { | |
293 overlapAddAndResample(j, RealSequence(m_olaBufs[j].size(), 0)); | |
294 } | |
295 } | |
296 | |
297 return drawFromBuffers(); | |
298 } | |
299 | |
300 void | |
301 CQInverse::processOctave(int octave, const ComplexBlock &columns) | |
302 { | |
303 // 2. Group each octave list by atomsPerFrame columns at a time, | |
304 // and stack these so as to achieve a list, for each octave, of | |
305 // taller columns of height binsPerOctave * atomsPerFrame | |
306 | |
307 int ncols = columns.size(); | |
308 | |
309 if (ncols % m_p.atomsPerFrame != 0) { | |
310 cerr << "ERROR: CQInverse::process: Number of columns (" | |
311 << ncols | |
312 << ") in octave " << octave | |
313 << " must be a multiple of atoms-per-frame (" | |
314 << m_p.atomsPerFrame << ")" << endl; | |
315 throw std::invalid_argument | |
316 ("Columns in octave must be a multiple of atoms per frame"); | |
317 } | |
318 | |
319 for (int i = 0; i < ncols; i += m_p.atomsPerFrame) { | |
320 | |
321 ComplexColumn tallcol; | |
322 for (int b = 0; b < m_binsPerOctave; ++b) { | |
323 for (int a = 0; a < m_p.atomsPerFrame; ++a) { | |
324 tallcol.push_back(columns[i + a][m_binsPerOctave - b - 1]); | |
325 } | |
326 } | |
327 | |
328 processOctaveColumn(octave, tallcol); | |
329 } | |
330 } | |
331 | |
332 void | |
333 CQInverse::processOctaveColumn(int octave, const ComplexColumn &column) | |
334 { | |
335 // 3. For each taller column, take the product with the inverse CQ | |
336 // kernel (which is the conjugate of the forward kernel) and | |
337 // perform an inverse FFT | |
338 | |
339 if ((int)column.size() != m_p.atomsPerFrame * m_binsPerOctave) { | |
340 cerr << "ERROR: CQInverse::processOctaveColumn: Height of column (" | |
341 << column.size() << ") in octave " << octave | |
342 << " must be atoms-per-frame * bins-per-octave (" | |
343 << m_p.atomsPerFrame << " * " << m_binsPerOctave << " = " | |
344 << m_p.atomsPerFrame * m_binsPerOctave << ")" << endl; | |
345 throw std::invalid_argument | |
346 ("Column height must match atoms-per-frame * bins-per-octave"); | |
347 } | |
348 | |
349 ComplexSequence transformed = m_kernel->processInverse(column); | |
350 | |
351 int halfLen = m_p.fftSize/2 + 1; | |
352 | |
353 RealSequence ri(halfLen, 0); | |
354 RealSequence ii(halfLen, 0); | |
355 | |
356 for (int i = 0; i < halfLen; ++i) { | |
357 ri[i] = transformed[i].real(); | |
358 ii[i] = transformed[i].imag(); | |
359 } | |
360 | |
361 RealSequence timeDomain(m_p.fftSize, 0); | |
362 | |
363 m_fft->inverse(ri.data(), ii.data(), timeDomain.data()); | |
364 | |
365 overlapAddAndResample(octave, timeDomain); | |
366 } | |
367 | |
368 void | |
369 CQInverse::overlapAddAndResample(int octave, const RealSequence &seq) | |
370 { | |
371 // 4. Overlap-add each octave's resynthesised blocks (unwindowed) | |
372 // | |
373 // and | |
374 // | |
375 // 5. Resample each octave's overlap-add stream to the original | |
376 // rate | |
377 | |
378 if (seq.size() != m_olaBufs[octave].size()) { | |
379 cerr << "ERROR: CQInverse::overlapAdd: input sequence length (" | |
380 << seq.size() << ") is expected to match OLA buffer size (" | |
381 << m_olaBufs[octave].size() << ")" << endl; | |
382 throw std::invalid_argument | |
383 ("Input sequence length should match OLA buffer size"); | |
384 } | |
385 | |
386 RealSequence toResample(m_olaBufs[octave].begin(), | |
387 m_olaBufs[octave].begin() + m_p.fftHop); | |
388 | |
389 RealSequence resampled = | |
390 octave > 0 ? | |
391 m_upsamplers[octave]->process(toResample.data(), toResample.size()) : | |
392 toResample; | |
393 | |
394 m_buffers[octave].insert(m_buffers[octave].end(), | |
395 resampled.begin(), | |
396 resampled.end()); | |
397 | |
398 m_olaBufs[octave] = RealSequence(m_olaBufs[octave].begin() + m_p.fftHop, | |
399 m_olaBufs[octave].end()); | |
400 | |
401 RealSequence pad(m_p.fftHop, 0); | |
402 | |
403 m_olaBufs[octave].insert(m_olaBufs[octave].end(), | |
404 pad.begin(), | |
405 pad.end()); | |
406 | |
407 for (int i = 0; i < m_p.fftSize; ++i) { | |
408 m_olaBufs[octave][i] += seq[i]; | |
409 } | |
410 } | |
411 |