comparison constant-q-cpp/src/ConstantQ.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 "ConstantQ.h"
33
34 #include "CQKernel.h"
35
36 #include "dsp/Resampler.h"
37 #include "dsp/MathUtilities.h"
38 #include "dsp/FFT.h"
39
40 #include <algorithm>
41 #include <iostream>
42 #include <stdexcept>
43
44 #include <cmath>
45
46 using std::vector;
47 using std::cerr;
48 using std::endl;
49
50 //#define DEBUG_CQ 1
51
52 ConstantQ::ConstantQ(CQParameters params) :
53 m_inparams(params),
54 m_sampleRate(params.sampleRate),
55 m_maxFrequency(params.maxFrequency),
56 m_minFrequency(params.minFrequency),
57 m_binsPerOctave(params.binsPerOctave),
58 m_kernel(0),
59 m_fft(0)
60 {
61 if (m_minFrequency <= 0.0 || m_maxFrequency <= 0.0) {
62 throw std::invalid_argument("Frequency extents must be positive");
63 }
64
65 initialise();
66 }
67
68 ConstantQ::~ConstantQ()
69 {
70 delete m_fft;
71 for (int i = 0; i < (int)m_decimators.size(); ++i) {
72 delete m_decimators[i];
73 }
74 delete m_kernel;
75 }
76
77 double
78 ConstantQ::getMinFrequency() const
79 {
80 return m_p.minFrequency / pow(2.0, m_octaves - 1);
81 }
82
83 double
84 ConstantQ::getBinFrequency(double bin) const
85 {
86 // our bins are returned in high->low order
87 bin = (getBinsPerOctave() * getOctaves()) - bin - 1;
88 return getMinFrequency() * pow(2, (bin / getBinsPerOctave()));
89 }
90
91 void
92 ConstantQ::initialise()
93 {
94 m_octaves = int(ceil(log(m_maxFrequency / m_minFrequency) / log(2)));
95
96 if (m_octaves < 1) {
97 m_kernel = 0; // incidentally causing isValid() to return false
98 return;
99 }
100
101 m_kernel = new CQKernel(m_inparams);
102 m_p = m_kernel->getProperties();
103
104 if (!m_kernel->isValid()) {
105 return;
106 }
107
108 // Use exact powers of two for resampling rates. They don't have
109 // to be related to our actual samplerate: the resampler only
110 // cares about the ratio, but it only accepts integer source and
111 // target rates, and if we start from the actual samplerate we
112 // risk getting non-integer rates for lower octaves
113
114 int sourceRate = pow(2, m_octaves);
115 vector<int> latencies;
116
117 // top octave, no resampling
118 latencies.push_back(0);
119 m_decimators.push_back(0);
120
121 for (int i = 1; i < m_octaves; ++i) {
122
123 int factor = pow(2, i);
124
125 Resampler *r;
126
127 if (m_inparams.decimator == CQParameters::BetterDecimator) {
128 r = new Resampler
129 (sourceRate, sourceRate / factor, 50, 0.05);
130 } else {
131 r = new Resampler
132 (sourceRate, sourceRate / factor, 25, 0.3);
133 }
134
135 #ifdef DEBUG_CQ
136 cerr << "forward: octave " << i << ": resample from " << sourceRate << " to " << sourceRate / factor << endl;
137 #endif
138
139 // We need to adapt the latencies so as to get the first input
140 // sample to be aligned, in time, at the decimator output
141 // across all octaves.
142 //
143 // Our decimator uses a linear phase filter, but being causal
144 // it is not zero phase: it has a latency that depends on the
145 // decimation factor. Those latencies have been calculated
146 // per-octave and are available to us in the latencies
147 // array. Left to its own devices, the first input sample will
148 // appear at output sample 0 in the highest octave (where no
149 // decimation is needed), sample number latencies[1] in the
150 // next octave down, latencies[2] in the next one, etc. We get
151 // to apply some artificial per-octave latency after the
152 // decimator in the processing chain, in order to compensate
153 // for the differing latencies associated with different
154 // decimation factors. How much should we insert?
155 //
156 // The outputs of the decimators are at different rates (in
157 // terms of the relation between clock time and samples) and
158 // we want them aligned in terms of time. So, for example, a
159 // latency of 10 samples with a decimation factor of 2 is
160 // equivalent to a latency of 20 with no decimation -- they
161 // both result in the first output sample happening at the
162 // same equivalent time in milliseconds.
163 //
164 // So here we record the latency added by the decimator, in
165 // terms of the sample rate of the undecimated signal. Then we
166 // use that to compensate in a moment, when we've discovered
167 // what the longest latency across all octaves is.
168
169 latencies.push_back(r->getLatency() * factor);
170 m_decimators.push_back(r);
171 }
172
173 m_bigBlockSize = m_p.fftSize * pow(2, m_octaves - 1);
174
175 // Now add in the extra padding and compensate for hops that must
176 // be dropped in order to align the atom centres across
177 // octaves. Again this is a bit trickier because we are doing it
178 // at input rather than output and so must work in per-octave
179 // sample rates rather than output blocks
180
181 int emptyHops = m_p.firstCentre / m_p.atomSpacing;
182
183 vector<int> drops;
184 for (int i = 0; i < m_octaves; ++i) {
185 int factor = pow(2, i);
186 int dropHops = emptyHops * pow(2, m_octaves - i - 1) - emptyHops;
187 int drop = ((dropHops * m_p.fftHop) * factor) / m_p.atomsPerFrame;
188 drops.push_back(drop);
189 }
190
191 int maxLatPlusDrop = 0;
192 for (int i = 0; i < m_octaves; ++i) {
193 int latPlusDrop = latencies[i] + drops[i];
194 if (latPlusDrop > maxLatPlusDrop) maxLatPlusDrop = latPlusDrop;
195 }
196
197 int totalLatency = maxLatPlusDrop;
198
199 int lat0 = totalLatency - latencies[0] - drops[0];
200 totalLatency = ceil(double(lat0 / m_p.fftHop) * m_p.fftHop)
201 + latencies[0] + drops[0];
202
203 // We want (totalLatency - latencies[i]) to be a multiple of 2^i
204 // for each octave i, so that we do not end up with fractional
205 // octave latencies below. In theory this is hard, in practice if
206 // we ensure it for the last octave we should be OK.
207 double finalOctLat = latencies[m_octaves-1];
208 double finalOctFact = pow(2, m_octaves-1);
209 totalLatency =
210 int(finalOctLat +
211 finalOctFact *
212 ceil((totalLatency - finalOctLat) / finalOctFact) + .5);
213
214 #ifdef DEBUG_CQ
215 cerr << "total latency = " << totalLatency << endl;
216 #endif
217
218 // Padding as in the reference (will be introduced with the
219 // latency compensation in the loop below)
220 m_outputLatency = totalLatency + m_bigBlockSize
221 - m_p.firstCentre * pow(2, m_octaves-1);
222
223 #ifdef DEBUG_CQ
224 cerr << "m_bigBlockSize = " << m_bigBlockSize << ", firstCentre = "
225 << m_p.firstCentre << ", m_octaves = " << m_octaves
226 << ", so m_outputLatency = " << m_outputLatency << endl;
227 #endif
228
229 for (int i = 0; i < m_octaves; ++i) {
230
231 double factor = pow(2, i);
232
233 // Calculate the difference between the total latency applied
234 // across all octaves, and the existing latency due to the
235 // decimator for this octave, and then convert it back into
236 // the sample rate appropriate for the output latency of this
237 // decimator -- including one additional big block of padding
238 // (as in the reference).
239
240 double octaveLatency =
241 double(totalLatency - latencies[i] - drops[i]
242 + m_bigBlockSize) / factor;
243
244 #ifdef DEBUG_CQ
245 cerr << "octave " << i << ": resampler latency = " << latencies[i]
246 << ", drop " << drops[i] << " (/factor = " << drops[i]/factor
247 << "), octaveLatency = " << octaveLatency << " -> "
248 << int(round(octaveLatency)) << " (diff * factor = "
249 << (octaveLatency - round(octaveLatency)) << " * "
250 << factor << " = "
251 << (octaveLatency - round(octaveLatency)) * factor << ")" << endl;
252
253 cerr << "double(" << totalLatency << " - "
254 << latencies[i] << " - " << drops[i] << " + "
255 << m_bigBlockSize << ") / " << factor << " = "
256 << octaveLatency << endl;
257 #endif
258
259 m_buffers.push_back
260 (RealSequence(int(octaveLatency + 0.5), 0.0));
261 }
262
263 m_fft = new FFTReal(m_p.fftSize);
264 }
265
266 ConstantQ::ComplexBlock
267 ConstantQ::process(const RealSequence &td)
268 {
269 m_buffers[0].insert(m_buffers[0].end(), td.begin(), td.end());
270
271 for (int i = 1; i < m_octaves; ++i) {
272 RealSequence dec = m_decimators[i]->process(td.data(), td.size());
273 m_buffers[i].insert(m_buffers[i].end(), dec.begin(), dec.end());
274 }
275
276 ComplexBlock out;
277
278 while (true) {
279
280 // We could have quite different remaining sample counts in
281 // different octaves, because (apart from the predictable
282 // added counts for decimator output on each block) we also
283 // have variable additional latency per octave
284 bool enough = true;
285 for (int i = 0; i < m_octaves; ++i) {
286 int required = m_p.fftSize * pow(2, m_octaves - i - 1);
287 if ((int)m_buffers[i].size() < required) {
288 enough = false;
289 }
290 }
291 if (!enough) break;
292
293 int base = out.size();
294 int totalColumns = pow(2, m_octaves - 1) * m_p.atomsPerFrame;
295 for (int i = 0; i < totalColumns; ++i) {
296 out.push_back(ComplexColumn());
297 }
298
299 for (int octave = 0; octave < m_octaves; ++octave) {
300
301 int blocksThisOctave = pow(2, (m_octaves - octave - 1));
302
303 for (int b = 0; b < blocksThisOctave; ++b) {
304 ComplexBlock block = processOctaveBlock(octave);
305
306 for (int j = 0; j < m_p.atomsPerFrame; ++j) {
307
308 int target = base +
309 (b * (totalColumns / blocksThisOctave) +
310 (j * ((totalColumns / blocksThisOctave) /
311 m_p.atomsPerFrame)));
312
313 while (int(out[target].size()) <
314 m_p.binsPerOctave * (octave + 1)) {
315 out[target].push_back(Complex());
316 }
317
318 for (int i = 0; i < m_p.binsPerOctave; ++i) {
319 out[target][m_p.binsPerOctave * octave + i] =
320 block[j][m_p.binsPerOctave - i - 1];
321 }
322 }
323 }
324 }
325 }
326
327 return out;
328 }
329
330 ConstantQ::ComplexBlock
331 ConstantQ::getRemainingOutput()
332 {
333 // Same as padding added at start, though rounded up
334 int pad = ceil(double(m_outputLatency) / m_bigBlockSize) * m_bigBlockSize;
335 RealSequence zeros(pad, 0.0);
336 return process(zeros);
337 }
338
339 ConstantQ::ComplexBlock
340 ConstantQ::processOctaveBlock(int octave)
341 {
342 RealSequence ro(m_p.fftSize, 0.0);
343 RealSequence io(m_p.fftSize, 0.0);
344
345 m_fft->forward(m_buffers[octave].data(), ro.data(), io.data());
346
347 m_buffers[octave] = RealSequence(m_buffers[octave].begin() + m_p.fftHop,
348 m_buffers[octave].end());
349
350 ComplexSequence cv(m_p.fftSize);
351 for (int i = 0; i < m_p.fftSize; ++i) {
352 cv[i] = Complex(ro[i], io[i]);
353 }
354
355 ComplexSequence cqrowvec = m_kernel->processForward(cv);
356
357 // Reform into a column matrix
358 ComplexBlock cqblock;
359 for (int j = 0; j < m_p.atomsPerFrame; ++j) {
360 cqblock.push_back(ComplexColumn());
361 for (int i = 0; i < m_p.binsPerOctave; ++i) {
362 cqblock[j].push_back(cqrowvec[i * m_p.atomsPerFrame + j]);
363 }
364 }
365
366 return cqblock;
367 }
368
369