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