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