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

History | View | Annotate | Download (11.7 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 "CQKernel.h"
33

    
34
#include "dsp/MathUtilities.h"
35
#include "dsp/FFT.h"
36
#include "dsp/Window.h"
37

    
38
#include <cmath>
39
#include <cassert>
40
#include <vector>
41
#include <iostream>
42
#include <algorithm>
43

    
44
#include <cmath>
45

    
46
using std::vector;
47
using std::complex;
48
using std::cerr;
49
using std::endl;
50

    
51
typedef std::complex<double> C;
52

    
53
//#define DEBUG_CQ_KERNEL 1
54

    
55
CQKernel::CQKernel(CQParameters params) :
56
    m_inparams(params),
57
    m_valid(false),
58
    m_fft(0)
59
{
60
    m_p.sampleRate = params.sampleRate;
61
    m_p.maxFrequency = params.maxFrequency;
62
    m_p.binsPerOctave = params.binsPerOctave;
63
    m_valid = generateKernel();
64
}
65

    
66
CQKernel::~CQKernel()
67
{
68
    delete m_fft;
69
}
70

    
71
vector<double>
72
CQKernel::makeWindow(int len) const
73
{
74
    // The MATLAB version uses a symmetric window, but our windows
75
    // are periodic. A symmetric window of size N is a periodic
76
    // one of size N-1 with the first element stuck on the end.
77

    
78
    WindowType wt(BlackmanHarrisWindow);
79

    
80
    switch (m_inparams.window) {
81
    case CQParameters::SqrtBlackmanHarris:
82
    case CQParameters::BlackmanHarris:
83
        wt = BlackmanHarrisWindow;
84
        break;
85
    case CQParameters::SqrtBlackman:
86
    case CQParameters::Blackman:
87
        wt = BlackmanWindow;
88
        break;
89
    case CQParameters::SqrtHann:
90
    case CQParameters::Hann:
91
        wt = HanningWindow;
92
        break;
93
    }
94

    
95
    Window<double> w(wt, len-1);
96
    vector<double> win = w.getWindowData();
97
    win.push_back(win[0]);
98

    
99
    switch (m_inparams.window) {
100
    case CQParameters::SqrtBlackmanHarris:
101
    case CQParameters::SqrtBlackman:
102
    case CQParameters::SqrtHann:
103
        for (int i = 0; i < (int)win.size(); ++i) {
104
            win[i] = sqrt(win[i]) / len;
105
        }
106
        break;
107
    case CQParameters::BlackmanHarris:
108
    case CQParameters::Blackman:
109
    case CQParameters::Hann:
110
        for (int i = 0; i < (int)win.size(); ++i) {
111
            win[i] = win[i] / len;
112
        }
113
        break;
114
    }
115

    
116
    return win;
117
}
118

    
119
bool
120
CQKernel::generateKernel()
121
{
122
    double q = m_inparams.q;
123
    double atomHopFactor = m_inparams.atomHopFactor;
124
    double thresh = m_inparams.threshold;
125

    
126
    double bpo = m_p.binsPerOctave;
127

    
128
    m_p.minFrequency = (m_p.maxFrequency / 2) * pow(2, 1.0/bpo);
129
    m_p.Q = q / (pow(2, 1.0/bpo) - 1.0);
130

    
131
    double maxNK = int(m_p.Q * m_p.sampleRate / m_p.minFrequency + 0.5);
132
    double minNK = int
133
        (m_p.Q * m_p.sampleRate /
134
         (m_p.minFrequency * pow(2, (bpo - 1.0) / bpo)) + 0.5);
135

    
136
    if (minNK == 0 || maxNK == 0) {
137
        // most likely pathological parameters of some sort
138
        cerr << "WARNING: CQKernel::generateKernel: minNK or maxNK is zero (minNK == " << minNK << ", maxNK == " << maxNK << "), not generating a kernel" << endl;
139
        m_p.atomSpacing = 0;
140
        m_p.firstCentre = 0;
141
        m_p.fftSize = 0;
142
        m_p.atomsPerFrame = 0;
143
        m_p.lastCentre = 0;
144
        m_p.fftHop = 0;
145
        return false;
146
    }
147

    
148
    m_p.atomSpacing = int(minNK * atomHopFactor + 0.5);
149
    m_p.firstCentre = m_p.atomSpacing * ceil(ceil(maxNK / 2.0) / m_p.atomSpacing);
150
    m_p.fftSize = MathUtilities::nextPowerOfTwo
151
        (m_p.firstCentre + ceil(maxNK / 2.0));
152

    
153
    m_p.atomsPerFrame = floor
154
        (1.0 + (m_p.fftSize - ceil(maxNK / 2.0) - m_p.firstCentre) / m_p.atomSpacing);
155

    
156
#ifdef DEBUG_CQ_KERNEL
157
    cerr << "atomsPerFrame = " << m_p.atomsPerFrame << " (q = " << q << ", Q = " << m_p.Q << ", atomHopFactor = " << atomHopFactor << ", atomSpacing = " << m_p.atomSpacing << ", fftSize = " << m_p.fftSize << ", maxNK = " << maxNK << ", firstCentre = " << m_p.firstCentre << ")" << endl;
158
#endif
159

    
160
    m_p.lastCentre = m_p.firstCentre + (m_p.atomsPerFrame - 1) * m_p.atomSpacing;
161

    
162
    m_p.fftHop = (m_p.lastCentre + m_p.atomSpacing) - m_p.firstCentre;
163

    
164
#ifdef DEBUG_CQ_KERNEL
165
    cerr << "fftHop = " << m_p.fftHop << endl;
166
#endif
167

    
168
    m_fft = new FFT(m_p.fftSize);
169

    
170
    for (int k = 1; k <= m_p.binsPerOctave; ++k) {
171
        
172
        int nk = int(m_p.Q * m_p.sampleRate /
173
                     (m_p.minFrequency * pow(2, ((k-1.0) / bpo))) + 0.5);
174

    
175
        vector<double> win = makeWindow(nk);
176

    
177
        double fk = m_p.minFrequency * pow(2, ((k-1.0) / bpo));
178

    
179
        vector<double> reals, imags;
180
        
181
        for (int i = 0; i < nk; ++i) {
182
            double arg = (2.0 * M_PI * fk * i) / m_p.sampleRate;
183
            reals.push_back(win[i] * cos(arg));
184
            imags.push_back(win[i] * sin(arg));
185
        }
186

    
187
        int atomOffset = m_p.firstCentre - int(ceil(nk/2.0));
188

    
189
        for (int i = 0; i < m_p.atomsPerFrame; ++i) {
190

    
191
            int shift = atomOffset + (i * m_p.atomSpacing);
192

    
193
            vector<double> rin(m_p.fftSize, 0.0);
194
            vector<double> iin(m_p.fftSize, 0.0);
195

    
196
            for (int j = 0; j < nk; ++j) {
197
                rin[j + shift] = reals[j];
198
                iin[j + shift] = imags[j];
199
            }
200

    
201
            vector<double> rout(m_p.fftSize, 0.0);
202
            vector<double> iout(m_p.fftSize, 0.0);
203

    
204
            m_fft->process(false,
205
                           rin.data(), iin.data(),
206
                           rout.data(), iout.data());
207

    
208
            // Keep this dense for the moment (until after
209
            // normalisation calculations)
210

    
211
            vector<C> row;
212

    
213
            for (int j = 0; j < m_p.fftSize; ++j) {
214
                if (sqrt(rout[j] * rout[j] + iout[j] * iout[j]) < thresh) {
215
                    row.push_back(C(0, 0));
216
                } else {
217
                    row.push_back(C(rout[j] / m_p.fftSize,
218
                                    iout[j] / m_p.fftSize));
219
                }
220
            }
221

    
222
            m_kernel.origin.push_back(0);
223
            m_kernel.data.push_back(row);
224
        }
225
    }
226

    
227
    assert((int)m_kernel.data.size() == m_p.binsPerOctave * m_p.atomsPerFrame);
228

    
229
    // print density as diagnostic
230

    
231
    int nnz = 0;
232
    for (int i = 0; i < (int)m_kernel.data.size(); ++i) {
233
        for (int j = 0; j < (int)m_kernel.data[i].size(); ++j) {
234
            if (m_kernel.data[i][j] != C(0, 0)) {
235
                ++nnz;
236
            }
237
        }
238
    }
239

    
240
#ifdef DEBUG_CQ_KERNEL
241
    cerr << "size = " << m_kernel.data.size() << "*" << m_kernel.data[0].size() << " (fft size = " << m_p.fftSize << ")" << endl;
242
#endif
243

    
244
    assert((int)m_kernel.data.size() == m_p.binsPerOctave * m_p.atomsPerFrame);
245
    assert((int)m_kernel.data[0].size() == m_p.fftSize);
246

    
247
#ifdef DEBUG_CQ_KERNEL
248
    cerr << "density = " << double(nnz) / double(m_p.binsPerOctave * m_p.atomsPerFrame * m_p.fftSize) << " (" << nnz << " of " << m_p.binsPerOctave * m_p.atomsPerFrame * m_p.fftSize << ")" << endl;
249
#endif
250

    
251
    finaliseKernel();
252
    return true;
253
}
254

    
255
static bool ccomparator(C &c1, C &c2)
256
{
257
    return abs(c1) < abs(c2);
258
}
259

    
260
static int maxidx(vector<C> &v)
261
{
262
    return std::max_element(v.begin(), v.end(), ccomparator) - v.begin();
263
}
264

    
265
void
266
CQKernel::finaliseKernel()
267
{
268
    // calculate weight for normalisation
269

    
270
    int wx1 = maxidx(m_kernel.data[0]);
271
    int wx2 = maxidx(m_kernel.data[m_kernel.data.size()-1]);
272

    
273
    vector<vector<C> > subset(m_kernel.data.size());
274
    for (int j = wx1; j <= wx2; ++j) {
275
        for (int i = 0; i < (int)m_kernel.data.size(); ++i) {
276
            subset[i].push_back(m_kernel.data[i][j]);
277
        }
278
    }
279

    
280
    int nrows = subset.size();
281
    int ncols = subset[0].size();
282
    vector<vector<C> > square(ncols); // conjugate transpose of subset * subset
283

    
284
    for (int i = 0; i < nrows; ++i) {
285
        assert((int)subset[i].size() == ncols);
286
    }
287

    
288
    for (int j = 0; j < ncols; ++j) {
289
        for (int i = 0; i < ncols; ++i) {
290
            C v(0, 0);
291
            for (int k = 0; k < nrows; ++k) {
292
                v += subset[k][i] * conj(subset[k][j]);
293
            }
294
            square[i].push_back(v);
295
        }
296
    }
297

    
298
    vector<double> wK;
299
    double q = m_inparams.q;
300
    for (int i = int(1.0/q + 0.5); i < ncols - int(1.0/q + 0.5) - 2; ++i) {
301
        wK.push_back(abs(square[i][i]));
302
    }
303

    
304
    double weight = double(m_p.fftHop) / m_p.fftSize;
305
    if (!wK.empty()) {
306
        weight /= MathUtilities::mean(wK.data(), wK.size());
307
    }
308
    weight = sqrt(weight);
309

    
310
#ifdef DEBUG_CQ_KERNEL    
311
    cerr << "weight = " << weight << " (from " << wK.size() << " elements in wK, ncols = " << ncols << ", q = " << q << ")" << endl;
312
#endif
313

    
314
    // apply normalisation weight, make sparse, and store conjugate
315
    // (we use the adjoint or conjugate transpose of the kernel matrix
316
    // for the forward transform, the plain kernel for the inverse
317
    // which we expect to be less common)
318

    
319
    KernelMatrix sk;
320

    
321
    for (int i = 0; i < (int)m_kernel.data.size(); ++i) {
322

    
323
        sk.origin.push_back(0);
324
        sk.data.push_back(vector<C>());
325

    
326
        int lastNZ = 0;
327
        for (int j = (int)m_kernel.data[i].size()-1; j >= 0; --j) {
328
            if (abs(m_kernel.data[i][j]) != 0.0) {
329
                lastNZ = j;
330
                break;
331
            }
332
        }
333

    
334
        bool haveNZ = false;
335
        for (int j = 0; j <= lastNZ; ++j) {
336
            if (haveNZ || abs(m_kernel.data[i][j]) != 0.0) {
337
                if (!haveNZ) sk.origin[i] = j;
338
                haveNZ = true;
339
                sk.data[i].push_back(conj(m_kernel.data[i][j]) * weight);
340
            }
341
        }
342
    }
343

    
344
    m_kernel = sk;
345
}
346

    
347
vector<C>
348
CQKernel::processForward(const vector<C> &cv)
349
{
350
    // straightforward matrix multiply (taking into account m_kernel's
351
    // slightly-sparse representation)
352

    
353
    if (m_kernel.data.empty()) return vector<C>();
354

    
355
    int nrows = m_p.binsPerOctave * m_p.atomsPerFrame;
356

    
357
    vector<C> rv(nrows, C());
358

    
359
    for (int i = 0; i < nrows; ++i) {
360
        int len = m_kernel.data[i].size();
361
        for (int j = 0; j < len; ++j) {
362
            rv[i] += cv[j + m_kernel.origin[i]] * m_kernel.data[i][j];
363
        }
364
    }
365

    
366
    return rv;
367
}
368

    
369
vector<C>
370
CQKernel::processInverse(const vector<C> &cv)
371
{
372
    // matrix multiply by conjugate transpose of m_kernel. This is
373
    // actually the original kernel as calculated, we just stored the
374
    // conjugate-transpose of the kernel because we expect to be doing
375
    // more forward transforms than inverse ones.
376

    
377
    if (m_kernel.data.empty()) return vector<C>();
378

    
379
    int ncols = m_p.binsPerOctave * m_p.atomsPerFrame;
380
    int nrows = m_p.fftSize;
381

    
382
    vector<C> rv(nrows, C());
383

    
384
    for (int j = 0; j < ncols; ++j) {
385
        int i0 = m_kernel.origin[j];
386
        int i1 = i0 + m_kernel.data[j].size();
387
        for (int i = i0; i < i1; ++i) {
388
            rv[i] += cv[j] * conj(m_kernel.data[j][i - i0]);
389
        }
390
    }
391

    
392
    return rv;
393
}
394

    
395