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

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / CepstralPitchTracker.cpp @ 50:d84049e20c61

History | View | Annotate | Download (9.35 KB)

1 3:9366c8a58778 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
/*
3 31:2c175adf8736 Chris
    This file is Copyright (c) 2012 Chris Cannam
4

5 3:9366c8a58778 Chris
    Permission is hereby granted, free of charge, to any person
6
    obtaining a copy of this software and associated documentation
7
    files (the "Software"), to deal in the Software without
8
    restriction, including without limitation the rights to use, copy,
9
    modify, merge, publish, distribute, sublicense, and/or sell copies
10
    of the Software, and to permit persons to whom the Software is
11
    furnished to do so, subject to the following conditions:
12

13
    The above copyright notice and this permission notice shall be
14
    included in all copies or substantial portions of the Software.
15

16
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
20
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
*/
24
25 31:2c175adf8736 Chris
#include "CepstralPitchTracker.h"
26 47:f72a470fe4b5 Chris
#include "MeanFilter.h"
27 50:d84049e20c61 Chris
#include "PeakInterpolator.h"
28 3:9366c8a58778 Chris
29 26:13568f1ccff0 Chris
#include "vamp-sdk/FFT.h"
30
31 3:9366c8a58778 Chris
#include <vector>
32
#include <algorithm>
33
34
#include <cstdio>
35
#include <cmath>
36
#include <complex>
37
38
using std::string;
39 7:32defdb2f9d9 Chris
using std::vector;
40 16:d717911aca3c Chris
using Vamp::RealTime;
41 7:32defdb2f9d9 Chris
42 16:d717911aca3c Chris
43 31:2c175adf8736 Chris
CepstralPitchTracker::CepstralPitchTracker(float inputSampleRate) :
44 3:9366c8a58778 Chris
    Plugin(inputSampleRate),
45
    m_channels(0),
46
    m_stepSize(256),
47
    m_blockSize(1024),
48
    m_fmin(50),
49 25:9aee1a0e6223 Chris
    m_fmax(900),
50 18:131b1c40be1a Chris
    m_vflen(1),
51 3:9366c8a58778 Chris
    m_binFrom(0),
52
    m_binTo(0),
53 15:bd7fb10646fc Chris
    m_bins(0)
