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 / CepstrumPitchTracker.cpp @ 28:7927e7afbe07

History | View | Annotate | Download (14.9 KB)

1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
/*
3
    Permission is hereby granted, free of charge, to any person
4
    obtaining a copy of this software and associated documentation
5
    files (the "Software"), to deal in the Software without
6
    restriction, including without limitation the rights to use, copy,
7
    modify, merge, publish, distribute, sublicense, and/or sell copies
8
    of the Software, and to permit persons to whom the Software is
9
    furnished to do so, subject to the following conditions:
10

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

14
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
18
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
*/
22

    
23
#include "CepstrumPitchTracker.h"
24

    
25
#include "vamp-sdk/FFT.h"
26

    
27
#include <vector>
28
#include <algorithm>
29

    
30
#include <cstdio>
31
#include <cmath>
32
#include <complex>
33

    
34
using std::string;
35
using std::vector;
36
using Vamp::RealTime;
37

    
38
CepstrumPitchTracker::Hypothesis::Hypothesis()
39
{
40
    m_state = New;
41
}
42

    
43
CepstrumPitchTracker::Hypothesis::~Hypothesis()
44
{
45
}
46

    
47
bool
48
CepstrumPitchTracker::Hypothesis::isWithinTolerance(Estimate s)
49
{
50
    if (m_pending.empty()) {
51
        return true;
52
    }
53

    
54
    // check we are within a relatively close tolerance of the last
55
    // candidate
56
    Estimate last = m_pending[m_pending.size()-1];
57
    double r = s.freq / last.freq;
58
    int cents = lrint(1200.0 * (log(r) / log(2.0)));
59
    if (cents < -60 || cents > 60) return false;
60

    
61
    // and within a slightly bigger tolerance of the current mean
62
    double meanFreq = getMeanFrequency();
63
    r = s.freq / meanFreq;
64
    cents = lrint(1200.0 * (log(r) / log(2.0)));
65
    if (cents < -80 || cents > 80) return false;
66
    
67
    return true;
68
}
69

    
70
bool
71
CepstrumPitchTracker::Hypothesis::isOutOfDateFor(Estimate s)
72
{
73
    if (m_pending.empty()) return false;
74

    
75
    return ((s.time - m_pending[m_pending.size()-1].time) > 
76
            RealTime::fromMilliseconds(40));
77
}
78

    
79
bool 
80
CepstrumPitchTracker::Hypothesis::isSatisfied()
81
{
82
    if (m_pending.empty()) return false;
83
    
84
    double meanConfidence = 0.0;
85
    for (int i = 0; i < m_pending.size(); ++i) {
86
        meanConfidence += m_pending[i].confidence;
87
    }
88
    meanConfidence /= m_pending.size();
89

    
90
    int lengthRequired = 10000;
91
    if (meanConfidence > 0.0) {
92
        lengthRequired = int(2.0 / meanConfidence + 0.5);
93
    }
94
    std::cerr << "meanConfidence = " << meanConfidence << ", lengthRequired = " << lengthRequired << ", length = " << m_pending.size() << std::endl;
95

    
96
    return (m_pending.size() > lengthRequired);
97
}
98

    
99
bool
100
CepstrumPitchTracker::Hypothesis::accept(Estimate s)
101
{
102
    bool accept = false;
103

    
104
    switch (m_state) {
105

    
106
    case New:
107
        m_state = Provisional;
108
        accept = true;
109
        break;
110

    
111
    case Provisional:
112
        if (isOutOfDateFor(s)) {
113
            m_state = Rejected;
114
        } else if (isWithinTolerance(s)) {
115
            accept = true;
116
        }
117
        break;
118
        
119
    case Satisfied:
120
        if (isOutOfDateFor(s)) {
121
            m_state = Expired;
122
        } else if (isWithinTolerance(s)) {
123
            accept = true;
124
        }
125
        break;
126

    
127
    case Rejected:
128
        break;
129

    
130
    case Expired:
131
        break;
132
    }
133

    
134
    if (accept) {
135
        m_pending.push_back(s);
136
        if (m_state == Provisional && isSatisfied()) {
137
            m_state = Satisfied;
138
        }
139
    }
140

    
141
    return accept;
142
}        
143

    
144
CepstrumPitchTracker::Hypothesis::State
145
CepstrumPitchTracker::Hypothesis::getState()
146
{
147
    return m_state;
148
}
149

    
150
int
151
CepstrumPitchTracker::Hypothesis::getPendingLength()
152
{
153
    return m_pending.size();
154
}
155

    
156
CepstrumPitchTracker::Hypothesis::Estimates
157
CepstrumPitchTracker::Hypothesis::getAcceptedEstimates()
158
{
159
    if (m_state == Satisfied || m_state == Expired) {
160
        return m_pending;
161
    } else {
162
        return Estimates();
163
    }
164
}
165

    
166
double
167
CepstrumPitchTracker::Hypothesis::getMeanFrequency()
168
{
169
    double acc = 0.0;
170
    for (int i = 0; i < m_pending.size(); ++i) {
171
        acc += m_pending[i].freq;
172
    }
173
    acc /= m_pending.size();
174
    return acc;
175
}
176

    
177
CepstrumPitchTracker::Hypothesis::Note
178
CepstrumPitchTracker::Hypothesis::getAveragedNote()
179
{
180
    Note n;
181

    
182
    if (!(m_state == Satisfied || m_state == Expired)) {
183
        n.freq = 0.0;
184
        n.time = RealTime::zeroTime;
185
        n.duration = RealTime::zeroTime;
186
        return n;
187
    }
188

    
189
    n.time = m_pending.begin()->time;
190

    
191
    Estimates::iterator i = m_pending.end();
192
    --i;
193
    n.duration = i->time - n.time;
194

    
195
    // just mean frequency for now, but this isn't at all right perceptually
196
    n.freq = getMeanFrequency();
197
    
198
    return n;
199
}
200

    
201
void
202
CepstrumPitchTracker::Hypothesis::addFeatures(FeatureSet &fs)
203
{
204
    for (int i = 0; i < m_pending.size(); ++i) {
205
        Feature f;
206
        f.hasTimestamp = true;
207
        f.timestamp = m_pending[i].time;
208
        f.values.push_back(m_pending[i].freq);
209
        fs[0].push_back(f);
210
    }
211

    
212
    Feature nf;
213
    nf.hasTimestamp = true;
214
    nf.hasDuration = true;
215
    Note n = getAveragedNote();
216
    nf.timestamp = n.time;
217
    nf.duration = n.duration;
218
    nf.values.push_back(n.freq);
219
    fs[1].push_back(nf);
220
}
221

    
222
CepstrumPitchTracker::CepstrumPitchTracker(float inputSampleRate) :
223
    Plugin(inputSampleRate),
