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 @ 51:0997774f5fdc

History | View | Annotate | Download (8.82 KB)

1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
/*
3
    This file is Copyright (c) 2012 Chris Cannam
4
  
5
    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
#include "CepstralPitchTracker.h"
26
#include "Cepstrum.h"
27
#include "MeanFilter.h"
28
#include "PeakInterpolator.h"
29

    
30
#include "vamp-sdk/FFT.h"
31

    
32
#include <vector>
33
#include <algorithm>
34

    
35
#include <cstdio>
36
#include <cmath>
37
#include <complex>
38

    
39
using std::string;
40
using std::vector;
41
using Vamp::RealTime;
42

    
43

    
44
CepstralPitchTracker::CepstralPitchTracker(float inputSampleRate) :
45
    Plugin(inputSampleRate),
46
    m_channels(0),
47
    m_stepSize(256),
48
    m_blockSize(1024),
49
    m_fmin(50),
50
    m_fmax(900),
51
    m_vflen(1),
52
    m_binFrom(0),
53
    m_binTo(0),
54
    m_bins(0)
55
{
56
}
57

    
58
CepstralPitchTracker::~CepstralPitchTracker()
59
{
60
}
61

    
62
string
63
CepstralPitchTracker::getIdentifier() const
64
{
65
    return "cepstral-pitchtracker";
66
}
67

    
68
string
69
CepstralPitchTracker::getName() const
70
{
71
    return "Cepstral Pitch Tracker";
72
}
73

    
74
string
75
CepstralPitchTracker::getDescription() const
76
{
77
    return "Estimate f0 of monophonic material using a cepstrum method.";
78
}
79

    
80
string
81
CepstralPitchTracker::getMaker() const
82
{
83
    return "Chris Cannam";
84
}
85

    
86
int
87
CepstralPitchTracker::getPluginVersion() const
88
{
89
    // Increment this each time you release a version that behaves
90
    // differently from the previous one
91
    return 1;
92
}
93

    
94
string
95
CepstralPitchTracker::getCopyright() const
96
{
97
    return "Freely redistributable (BSD license)";
98
}
99

    
100
CepstralPitchTracker::InputDomain
101
CepstralPitchTracker::getInputDomain() const
102
{
103
    return FrequencyDomain;
104
}
105

    
106
size_t
107
CepstralPitchTracker::getPreferredBlockSize() const
108
{
109
    return 1024;
110
}
111

    
112
size_t 
113
CepstralPitchTracker::getPreferredStepSize() const
114
{
115
    return 256;
116
}
117

    
118
size_t
119
CepstralPitchTracker::getMinChannelCount() const
120
{
121
    return 1;
122
}
123

    
124
size_t
125
CepstralPitchTracker::getMaxChannelCount() const
126
{
127
    return 1;
128
}
129

    
130
CepstralPitchTracker::ParameterList
131
CepstralPitchTracker::getParameterDescriptors() const
132
{
133
    ParameterList list;
134
    return list;
135
}
136

    
137
float
138
CepstralPitchTracker::getParameter(string identifier) const
139
{
140
    return 0.f;
141
}
142

    
143
void
144
CepstralPitchTracker::setParameter(string identifier, float value) 
145
{
146
}
147

    
148
CepstralPitchTracker::ProgramList
149
CepstralPitchTracker::getPrograms() const
150
{
151
    ProgramList list;
152
    return list;
153
}
154

    
155
string
156
CepstralPitchTracker::getCurrentProgram() const
157
{
158
    return ""; // no programs
159
}
160

    
161
void
162
CepstralPitchTracker::selectProgram(string name)
163
{
164
}
165

    
166
CepstralPitchTracker::OutputList
167
CepstralPitchTracker::getOutputDescriptors() const
168
{
169
    OutputList outputs;
170

    
171
    OutputDescriptor d;
172

    
173
    d.identifier = "f0";
174
    d.name = "Estimated f0";
175
    d.description = "Estimated fundamental frequency";
176
    d.unit = "Hz";
177
    d.hasFixedBinCount = true;
178
    d.binCount = 1;
179
    d.hasKnownExtents = true;
180
    d.minValue = m_fmin;
181
    d.maxValue = m_fmax;
182
    d.isQuantized = false;
183
    d.sampleType = OutputDescriptor::FixedSampleRate;
184
    d.sampleRate = (m_inputSampleRate / m_stepSize);
185
    d.hasDuration = false;
186
    outputs.push_back(d);
187

    
188
    d.identifier = "notes";
189
    d.name = "Notes";
190
    d.description = "Derived fixed-pitch note frequencies";
191
    d.unit = "Hz";
192
    d.hasFixedBinCount = true;
193
    d.binCount = 1;
194
    d.hasKnownExtents = true;
195
    d.minValue = m_fmin;
196
    d.maxValue = m_fmax;
197
    d.isQuantized = false;
198
    d.sampleType = OutputDescriptor::FixedSampleRate;
199
    d.sampleRate = (m_inputSampleRate / m_stepSize);
200
    d.hasDuration = true;
201
    outputs.push_back(d);
202

    
203
    return outputs;
204
}
205

    
206
bool
207
CepstralPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
208
{
209
    if (channels < getMinChannelCount() ||
210
        channels > getMaxChannelCount()) return false;
211

    
212
//    std::cerr << "CepstralPitchTracker::initialise: channels = " << channels
213
//              << ", stepSize = " << stepSize << ", blockSize = " << blockSize
214
//              << std::endl;
215

    
216
    m_channels = channels;
217
    m_stepSize = stepSize;
218
    m_blockSize = blockSize;
219

    
220
    m_binFrom = int(m_inputSampleRate / m_fmax);
221
    m_binTo = int(m_inputSampleRate / m_fmin); 
222

    
223
    if (m_binTo >= (int)m_blockSize / 2) {
224
        m_binTo = m_blockSize / 2 - 1;
225
    }
226

    
227
    m_bins = (m_binTo - m_binFrom) + 1;
228

    
229
    reset();
230

    
231
    return true;
232
}
233

    
234
void
235
CepstralPitchTracker::reset()
236
{
237
}
238

    
239
void
240
CepstralPitchTracker::addFeaturesFrom(NoteHypothesis h, FeatureSet &fs)
241
{
242
    NoteHypothesis::Estimates es = h.getAcceptedEstimates();
243

    
244
    for (int i = 0; i < (int)es.size(); ++i) {
245
        Feature f;
246
        f.hasTimestamp = true;
247
        f.timestamp = es[i].time;
248
        f.values.push_back(es[i].freq);
249
        fs[0].push_back(f);
250
    }
251

    
252
    Feature nf;
253
    nf.hasTimestamp = true;
254
    nf.hasDuration = true;
255
    NoteHypothesis::Note n = h.getAveragedNote();
256
    nf.timestamp = n.time;
257
    nf.duration = n.duration;
258
    nf.values.push_back(n.freq);
259
    fs[1].push_back(nf);
260
}
261

    
262
CepstralPitchTracker::FeatureSet
263
CepstralPitchTracker::process(const float *const *inputBuffers, RealTime timestamp)
264
{
265
    FeatureSet fs;
266

    
267
    double *rawcep = new double[m_blockSize];
268
    double magmean = Cepstrum(m_blockSize).process(inputBuffers[0], rawcep);
269

    
270
    int n = m_bins;
271
    double *data = new double[n];
272
    MeanFilter(m_vflen).filterSubsequence
273
        (rawcep, data, m_blockSize, n, m_binFrom);
274

    
275
    delete[] rawcep;
276

    
277
    double maxval = 0.0;
278
    int maxbin = -1;
279

    
280
    for (int i = 0; i < n; ++i) {
281
        if (data[i] > maxval) {
282
            maxval = data[i];
283
            maxbin = i;
284
        }
285
    }
286

    
287
    if (maxbin < 0) {
288
        delete[] data;
289
        return fs;
290
    }
291

    
292
    double nextPeakVal = 0.0;
293
    for (int i = 1; i+1 < n; ++i) {
294
        if (data[i] > data[i-1] &&
295
            data[i] > data[i+1] &&
296
            i != maxbin &&
297
            data[i] > nextPeakVal) {
298
            nextPeakVal = data[i];
299
        }
300
    }
301

    
302
    PeakInterpolator pi;
303
    double cimax = pi.findPeakLocation(data, m_bins, maxbin);
304
    double peakfreq = m_inputSampleRate / (cimax + m_binFrom);
305

    
306
    double confidence = 0.0;
307
    double threshold = 0.1; // for magmean
308

    
309
    if (nextPeakVal != 0.0) {
310
        confidence = (maxval - nextPeakVal) * 10.0;
311
        if (magmean < threshold) confidence = 0.0;
312
//        std::cerr << "magmean = " << magmean << ", confidence = " << confidence << std::endl;
313
    }
314

    
315
    NoteHypothesis::Estimate e;
316
    e.freq = peakfreq;
317
    e.time = timestamp;
318
    e.confidence = confidence;
319

    
320
    if (!m_good.accept(e)) {
321

    
322
        int candidate = -1;
323
        bool accepted = false;
324

    
325
        for (int i = 0; i < (int)m_possible.size(); ++i) {
326
            if (m_possible[i].accept(e)) {
327
                if (m_possible[i].getState() == NoteHypothesis::Satisfied) {
328
                    accepted = true;
329
                    candidate = i;
330
                }
331
                break;
332
            }
333
        }
334

    
335
        if (!accepted) {
336
            NoteHypothesis h;
337
            h.accept(e); //!!! must succeed as h is new, so perhaps there should be a ctor for this
338
            m_possible.push_back(h);
339
        }
340

    
341
        if (m_good.getState() == NoteHypothesis::Expired) {
342
            addFeaturesFrom(m_good, fs);
343
        }
344
        
345
        if (m_good.getState() == NoteHypothesis::Expired ||
346
            m_good.getState() == NoteHypothesis::Rejected) {
347
            if (candidate >= 0) {
348
                m_good = m_possible[candidate];
349
            } else {
350
                m_good = NoteHypothesis();
351
            }
352
        }
353

    
354
        // reap rejected/expired hypotheses from possible list
355
        Hypotheses toReap = m_possible;
356
        m_possible.clear();
357
        for (int i = 0; i < (int)toReap.size(); ++i) {
358
            NoteHypothesis h = toReap[i];
359
            if (h.getState() != NoteHypothesis::Rejected && 
360
                h.getState() != NoteHypothesis::Expired) {
361
                m_possible.push_back(h);
362
            }
363
        }
364
    }  
365

    
366
    delete[] data;
367
    return fs;
368
}
369

    
370
CepstralPitchTracker::FeatureSet
371
CepstralPitchTracker::getRemainingFeatures()
372
{
373
    FeatureSet fs;
374
    if (m_good.getState() == NoteHypothesis::Satisfied) {
375
        addFeaturesFrom(m_good, fs);
376
    }
377
    return fs;
378
}