54 3:9366c8a58778 Chris
{
55
}
56
57 31:2c175adf8736 Chris
CepstralPitchTracker::~CepstralPitchTracker()
58 3:9366c8a58778 Chris
{
59
}
60
61
string
62 31:2c175adf8736 Chris
CepstralPitchTracker::getIdentifier() const
63 3:9366c8a58778 Chris
{
64 39:822cf7b8e070 Chris
    return "cepstral-pitchtracker";
65 3:9366c8a58778 Chris
}
66
67
string
68 31:2c175adf8736 Chris
CepstralPitchTracker::getName() const
69 3:9366c8a58778 Chris
{
70 39:822cf7b8e070 Chris
    return "Cepstral Pitch Tracker";
71 3:9366c8a58778 Chris
}
72
73
string
74 31:2c175adf8736 Chris
CepstralPitchTracker::getDescription() const
75 3:9366c8a58778 Chris
{
76
    return "Estimate f0 of monophonic material using a cepstrum method.";
77
}
78
79
string
80 31:2c175adf8736 Chris
CepstralPitchTracker::getMaker() const
81 3:9366c8a58778 Chris
{
82
    return "Chris Cannam";
83
}
84
85
int
86 31:2c175adf8736 Chris
CepstralPitchTracker::getPluginVersion() const
87 3:9366c8a58778 Chris
{
88
    // Increment this each time you release a version that behaves
89
    // differently from the previous one
90
    return 1;
91
}
92
93
string
94 31:2c175adf8736 Chris
CepstralPitchTracker::getCopyright() const
95 3:9366c8a58778 Chris
{
96
    return "Freely redistributable (BSD license)";
97
}
98
99 31:2c175adf8736 Chris
CepstralPitchTracker::InputDomain
100
CepstralPitchTracker::getInputDomain() const
101 3:9366c8a58778 Chris
{
102
    return FrequencyDomain;
103
}
104
105
size_t
106 31:2c175adf8736 Chris
CepstralPitchTracker::getPreferredBlockSize() const
107 3:9366c8a58778 Chris
{
108
    return 1024;
109
}
110
111
size_t
112 31:2c175adf8736 Chris
CepstralPitchTracker::getPreferredStepSize() const
113 3:9366c8a58778 Chris
{
114
    return 256;
115
}
116
117
size_t
118 31:2c175adf8736 Chris
CepstralPitchTracker::getMinChannelCount() const
119 3:9366c8a58778 Chris
{
120
    return 1;
121
}
122
123
size_t
124 31:2c175adf8736 Chris
CepstralPitchTracker::getMaxChannelCount() const
125 3:9366c8a58778 Chris
{
126
    return 1;
127
}
128
129 31:2c175adf8736 Chris
CepstralPitchTracker::ParameterList
130
CepstralPitchTracker::getParameterDescriptors() const
131 3:9366c8a58778 Chris
{
132
    ParameterList list;
133
    return list;
134
}
135
136
float
137 31:2c175adf8736 Chris
CepstralPitchTracker::getParameter(string identifier) const
138 3:9366c8a58778 Chris
{
139
    return 0.f;
140
}
141
142
void
143 31:2c175adf8736 Chris
CepstralPitchTracker::setParameter(string identifier, float value)
144 3:9366c8a58778 Chris
{
145
}
146
147 31:2c175adf8736 Chris
CepstralPitchTracker::ProgramList
148
CepstralPitchTracker::getPrograms() const
149 3:9366c8a58778 Chris
{
150
    ProgramList list;
151
    return list;
152
}
153
154
string
155 31:2c175adf8736 Chris
CepstralPitchTracker::getCurrentProgram() const
156 3:9366c8a58778 Chris
{
157
    return ""; // no programs
158
}
159
160
void
161 31:2c175adf8736 Chris
CepstralPitchTracker::selectProgram(string name)
162 3:9366c8a58778 Chris
{
163
}
164
165 31:2c175adf8736 Chris
CepstralPitchTracker::OutputList
166
CepstralPitchTracker::getOutputDescriptors() const
167 3:9366c8a58778 Chris
{
168
    OutputList outputs;
169
170
    OutputDescriptor d;
171
172
    d.identifier = "f0";
173
    d.name = "Estimated f0";
174
    d.description = "Estimated fundamental frequency";
175
    d.unit = "Hz";
176
    d.hasFixedBinCount = true;
177
    d.binCount = 1;
178
    d.hasKnownExtents = true;
179
    d.minValue = m_fmin;
180
    d.maxValue = m_fmax;
181
    d.isQuantized = false;
182
    d.sampleType = OutputDescriptor::FixedSampleRate;
183
    d.sampleRate = (m_inputSampleRate / m_stepSize);
184
    d.hasDuration = false;
185
    outputs.push_back(d);
186
187 16:d717911aca3c Chris
    d.identifier = "notes";
188
    d.name = "Notes";
189
    d.description = "Derived fixed-pitch note frequencies";
190
    d.unit = "Hz";
191
    d.hasFixedBinCount = true;
192
    d.binCount = 1;
193
    d.hasKnownExtents = true;
194
    d.minValue = m_fmin;
195
    d.maxValue = m_fmax;
196
    d.isQuantized = false;
197
    d.sampleType = OutputDescriptor::FixedSampleRate;
198
    d.sampleRate = (m_inputSampleRate / m_stepSize);
199
    d.hasDuration = true;
200
    outputs.push_back(d);
201
202 3:9366c8a58778 Chris
    return outputs;
203
}
204
205
bool
206 31:2c175adf8736 Chris
CepstralPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
207 3:9366c8a58778 Chris
{
208
    if (channels < getMinChannelCount() ||
209
        channels > getMaxChannelCount()) return false;
210
211 31:2c175adf8736 Chris
//    std::cerr << "CepstralPitchTracker::initialise: channels = " << channels
212 3:9366c8a58778 Chris
//              << ", stepSize = " << stepSize << ", blockSize = " << blockSize
213
//              << std::endl;
214
215
    m_channels = channels;
216
    m_stepSize = stepSize;
217
    m_blockSize = blockSize;
218
219
    m_binFrom = int(m_inputSampleRate / m_fmax);
220
    m_binTo = int(m_inputSampleRate / m_fmin);
221
222
    if (m_binTo >= (int)m_blockSize / 2) {
223
        m_binTo = m_blockSize / 2 - 1;
224
    }
225
226
    m_bins = (m_binTo - m_binFrom) + 1;
227
228
    reset();
229
230
    return true;
231
}
232
233
void
234 31:2c175adf8736 Chris
CepstralPitchTracker::reset()
235 3:9366c8a58778 Chris
{
236
}
237
238
void
239 35:2f5b169e4a3b Chris
CepstralPitchTracker::addFeaturesFrom(NoteHypothesis h, FeatureSet &fs)
240 30:2554aab152a5 Chris
{
241 35:2f5b169e4a3b Chris
    NoteHypothesis::Estimates es = h.getAcceptedEstimates();
242 30:2554aab152a5 Chris
243 35:2f5b169e4a3b Chris
    for (int i = 0; i < (int)es.size(); ++i) {
244 30:2554aab152a5 Chris
        Feature f;
245
        f.hasTimestamp = true;
246
        f.timestamp = es[i].time;
247
        f.values.push_back(es[i].freq);
248
        fs[0].push_back(f);
249
    }
250
251
    Feature nf;
252
    nf.hasTimestamp = true;
253
    nf.hasDuration = true;
254 35:2f5b169e4a3b Chris
    NoteHypothesis::Note n = h.getAveragedNote();
255 30:2554aab152a5 Chris
    nf.timestamp = n.time;
256
    nf.duration = n.duration;
257
    nf.values.push_back(n.freq);
258
    fs[1].push_back(nf);
259
}
260
261 31:2c175adf8736 Chris
CepstralPitchTracker::FeatureSet
262
CepstralPitchTracker::process(const float *const *inputBuffers, RealTime timestamp)
263 3:9366c8a58778 Chris
{
264
    FeatureSet fs;
265
266
    int bs = m_blockSize;
267
    int hs = m_blockSize/2 + 1;
268
269
    double *rawcep = new double[bs];
270
    double *io = new double[bs];
271
    double *logmag = new double[bs];
272
273 4:c74846514b09 Chris
    // The "inverse symmetric" method. Seems to be the most reliable
274 3:9366c8a58778 Chris
275 25:9aee1a0e6223 Chris
    double magmean = 0.0;
276
277 3:9366c8a58778 Chris
    for (int i = 0; i < hs; ++i) {
278
279
        double power =
280
            inputBuffers[0][i*2  ] * inputBuffers[0][i*2  ] +
281
            inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1];
282
        double mag = sqrt(power);
283 25:9aee1a0e6223 Chris
284
        magmean += mag;
285
286 3:9366c8a58778 Chris
        double lm = log(mag + 0.00000001);
287
288 4:c74846514b09 Chris
        logmag[i] = lm;
289
        if (i > 0) logmag[bs - i] = lm;
290 3:9366c8a58778 Chris
    }