224
    m_channels(0),
225
    m_stepSize(256),
226
    m_blockSize(1024),
227
    m_fmin(50),
228
    m_fmax(900),
229
    m_vflen(1),
230
    m_binFrom(0),
231
    m_binTo(0),
232
    m_bins(0)
233
{
234
}
235

    
236
CepstrumPitchTracker::~CepstrumPitchTracker()
237
{
238
}
239

    
240
string
241
CepstrumPitchTracker::getIdentifier() const
242
{
243
    return "cepstrum-pitch";
244
}
245

    
246
string
247
CepstrumPitchTracker::getName() const
248
{
249
    return "Cepstrum Pitch Tracker";
250
}
251

    
252
string
253
CepstrumPitchTracker::getDescription() const
254
{
255
    return "Estimate f0 of monophonic material using a cepstrum method.";
256
}
257

    
258
string
259
CepstrumPitchTracker::getMaker() const
260
{
261
    return "Chris Cannam";
262
}
263

    
264
int
265
CepstrumPitchTracker::getPluginVersion() const
266
{
267
    // Increment this each time you release a version that behaves
268
    // differently from the previous one
269
    return 1;
270
}
271

    
272
string
273
CepstrumPitchTracker::getCopyright() const
274
{
275
    return "Freely redistributable (BSD license)";
276
}
277

    
278
CepstrumPitchTracker::InputDomain
279
CepstrumPitchTracker::getInputDomain() const
280
{
281
    return FrequencyDomain;
282
}
283

    
284
size_t
285
CepstrumPitchTracker::getPreferredBlockSize() const
286
{
287
    return 1024;
288
}
289

    
290
size_t 
291
CepstrumPitchTracker::getPreferredStepSize() const
292
{
293
    return 256;
294
}
295

    
296
size_t
297
CepstrumPitchTracker::getMinChannelCount() const
298
{
299
    return 1;
300
}
301

    
302
size_t
303
CepstrumPitchTracker::getMaxChannelCount() const
304
{
305
    return 1;
306
}
307

    
308
CepstrumPitchTracker::ParameterList
309
CepstrumPitchTracker::getParameterDescriptors() const
310
{
311
    ParameterList list;
312
    return list;
313
}
314

    
315
float
316
CepstrumPitchTracker::getParameter(string identifier) const
317
{
318
    return 0.f;
319
}
320

    
321
void
322
CepstrumPitchTracker::setParameter(string identifier, float value) 
323
{
324
}
325

    
326
CepstrumPitchTracker::ProgramList
327
CepstrumPitchTracker::getPrograms() const
328
{
329
    ProgramList list;
330
    return list;
331
}
332

    
333
string
334
CepstrumPitchTracker::getCurrentProgram() const
335
{
336
    return ""; // no programs
337
}
338

    
339
void
340
CepstrumPitchTracker::selectProgram(string name)
341
{
342
}
343

    
344
CepstrumPitchTracker::OutputList
345
CepstrumPitchTracker::getOutputDescriptors() const
346
{
347
    OutputList outputs;
348

    
349
    int n = 0;
350

    
351
    OutputDescriptor d;
352

    
353
    d.identifier = "f0";
354
    d.name = "Estimated f0";
355
    d.description = "Estimated fundamental frequency";
356
    d.unit = "Hz";
357
    d.hasFixedBinCount = true;
358
    d.binCount = 1;
359
    d.hasKnownExtents = true;
360
    d.minValue = m_fmin;
361
    d.maxValue = m_fmax;
362
    d.isQuantized = false;
363
    d.sampleType = OutputDescriptor::FixedSampleRate;
364
    d.sampleRate = (m_inputSampleRate / m_stepSize);
365
    d.hasDuration = false;
366
    outputs.push_back(d);
367

    
368
    d.identifier = "notes";
369
    d.name = "Notes";
370
    d.description = "Derived fixed-pitch note frequencies";
371
    d.unit = "Hz";
372
    d.hasFixedBinCount = true;
373
    d.binCount = 1;
374
    d.hasKnownExtents = true;
375
    d.minValue = m_fmin;
376
    d.maxValue = m_fmax;
377
    d.isQuantized = false;
378
    d.sampleType = OutputDescriptor::FixedSampleRate;
379
    d.sampleRate = (m_inputSampleRate / m_stepSize);
380
    d.hasDuration = true;
381
    outputs.push_back(d);
382

    
383
    return outputs;
384
}
385

    
386
bool
387
CepstrumPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
388
{
389
    if (channels < getMinChannelCount() ||
390
        channels > getMaxChannelCount()) return false;
391

    
392
//    std::cerr << "CepstrumPitchTracker::initialise: channels = " << channels
393
//              << ", stepSize = " << stepSize << ", blockSize = " << blockSize
394
//              << std::endl;
395

    
396
    m_channels = channels;
397
    m_stepSize = stepSize;
398
    m_blockSize = blockSize;
399

    
400
    m_binFrom = int(m_inputSampleRate / m_fmax);
401
    m_binTo = int(m_inputSampleRate / m_fmin); 
402

    
403
    if (m_binTo >= (int)m_blockSize / 2) {
404
        m_binTo = m_blockSize / 2 - 1;
405
    }
406

    
407
    m_bins = (m_binTo - m_binFrom) + 1;
408

    
409
    reset();
410

    
411
    return true;
412
}
413

    
414
void
415
CepstrumPitchTracker::reset()
416
{
417
}
418

    
419
void
420
CepstrumPitchTracker::filter(const double *cep, double *data)
421
{
422
    for (int i = 0; i < m_bins; ++i) {
423
        double v = 0;
424
        int n = 0;
425
        // average according to the vertical filter length
426
        for (int j = -m_vflen/2; j <= m_vflen/2; ++j) {
427
            int ix = i + m_binFrom + j;
428
            if (ix >= 0 && ix < m_blockSize) {
429
                v += cep[ix];
430
                ++n;
431
            }
432
        }
433
        data[i] = v / n;
434
    }
435
}
436

    
437
double
438
CepstrumPitchTracker::cubicInterpolate(const double y[4], double x)
439
{
440
    double a0 = y[3] - y[2] - y[0] + y[1];
441
    double a1 = y[0] - y[1] - a0;
442
    double a2 = y[2] - y[0];
443
    double a3 = y[1];
444
    return
445
        a0 * x * x * x +
446
        a1 * x * x +
447
        a2 * x +
448
        a3;
449
}
450

    
451
double
452
CepstrumPitchTracker::findInterpolatedPeak(const double *in, int maxbin)
453
{
454
    if (maxbin < 2 || maxbin > m_bins - 3) {
455
        return maxbin;
456
    }
457

    
458
    double maxval = 0.0;
459
    double maxidx = maxbin;
460

    
461
    const int divisions = 10;
462
    double y[4];
463

    
464
    y[0] = in[maxbin-1];
465
    y[1] = in[maxbin];
466
    y[2] = in[maxbin+1];
467
    y[3] = in[maxbin+2];
468
    for (int i = 0; i < divisions; ++i) {
469
        double probe = double(i) / double(divisions);
470
        double value = cubicInterpolate(y, probe);
471
        if (value > maxval) {
472
            maxval = value; 
473
            maxidx = maxbin + probe;
474
        }
475
    }
476

    
477
    y[3] = y[2];
478
    y[2] = y[1];
479
    y[1] = y[0];
480
    y[0] = in[maxbin-2];
481
    for (int i = 0; i < divisions; ++i) {
482
        double probe = double(i) / double(divisions);
483
        double value = cubicInterpolate(y, probe);
484
        if (value > maxval) {
485
            maxval = value; 
486
            maxidx = maxbin - 1 + probe;
487
        }
488
    }
489

    
490
/*
491
    std::cerr << "centre = " << maxbin << ": ["
492
              << in[maxbin-2] << ","
493
              << in[maxbin-1] << ","
494
              << in[maxbin] << ","
495
              << in[maxbin+1] << ","
496
              << in[maxbin+2] << "] -> " << maxidx << std::endl;
497
*/
498

    
499
    return maxidx;
500
}
501

    
502
CepstrumPitchTracker::FeatureSet
503
CepstrumPitchTracker::process(const float *const *inputBuffers, RealTime timestamp)
504
{
505
    FeatureSet fs;
506

    
507
    int bs = m_blockSize;
508
    int hs = m_blockSize/2 + 1;
509

    
510
    double *rawcep = new double[bs];
511
    double *io = new double[bs];
512
    double *logmag = new double[bs];
513

    
514
    // The "inverse symmetric" method. Seems to be the most reliable
515
        
516
    double magmean = 0.0;
517

    
518
    for (int i = 0; i < hs; ++i) {
519

    
520
        double power =
521
            inputBuffers[0][i*2  ] * inputBuffers[0][i*2  ] +
522
            inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1];
523
        double mag = sqrt(power);
524

    
525
        magmean += mag;
526

    
527
        double lm = log(mag + 0.00000001);
528
        
529
        logmag[i] = lm;
530
        if (i > 0) logmag[bs - i] = lm;
531
    }
