comparison constant-q-cpp/src/CQInverse.cpp @ 366:5d0a2ebb4d17

Bring dependent libraries in to repo
author Chris Cannam
date Fri, 24 Jun 2016 14:47:45 +0100
parents
children
comparison
equal deleted inserted replaced
365:112766f4c34b 366:5d0a2ebb4d17
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/Resampler.h"
35 #include "dsp/MathUtilities.h"
36 #include "dsp/FFT.h"
37
38 #include <algorithm>
39 #include <iostream>
40 #include <stdexcept>
41
42 #include <cmath>
43
44 using std::vector;
45 using std::cerr;
46 using std::endl;
47
48 //#define DEBUG_CQ 1
49
50 CQInverse::CQInverse(CQParameters params) :
51 m_inparams(params),
52 m_sampleRate(params.sampleRate),
53 m_maxFrequency(params.maxFrequency),
54 m_minFrequency(params.minFrequency),
55 m_binsPerOctave(params.binsPerOctave),
56 m_fft(0)
57 {
58 if (m_minFrequency <= 0.0 || m_maxFrequency <= 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(double bin) const
82 {
83 // our bins are returned in high->low order
84 bin = (getBinsPerOctave() * getOctaves()) - bin - 1;
85 return getMinFrequency() * pow(2, (bin / getBinsPerOctave()));
86 }
87
88 void
89 CQInverse::initialise()
90 {
91 m_octaves = int(ceil(log(m_maxFrequency / m_minFrequency) / log(2)));
92
93 if (m_octaves < 1) {
94 m_kernel = 0; // incidentally causing isValid() to return false
95 return;
96 }
97
98 m_kernel = new CQKernel(m_inparams);
99 m_p = m_kernel->getProperties();
100
101 // Use exact powers of two for resampling rates. They don't have
102 // to be related to our actual samplerate: the resampler only
103 // cares about the ratio, but it only accepts integer source and
104 // target rates, and if we start from the actual samplerate we
105 // risk getting non-integer rates for lower octaves
106
107 int sourceRate = pow(2, m_octaves);
108 vector<int> latencies;
109
110 // top octave, no resampling
111 latencies.push_back(0);
112 m_upsamplers.push_back(0);
113
114 for (int i = 1; i < m_octaves; ++i) {
115
116 int factor = pow(2, i);
117
118 Resampler *r = new Resampler
119 (sourceRate / factor, sourceRate, 50, 0.05);
120
121 #ifdef DEBUG_CQ
122 cerr << "inverse: octave " << i << ": resample from " << sourceRate/factor << " to " << sourceRate << endl;
123 #endif
124
125 // See ConstantQ.cpp for discussion on latency -- output
126 // latency here is at target rate which, this way around, is
127 // what we want
128
129 latencies.push_back(r->getLatency());
130 m_upsamplers.push_back(r);
131 }
132
133 // additionally we will have fftHop latency at individual octave
134 // rate (before upsampling) for the overlap-add in each octave
135 for (int i = 0; i < m_octaves; ++i) {
136 latencies[i] += m_p.fftHop * pow(2, i);
137 }
138
139 // Now reverse the drop adjustment made in ConstantQ to align the
140 // atom centres across different octaves (but this time at output
141 // sample rate)
142
143 int emptyHops = m_p.firstCentre / m_p.atomSpacing;
144
145 vector<int> pushes;
146 for (int i = 0; i < m_octaves; ++i) {
147 int factor = pow(2, i);
148 int pushHops = emptyHops * pow(2, m_octaves - i - 1) - emptyHops;
149 int push = ((pushHops * m_p.fftHop) * factor) / m_p.atomsPerFrame;
150 pushes.push_back(push);
151 }
152
153 int maxLatLessPush = 0;
154 for (int i = 0; i < m_octaves; ++i) {
155 int latLessPush = latencies[i] - pushes[i];
156 if (latLessPush > maxLatLessPush) maxLatLessPush = latLessPush;
157 }
158
159 int totalLatency = maxLatLessPush + 10;
160 if (totalLatency < 0) totalLatency = 0;
161
162 m_outputLatency = totalLatency + m_p.firstCentre * pow(2, m_octaves-1);
163
164 #ifdef DEBUG_CQ
165 cerr << "totalLatency = " << totalLatency << ", m_outputLatency = " << m_outputLatency << endl;
166 #endif
167
168 for (int i = 0; i < m_octaves; ++i) {
169
170 // Calculate the difference between the total latency applied
171 // across all octaves, and the existing latency due to the
172 // upsampler for this octave.
173
174 int latencyPadding = totalLatency - latencies[i] + pushes[i];
175
176 #ifdef DEBUG_CQ
177 cerr << "octave " << i << ": push " << pushes[i] << ", resampler latency inc overlap space " << latencies[i] << ", latencyPadding = " << latencyPadding << " (/factor = " << latencyPadding / pow(2, i) << ")" << endl;
178 #endif
179
180 m_buffers.push_back(RealSequence(latencyPadding, 0.0));
181 }
182
183 for (int i = 0; i < m_octaves; ++i) {
184 // Fixed-size buffer for IFFT overlap-add
185 m_olaBufs.push_back(RealSequence(m_p.fftSize, 0.0));
186 }
187
188 m_fft = new FFTReal(m_p.fftSize);
189 }
190
191 CQInverse::RealSequence
192 CQInverse::process(const ComplexBlock &block)
193 {
194 // The input data is of the form produced by ConstantQ::process --
195 // an unknown number N of columns of varying height. We assert
196 // that N is a multiple of atomsPerFrame * 2^(octaves-1), as must
197 // be the case for data that came directly from our ConstantQ
198 // implementation.
199
200 int widthProvided = block.size();
201
202 if (widthProvided == 0) {
203 return drawFromBuffers();
204 }
205
206 int blockWidth = m_p.atomsPerFrame * int(pow(2, m_octaves - 1));
207
208 if (widthProvided % blockWidth != 0) {
209 cerr << "ERROR: CQInverse::process: Input block size ("
210 << widthProvided
211 << ") must be a multiple of processing block width "
212 << "(atoms-per-frame * 2^(octaves-1) = "
213 << m_p.atomsPerFrame << " * 2^(" << m_octaves << "-1) = "
214 << blockWidth << ")" << endl;
215 throw std::invalid_argument
216 ("Input block size must be a multiple of processing block width");
217 }
218
219 // Procedure:
220 //
221 // 1. Slice the list of columns into a set of lists of columns,
222 // one per octave, each of width N / (2^octave-1) and height
223 // binsPerOctave, containing the values present in that octave
224 //
225 // 2. Group each octave list by atomsPerFrame columns at a time,
226 // and stack these so as to achieve a list, for each octave, of
227 // taller columns of height binsPerOctave * atomsPerFrame
228 //
229 // 3. For each taller column, take the product with the inverse CQ
230 // kernel (which is the conjugate of the forward kernel) and
231 // perform an inverse FFT
232 //
233 // 4. Overlap-add each octave's resynthesised blocks (unwindowed)
234 //
235 // 5. Resample each octave's overlap-add stream to the original
236 // rate
237 //
238 // 6. Sum the resampled streams and return
239
240 for (int i = 0; i < m_octaves; ++i) {
241
242 // Step 1
243
244 ComplexBlock oct;
245
246 for (int j = 0; j < widthProvided; ++j) {
247 int h = block[j].size();
248 if (h < m_binsPerOctave * (i+1)) {
249 continue;
250 }
251 ComplexColumn col(block[j].begin() + m_binsPerOctave * i,
252 block[j].begin() + m_binsPerOctave * (i+1));
253 oct.push_back(col);
254 }
255
256 // Steps 2, 3, 4, 5
257 processOctave(i, oct);
258 }
259
260 // Step 6
261 return drawFromBuffers();
262 }
263
264 CQInverse::RealSequence
265 CQInverse::drawFromBuffers()
266 {
267 // 6. Sum the resampled streams and return
268
269 int available = 0;
270
271 for (int i = 0; i < m_octaves; ++i) {
272 if (i == 0 || int(m_buffers[i].size()) < available) {
273 available = m_buffers[i].size();
274 }
275 }
276
277 RealSequence result(available, 0);
278
279 if (available == 0) {
280 return result;
281 }
282
283 for (int i = 0; i < m_octaves; ++i) {
284 for (int j = 0; j < available; ++j) {
285 result[j] += m_buffers[i][j];
286 }
287 m_buffers[i] = RealSequence(m_buffers[i].begin() + available,
288 m_buffers[i].end());
289 }
290
291 return result;
292 }
293
294 CQInverse::RealSequence
295 CQInverse::getRemainingOutput()
296 {
297 for (int j = 0; j < m_octaves; ++j) {
298 int factor = pow(2, j);
299 int latency = (j > 0 ? m_upsamplers[j]->getLatency() : 0) / factor;
300 for (int i = 0; i < (latency + m_p.fftSize) / m_p.fftHop; ++i) {
301 overlapAddAndResample(j, RealSequence(m_olaBufs[j].size(), 0));
302 }
303 }
304
305 return drawFromBuffers();
306 }
307
308 void
309 CQInverse::processOctave(int octave, const ComplexBlock &columns)
310 {
311 // 2. Group each octave list by atomsPerFrame columns at a time,
312 // and stack these so as to achieve a list, for each octave, of
313 // taller columns of height binsPerOctave * atomsPerFrame
314
315 int ncols = columns.size();
316
317 if (ncols % m_p.atomsPerFrame != 0) {
318 cerr << "ERROR: CQInverse::process: Number of columns ("
319 << ncols
320 << ") in octave " << octave
321 << " must be a multiple of atoms-per-frame ("
322 << m_p.atomsPerFrame << ")" << endl;
323 throw std::invalid_argument
324 ("Columns in octave must be a multiple of atoms per frame");
325 }
326
327 for (int i = 0; i < ncols; i += m_p.atomsPerFrame) {
328
329 ComplexColumn tallcol;
330 for (int b = 0; b < m_binsPerOctave; ++b) {
331 for (int a = 0; a < m_p.atomsPerFrame; ++a) {
332 tallcol.push_back(columns[i + a][m_binsPerOctave - b - 1]);
333 }
334 }
335
336 processOctaveColumn(octave, tallcol);
337 }
338 }
339
340 void
341 CQInverse::processOctaveColumn(int octave, const ComplexColumn &column)
342 {
343 // 3. For each taller column, take the product with the inverse CQ
344 // kernel (which is the conjugate of the forward kernel) and
345 // perform an inverse FFT
346
347 if ((int)column.size() != m_p.atomsPerFrame * m_binsPerOctave) {
348 cerr << "ERROR: CQInverse::processOctaveColumn: Height of column ("
349 << column.size() << ") in octave " << octave
350 << " must be atoms-per-frame * bins-per-octave ("
351 << m_p.atomsPerFrame << " * " << m_binsPerOctave << " = "
352 << m_p.atomsPerFrame * m_binsPerOctave << ")" << endl;
353 throw std::invalid_argument
354 ("Column height must match atoms-per-frame * bins-per-octave");
355 }
356
357 ComplexSequence transformed = m_kernel->processInverse(column);
358
359 int halfLen = m_p.fftSize/2 + 1;
360
361 RealSequence ri(halfLen, 0);
362 RealSequence ii(halfLen, 0);
363
364 for (int i = 0; i < halfLen; ++i) {
365 ri[i] = transformed[i].real();
366 ii[i] = transformed[i].imag();
367 }
368
369 RealSequence timeDomain(m_p.fftSize, 0);
370
371 m_fft->inverse(ri.data(), ii.data(), timeDomain.data());
372
373 overlapAddAndResample(octave, timeDomain);
374 }
375
376 void
377 CQInverse::overlapAddAndResample(int octave, const RealSequence &seq)
378 {
379 // 4. Overlap-add each octave's resynthesised blocks (unwindowed)
380 //
381 // and
382 //
383 // 5. Resample each octave's overlap-add stream to the original
384 // rate
385
386 if (seq.size() != m_olaBufs[octave].size()) {
387 cerr << "ERROR: CQInverse::overlapAdd: input sequence length ("
388 << seq.size() << ") is expected to match OLA buffer size ("
389 << m_olaBufs[octave].size() << ")" << endl;
390 throw std::invalid_argument
391 ("Input sequence length should match OLA buffer size");
392 }
393
394 RealSequence toResample(m_olaBufs[octave].begin(),
395 m_olaBufs[octave].begin() + m_p.fftHop);
396
397 RealSequence resampled =
398 octave > 0 ?
399 m_upsamplers[octave]->process(toResample.data(), toResample.size()) :
400 toResample;
401
402 m_buffers[octave].insert(m_buffers[octave].end(),
403 resampled.begin(),
404 resampled.end());
405
406 m_olaBufs[octave] = RealSequence(m_olaBufs[octave].begin() + m_p.fftHop,
407 m_olaBufs[octave].end());
408
409 RealSequence pad(m_p.fftHop, 0);
410
411 m_olaBufs[octave].insert(m_olaBufs[octave].end(),
412 pad.begin(),
413 pad.end());
414
415 for (int i = 0; i < m_p.fftSize; ++i) {
416 m_olaBufs[octave][i] += seq[i];
417 }
418 }
419