291
292 25:9aee1a0e6223 Chris
    magmean /= hs;
293
    double threshold = 0.1; // for magmean
294
295 26:13568f1ccff0 Chris
    Vamp::FFT::inverse(bs, logmag, 0, rawcep, io);
296 3:9366c8a58778 Chris
297
    delete[] logmag;
298
    delete[] io;
299
300
    int n = m_bins;
301
    double *data = new double[n];
302 47:f72a470fe4b5 Chris
    MeanFilter(m_vflen).filterSubsequence(rawcep, data, m_blockSize, n, m_binFrom);
303 3:9366c8a58778 Chris
    delete[] rawcep;
304
305
    double maxval = 0.0;
306 6:291c75f6e837 Chris
    int maxbin = -1;
307 3:9366c8a58778 Chris
308
    for (int i = 0; i < n; ++i) {
309
        if (data[i] > maxval) {
310
            maxval = data[i];
311
            maxbin = i;
312
        }
313
    }
314
315 15:bd7fb10646fc Chris
    if (maxbin < 0) {
316
        delete[] data;
317
        return fs;
318
    }
319
320
    double nextPeakVal = 0.0;
321
    for (int i = 1; i+1 < n; ++i) {
322
        if (data[i] > data[i-1] &&
323
            data[i] > data[i+1] &&
324
            i != maxbin &&
325
            data[i] > nextPeakVal) {
326
            nextPeakVal = data[i];
327
        }
328
    }