532

    
533
    magmean /= hs;
534
    double threshold = 0.1; // for magmean
535
    
536
    Vamp::FFT::inverse(bs, logmag, 0, rawcep, io);
537
    
538
    delete[] logmag;
539
    delete[] io;
540

    
541
    int n = m_bins;
542
    double *data = new double[n];
543
    filter(rawcep, data);
544
    delete[] rawcep;
545

    
546
    double maxval = 0.0;
547
    int maxbin = -1;
548

    
549
    for (int i = 0; i < n; ++i) {
550
        if (data[i] > maxval) {
551
            maxval = data[i];
552
            maxbin = i;
553
        }
554
    }
555

    
556
    if (maxbin < 0) {
557
        delete[] data;
558
        return fs;
559
    }
560

    
561
    double nextPeakVal = 0.0;
562
    for (int i = 1; i+1 < n; ++i) {
563
        if (data[i] > data[i-1] &&
564
            data[i] > data[i+1] &&
565
            i != maxbin &&
566
            data[i] > nextPeakVal) {
567
            nextPeakVal = data[i];
568
        }
569
    }
570

    
571
    double cimax = findInterpolatedPeak(data, maxbin);
572
    double peakfreq = m_inputSampleRate / (cimax + m_binFrom);
573

    
574
    double confidence = 0.0;
