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 / CQInverse.cpp

History | View | Annotate | Download (13 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 "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