329 8:e9d86e129467 Chris
330 50:d84049e20c61 Chris
    PeakInterpolator pi;
331
    double cimax = pi.findPeakLocation(data, m_bins, maxbin);
332 18:131b1c40be1a Chris
    double peakfreq = m_inputSampleRate / (cimax + m_binFrom);
333 15:bd7fb10646fc Chris
334
    double confidence = 0.0;
335
    if (nextPeakVal != 0.0) {
336 27:e358f133e670 Chris
        confidence = (maxval - nextPeakVal) * 10.0;
337 25:9aee1a0e6223 Chris
        if (magmean < threshold) confidence = 0.0;
338 39:822cf7b8e070 Chris
//        std::cerr << "magmean = " << magmean << ", confidence = " << confidence << std::endl;
339 15:bd7fb10646fc Chris
    }
340
341 35:2f5b169e4a3b Chris
    NoteHypothesis::Estimate e;
342 8:e9d86e129467 Chris
    e.freq = peakfreq;
343
    e.time = timestamp;
344 15:bd7fb10646fc Chris
    e.confidence = confidence;
345 8:e9d86e129467 Chris
346 28:7927e7afbe07 Chris
    if (!m_good.accept(e)) {
347 13:6f73de098d35 Chris
348 11:c938c60de2de Chris
        int candidate = -1;
349 13:6f73de098d35 Chris
        bool accepted = false;
350
351 35:2f5b169e4a3b Chris
        for (int i = 0; i < (int)m_possible.size(); ++i) {
352 28:7927e7afbe07 Chris
            if (m_possible[i].accept(e)) {
353 35:2f5b169e4a3b Chris
                if (m_possible[i].getState() == NoteHypothesis::Satisfied) {
354 28:7927e7afbe07 Chris
                    accepted = true;
355 11:c938c60de2de Chris
                    candidate = i;
356
                }
357
                break;
358
            }
359
        }
360 12:1daaf43a5459 Chris
361 13:6f73de098d35 Chris
        if (!accepted) {
362 35:2f5b169e4a3b Chris
            NoteHypothesis h;
363 28:7927e7afbe07 Chris
            h.accept(e); //!!! must succeed as h is new, so perhaps there should be a ctor for this
364 13:6f73de098d35 Chris
            m_possible.push_back(h);
365
        }
366
367 35:2f5b169e4a3b Chris
        if (m_good.getState() == NoteHypothesis::Expired) {
368 30:2554aab152a5 Chris
            addFeaturesFrom(m_good, fs);
369 12:1daaf43a5459 Chris
        }
370
371 35:2f5b169e4a3b Chris
        if (m_good.getState() == NoteHypothesis::Expired ||
372
            m_good.getState() == NoteHypothesis::Rejected) {
373 11:c938c60de2de Chris
            if (candidate >= 0) {
374 28:7927e7afbe07 Chris
                m_good = m_possible[candidate];
375 11:c938c60de2de Chris
            } else {
376 35:2f5b169e4a3b Chris
                m_good = NoteHypothesis();
377 11:c938c60de2de Chris
            }
378
        }
379 8:e9d86e129467 Chris
380 14:98256077e2a2 Chris
        // reap rejected/expired hypotheses from possible list
381
        Hypotheses toReap = m_possible;
382
        m_possible.clear();
383 35:2f5b169e4a3b Chris
        for (int i = 0; i < (int)toReap.size(); ++i) {
384
            NoteHypothesis h = toReap[i];
385
            if (h.getState() != NoteHypothesis::Rejected &&
386
                h.getState() != NoteHypothesis::Expired) {
387 14:98256077e2a2 Chris
                m_possible.push_back(h);
388
            }
389
        }
390
    }
391
392 3:9366c8a58778 Chris
    delete[] data;
393
    return fs;
394
}
395
396 31:2c175adf8736 Chris
CepstralPitchTracker::FeatureSet
397
CepstralPitchTracker::getRemainingFeatures()
398 3:9366c8a58778 Chris
{
399
    FeatureSet fs;
400 35:2f5b169e4a3b Chris
    if (m_good.getState() == NoteHypothesis::Satisfied) {
401 30:2554aab152a5 Chris
        addFeaturesFrom(m_good, fs);
402 11:c938c60de2de Chris
    }
403 3:9366c8a58778 Chris
    return fs;
404
}