575
    if (nextPeakVal != 0.0) {
576
        confidence = (maxval - nextPeakVal) * 10.0;
577
        if (magmean < threshold) confidence = 0.0;
578
        std::cerr << "magmean = " << magmean << ", confidence = " << confidence << std::endl;
579
    }
580

    
581
    Hypothesis::Estimate e;
582
    e.freq = peakfreq;
583
    e.time = timestamp;
584
    e.confidence = confidence;
585

    
586
//    m_good.advanceTime();
587
    for (int i = 0; i < m_possible.size(); ++i) {
588
//        m_possible[i].advanceTime();
589
    }
590

    
591
    if (!m_good.accept(e)) {
592

    
593
        int candidate = -1;
594
        bool accepted = false;
595

    
596
        for (int i = 0; i < m_possible.size(); ++i) {
597
            if (m_possible[i].accept(e)) {
598
                if (m_possible[i].getState() == Hypothesis::Satisfied) {
599
                    accepted = true;
600
                    candidate = i;
601
                }
602
                break;
603
            }
604
        }
605

    
606
        if (!accepted) {
607
            Hypothesis h;
608
            h.accept(e); //!!! must succeed as h is new, so perhaps there should be a ctor for this
609
            m_possible.push_back(h);
610
        }
611

    
612
        if (m_good.getState() == Hypothesis::Expired) {
613
            m_good.addFeatures(fs);
614
        }
615
        
616
        if (m_good.getState() == Hypothesis::Expired ||
617
            m_good.getState() == Hypothesis::Rejected) {
618
            if (candidate >= 0) {
619
                m_good = m_possible[candidate];
620
            } else {
621
                m_good = Hypothesis();
622
            }
623
        }
624

    
625
        // reap rejected/expired hypotheses from possible list
626
        Hypotheses toReap = m_possible;
627
        m_possible.clear();
628
        for (int i = 0; i < toReap.size(); ++i) {
629
            Hypothesis h = toReap[i];
630
            if (h.getState() != Hypothesis::Rejected && 
631
                h.getState() != Hypothesis::Expired) {
632
                m_possible.push_back(h);
633
            }
634
        }
635
    }  
636

    
637
    std::cerr << "accepted length = " << m_good.getPendingLength()
638
              << ", state = " << m_good.getState()
639
              << ", hypothesis count = " << m_possible.size() << std::endl;
640

    
641
    delete[] data;
642
    return fs;
643
}
644

    
645
CepstrumPitchTracker::FeatureSet
646
CepstrumPitchTracker::getRemainingFeatures()
647
{
648
    FeatureSet fs;
649
    if (m_good.getState() == Hypothesis::Satisfied) {
650
        m_good.addFeatures(fs);
651
    }
652
    return fs;
653
}