To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

The primary repository for this project is hosted at https://github.com/cannam/constant-q-cpp/ .
This repository is a read-only copy which is updated automatically every hour.

Statistics Download as Zip
| Branch: | Revision:

root / src / ConstantQ.cpp

History | View | Annotate | Download (12.2 KB)

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