annotate constant-q-cpp/src/ConstantQ.cpp @ 372:af71cbdab621 tip

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