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

1 50:d84049e20c61 Chris
syntax: glob
2
*.o
3
*.so
4
*.bak
5
test/test-*
6 65:9e414ae1f2fb Chris
f31b2da9258dfc05000ab2741d2ce63aa45637ba v1.0
7 53:19c8c6ca4406 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
8
/*
9
    This file is Copyright (c) 2012 Chris Cannam
10
11
    Permission is hereby granted, free of charge, to any person
12
    obtaining a copy of this software and associated documentation
13
    files (the "Software"), to deal in the Software without
14
    restriction, including without limitation the rights to use, copy,
15
    modify, merge, publish, distribute, sublicense, and/or sell copies
16
    of the Software, and to permit persons to whom the Software is
17
    furnished to do so, subject to the following conditions:
18
19
    The above copyright notice and this permission notice shall be
20
    included in all copies or substantial portions of the Software.
21
22
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
26
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
*/
30
31
#include "AgentFeeder.h"
32
33
void AgentFeeder::feed(NoteHypothesis::Estimate e)
34
{
35 54:751b43d119cf Chris
    if (m_haveCurrent) {
36
        if (m_current.accept(e)) {
37
            return;
38
        }
39
        if (m_current.getState() == NoteHypothesis::Expired) {
40
            m_accepted.push_back(m_current);
41
            m_haveCurrent = false;
42
        }
43
    }
44 53:19c8c6ca4406 Chris
45 54:751b43d119cf Chris
    bool swallowed = false;
46 53:19c8c6ca4406 Chris
47 54:751b43d119cf Chris
    Hypotheses newCandidates;
48 53:19c8c6ca4406 Chris
49 54:751b43d119cf Chris
    for (Hypotheses::iterator i = m_candidates.begin();
50
         i != m_candidates.end(); ++i) {
51
52
        NoteHypothesis h = *i;
53
54
        if (swallowed) {
55
56
            // don't offer: each observation can only belong to one
57
            // satisfied hypothesis
58
            newCandidates.push_back(h);
59
60
        } else {
61
62
            if (h.accept(e)) {
63 53:19c8c6ca4406 Chris
64 54:751b43d119cf Chris
                if (h.getState() == NoteHypothesis::Satisfied) {
65
66
                    swallowed = true;
67
68
                    if (!m_haveCurrent ||
69
                        m_current.getState() == NoteHypothesis::Expired ||
70
                        m_current.getState() == NoteHypothesis::Rejected) {
71
                        m_current = h;
72
                        m_haveCurrent = true;
73
                    } else {
74
                        newCandidates.push_back(h);
75
                    }
76
77
                } else {
78
                    newCandidates.push_back(h);
79
                }
80
            }
81
        }
82
    }
83
84
    if (!swallowed) {
85
        NoteHypothesis h;
86
        if (h.accept(e)) {
87
            newCandidates.push_back(h);
88
        }
89
    }
90
91
    m_candidates = reap(newCandidates);
92 53:19c8c6ca4406 Chris
}
93
94 54:751b43d119cf Chris
AgentFeeder::Hypotheses
95
AgentFeeder::reap(Hypotheses candidates)
96
{
97
    // reap rejected/expired hypotheses from list of candidates
98
99
    Hypotheses survived;
100
    for (Hypotheses::const_iterator i = candidates.begin();
101
         i != candidates.end(); ++i) {
102
        NoteHypothesis h = *i;
103
        if (h.getState() != NoteHypothesis::Rejected &&
104
            h.getState() != NoteHypothesis::Expired) {
105
            survived.push_back(h);
106
        }
107
    }
108
109
    return survived;
110
}
111
112 53:19c8c6ca4406 Chris
void
113
AgentFeeder::finish()
114
{
115
    if (m_current.getState() == NoteHypothesis::Satisfied) {
116
	m_accepted.push_back(m_current);
117
    }
118
}
119
120 52:34f42a384a7f Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
121
/*
122
    This file is Copyright (c) 2012 Chris Cannam
123
124
    Permission is hereby granted, free of charge, to any person
125
    obtaining a copy of this software and associated documentation
126
    files (the "Software"), to deal in the Software without
127
    restriction, including without limitation the rights to use, copy,
128
    modify, merge, publish, distribute, sublicense, and/or sell copies
129
    of the Software, and to permit persons to whom the Software is
130
    furnished to do so, subject to the following conditions:
131
132
    The above copyright notice and this permission notice shall be
133
    included in all copies or substantial portions of the Software.
134
135
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
136
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
137
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
138
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
139
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
140
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
141
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
142
*/
143
144
#ifndef _AGENT_FEEDER_H_
145
#define _AGENT_FEEDER_H_
146
147
#include "NoteHypothesis.h"
148
149
#include <vector>
150
151
/**
152
 * Take a series of estimates (one at a time) and feed them to a set
153
 * of note hypotheses, creating a new candidate hypothesis for each
154
 * observation and also testing the observation against the existing
155
 * set of hypotheses.
156
 *
157
 * One satisfied hypothesis is considered to be "accepted" at any
158
 * moment (that is, the earliest contemporary hypothesis to have
159
 * become satisfied). The series of accepted and completed hypotheses
160
 * from construction to the present time can be queried through
161
 * getAcceptedHypotheses().
162
 *
163
 * Call feed() to provide a new observation. Call finish() when all
164
 * observations have been provided. The set of hypotheses returned by
165
 * getAcceptedHypotheses() will not be complete unless finish() has
166
 * been called.
167
 */
168
class AgentFeeder
169
{
170
public:
171 54:751b43d119cf Chris
    AgentFeeder() : m_haveCurrent(false) { }
172 52:34f42a384a7f Chris
173
    void feed(NoteHypothesis::Estimate);
174
    void finish();
175
176
    typedef std::vector<NoteHypothesis> Hypotheses;
177
178 57:82b3cdf6ca6b Chris
    const Hypotheses &getAcceptedHypotheses() const {
179 52:34f42a384a7f Chris
        return m_accepted;
180
    }
181
182 54:751b43d119cf Chris
    Hypotheses reap(Hypotheses);
183
184 52:34f42a384a7f Chris
private:
185
    Hypotheses m_candidates;
186
    NoteHypothesis m_current;
187 54:751b43d119cf Chris
    bool m_haveCurrent;
188 52:34f42a384a7f Chris
    Hypotheses m_accepted;
189
};
190
191
192
#endif
193
194 75:84d1a0647ce5 Chris
195
Copyright (c) 2012 Chris Cannam
196
197
Permission is hereby granted, free of charge, to any person
198
obtaining a copy of this software and associated documentation
199
files (the "Software"), to deal in the Software without
200
restriction, including without limitation the rights to use, copy,
201
modify, merge, publish, distribute, sublicense, and/or sell copies
202
of the Software, and to permit persons to whom the Software is
203
furnished to do so, subject to the following conditions:
204
205
The above copyright notice and this permission notice shall be
206
included in all copies or substantial portions of the Software.
207
208
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
209
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
210
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
211
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
212
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
213
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
214
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
215
216 3:9366c8a58778 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
217
/*
218 31:2c175adf8736 Chris
    This file is Copyright (c) 2012 Chris Cannam
219
220 3:9366c8a58778 Chris
    Permission is hereby granted, free of charge, to any person
221
    obtaining a copy of this software and associated documentation
222
    files (the "Software"), to deal in the Software without
223
    restriction, including without limitation the rights to use, copy,
224
    modify, merge, publish, distribute, sublicense, and/or sell copies
225
    of the Software, and to permit persons to whom the Software is
226
    furnished to do so, subject to the following conditions:
227
228
    The above copyright notice and this permission notice shall be
229
    included in all copies or substantial portions of the Software.
230
231
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
232
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
233
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
234
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
235
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
236
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
237
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
238
*/
239
240 31:2c175adf8736 Chris
#include "CepstralPitchTracker.h"
241 51:0997774f5fdc Chris
#include "Cepstrum.h"
242 47:f72a470fe4b5 Chris
#include "MeanFilter.h"
243 50:d84049e20c61 Chris
#include "PeakInterpolator.h"
244 56:d8eeba570d70 Chris
#include "AgentFeeder.h"
245 3:9366c8a58778 Chris
246 26:13568f1ccff0 Chris
#include "vamp-sdk/FFT.h"
247
248 3:9366c8a58778 Chris
#include <vector>
249
#include <algorithm>
250
251
#include <cstdio>
252
#include <cmath>
253
#include <complex>
254
255
using std::string;
256 7:32defdb2f9d9 Chris
using std::vector;
257 16:d717911aca3c Chris
using Vamp::RealTime;
258 7:32defdb2f9d9 Chris
259 16:d717911aca3c Chris
260 31:2c175adf8736 Chris
CepstralPitchTracker::CepstralPitchTracker(float inputSampleRate) :
261 3:9366c8a58778 Chris
    Plugin(inputSampleRate),
262
    m_channels(0),
263
    m_stepSize(256),
264
    m_blockSize(1024),
265
    m_fmin(50),
266 25:9aee1a0e6223 Chris
    m_fmax(900),
267 18:131b1c40be1a Chris
    m_vflen(1),
268 3:9366c8a58778 Chris
    m_binFrom(0),
269
    m_binTo(0),
270 56:d8eeba570d70 Chris
    m_bins(0),
271 57:82b3cdf6ca6b Chris
    m_nAccepted(0),
272 56:d8eeba570d70 Chris
    m_feeder(0)
273 3:9366c8a58778 Chris
{
274
}
275
276 31:2c175adf8736 Chris
CepstralPitchTracker::~CepstralPitchTracker()
277 3:9366c8a58778 Chris
{
278 56:d8eeba570d70 Chris
    delete m_feeder;
279 3:9366c8a58778 Chris
}
280
281
string
282 31:2c175adf8736 Chris
CepstralPitchTracker::getIdentifier() const
283 3:9366c8a58778 Chris
{
284 39:822cf7b8e070 Chris
    return "cepstral-pitchtracker";
285 3:9366c8a58778 Chris
}
286
287
string
288 31:2c175adf8736 Chris
CepstralPitchTracker::getName() const
289 3:9366c8a58778 Chris
{
290 39:822cf7b8e070 Chris
    return "Cepstral Pitch Tracker";
291 3:9366c8a58778 Chris
}
292
293
string
294 31:2c175adf8736 Chris
CepstralPitchTracker::getDescription() const
295 3:9366c8a58778 Chris
{
296
    return "Estimate f0 of monophonic material using a cepstrum method.";
297
}
298
299
string
300 31:2c175adf8736 Chris
CepstralPitchTracker::getMaker() const
301 3:9366c8a58778 Chris
{
302
    return "Chris Cannam";
303
}
304
305
int
306 31:2c175adf8736 Chris
CepstralPitchTracker::getPluginVersion() const
307 3:9366c8a58778 Chris
{
308
    // Increment this each time you release a version that behaves
309
    // differently from the previous one
310
    return 1;
311
}
312
313
string
314 31:2c175adf8736 Chris
CepstralPitchTracker::getCopyright() const
315 3:9366c8a58778 Chris
{
316
    return "Freely redistributable (BSD license)";
317
}
318
319 31:2c175adf8736 Chris
CepstralPitchTracker::InputDomain
320
CepstralPitchTracker::getInputDomain() const
321 3:9366c8a58778 Chris
{
322
    return FrequencyDomain;
323
}
324
325
size_t
326 31:2c175adf8736 Chris
CepstralPitchTracker::getPreferredBlockSize() const
327 3:9366c8a58778 Chris
{
328
    return 1024;
329
}
330
331
size_t
332 31:2c175adf8736 Chris
CepstralPitchTracker::getPreferredStepSize() const
333 3:9366c8a58778 Chris
{
334
    return 256;
335
}
336
337
size_t
338 31:2c175adf8736 Chris
CepstralPitchTracker::getMinChannelCount() const
339 3:9366c8a58778 Chris
{
340
    return 1;
341
}
342
343
size_t
344 31:2c175adf8736 Chris
CepstralPitchTracker::getMaxChannelCount() const
345 3:9366c8a58778 Chris
{
346
    return 1;
347
}
348
349 31:2c175adf8736 Chris
CepstralPitchTracker::ParameterList
350
CepstralPitchTracker::getParameterDescriptors() const
351 3:9366c8a58778 Chris
{
352
    ParameterList list;
353
    return list;
354
}
355
356
float
357 31:2c175adf8736 Chris
CepstralPitchTracker::getParameter(string identifier) const
358 3:9366c8a58778 Chris
{
359
    return 0.f;
360
}
361
362
void
363 31:2c175adf8736 Chris
CepstralPitchTracker::setParameter(string identifier, float value)
364 3:9366c8a58778 Chris
{
365
}
366
367 31:2c175adf8736 Chris
CepstralPitchTracker::ProgramList
368
CepstralPitchTracker::getPrograms() const
369 3:9366c8a58778 Chris
{
370
    ProgramList list;
371
    return list;
372
}
373
374
string
375 31:2c175adf8736 Chris
CepstralPitchTracker::getCurrentProgram() const
376 3:9366c8a58778 Chris
{
377
    return ""; // no programs
378
}
379
380
void
381 31:2c175adf8736 Chris
CepstralPitchTracker::selectProgram(string name)
382 3:9366c8a58778 Chris
{
383
}
384
385 31:2c175adf8736 Chris
CepstralPitchTracker::OutputList
386
CepstralPitchTracker::getOutputDescriptors() const
387 3:9366c8a58778 Chris
{
388
    OutputList outputs;
389
390
    OutputDescriptor d;
391
392
    d.identifier = "f0";
393
    d.name = "Estimated f0";
394
    d.description = "Estimated fundamental frequency";
395
    d.unit = "Hz";
396
    d.hasFixedBinCount = true;
397
    d.binCount = 1;
398
    d.hasKnownExtents = true;
399
    d.minValue = m_fmin;
400
    d.maxValue = m_fmax;
401
    d.isQuantized = false;
402
    d.sampleType = OutputDescriptor::FixedSampleRate;
403
    d.sampleRate = (m_inputSampleRate / m_stepSize);
404
    d.hasDuration = false;
405
    outputs.push_back(d);
406
407 16:d717911aca3c Chris
    d.identifier = "notes";
408
    d.name = "Notes";
409
    d.description = "Derived fixed-pitch note frequencies";
410
    d.unit = "Hz";
411
    d.hasFixedBinCount = true;
412
    d.binCount = 1;
413
    d.hasKnownExtents = true;
414
    d.minValue = m_fmin;
415
    d.maxValue = m_fmax;
416
    d.isQuantized = false;
417
    d.sampleType = OutputDescriptor::FixedSampleRate;
418
    d.sampleRate = (m_inputSampleRate / m_stepSize);
419
    d.hasDuration = true;
420
    outputs.push_back(d);
421
422 3:9366c8a58778 Chris
    return outputs;
423
}
424
425
bool
426 31:2c175adf8736 Chris
CepstralPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
427 3:9366c8a58778 Chris
{
428
    if (channels < getMinChannelCount() ||
429
	channels > getMaxChannelCount()) return false;
430
431 31:2c175adf8736 Chris
//    std::cerr << "CepstralPitchTracker::initialise: channels = " << channels
432 3:9366c8a58778 Chris
//	      << ", stepSize = " << stepSize << ", blockSize = " << blockSize
433
//	      << std::endl;
434
435
    m_channels = channels;
436
    m_stepSize = stepSize;
437
    m_blockSize = blockSize;
438
439
    m_binFrom = int(m_inputSampleRate / m_fmax);
440
    m_binTo = int(m_inputSampleRate / m_fmin);
441
442
    if (m_binTo >= (int)m_blockSize / 2) {
443
        m_binTo = m_blockSize / 2 - 1;
444
    }
445 56:d8eeba570d70 Chris
    if (m_binFrom >= m_binTo) {
446
        // shouldn't happen except for degenerate samplerate / blocksize combos
447
        m_binFrom = m_binTo - 1;
448
    }
449 3:9366c8a58778 Chris
450
    m_bins = (m_binTo - m_binFrom) + 1;
451
452
    reset();
453
454
    return true;
455
}
456
457
void
458 31:2c175adf8736 Chris
CepstralPitchTracker::reset()
459 3:9366c8a58778 Chris
{
460 56:d8eeba570d70 Chris
    delete m_feeder;
461
    m_feeder = new AgentFeeder();
462 57:82b3cdf6ca6b Chris
    m_nAccepted = 0;
463 3:9366c8a58778 Chris
}
464
465
void
466 35:2f5b169e4a3b Chris
CepstralPitchTracker::addFeaturesFrom(NoteHypothesis h, FeatureSet &fs)
467 30:2554aab152a5 Chris
{
468 35:2f5b169e4a3b Chris
    NoteHypothesis::Estimates es = h.getAcceptedEstimates();
469 30:2554aab152a5 Chris
470 35:2f5b169e4a3b Chris
    for (int i = 0; i < (int)es.size(); ++i) {
471 30:2554aab152a5 Chris
	Feature f;
472
	f.hasTimestamp = true;
473
	f.timestamp = es[i].time;
474
	f.values.push_back(es[i].freq);
475
	fs[0].push_back(f);
476
    }
477
478
    Feature nf;
479
    nf.hasTimestamp = true;
480
    nf.hasDuration = true;
481 35:2f5b169e4a3b Chris
    NoteHypothesis::Note n = h.getAveragedNote();
482 30:2554aab152a5 Chris
    nf.timestamp = n.time;
483
    nf.duration = n.duration;
484
    nf.values.push_back(n.freq);
485
    fs[1].push_back(nf);
486
}
487
488 57:82b3cdf6ca6b Chris
void
489
CepstralPitchTracker::addNewFeatures(FeatureSet &fs)
490
{
491
    int n = m_feeder->getAcceptedHypotheses().size();
492
    if (n == m_nAccepted) return;
493
494
    AgentFeeder::Hypotheses accepted = m_feeder->getAcceptedHypotheses();
495
496
    for (int i = m_nAccepted; i < n; ++i) {
497
        addFeaturesFrom(accepted[i], fs);
498
    }
499
500
    m_nAccepted = n;
501
}
502
503 31:2c175adf8736 Chris
CepstralPitchTracker::FeatureSet
504
CepstralPitchTracker::process(const float *const *inputBuffers, RealTime timestamp)
505 3:9366c8a58778 Chris
{
506 51:0997774f5fdc Chris
    double *rawcep = new double[m_blockSize];
507
    double magmean = Cepstrum(m_blockSize).process(inputBuffers[0], rawcep);
508 3:9366c8a58778 Chris
509
    int n = m_bins;
510
    double *data = new double[n];
511 51:0997774f5fdc Chris
    MeanFilter(m_vflen).filterSubsequence
512
        (rawcep, data, m_blockSize, n, m_binFrom);
513
514 3:9366c8a58778 Chris
    delete[] rawcep;
515
516
    double maxval = 0.0;
517 6:291c75f6e837 Chris
    int maxbin = -1;
518 3:9366c8a58778 Chris
519
    for (int i = 0; i < n; ++i) {
520
        if (data[i] > maxval) {
521
            maxval = data[i];
522
            maxbin = i;
523
        }
524
    }
525
526 15:bd7fb10646fc Chris
    if (maxbin < 0) {
527
        delete[] data;
528 57:82b3cdf6ca6b Chris
        return FeatureSet();
529 15:bd7fb10646fc Chris
    }
530
531
    double nextPeakVal = 0.0;
532
    for (int i = 1; i+1 < n; ++i) {
533
        if (data[i] > data[i-1] &&
534
            data[i] > data[i+1] &&
535
            i != maxbin &&
536
            data[i] > nextPeakVal) {
537
            nextPeakVal = data[i];
538
        }
539
    }
540 8:e9d86e129467 Chris
541 50:d84049e20c61 Chris
    PeakInterpolator pi;
542
    double cimax = pi.findPeakLocation(data, m_bins, maxbin);
543 18:131b1c40be1a Chris
    double peakfreq = m_inputSampleRate / (cimax + m_binFrom);
544 15:bd7fb10646fc Chris
545
    double confidence = 0.0;
546 51:0997774f5fdc Chris
    double threshold = 0.1; // for magmean
547
548 15:bd7fb10646fc Chris
    if (nextPeakVal != 0.0) {
549 27:e358f133e670 Chris
        confidence = (maxval - nextPeakVal) * 10.0;
550 25:9aee1a0e6223 Chris
        if (magmean < threshold) confidence = 0.0;
551 15:bd7fb10646fc Chris
    }
552
553 57:82b3cdf6ca6b Chris
    delete[] data;
554
555 35:2f5b169e4a3b Chris
    NoteHypothesis::Estimate e;
556 8:e9d86e129467 Chris
    e.freq = peakfreq;
557
    e.time = timestamp;
558 15:bd7fb10646fc Chris
    e.confidence = confidence;
559 8:e9d86e129467 Chris
560 56:d8eeba570d70 Chris
    m_feeder->feed(e);
561 14:98256077e2a2 Chris
562 57:82b3cdf6ca6b Chris
    FeatureSet fs;
563
    addNewFeatures(fs);
564 3:9366c8a58778 Chris
    return fs;
565
}
566
567 31:2c175adf8736 Chris
CepstralPitchTracker::FeatureSet
568
CepstralPitchTracker::getRemainingFeatures()
569 3:9366c8a58778 Chris
{
570 56:d8eeba570d70 Chris
    m_feeder->finish();
571
572 3:9366c8a58778 Chris
    FeatureSet fs;
573 57:82b3cdf6ca6b Chris
    addNewFeatures(fs);
574 3:9366c8a58778 Chris
    return fs;
575
}
576
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
577
/*
578 31:2c175adf8736 Chris
    This file is Copyright (c) 2012 Chris Cannam
579
580 3:9366c8a58778 Chris
    Permission is hereby granted, free of charge, to any person
581
    obtaining a copy of this software and associated documentation
582
    files (the "Software"), to deal in the Software without
583
    restriction, including without limitation the rights to use, copy,
584
    modify, merge, publish, distribute, sublicense, and/or sell copies
585
    of the Software, and to permit persons to whom the Software is
586
    furnished to do so, subject to the following conditions:
587
588
    The above copyright notice and this permission notice shall be
589
    included in all copies or substantial portions of the Software.
590
591
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
592
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
593
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
594
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
595
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
596
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
597
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
598
*/
599
600 35:2f5b169e4a3b Chris
#ifndef _CEPSTRAL_PITCH_H_
601
#define _CEPSTRAL_PITCH_H_
602 3:9366c8a58778 Chris
603
#include <vamp-sdk/Plugin.h>
604
605 35:2f5b169e4a3b Chris
#include "NoteHypothesis.h"
606
607 55:b32290646213 Chris
class AgentFeeder;
608
609 31:2c175adf8736 Chris
class CepstralPitchTracker : public Vamp::Plugin
610 3:9366c8a58778 Chris
{
611
public:
612 31:2c175adf8736 Chris
    CepstralPitchTracker(float inputSampleRate);
613
    virtual ~CepstralPitchTracker();
614 3:9366c8a58778 Chris
615
    std::string getIdentifier() const;
616
    std::string getName() const;
617
    std::string getDescription() const;
618
    std::string getMaker() const;
619
    int getPluginVersion() const;
620
    std::string getCopyright() const;
621
622
    InputDomain getInputDomain() const;
623
    size_t getPreferredBlockSize() const;
624
    size_t getPreferredStepSize() const;
625
    size_t getMinChannelCount() const;
626
    size_t getMaxChannelCount() const;
627
628
    ParameterList getParameterDescriptors() const;
629
    float getParameter(std::string identifier) const;
630
    void setParameter(std::string identifier, float value);
631
632
    ProgramList getPrograms() const;
633
    std::string getCurrentProgram() const;
634
    void selectProgram(std::string name);
635
636
    OutputList getOutputDescriptors() const;
637
638
    bool initialise(size_t channels, size_t stepSize, size_t blockSize);
639
    void reset();
640
641
    FeatureSet process(const float *const *inputBuffers,
642
                       Vamp::RealTime timestamp);
643
644
    FeatureSet getRemainingFeatures();
645
646
protected:
647
    size_t m_channels;
648
    size_t m_stepSize;
649
    size_t m_blockSize;
650
    float m_fmin;
651
    float m_fmax;
652 5:383c5b497f4a Chris
    int m_vflen;
653 3:9366c8a58778 Chris
654
    int m_binFrom;
655
    int m_binTo;
656
    int m_bins; // count of "interesting" bins, those returned in m_cepOutput
657
658 57:82b3cdf6ca6b Chris
    int m_nAccepted;
659
660 55:b32290646213 Chris
    AgentFeeder *m_feeder;
661 35:2f5b169e4a3b Chris
    void addFeaturesFrom(NoteHypothesis h, FeatureSet &fs);
662 57:82b3cdf6ca6b Chris
    void addNewFeatures(FeatureSet &fs);
663 3:9366c8a58778 Chris
};
664
665
#endif
666 51:0997774f5fdc Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
667
/*
668
    This file is Copyright (c) 2012 Chris Cannam
669
670
    Permission is hereby granted, free of charge, to any person
671
    obtaining a copy of this software and associated documentation
672
    files (the "Software"), to deal in the Software without
673
    restriction, including without limitation the rights to use, copy,
674
    modify, merge, publish, distribute, sublicense, and/or sell copies
675
    of the Software, and to permit persons to whom the Software is
676
    furnished to do so, subject to the following conditions:
677
678
    The above copyright notice and this permission notice shall be
679
    included in all copies or substantial portions of the Software.
680
681
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
682
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
683
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
684
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
685
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
686
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
687
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
688
*/
689
690 73:939cf0e86268 Chris
#ifndef CEPSTRUM_H
691
#define CEPSTRUM_H
692 51:0997774f5fdc Chris
693
#include "vamp-sdk/FFT.h"
694
#include <cmath>
695
696
#include <iostream>
697
#include <exception>
698
699
class Cepstrum
700
{
701
public:
702
    /**
703
     * Construct a cepstrum converter based on an n-point FFT.
704
     */
705
    Cepstrum(int n) : m_n(n) {
706
	if (n & (n-1)) {
707
	    throw "N must be a power of two";
708
	}
709
    }
710
    ~Cepstrum() { }
711
712
    /**
713
     * Convert the given frequency-domain data to the cepstral domain.
714
     *
715
     * The input must be in the format used for FrequencyDomain data
716
     * in the Vamp SDK: n/2+1 consecutive pairs of real and imaginary
717
     * component floats corresponding to bins 0..(n/2) of the FFT
718
     * output. Thus, n+2 values in total.
719
     *
720
     * The output consists of the raw cepstrum of length n.
721
     *
722
     * The cepstrum is calculated as the inverse FFT of a
723
     * synthetically symmetrical base-10 log magnitude spectrum.
724
     *
725
     * Returns the mean magnitude of the input spectrum.
726
     */
727
    double process(const float *in, double *out) {
728
729
	int hs = m_n/2 + 1;
730
	double *io = new double[m_n];
731
	double *logmag = new double[m_n];
732
	double epsilon = 1e-10;
733
734
	double magmean = 0.0;
735
736
	for (int i = 0; i < hs; ++i) {
737
738 73:939cf0e86268 Chris
            double re = in[i*2];
739
            double im = in[i*2+1];
740
            double power = re * re + im * im;
741 51:0997774f5fdc Chris
	    double mag = sqrt(power);
742
	    magmean += mag;
743
744
	    logmag[i] = log10(mag + epsilon);
745
746
	    if (i > 0) {
747
		// make the log magnitude spectrum symmetrical
748
		logmag[m_n - i] = logmag[i];
749
	    }
750
	}
751
752
	magmean /= hs;
753
	/*
754
	std::cerr << "logmags:" << std::endl;
755
	for (int i = 0; i < m_n; ++i) {
756
	    std::cerr << logmag[i] << " ";
757
	}
758
	std::cerr << std::endl;
759
	*/
760
	Vamp::FFT::inverse(m_n, logmag, 0, out, io);
761
762
	delete[] logmag;
763
	delete[] io;
764
765
	return magmean;
766
    }
767
768
private:
769
    int m_n;
770
};
771
772
#endif
773 0:89c5d5998cda Chris
774
PLUGIN_EXT	?= .so
775 41:16908c2bd781 Chris
776 0:89c5d5998cda Chris
CXX	?= g++
777
CC	?= gcc
778
779 41:16908c2bd781 Chris
CFLAGS := $(CFLAGS)
780
CXXFLAGS := -I. $(CXXFLAGS)
781
782
LDFLAGS := $(LDFLAGS) -lvamp-sdk
783
PLUGIN_LDFLAGS := $(LDFLAGS) $(PLUGIN_LDFLAGS)
784
TEST_LDFLAGS := $(LDFLAGS) -lboost_unit_test_framework
785
786
PLUGIN := cepstral-pitchtracker$(PLUGIN_EXT)
787 0:89c5d5998cda Chris
788 35:2f5b169e4a3b Chris
HEADERS := CepstralPitchTracker.h \
789 52:34f42a384a7f Chris
           AgentFeeder.h \
790 47:f72a470fe4b5 Chris
           MeanFilter.h \
791 39:822cf7b8e070 Chris
	   NoteHypothesis.h \
792
	   PeakInterpolator.h
793 0:89c5d5998cda Chris
794 31:2c175adf8736 Chris
SOURCES := CepstralPitchTracker.cpp \
795 52:34f42a384a7f Chris
           AgentFeeder.cpp \
796 35:2f5b169e4a3b Chris
	   NoteHypothesis.cpp \
797 41:16908c2bd781 Chris
	   PeakInterpolator.cpp
798
799
PLUGIN_MAIN := libmain.cpp
800
801 63:686ef2976366 Chris
TESTS ?= test/test-meanfilter \
802 48:1343ecde267e Chris
         test/test-fft \
803 51:0997774f5fdc Chris
	 test/test-cepstrum \
804
         test/test-peakinterpolator \
805 52:34f42a384a7f Chris
	 test/test-notehypothesis \
806
	 test/test-agentfeeder
807 51:0997774f5fdc Chris
808 0:89c5d5998cda Chris
OBJECTS := $(SOURCES:.cpp=.o)
809
OBJECTS := $(OBJECTS:.c=.o)
810
811 41:16908c2bd781 Chris
PLUGIN_OBJECTS := $(OBJECTS) $(PLUGIN_MAIN:.cpp=.o)
812 38:944898c2e14e Chris
813 41:16908c2bd781 Chris
all: $(PLUGIN) $(TESTS)
814
	for t in $(TESTS); do echo "Running $$t"; ./"$$t" || exit 1; done
815
816
$(PLUGIN): $(PLUGIN_OBJECTS)
817
	$(CXX) -o $@ $^ $(PLUGIN_LDFLAGS)
818
819
test/test-notehypothesis: test/TestNoteHypothesis.o $(OBJECTS)
820
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
821
822 52:34f42a384a7f Chris
test/test-agentfeeder: test/TestAgentFeeder.o $(OBJECTS)
823
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
824
825 47:f72a470fe4b5 Chris
test/test-meanfilter: test/TestMeanFilter.o $(OBJECTS)
826
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
827
828 51:0997774f5fdc Chris
test/test-cepstrum: test/TestCepstrum.o $(OBJECTS)
829
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
830
831 48:1343ecde267e Chris
test/test-fft: test/TestFFT.o $(OBJECTS)
832
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
833
834 41:16908c2bd781 Chris
test/test-peakinterpolator: test/TestPeakInterpolator.o $(OBJECTS)
835
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
836 0:89c5d5998cda Chris
837
clean:
838 41:16908c2bd781 Chris
		rm -f $(OBJECTS) test/*.o
839 0:89c5d5998cda Chris
840
distclean:	clean
841 41:16908c2bd781 Chris
		rm -f $(PLUGIN) $(TESTS)
842 1:c73b7b595d9c Chris
843 51:0997774f5fdc Chris
depend:
844
		makedepend -Y -fMakefile.inc *.cpp test/*.cpp *.h test/*.h
845
846 41:16908c2bd781 Chris
# DO NOT DELETE
847
848 51:0997774f5fdc Chris
CepstralPitchTracker.o: CepstralPitchTracker.h NoteHypothesis.h Cepstrum.h
849
CepstralPitchTracker.o: MeanFilter.h PeakInterpolator.h
850 41:16908c2bd781 Chris
libmain.o: CepstralPitchTracker.h NoteHypothesis.h
851
NoteHypothesis.o: NoteHypothesis.h
852
PeakInterpolator.o: PeakInterpolator.h
853 51:0997774f5fdc Chris
test/TestCepstrum.o: Cepstrum.h
854
test/TestMeanFilter.o: MeanFilter.h
855 41:16908c2bd781 Chris
test/TestNoteHypothesis.o: NoteHypothesis.h
856
test/TestPeakInterpolator.o: PeakInterpolator.h
857 51:0997774f5fdc Chris
CepstralPitchTracker.o: NoteHypothesis.h
858 0:89c5d5998cda Chris
859 73:939cf0e86268 Chris
CFLAGS := -Wall -O2 -fPIC
860 0:89c5d5998cda Chris
CXXFLAGS := $(CFLAGS)
861
862 70:29936292cec0 Chris
PLUGIN_LDFLAGS := -shared -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -Wl,-Bsymbolic -Wl,-z,defs -Wl,--version-script=vamp-plugin.map
863 0:89c5d5998cda Chris
864
PLUGIN_EXT := .so
865
866
include Makefile.inc
867
868 63:686ef2976366 Chris
869
CXX		= i486-mingw32-c++
870
CC		= i486-mingw32-gcc
871
LD            	= i486-mingw32-c++
872
AR            	= i486-mingw32-ar
873
RANLIB          = i486-mingw32-ranlib
874
875
TESTS		= test/null
876
877
CFLAGS := -Wall -O2 -I../include
878
CXXFLAGS := $(CFLAGS)
879
880
LDFLAGS	 := -L../lib
881
PLUGIN_LDFLAGS := -shared -Wl,-Bstatic -static-libgcc -Wl,--version-script=vamp-plugin.map
882
883
PLUGIN_EXT := .dll
884
885
include Makefile.inc
886
887
test/null:
888
		ln -s /bin/true test/null
889 19:895d9357dce8 Chris
890 54:751b43d119cf Chris
CFLAGS := -I../inst/include -I/usr/local/boost -Wall -g -fPIC
891 19:895d9357dce8 Chris
CXXFLAGS := $(CFLAGS)
892
893 54:751b43d119cf Chris
LDFLAGS := -L../inst/lib -lvamp-sdk -L/usr/local/boost/stage/lib
894 63:686ef2976366 Chris
PLUGIN_LDFLAGS := -dynamiclib -exported_symbols_list=vamp-plugin.list
895 19:895d9357dce8 Chris
PLUGIN_EXT := .dylib
896
897
include Makefile.inc
898
899 47:f72a470fe4b5 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
900
/*
901
    This file is Copyright (c) 2012 Chris Cannam
902
903
    Permission is hereby granted, free of charge, to any person
904
    obtaining a copy of this software and associated documentation
905
    files (the "Software"), to deal in the Software without
906
    restriction, including without limitation the rights to use, copy,
907
    modify, merge, publish, distribute, sublicense, and/or sell copies
908
    of the Software, and to permit persons to whom the Software is
909
    furnished to do so, subject to the following conditions:
910
911
    The above copyright notice and this permission notice shall be
912
    included in all copies or substantial portions of the Software.
913
914
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
915
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
916
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
917
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
918
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
919
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
920
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
921
*/
922
923 73:939cf0e86268 Chris
#ifndef MEAN_FILTER_H
924
#define MEAN_FILTER_H
925 47:f72a470fe4b5 Chris
926
class MeanFilter
927
{
928
public:
929
    /**
930
     * Construct a non-causal mean filter with filter length flen,
931
     * that replaces each sample N with the mean of samples
932
     * [N-floor(F/2) .. N+floor(F/2)] where F is the filter length.
933
     * Only odd F are supported.
934
     */
935
    MeanFilter(int flen) : m_flen(flen) { }
936
    ~MeanFilter() { }
937
938
    /**
939
     * Filter the n samples in "in" and place the results in "out"
940
     */
941
    void filter(const double *in, double *out, const int n) {
942
	filterSubsequence(in, out, n, n, 0);
943
    }
944
945
    /**
946
     * Filter the n samples starting at the given offset in the
947 48:1343ecde267e Chris
     * m-element array "in" and place the results in the n-element
948
     * array "out"
949 47:f72a470fe4b5 Chris
     */
950
    void filterSubsequence(const double *in, double *out,
951
			   const int m, const int n,
952
			   const int offset) {
953
	int half = m_flen/2;
954
	for (int i = 0; i < n; ++i) {
955
	    double v = 0;
956
	    int n = 0;
957
	    for (int j = -half; j <= half; ++j) {
958
		int ix = i + j + offset;
959
		if (ix >= 0 && ix < m) {
960 73:939cf0e86268 Chris
                    double value = in[ix];
961
                    if (value == value) { // i.e. not NaN
962
                        v += value;
963
                    }
964
                    ++n;
965 47:f72a470fe4b5 Chris
		}
966
	    }
967 73:939cf0e86268 Chris
            if (n > 0) {
968
                out[i] = v / n;
969
            } else {
970
                out[i] = 0.0;
971
            }
972 47:f72a470fe4b5 Chris
	}
973
    }
974
975
private:
976
    int m_flen;
977
};
978
979
#endif
980 32:c88a9972975b Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
981 37:7bf67d2dfc30 Chris
/*
982
    This file is Copyright (c) 2012 Chris Cannam
983
984
    Permission is hereby granted, free of charge, to any person
985
    obtaining a copy of this software and associated documentation
986
    files (the "Software"), to deal in the Software without
987
    restriction, including without limitation the rights to use, copy,
988
    modify, merge, publish, distribute, sublicense, and/or sell copies
989
    of the Software, and to permit persons to whom the Software is
990
    furnished to do so, subject to the following conditions:
991
992
    The above copyright notice and this permission notice shall be
993
    included in all copies or substantial portions of the Software.
994
995
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
996
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
997
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
998
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
999
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1000
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1001
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1002
*/
1003 32:c88a9972975b Chris
1004
#include "NoteHypothesis.h"
1005
1006
#include <cmath>
1007
1008 35:2f5b169e4a3b Chris
using Vamp::RealTime;
1009 32:c88a9972975b Chris
1010
NoteHypothesis::NoteHypothesis()
1011
{
1012
    m_state = New;
1013
}
1014
1015
NoteHypothesis::~NoteHypothesis()
1016
{
1017
}
1018
1019
bool
1020
NoteHypothesis::isWithinTolerance(Estimate s) const
1021
{
1022
    if (m_pending.empty()) {
1023
        return true;
1024
    }
1025
1026
    // check we are within a relatively close tolerance of the last
1027
    // candidate
1028
    Estimate last = m_pending[m_pending.size()-1];
1029
    double r = s.freq / last.freq;
1030
    int cents = lrint(1200.0 * (log(r) / log(2.0)));
1031
    if (cents < -60 || cents > 60) return false;
1032
1033
    // and within a slightly bigger tolerance of the current mean
1034
    double meanFreq = getMeanFrequency();
1035
    r = s.freq / meanFreq;
1036
    cents = lrint(1200.0 * (log(r) / log(2.0)));
1037
    if (cents < -80 || cents > 80) return false;
1038
1039
    return true;
1040
}
1041
1042
bool
1043
NoteHypothesis::isOutOfDateFor(Estimate s) const
1044
{
1045
    if (m_pending.empty()) return false;
1046
    return ((s.time - m_pending[m_pending.size()-1].time) >
1047
            RealTime::fromMilliseconds(40));
1048
}
1049
1050
bool
1051
NoteHypothesis::isSatisfied() const
1052
{
1053
    if (m_pending.empty()) return false;
1054
1055
    double meanConfidence = 0.0;
1056
    for (int i = 0; i < (int)m_pending.size(); ++i) {
1057
        meanConfidence += m_pending[i].confidence;
1058
    }
1059
    meanConfidence /= m_pending.size();
1060
1061 59:82552664d471 Chris
    int lengthRequired = 100;
1062 32:c88a9972975b Chris
    if (meanConfidence > 0.0) {
1063
        lengthRequired = int(2.0 / meanConfidence + 0.5);
1064
    }
1065
1066
    return ((int)m_pending.size() > lengthRequired);
1067
}
1068
1069
bool
1070
NoteHypothesis::accept(Estimate s)
1071
{
1072
    bool accept = false;
1073
1074 58:9f50a5876dd3 Chris
    static double negligibleConfidence = 0.0001;
1075
1076
    if (s.confidence < negligibleConfidence) {
1077
        // avoid piling up a lengthy sequence of estimates that are
1078
        // all acceptable but are in total not enough to cause us to
1079
        // be satisfied
1080 60:c06fe5350b34 Chris
        if (m_pending.empty()) {
1081
            m_state = Rejected;
1082
        }
1083 58:9f50a5876dd3 Chris
        return false;
1084
    }
1085
1086 32:c88a9972975b Chris
    switch (m_state) {
1087
1088
    case New:
1089
        m_state = Provisional;
1090
        accept = true;
1091
        break;
1092
1093
    case Provisional:
1094
        if (isOutOfDateFor(s)) {
1095
            m_state = Rejected;
1096
        } else if (isWithinTolerance(s)) {
1097
            accept = true;
1098
        }
1099
        break;
1100
1101
    case Satisfied:
1102
        if (isOutOfDateFor(s)) {
1103
            m_state = Expired;
1104
        } else if (isWithinTolerance(s)) {
1105
            accept = true;
1106
        }
1107
        break;
1108
1109
    case Rejected:
1110
        break;
1111
1112
    case Expired:
1113
        break;
1114
    }
1115
1116
    if (accept) {
1117
        m_pending.push_back(s);
1118
        if (m_state == Provisional && isSatisfied()) {
1119
            m_state = Satisfied;
1120
        }
1121
    }
1122
1123
    return accept;
1124
}
1125
1126
NoteHypothesis::State
1127
NoteHypothesis::getState() const
1128
{
1129
    return m_state;
1130
}
1131
1132
NoteHypothesis::Estimates
1133
NoteHypothesis::getAcceptedEstimates() const
1134
{
1135
    if (m_state == Satisfied || m_state == Expired) {
1136
        return m_pending;
1137
    } else {
1138
        return Estimates();
1139
    }
1140
}
1141
1142 52:34f42a384a7f Chris
RealTime
1143
NoteHypothesis::getStartTime() const
1144
{
1145
    if (!(m_state == Satisfied || m_state == Expired)) {
1146
        return RealTime::zeroTime;
1147
    } else {
1148
        return m_pending.begin()->time;
1149
    }
1150
}
1151
1152 32:c88a9972975b Chris
double
1153
NoteHypothesis::getMeanFrequency() const
1154
{
1155
    double acc = 0.0;
1156 34:3fb9c657d86b Chris
    if (m_pending.empty()) return acc;
1157 32:c88a9972975b Chris
    for (int i = 0; i < (int)m_pending.size(); ++i) {
1158
        acc += m_pending[i].freq;
1159
    }
1160
    acc /= m_pending.size();
1161
    return acc;
1162
}
1163
1164
NoteHypothesis::Note
1165
NoteHypothesis::getAveragedNote() const
1166
{
1167
    Note n;
1168
1169
    if (!(m_state == Satisfied || m_state == Expired)) {
1170
        n.freq = 0.0;
1171
        n.time = RealTime::zeroTime;
1172
        n.duration = RealTime::zeroTime;
1173
        return n;
1174
    }
1175
1176
    n.time = m_pending.begin()->time;
1177
1178
    Estimates::const_iterator i = m_pending.end();
1179
    --i;
1180
    n.duration = i->time - n.time;
1181
1182
    // just mean frequency for now, but this isn't at all right perceptually
1183
    n.freq = getMeanFrequency();
1184
1185
    return n;
1186
}
1187
1188
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1189 37:7bf67d2dfc30 Chris
/*
1190
    This file is Copyright (c) 2012 Chris Cannam
1191
1192
    Permission is hereby granted, free of charge, to any person
1193
    obtaining a copy of this software and associated documentation
1194
    files (the "Software"), to deal in the Software without
1195
    restriction, including without limitation the rights to use, copy,
1196
    modify, merge, publish, distribute, sublicense, and/or sell copies
1197
    of the Software, and to permit persons to whom the Software is
1198
    furnished to do so, subject to the following conditions:
1199
1200
    The above copyright notice and this permission notice shall be
1201
    included in all copies or substantial portions of the Software.
1202
1203
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1204
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1205
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1206
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1207
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1208
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1209
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1210
*/
1211 32:c88a9972975b Chris
1212
#ifndef _NOTE_HYPOTHESIS_H_
1213
#define _NOTE_HYPOTHESIS_H_
1214
1215 35:2f5b169e4a3b Chris
#include "vamp-sdk/RealTime.h"
1216 32:c88a9972975b Chris
#include <vector>
1217
1218
/**
1219
 * An agent used to test an incoming series of instantaneous pitch
1220
 * estimates to see whether they fit a consistent single-note
1221
 * relationship. Contains rules specific to testing note pitch and
1222
 * timing.
1223
 */
1224
1225
class NoteHypothesis
1226
{
1227
public:
1228
    enum State {
1229
1230 45:8db4a1f096f0 Chris
        /// Just constructed, will provisionally accept any estimate
1231
        New,
1232 32:c88a9972975b Chris
1233 45:8db4a1f096f0 Chris
        /// Accepted at least one estimate, but not enough evidence to satisfy
1234
        Provisional,
1235 32:c88a9972975b Chris
1236 45:8db4a1f096f0 Chris
        /// Could not find enough consistency in offered estimates
1237
        Rejected,
1238 32:c88a9972975b Chris
1239 45:8db4a1f096f0 Chris
        /// Have accepted enough consistent estimates to satisfy hypothesis
1240
        Satisfied,
1241 32:c88a9972975b Chris
1242 45:8db4a1f096f0 Chris
        /// Have been satisfied, but evidence has now changed: we're done
1243
        Expired
1244 32:c88a9972975b Chris
    };
1245
1246
    /**
1247
     * Construct an empty hypothesis. This will be in New state and
1248
     * will provisionally accept any estimate.
1249
     */
1250
    NoteHypothesis();
1251
1252
    /**
1253
     * Destroy the hypothesis
1254
     */
1255
    ~NoteHypothesis();
1256
1257
    struct Estimate {
1258 58:9f50a5876dd3 Chris
        Estimate() : freq(0), time(), confidence(1) { }
1259 35:2f5b169e4a3b Chris
        Estimate(double _f, Vamp::RealTime _t, double _c) :
1260 33:5f2a57b1a75a Chris
            freq(_f), time(_t), confidence(_c) { }
1261 34:3fb9c657d86b Chris
        bool operator==(const Estimate &e) const {
1262
            return e.freq == freq && e.time == time && e.confidence == confidence;
1263
        }
1264 45:8db4a1f096f0 Chris
        double freq;
1265 35:2f5b169e4a3b Chris
        Vamp::RealTime time;
1266 45:8db4a1f096f0 Chris
        double confidence;
1267 32:c88a9972975b Chris
    };
1268
    typedef std::vector<Estimate> Estimates;
1269
1270
    /**
1271
     * Test the given estimate to see whether it is consistent with
1272
     * this hypothesis, and adjust the hypothesis' internal state
1273
     * accordingly. If the estimate is not inconsistent with the
1274
     * hypothesis, return true.
1275
     */
1276
    bool accept(Estimate);
1277
1278
    /**
1279
     * Return the current state of this hypothesis.
1280
     */
1281
    State getState() const;
1282
1283
    /**
1284
     * If the hypothesis has been satisfied (i.e. is in Satisfied or
1285
     * Expired state), return the series of estimates that it
1286
     * accepted. Otherwise return an empty list
1287
     */
1288
    Estimates getAcceptedEstimates() const;
1289
1290
    struct Note {
1291 33:5f2a57b1a75a Chris
        Note() : freq(0), time(), duration() { }
1292 35:2f5b169e4a3b Chris
        Note(double _f, Vamp::RealTime _t, Vamp::RealTime _d) :
1293 33:5f2a57b1a75a Chris
            freq(_f), time(_t), duration(_d) { }
1294 34:3fb9c657d86b Chris
        bool operator==(const Note &e) const {
1295
            return e.freq == freq && e.time == time && e.duration == duration;
1296
        }
1297 45:8db4a1f096f0 Chris
        double freq;
1298
        Vamp::RealTime time;
1299
        Vamp::RealTime duration;
1300 32:c88a9972975b Chris
    };
1301
1302
    /**
1303 52:34f42a384a7f Chris
     * Return the time of the first accepted estimate
1304
     */
1305
    Vamp::RealTime getStartTime() const;
1306
1307
    /**
1308 34:3fb9c657d86b Chris
     * Return the mean frequency of the accepted estimates
1309
     */
1310
    double getMeanFrequency() const;
1311
1312
    /**
1313 32:c88a9972975b Chris
     * Return a single note roughly matching this hypothesis
1314
     */
1315
    Note getAveragedNote() const;
1316
1317
private:
1318
    bool isWithinTolerance(Estimate) const;
1319
    bool isOutOfDateFor(Estimate) const;
1320
    bool isSatisfied() const;
1321
1322
    State m_state;
1323
    Estimates m_pending;
1324
};
1325
1326
#endif
1327 39:822cf7b8e070 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1328
/*
1329
    This file is Copyright (c) 2012 Chris Cannam
1330
1331
    Permission is hereby granted, free of charge, to any person
1332
    obtaining a copy of this software and associated documentation
1333
    files (the "Software"), to deal in the Software without
1334
    restriction, including without limitation the rights to use, copy,
1335
    modify, merge, publish, distribute, sublicense, and/or sell copies
1336
    of the Software, and to permit persons to whom the Software is
1337
    furnished to do so, subject to the following conditions:
1338
1339
    The above copyright notice and this permission notice shall be
1340
    included in all copies or substantial portions of the Software.
1341
1342
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1343
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1344
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1345
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1346
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1347
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1348
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1349
*/
1350
1351
#include "PeakInterpolator.h"
1352
1353 40:8f56ef28b0b1 Chris
#include <iostream>
1354
1355 39:822cf7b8e070 Chris
double
1356 42:45b4401136f6 Chris
PeakInterpolator::findPeakLocation(const double *data, int size)
1357
{
1358
    double maxval;
1359
    int maxidx = 0;
1360
    int i;
1361
    for (i = 0; i < size; ++i) {
1362
        if (i == 0 || data[i] > maxval) {
1363
            maxval = data[i];
1364
            maxidx = i;
1365
        }
1366
    }
1367
    return findPeakLocation(data, size, maxidx);
1368
}
1369
1370
double
1371 39:822cf7b8e070 Chris
PeakInterpolator::findPeakLocation(const double *data, int size, int peakIndex)
1372
{
1373 43:53260757fc9f Chris
    // after jos,
1374
    // https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
1375 40:8f56ef28b0b1 Chris
1376
    if (peakIndex < 1 || peakIndex > size - 2) {
1377 39:822cf7b8e070 Chris
        return peakIndex;
1378
    }
1379
1380 43:53260757fc9f Chris
    double alpha = data[peakIndex-1];
1381
    double beta  = data[peakIndex];
1382
    double gamma = data[peakIndex+1];
1383 39:822cf7b8e070 Chris
1384 43:53260757fc9f Chris
    double denom = (alpha - 2*beta + gamma);
1385 39:822cf7b8e070 Chris
1386 43:53260757fc9f Chris
    if (denom == 0) {
1387
        // flat
1388
        return peakIndex;
1389 39:822cf7b8e070 Chris
    }
1390
1391 43:53260757fc9f Chris
    double p = ((alpha - gamma) / denom) / 2.0;
1392 39:822cf7b8e070 Chris
1393 43:53260757fc9f Chris
    return double(peakIndex) + p;
1394 39:822cf7b8e070 Chris
}
1395
1396
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1397
/*
1398
    This file is Copyright (c) 2012 Chris Cannam
1399
1400
    Permission is hereby granted, free of charge, to any person
1401
    obtaining a copy of this software and associated documentation
1402
    files (the "Software"), to deal in the Software without
1403
    restriction, including without limitation the rights to use, copy,
1404
    modify, merge, publish, distribute, sublicense, and/or sell copies
1405
    of the Software, and to permit persons to whom the Software is
1406
    furnished to do so, subject to the following conditions:
1407
1408
    The above copyright notice and this permission notice shall be
1409
    included in all copies or substantial portions of the Software.
1410
1411
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1412
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1413
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1414
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1415
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1416
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1417
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1418
*/
1419
1420
#ifndef _PEAK_INTERPOLATOR_H_
1421
#define _PEAK_INTERPOLATOR_H_
1422
1423
class PeakInterpolator
1424
{
1425
public:
1426
    PeakInterpolator() { }
1427
    ~PeakInterpolator() { }
1428
1429
    /**
1430 42:45b4401136f6 Chris
     * Return the interpolated location (i.e. possibly between sample
1431
     * point indices) of the peak in the given sampled range.
1432
     *
1433
     * "The peak" is defined as the (approximate) location of the
1434
     * maximum of a function interpolating between the points
1435
     * neighbouring the sample index with the maximum value in the
1436
     * range.
1437
     *
1438
     * If multiple local peak samples in the input range are equal,
1439
     * i.e. there is more than one apparent peak in the range, the one
1440
     * with the lowest index will be used. This is the case even if a
1441
     * later peak would be of superior height after interpolation.
1442
     */
1443
    double findPeakLocation(const double *data, int size);
1444
1445
    /**
1446 39:822cf7b8e070 Chris
     * Return the interpolated location (i.e. between sample point
1447 42:45b4401136f6 Chris
     * indices) of the peak whose nearest sample is found at peakIndex
1448
     * in the given sampled range. This method allows you to specify
1449
     * which peak to find, if local rather than global peaks are of
1450
     * interest.
1451 39:822cf7b8e070 Chris
     */
1452
    double findPeakLocation(const double *data, int size, int peakIndex);
1453
};
1454
1455
#endif
1456 64:f31b2da9258d Chris
1457 72:487321083d51 Chris
Cepstral Pitch Tracker Vamp plugin
1458
==================================
1459 64:f31b2da9258d Chris
1460 72:487321083d51 Chris
This is a straightforward cepstral pitch- and note-tracker Vamp
1461
plugin, probably most suited to tracking singing pitch.
1462 64:f31b2da9258d Chris
1463 72:487321083d51 Chris
https://code.soundsoftware.ac.uk/projects/cepstral-pitchtracker
1464 61:0a2827a35031 Chris
vamp:cepstral-pitchtracker:cepstral-pitchtracker::Notes
1465
@prefix rdfs:     <http://www.w3.org/2000/01/rdf-schema#> .
1466
@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> .
1467
@prefix vamp:     <http://purl.org/ontology/vamp/> .
1468
@prefix plugbase: <http://vamp-plugins.org/rdf/plugins/cepstral-pitchtracker#> .
1469
@prefix owl:      <http://www.w3.org/2002/07/owl#> .
1470
@prefix dc:       <http://purl.org/dc/elements/1.1/> .
1471
@prefix af:       <http://purl.org/ontology/af/> .
1472
@prefix foaf:     <http://xmlns.com/foaf/0.1/> .
1473 69:180c95f52871 Chris
@prefix doap:     <http://usefulinc.com/ns/doap#> .
1474 61:0a2827a35031 Chris
@prefix cc:       <http://web.resource.org/cc/> .
1475
@prefix :         <#> .
1476
1477
<>  a   vamp:PluginDescription ;
1478
    foaf:maker          <http://www.vamp-plugins.org/doap.rdf#template-generator> ;
1479
    foaf:primaryTopic   <http://vamp-plugins.org/rdf/plugins/cepstral-pitchtracker> .
1480
1481 69:180c95f52871 Chris
:library_maker
1482
    foaf:name "Chris Cannam" ;
1483
    foaf:logo <http://vamp-plugins.org/rdf/plugins/makers/qm.png> ;
1484
    foaf:page <http://c4dm.eecs.qmul.ac.uk/> .
1485
1486
plugbase:library a  vamp:PluginLibrary ;
1487
    dc:title "Cepstral Pitch Tracker" ;
1488
    dc:description "A straightforward cepstral pitch- and note-tracker Vamp plugin, probably most suited to tracking singing pitch." ;
1489 61:0a2827a35031 Chris
    vamp:identifier "cepstral-pitchtracker"  ;
1490 69:180c95f52871 Chris
    foaf:maker             :library_maker ;
1491 61:0a2827a35031 Chris
    vamp:available_plugin plugbase:cepstral-pitchtracker ;
1492
    foaf:page <http://code.soundsoftware.ac.uk/projects/cepstral-pitchtracker> ;
1493 69:180c95f52871 Chris
    doap:download-page <http://code.soundsoftware.ac.uk/projects/cepstral-pitchtracker/files> ;
1494
    vamp:has_source true ;
1495 61:0a2827a35031 Chris
    .
1496
1497
plugbase:cepstral-pitchtracker a   vamp:Plugin ;
1498
    dc:title              "Cepstral Pitch Tracker" ;
1499
    vamp:name             "Cepstral Pitch Tracker" ;
1500
    dc:description        """Estimate f0 of monophonic material using a cepstrum method.""" ;
1501 69:180c95f52871 Chris
    foaf:maker            :library_maker ;
1502 61:0a2827a35031 Chris
    dc:rights             """Freely redistributable (BSD license)""" ;
1503
#   cc:license            <Place plugin license URI here and uncomment> ;
1504
    vamp:identifier       "cepstral-pitchtracker" ;
1505
    vamp:vamp_API_version vamp:api_version_2 ;
1506
    owl:versionInfo       "1" ;
1507
    vamp:input_domain     vamp:FrequencyDomain ;
1508
1509
    vamp:output      plugbase:cepstral-pitchtracker_output_f0 ;
1510
    vamp:output      plugbase:cepstral-pitchtracker_output_notes ;
1511
    .
1512
plugbase:cepstral-pitchtracker_output_f0 a  vamp:DenseOutput ;
1513
    vamp:identifier       "f0" ;
1514
    dc:title              "Estimated f0" ;
1515
    dc:description        """Estimated fundamental frequency"""  ;
1516
    vamp:fixed_bin_count  "true" ;
1517
    vamp:unit             "Hz" ;
1518
    a                 vamp:KnownExtentsOutput ;
1519
    vamp:min_value    50  ;
1520
    vamp:max_value    900  ;
1521
    vamp:bin_count        1 ;
1522 74:759ea6e988fe Chris
    vamp:computes_event_type   af:Pitch ;
1523 61:0a2827a35031 Chris
#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
1524
#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
1525
    .
1526 74:759ea6e988fe Chris
plugbase:cepstral-pitchtracker_output_notes a  vamp:SparseOutput ;
1527 61:0a2827a35031 Chris
    vamp:identifier       "notes" ;
1528
    dc:title              "Notes" ;
1529
    dc:description        """Derived fixed-pitch note frequencies"""  ;
1530
    vamp:fixed_bin_count  "true" ;
1531
    vamp:unit             "Hz" ;
1532
    a                 vamp:KnownExtentsOutput ;
1533
    vamp:min_value    50  ;
1534
    vamp:max_value    900  ;
1535
    vamp:bin_count        1 ;
1536 74:759ea6e988fe Chris
    vamp:computes_event_type   af:Note ;
1537 61:0a2827a35031 Chris
    .
1538
1539 0:89c5d5998cda Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1540 31:2c175adf8736 Chris
/*
1541
    This file is Copyright (c) 2012 Chris Cannam
1542
1543
    Permission is hereby granted, free of charge, to any person
1544
    obtaining a copy of this software and associated documentation
1545
    files (the "Software"), to deal in the Software without
1546
    restriction, including without limitation the rights to use, copy,
1547
    modify, merge, publish, distribute, sublicense, and/or sell copies
1548
    of the Software, and to permit persons to whom the Software is
1549
    furnished to do so, subject to the following conditions:
1550
1551
    The above copyright notice and this permission notice shall be
1552
    included in all copies or substantial portions of the Software.
1553
1554
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1555
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1556
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1557
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1558
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1559
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1560
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1561
*/
1562 0:89c5d5998cda Chris
1563
#include <vamp/vamp.h>
1564
#include <vamp-sdk/PluginAdapter.h>
1565
1566 31:2c175adf8736 Chris
#include "CepstralPitchTracker.h"
1567 0:89c5d5998cda Chris
1568 31:2c175adf8736 Chris
static Vamp::PluginAdapter<CepstralPitchTracker> cepitchPluginAdapter;
1569 0:89c5d5998cda Chris
1570
const VampPluginDescriptor *
1571
vampGetPluginDescriptor(unsigned int version, unsigned int index)
1572
{
1573
    if (version < 1) return 0;
1574
1575
    switch (index) {
1576 39:822cf7b8e070 Chris
    case  0: return cepitchPluginAdapter.getDescriptor();
1577 0:89c5d5998cda Chris
    default: return 0;
1578
    }
1579
}
1580
1581
1582 52:34f42a384a7f Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1583
/*
1584
    This file is Copyright (c) 2012 Chris Cannam
1585
1586
    Permission is hereby granted, free of charge, to any person
1587
    obtaining a copy of this software and associated documentation
1588
    files (the "Software"), to deal in the Software without
1589
    restriction, including without limitation the rights to use, copy,
1590
    modify, merge, publish, distribute, sublicense, and/or sell copies
1591
    of the Software, and to permit persons to whom the Software is
1592
    furnished to do so, subject to the following conditions:
1593
1594
    The above copyright notice and this permission notice shall be
1595
    included in all copies or substantial portions of the Software.
1596
1597
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1598
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1599
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1600
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1601
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1602
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1603
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1604
*/
1605
1606
#include "AgentFeeder.h"
1607
1608
#define BOOST_TEST_DYN_LINK
1609
#define BOOST_TEST_MAIN
1610
1611
#include <boost/test/unit_test.hpp>
1612
1613
static Vamp::RealTime ms(int n) { return Vamp::RealTime::fromMilliseconds(n); }
1614
1615 54:751b43d119cf Chris
static const int low = 500, high = 700;
1616
1617 52:34f42a384a7f Chris
typedef NoteHypothesis::Estimate Est;
1618
1619
BOOST_AUTO_TEST_SUITE(TestAgentFeeder)
1620
1621
BOOST_AUTO_TEST_CASE(feederEmpty)
1622
{
1623
    AgentFeeder f;
1624
    f.finish();
1625
    AgentFeeder::Hypotheses accepted = f.getAcceptedHypotheses();
1626
    BOOST_CHECK(accepted.empty());
1627
}
1628
1629
BOOST_AUTO_TEST_CASE(feederSingle)
1630
{
1631 54:751b43d119cf Chris
    Est e0(low, ms(0), 1);
1632
    Est e10(low, ms(10), 1);
1633
    Est e20(low, ms(20), 1);
1634
    Est e30(low, ms(30), 1);
1635 52:34f42a384a7f Chris
1636
    AgentFeeder f;
1637
    f.feed(e0);
1638
    f.feed(e10);
1639
    f.feed(e20);
1640
    f.feed(e30);
1641
    f.finish();
1642
1643
    AgentFeeder::Hypotheses accepted = f.getAcceptedHypotheses();
1644
1645
    BOOST_CHECK_EQUAL(accepted.size(), size_t(1));
1646
}
1647
1648
BOOST_AUTO_TEST_CASE(feederPairSeparate)
1649
{
1650 54:751b43d119cf Chris
    Est e0(low, ms(0), 1);
1651
    Est e10(low, ms(10), 1);
1652
    Est e20(low, ms(20), 1);
1653
    Est e30(low, ms(30), 1);
1654 52:34f42a384a7f Chris
1655 54:751b43d119cf Chris
    Est f0(high, ms(2000), 1);
1656
    Est f10(high, ms(2010), 1);
1657
    Est f20(high, ms(2020), 1);
1658
    Est f30(high, ms(2030), 1);
1659 52:34f42a384a7f Chris
1660
    AgentFeeder f;
1661
    f.feed(e0);
1662
    f.feed(e10);
1663
    f.feed(e20);
1664
    f.feed(e30);
1665
    f.feed(f0);
1666
    f.feed(f10);
1667
    f.feed(f20);
1668
    f.feed(f30);
1669
    f.finish();
1670
1671
    AgentFeeder::Hypotheses accepted =
1672
	f.getAcceptedHypotheses();
1673
1674
    BOOST_CHECK_EQUAL(accepted.size(), size_t(2));
1675
}
1676
1677
BOOST_AUTO_TEST_CASE(feederPairOverlapping)
1678
{
1679
    // eeee
1680
    //   fffffff
1681
1682
    // (With fffffff stopping before eeee has expired.)
1683
1684 54:751b43d119cf Chris
    // This should give us one hypothesis, eeee, because eeee is still
1685
    // the current hypothesis by the time fffffff ends.
1686 52:34f42a384a7f Chris
1687 54:751b43d119cf Chris
    Est e0(low, ms(0), 1);
1688
    Est e10(low, ms(10), 1);
1689 52:34f42a384a7f Chris
1690 54:751b43d119cf Chris
    Est e20(low, ms(20), 1);
1691
    Est f20(high, ms(20), 1);
1692 52:34f42a384a7f Chris
1693 54:751b43d119cf Chris
    Est e30(low, ms(30), 1);
1694
    Est f30(high, ms(30), 1);
1695 52:34f42a384a7f Chris
1696 54:751b43d119cf Chris
    Est f40(high, ms(40), 1);
1697
    Est f41(high, ms(41), 1);
1698
    Est f42(high, ms(42), 1);
1699
    Est f43(high, ms(43), 1);
1700
    Est f44(high, ms(44), 1);
1701 52:34f42a384a7f Chris
1702
    AgentFeeder f;
1703
    f.feed(e0);
1704
    f.feed(e10);
1705
    f.feed(e20);
1706
    f.feed(f20);
1707
    f.feed(e30);
1708
    f.feed(f30);
1709
    f.feed(f40);
1710 54:751b43d119cf Chris
    f.feed(f41);
1711
    f.feed(f42);
1712
    f.feed(f43);
1713
    f.feed(f44);
1714 52:34f42a384a7f Chris
    f.finish();
1715
1716
    AgentFeeder::Hypotheses accepted = f.getAcceptedHypotheses();
1717
1718
    BOOST_CHECK_EQUAL(accepted.size(), size_t(1));
1719
1720
    AgentFeeder::Hypotheses::const_iterator i = accepted.begin();
1721
1722
    BOOST_CHECK_EQUAL(i->getStartTime(), ms(0));
1723
    BOOST_CHECK_EQUAL(i->getAcceptedEstimates().size(), size_t(4));
1724
}
1725
1726
BOOST_AUTO_TEST_CASE(feederPairOverlappingLong)
1727
{
1728
    // eeee
1729
    //   fffffff
1730
1731
    // (With fffffff continuing until after eeee has expired.)
1732
1733
    // This should give us two overlapping hypotheses. Even though
1734
    // the mono feeder only has one satisfied hypothesis at a
1735
    // time, the eeee hypothesis should become satisfied before
1736
    // the fffffff hypothesis has been, but when the eeee
1737
    // hypothesis ends, the fffffff one should replace it. So,
1738
    // both should be recognised.
1739
1740 54:751b43d119cf Chris
    Est e0(low, ms(0), 1);
1741
    Est e10(low, ms(10), 1);
1742 52:34f42a384a7f Chris
1743 54:751b43d119cf Chris
    Est e20(low, ms(20), 1);
1744
    Est f20(high, ms(20), 1);
1745 52:34f42a384a7f Chris
1746 54:751b43d119cf Chris
    Est e30(low, ms(30), 1);
1747
    Est f30(high, ms(30), 1);
1748 52:34f42a384a7f Chris
1749 54:751b43d119cf Chris
    Est f40(high, ms(40), 1);
1750
    Est f50(high, ms(50), 1);
1751
    Est f60(high, ms(60), 1);
1752
    Est f70(high, ms(70), 1);
1753
    Est f80(high, ms(80), 1);
1754 52:34f42a384a7f Chris
1755
    AgentFeeder f;
1756
    f.feed(e0);
1757
    f.feed(e10);
1758
    f.feed(e20);
1759
    f.feed(f20);
1760
    f.feed(e30);
1761
    f.feed(f30);
1762
    f.feed(f40);
1763
    f.feed(f50);
1764
    f.feed(f60);
1765
    f.feed(f70);
1766
    f.feed(f80);
1767
    f.finish();
1768
1769
    AgentFeeder::Hypotheses accepted = f.getAcceptedHypotheses();
1770
1771
    BOOST_CHECK_EQUAL(accepted.size(), size_t(2));
1772
1773
    AgentFeeder::Hypotheses::const_iterator i = accepted.begin();
1774
1775
    BOOST_CHECK_EQUAL(i->getStartTime(), ms(0));
1776
    BOOST_CHECK_EQUAL(i->getAcceptedEstimates().size(), size_t(4));
1777
    ++i;
1778
1779 54:751b43d119cf Chris
    BOOST_CHECK_EQUAL(i->getStartTime(), ms(20));
1780 52:34f42a384a7f Chris
    BOOST_CHECK_EQUAL(i->getAcceptedEstimates().size(), size_t(7));
1781
    ++i;
1782
}
1783
1784
1785
BOOST_AUTO_TEST_CASE(feederPairContaining)
1786
{
1787
    // eeeeeeee
1788
    //   ffff
1789
1790
    // This should give us eeeeeeee only. The ffff hypothesis
1791
    // (even when satisfied itself) cannot replace the single
1792
    // satisfied hypothesis eeeeeeee while it is still in
1793
    // progress.
1794
1795 54:751b43d119cf Chris
    Est e0(low, ms(0), 1);
1796
    Est e10(low, ms(10), 1);
1797
    Est e20(low, ms(20), 1);
1798
    Est e30(low, ms(30), 1);
1799
    Est e40(low, ms(40), 1);
1800
    Est e50(low, ms(50), 1);
1801
    Est e60(low, ms(60), 1);
1802
    Est e70(low, ms(70), 1);
1803 52:34f42a384a7f Chris
1804 54:751b43d119cf Chris
    Est f20(high, ms(20), 1);
1805
    Est f30(high, ms(30), 1);
1806
    Est f40(high, ms(40), 1);
1807
    Est f50(high, ms(50), 1);
1808 52:34f42a384a7f Chris
1809
    AgentFeeder f;
1810
1811
    f.feed(e0);
1812
    f.feed(e10);
1813
1814
    f.feed(e20);
1815
    f.feed(f20);
1816
1817
    f.feed(e30);
1818
    f.feed(f30);
1819
1820
    f.feed(e40);
1821
    f.feed(f40);
1822
1823
    f.feed(e50);
1824
    f.feed(f50);
1825
1826
    f.feed(e60);
1827
    f.feed(e70);
1828
1829
    f.finish();
1830
1831
    AgentFeeder::Hypotheses accepted = f.getAcceptedHypotheses();
1832
1833
    BOOST_CHECK_EQUAL(accepted.size(), size_t(1));
1834
1835
    AgentFeeder::Hypotheses::const_iterator i = accepted.begin();
1836
1837
    BOOST_CHECK_EQUAL(i->getStartTime(), ms(0));
1838
    BOOST_CHECK_EQUAL(i->getAcceptedEstimates().size(), size_t(8));
1839
    ++i;
1840
}
1841
1842
BOOST_AUTO_TEST_SUITE_END()
1843
1844 51:0997774f5fdc Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1845
/*
1846
    This file is Copyright (c) 2012 Chris Cannam
1847
1848
    Permission is hereby granted, free of charge, to any person
1849
    obtaining a copy of this software and associated documentation
1850
    files (the "Software"), to deal in the Software without
1851
    restriction, including without limitation the rights to use, copy,
1852
    modify, merge, publish, distribute, sublicense, and/or sell copies
1853
    of the Software, and to permit persons to whom the Software is
1854
    furnished to do so, subject to the following conditions:
1855
1856
    The above copyright notice and this permission notice shall be
1857
    included in all copies or substantial portions of the Software.
1858
1859
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1860
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1861
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1862
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1863
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1864
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1865
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1866
*/
1867
1868
#include "Cepstrum.h"
1869
1870
#define BOOST_TEST_DYN_LINK
1871
#define BOOST_TEST_MAIN
1872
1873
#include <boost/test/unit_test.hpp>
1874
1875
BOOST_AUTO_TEST_SUITE(TestCepstrum)
1876
1877
BOOST_AUTO_TEST_CASE(cosine)
1878
{
1879
    // input format is re0, im0, re1, im1...
1880
    float in[] = { 0,0, 10,0, 0,0 };
1881
    double out[4];
1882
    double mm = Cepstrum(4).process(in, out);
1883
    BOOST_CHECK_SMALL(out[0] - (-4.5), 1e-10);
1884
    BOOST_CHECK_EQUAL(out[1], 0);
1885
    BOOST_CHECK_SMALL(out[2] - (-5.5), 1e-10);
1886
    BOOST_CHECK_EQUAL(out[3], 0);
1887
    BOOST_CHECK_EQUAL(mm, 10.0/3.0);
1888
}
1889
1890
BOOST_AUTO_TEST_CASE(symmetry)
1891
{
1892
    // Cepstrum output bins 1..n-1 are symmetric about bin n/2
1893
    float in[] = { 1,2,3,4,5,6,7,8,9,10 };
1894
    double out[8];
1895
    double mm = Cepstrum(8).process(in, out);
1896
    BOOST_CHECK_SMALL(out[1] - out[7], 1e-14);
1897
    BOOST_CHECK_SMALL(out[2] - out[6], 1e-14);
1898
    BOOST_CHECK_SMALL(out[3] - out[5], 1e-14);
1899 73:939cf0e86268 Chris
    double mmcheck = 0;
1900
    for (int i = 0; i < 5; ++i) {
1901
        mmcheck += sqrt(in[i*2] * in[i*2] + in[i*2+1] * in[i*2+1]);
1902
    }
1903
    mmcheck /= 5;
1904
    BOOST_CHECK_EQUAL(mm, mmcheck);
1905 51:0997774f5fdc Chris
}
1906
1907
BOOST_AUTO_TEST_CASE(oneHarmonic)
1908
{
1909
    // input format is re0, im0, re1, im1, re2, im2
1910
    // freq for bin i is i * samplerate / n
1911
    // freqs:        0  sr/n  2sr/n  3sr/n  4sr/n  5sr/n  6sr/n  7sr/n  sr/2
1912
    float in[] = { 0,0,  0,0,  10,0,   0,0,  10,0,   0,0,  10,0,   0,0,  0,0 };
1913
    double out[16];
1914
    double mm = Cepstrum(16).process(in, out);
1915
    BOOST_CHECK_EQUAL(mm, 30.0/9.0);
1916
    // peak is at 8
1917
    BOOST_CHECK(out[8] > 0);
1918
    // odd bins are all zero
1919
    BOOST_CHECK_EQUAL(out[1], 0);
1920
    BOOST_CHECK_EQUAL(out[3], 0);
1921
    BOOST_CHECK_EQUAL(out[5], 0);
1922
    BOOST_CHECK_EQUAL(out[7], 0);
1923
    BOOST_CHECK_EQUAL(out[9], 0);
1924
    BOOST_CHECK_EQUAL(out[11], 0);
1925
    BOOST_CHECK_EQUAL(out[13], 0);
1926
    BOOST_CHECK_EQUAL(out[15], 0);
1927
    // the rest are negative
1928
    BOOST_CHECK(out[0] < 0);
1929
    BOOST_CHECK(out[2] < 0);
1930
    BOOST_CHECK(out[4] < 0);
1931
    BOOST_CHECK(out[6] < 0);
1932
    BOOST_CHECK(out[10] < 0);
1933
    BOOST_CHECK(out[12] < 0);
1934
    BOOST_CHECK(out[14] < 0);
1935
}
1936
1937
BOOST_AUTO_TEST_SUITE_END()
1938
1939 48:1343ecde267e Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
1940
/*
1941
  This file is Copyright (c) 2012 Chris Cannam
1942
1943
  Permission is hereby granted, free of charge, to any person
1944
  obtaining a copy of this software and associated documentation
1945
  files (the "Software"), to deal in the Software without
1946
  restriction, including without limitation the rights to use, copy,
1947
  modify, merge, publish, distribute, sublicense, and/or sell copies
1948
  of the Software, and to permit persons to whom the Software is
1949
  furnished to do so, subject to the following conditions:
1950
1951
  The above copyright notice and this permission notice shall be
1952
  included in all copies or substantial portions of the Software.
1953
1954
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1955
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1956
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1957
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1958
  ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1959
  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1960
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1961
*/
1962
1963
/*
1964
  This unit test suite for the Vamp SDK FFT implementation is included
1965
  here mostly for illustrative purposes!
1966
*/
1967
1968
#include "vamp-sdk/FFT.h"
1969
1970
#define BOOST_TEST_DYN_LINK
1971
#define BOOST_TEST_MAIN
1972
1973
#include <boost/test/unit_test.hpp>
1974
1975
BOOST_AUTO_TEST_SUITE(TestFFT)
1976
1977
#define COMPARE_CONST(a, n) \
1978
    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
1979 49:d6705fec7a94 Chris
        BOOST_CHECK_SMALL(a[cmp_i] - n, 1e-14);				\
1980 48:1343ecde267e Chris
    }
1981
1982
#define COMPARE_ARRAY(a, b)						\
1983
    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
1984 49:d6705fec7a94 Chris
        BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14);			\
1985 48:1343ecde267e Chris
    }
1986
1987
BOOST_AUTO_TEST_CASE(dc)
1988
{
1989
    // DC-only signal. The DC bin is purely real
1990
    double in[] = { 1, 1, 1, 1 };
1991
    double re[4], im[4];
1992
    Vamp::FFT::forward(4, in, 0, re, im);
1993
    BOOST_CHECK_EQUAL(re[0], 4.0);
1994
    BOOST_CHECK_EQUAL(re[1], 0.0);
1995
    BOOST_CHECK_EQUAL(re[2], 0.0);
1996
    COMPARE_CONST(im, 0.0);
1997
    double back[4];
1998
    double backim[4];
1999
    Vamp::FFT::inverse(4, re, im, back, backim);
2000
    COMPARE_ARRAY(back, in);
2001
}
2002
2003
BOOST_AUTO_TEST_CASE(sine)
2004
{
2005
    // Sine. Output is purely imaginary
2006
    double in[] = { 0, 1, 0, -1 };
2007
    double re[4], im[4];
2008
    Vamp::FFT::forward(4, in, 0, re, im);
2009
    COMPARE_CONST(re, 0.0);
2010
    BOOST_CHECK_EQUAL(im[0], 0.0);
2011
    BOOST_CHECK_EQUAL(im[1], -2.0);
2012
    BOOST_CHECK_EQUAL(im[2], 0.0);
2013
    double back[4];
2014
    double backim[4];
2015
    Vamp::FFT::inverse(4, re, im, back, backim);
2016
    COMPARE_ARRAY(back, in);
2017
}
2018
2019
BOOST_AUTO_TEST_CASE(cosine)
2020
{
2021
    // Cosine. Output is purely real
2022
    double in[] = { 1, 0, -1, 0 };
2023
    double re[4], im[4];
2024
    Vamp::FFT::forward(4, in, 0, re, im);
2025
    BOOST_CHECK_EQUAL(re[0], 0.0);
2026
    BOOST_CHECK_EQUAL(re[1], 2.0);
2027
    BOOST_CHECK_EQUAL(re[2], 0.0);
2028
    COMPARE_CONST(im, 0.0);
2029
    double back[4];
2030
    double backim[4];
2031
    Vamp::FFT::inverse(4, re, im, back, backim);
2032
    COMPARE_ARRAY(back, in);
2033
}
2034
2035
BOOST_AUTO_TEST_CASE(sineCosine)
2036
{
2037
    // Sine and cosine mixed
2038
    double in[] = { 0.5, 1, -0.5, -1 };
2039
    double re[4], im[4];
2040
    Vamp::FFT::forward(4, in, 0, re, im);
2041
    BOOST_CHECK_EQUAL(re[0], 0.0);
2042 49:d6705fec7a94 Chris
    BOOST_CHECK_CLOSE(re[1], 1.0, 1e-12);
2043 48:1343ecde267e Chris
    BOOST_CHECK_EQUAL(re[2], 0.0);
2044
    BOOST_CHECK_EQUAL(im[0], 0.0);
2045 49:d6705fec7a94 Chris
    BOOST_CHECK_CLOSE(im[1], -2.0, 1e-12);
2046 48:1343ecde267e Chris
    BOOST_CHECK_EQUAL(im[2], 0.0);
2047
    double back[4];
2048
    double backim[4];
2049
    Vamp::FFT::inverse(4, re, im, back, backim);
2050
    COMPARE_ARRAY(back, in);
2051
}
2052
2053
BOOST_AUTO_TEST_CASE(nyquist)
2054
{
2055
    double in[] = { 1, -1, 1, -1 };
2056
    double re[4], im[4];
2057
    Vamp::FFT::forward(4, in, 0, re, im);
2058
    BOOST_CHECK_EQUAL(re[0], 0.0);
2059
    BOOST_CHECK_EQUAL(re[1], 0.0);
2060
    BOOST_CHECK_EQUAL(re[2], 4.0);
2061
    COMPARE_CONST(im, 0.0);
2062
    double back[4];
2063
    double backim[4];
2064
    Vamp::FFT::inverse(4, re, im, back, backim);
2065
    COMPARE_ARRAY(back, in);
2066
}
2067
2068
BOOST_AUTO_TEST_CASE(dirac)
2069
{
2070
    double in[] = { 1, 0, 0, 0 };
2071
    double re[4], im[4];
2072
    Vamp::FFT::forward(4, in, 0, re, im);
2073
    BOOST_CHECK_EQUAL(re[0], 1.0);
2074
    BOOST_CHECK_EQUAL(re[1], 1.0);
2075
    BOOST_CHECK_EQUAL(re[2], 1.0);
2076
    COMPARE_CONST(im, 0.0);
2077
    double back[4];
2078
    double backim[4];
2079
    Vamp::FFT::inverse(4, re, im, back, backim);
2080
    COMPARE_ARRAY(back, in);
2081
}
2082
2083
BOOST_AUTO_TEST_CASE(forwardArrayBounds)
2084
{
2085
    // initialise bins to something recognisable, so we can tell
2086
    // if they haven't been written
2087
    double in[] = { 1, 1, -1, -1 };
2088
    double re[] = { 999, 999, 999, 999, 999, 999 };
2089
    double im[] = { 999, 999, 999, 999, 999, 999 };
2090
    Vamp::FFT::forward(4, in, 0, re+1, im+1);
2091
    // And check we haven't overrun the arrays
2092
    BOOST_CHECK_EQUAL(re[0], 999.0);
2093
    BOOST_CHECK_EQUAL(im[0], 999.0);
2094
    BOOST_CHECK_EQUAL(re[5], 999.0);
2095
    BOOST_CHECK_EQUAL(im[5], 999.0);
2096
}
2097
2098
BOOST_AUTO_TEST_CASE(inverseArrayBounds)
2099
{
2100
    // initialise bins to something recognisable, so we can tell
2101
    // if they haven't been written
2102
    double re[] = { 0, 1, 0 };
2103
    double im[] = { 0, -2, 0 };
2104
    double outre[] = { 999, 999, 999, 999, 999, 999 };
2105
    double outim[] = { 999, 999, 999, 999, 999, 999 };
2106
    Vamp::FFT::forward(4, re, im, outre+1, outim+1);
2107
    // And check we haven't overrun the arrays
2108
    BOOST_CHECK_EQUAL(outre[0], 999.0);
2109
    BOOST_CHECK_EQUAL(outim[0], 999.0);
2110
    BOOST_CHECK_EQUAL(outre[5], 999.0);
2111
    BOOST_CHECK_EQUAL(outim[5], 999.0);
2112
}
2113
2114
BOOST_AUTO_TEST_SUITE_END()
2115
2116 47:f72a470fe4b5 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2117
/*
2118
    This file is Copyright (c) 2012 Chris Cannam
2119
2120
    Permission is hereby granted, free of charge, to any person
2121
    obtaining a copy of this software and associated documentation
2122
    files (the "Software"), to deal in the Software without
2123
    restriction, including without limitation the rights to use, copy,
2124
    modify, merge, publish, distribute, sublicense, and/or sell copies
2125
    of the Software, and to permit persons to whom the Software is
2126
    furnished to do so, subject to the following conditions:
2127
2128
    The above copyright notice and this permission notice shall be
2129
    included in all copies or substantial portions of the Software.
2130
2131
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2132
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2133
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2134
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
2135
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
2136
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2137
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2138
*/
2139
2140
#include "MeanFilter.h"
2141
2142
#define BOOST_TEST_DYN_LINK
2143
#define BOOST_TEST_MAIN
2144
2145
#include <boost/test/unit_test.hpp>
2146
2147
BOOST_AUTO_TEST_SUITE(TestMeanFilter)
2148
2149
BOOST_AUTO_TEST_CASE(simpleFilter)
2150
{
2151
    double in[] = { 1.0, 2.0, 3.0 };
2152
    double out[3];
2153
    MeanFilter(3).filter(in, out, 3);
2154
    BOOST_CHECK_EQUAL(out[0], 1.5);
2155
    BOOST_CHECK_EQUAL(out[1], 2.0);
2156
    BOOST_CHECK_EQUAL(out[2], 2.5);
2157
}
2158
2159
BOOST_AUTO_TEST_CASE(simpleSubset)
2160
{
2161
    double in[] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
2162
    double out[3];
2163
    MeanFilter(3).filterSubsequence(in, out, 5, 3, 1);
2164
    BOOST_CHECK_EQUAL(out[0], 2.0);
2165
    BOOST_CHECK_EQUAL(out[1], 3.0);
2166
    BOOST_CHECK_EQUAL(out[2], 4.0);
2167
}
2168
2169
BOOST_AUTO_TEST_CASE(flenExceedsArraySize)
2170
{
2171
    double in[] = { 1.0, 2.0, 3.0 };
2172
    double out[3];
2173
    MeanFilter(5).filter(in, out, 3);
2174
    BOOST_CHECK_EQUAL(out[0], 2.0);
2175
    BOOST_CHECK_EQUAL(out[1], 2.0);
2176
    BOOST_CHECK_EQUAL(out[2], 2.0);
2177
}
2178
2179
BOOST_AUTO_TEST_CASE(flenIs1)
2180
{
2181
    double in[] = { 1.0, 2.0, 3.0 };
2182
    double out[3];
2183
    MeanFilter(1).filter(in, out, 3);
2184
    BOOST_CHECK_EQUAL(out[0], in[0]);
2185
    BOOST_CHECK_EQUAL(out[1], in[1]);
2186
    BOOST_CHECK_EQUAL(out[2], in[2]);
2187
}
2188
2189
BOOST_AUTO_TEST_CASE(arraySizeIs1)
2190
{
2191
    double in[] = { 1.0 };
2192
    double out[1];
2193
    MeanFilter(3).filter(in, out, 1);
2194
    BOOST_CHECK_EQUAL(out[0], in[0]);
2195
}
2196
2197
BOOST_AUTO_TEST_CASE(subsequenceLengthIs1)
2198
{
2199
    double in[] = { 1.0, 2.0, 3.0 };
2200
    double out[1];
2201
    MeanFilter(3).filterSubsequence(in, out, 3, 1, 2);
2202
    BOOST_CHECK_EQUAL(out[0], 2.5);
2203
}
2204
2205
BOOST_AUTO_TEST_SUITE_END()
2206
2207 33:5f2a57b1a75a Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2208 37:7bf67d2dfc30 Chris
/*
2209
    This file is Copyright (c) 2012 Chris Cannam
2210
2211
    Permission is hereby granted, free of charge, to any person
2212
    obtaining a copy of this software and associated documentation
2213
    files (the "Software"), to deal in the Software without
2214
    restriction, including without limitation the rights to use, copy,
2215
    modify, merge, publish, distribute, sublicense, and/or sell copies
2216
    of the Software, and to permit persons to whom the Software is
2217
    furnished to do so, subject to the following conditions:
2218
2219
    The above copyright notice and this permission notice shall be
2220
    included in all copies or substantial portions of the Software.
2221
2222
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2223
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2224
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2225
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
2226
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
2227
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2228
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2229
*/
2230 33:5f2a57b1a75a Chris
2231 38:944898c2e14e Chris
#include "NoteHypothesis.h"
2232 33:5f2a57b1a75a Chris
2233 38:944898c2e14e Chris
std::ostream &operator<<(std::ostream &out, const NoteHypothesis::Estimate &n)
2234 33:5f2a57b1a75a Chris
{
2235 38:944898c2e14e Chris
    return out << "[" << n.freq << "@" << n.time << ":" << n.confidence << "]" << std::endl;
2236 33:5f2a57b1a75a Chris
}
2237
2238 38:944898c2e14e Chris
std::ostream &operator<<(std::ostream &out, const NoteHypothesis::Estimates &e)
2239
{
2240
    out << "( ";
2241
    for (int i = 0; i < (int)e.size(); ++i) out << e[i] << "; ";
2242
    out << " )";
2243
    return out;
2244
}
2245
2246
std::ostream &operator<<(std::ostream &out, const NoteHypothesis::Note &n)
2247
{
2248
    return out << "[" << n.freq << "@" << n.time << ":" << n.duration << "]" << std::endl;
2249
}
2250
2251
#define BOOST_TEST_DYN_LINK
2252
#define BOOST_TEST_MAIN
2253
2254
#include <boost/test/unit_test.hpp>
2255
2256
using Vamp::RealTime;
2257
2258
BOOST_AUTO_TEST_SUITE(TestNoteHypothesis)
2259
2260
BOOST_AUTO_TEST_CASE(emptyAccept)
2261
{
2262 58:9f50a5876dd3 Chris
    // An empty hypothesis should accept any estimate with a
2263
    // non-negligible confidence, and enter provisional state
2264 38:944898c2e14e Chris
    NoteHypothesis h;
2265 58:9f50a5876dd3 Chris
    NoteHypothesis::Estimate e; // default estimate has confidence 1
2266 38:944898c2e14e Chris
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2267
    BOOST_CHECK(h.accept(e));
2268
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2269
}
2270 58:9f50a5876dd3 Chris
2271
BOOST_AUTO_TEST_CASE(noConfidence)
2272
{
2273
    // A hypothesis should reject any estimate that has a negligible
2274
    // confidence
2275
    NoteHypothesis h;
2276
    NoteHypothesis::Estimate e;
2277
    e.confidence = 0;
2278
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2279
    BOOST_CHECK(!h.accept(e));
2280
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Rejected);
2281
}
2282 60:c06fe5350b34 Chris
2283
BOOST_AUTO_TEST_CASE(noConfidenceIgnore)
2284
{
2285
    // But if we're already in process we don't go to rejected state,
2286
    // we just ignore this hypothesis
2287
    NoteHypothesis h;
2288
    NoteHypothesis::Estimate e;
2289
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2290
    BOOST_CHECK(h.accept(e));
2291
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2292
    e.confidence = 0;
2293
    BOOST_CHECK(!h.accept(e));
2294
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2295
}
2296 45:8db4a1f096f0 Chris
2297
BOOST_AUTO_TEST_CASE(tooSlow)
2298
{
2299
    // Having accepted a first estimate, a hypothesis should reject a
2300
    // second (and enter rejected state) if there is too long a gap
2301
    // between them for them to belong to a single note
2302
    NoteHypothesis h;
2303
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
2304
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(50), 1);
2305
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2306
    BOOST_CHECK(h.accept(e1));
2307
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2308
    BOOST_CHECK(!h.accept(e2));
2309
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Rejected);
2310
}
2311 38:944898c2e14e Chris
2312
BOOST_AUTO_TEST_CASE(simpleSatisfy)
2313
{
2314 45:8db4a1f096f0 Chris
    // A hypothesis should enter satisfied state after accepting three
2315
    // consistent estimates, and then remain satisfied while accepting
2316
    // further consistent estimates
2317
    NoteHypothesis h;
2318
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
2319
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1);
2320
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
2321
    NoteHypothesis::Estimate e4(500, RealTime::fromMilliseconds(30), 1);
2322
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2323
    BOOST_CHECK(h.accept(e1));
2324
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2325
    BOOST_CHECK(h.accept(e2));
2326
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2327
    BOOST_CHECK(h.accept(e3));
2328
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2329
    BOOST_CHECK(h.accept(e4));
2330
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2331
}
2332
2333
BOOST_AUTO_TEST_CASE(expiry)
2334
{
2335
    // A hypothesis that has been satisfied, but that is subsequently
2336
    // offered an estimate that follows too long a gap, should enter
2337
    // expired state rather than rejected state (showing that it has a
2338
    // valid note but that the note has apparently finished)
2339 38:944898c2e14e Chris
    NoteHypothesis h;
2340
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
2341
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1);
2342
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
2343
    NoteHypothesis::Estimate e4(500, RealTime::fromMilliseconds(30), 1);
2344
    NoteHypothesis::Estimate e5(500, RealTime::fromMilliseconds(80), 1);
2345
    NoteHypothesis::Estimate e6(500, RealTime::fromMilliseconds(90), 1);
2346
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2347
    BOOST_CHECK(h.accept(e1));
2348
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2349
    BOOST_CHECK(h.accept(e2));
2350
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2351
    BOOST_CHECK(h.accept(e3));
2352
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2353
    BOOST_CHECK(h.accept(e4));
2354
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2355
    BOOST_CHECK(!h.accept(e5));
2356
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired);
2357
    BOOST_CHECK(!h.accept(e6));
2358
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired);
2359
}
2360
2361 45:8db4a1f096f0 Chris
BOOST_AUTO_TEST_CASE(strayReject1)
2362 38:944898c2e14e Chris
{
2363 45:8db4a1f096f0 Chris
    // A wildly different frequency occurring in the middle of a
2364
    // provisionally accepted note should be ignored
2365 38:944898c2e14e Chris
    NoteHypothesis h;
2366
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
2367
    NoteHypothesis::Estimate e2(1000, RealTime::fromMilliseconds(10), 1);
2368
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
2369
    NoteHypothesis::Estimate e4(500, RealTime::fromMilliseconds(30), 1);
2370
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2371
    BOOST_CHECK(h.accept(e1));
2372
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2373
    BOOST_CHECK(!h.accept(e2));
2374
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2375
    BOOST_CHECK(h.accept(e3));
2376
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2377
    BOOST_CHECK(h.accept(e4));
2378
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2379
}
2380 45:8db4a1f096f0 Chris
2381
BOOST_AUTO_TEST_CASE(strayReject2)
2382 38:944898c2e14e Chris
{
2383 45:8db4a1f096f0 Chris
    // A wildly different frequency occurring in the middle of a
2384
    // satisfied note should be ignored
2385 38:944898c2e14e Chris
    NoteHypothesis h;
2386
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
2387 45:8db4a1f096f0 Chris
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1);
2388
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
2389
    NoteHypothesis::Estimate e4(1000, RealTime::fromMilliseconds(30), 1);
2390
    NoteHypothesis::Estimate e5(500, RealTime::fromMilliseconds(40), 1);
2391 38:944898c2e14e Chris
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2392
    BOOST_CHECK(h.accept(e1));
2393
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2394 45:8db4a1f096f0 Chris
    BOOST_CHECK(h.accept(e2));
2395
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2396
    BOOST_CHECK(h.accept(e3));
2397
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2398
    BOOST_CHECK(!h.accept(e4));
2399
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2400
    BOOST_CHECK(h.accept(e5));
2401
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2402 38:944898c2e14e Chris
}
2403
2404
BOOST_AUTO_TEST_CASE(weakSatisfy)
2405
{
2406 45:8db4a1f096f0 Chris
    // Behaviour with slightly varying frequencies should be as for
2407
    // that with fixed frequency
2408 38:944898c2e14e Chris
    NoteHypothesis h;
2409
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 0.5);
2410
    NoteHypothesis::Estimate e2(502, RealTime::fromMilliseconds(10), 0.5);
2411
    NoteHypothesis::Estimate e3(504, RealTime::fromMilliseconds(20), 0.5);
2412
    NoteHypothesis::Estimate e4(506, RealTime::fromMilliseconds(30), 0.5);
2413
    NoteHypothesis::Estimate e5(508, RealTime::fromMilliseconds(40), 0.5);
2414
    NoteHypothesis::Estimate e6(510, RealTime::fromMilliseconds(90), 0.5);
2415
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2416
    BOOST_CHECK(h.accept(e1));
2417
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2418
    BOOST_CHECK(h.accept(e2));
2419
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2420
    BOOST_CHECK(h.accept(e3));
2421
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2422
    BOOST_CHECK(h.accept(e4));
2423
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2424
    BOOST_CHECK(h.accept(e5));
2425
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2426
    BOOST_CHECK(!h.accept(e6));
2427
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired);
2428
}
2429
2430
BOOST_AUTO_TEST_CASE(frequencyRange)
2431
{
2432 45:8db4a1f096f0 Chris
    // But there's a limit: outside a certain range we should reject
2433
    //!!! (but what is this range? is it part of the spec?)
2434 38:944898c2e14e Chris
    NoteHypothesis h;
2435
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1);
2436
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1);
2437
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1);
2438
    NoteHypothesis::Estimate e4(470, RealTime::fromMilliseconds(30), 1);
2439
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2440
    BOOST_CHECK(h.accept(e1));
2441
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2442
    BOOST_CHECK(h.accept(e2));
2443
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2444
    BOOST_CHECK(h.accept(e3));
2445
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2446
    BOOST_CHECK(!h.accept(e4));
2447
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2448
}
2449
2450
BOOST_AUTO_TEST_CASE(acceptedEstimates)
2451
{
2452 45:8db4a1f096f0 Chris
    // Check that getAcceptedEstimates() returns the right result
2453 38:944898c2e14e Chris
    NoteHypothesis h;
2454
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1);
2455
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1);
2456
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1);
2457
    NoteHypothesis::Estimate e4(470, RealTime::fromMilliseconds(30), 1);
2458
    NoteHypothesis::Estimate e5(444, RealTime::fromMilliseconds(90), 1);
2459
    NoteHypothesis::Estimates es;
2460
    es.push_back(e1);
2461
    es.push_back(e2);
2462
    es.push_back(e3);
2463
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
2464
    BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), NoteHypothesis::Estimates());
2465
    BOOST_CHECK(h.accept(e1));
2466
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2467
    BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), NoteHypothesis::Estimates());
2468
    BOOST_CHECK(h.accept(e2));
2469
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
2470
    BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), NoteHypothesis::Estimates());
2471
    BOOST_CHECK(h.accept(e3));
2472
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2473
    BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), es);
2474
    BOOST_CHECK(!h.accept(e4));
2475
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied);
2476
    BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), es);
2477
    BOOST_CHECK(!h.accept(e5));
2478
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired);
2479
    BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), es);
2480
}
2481
2482
BOOST_AUTO_TEST_CASE(meanFrequency)
2483
{
2484 45:8db4a1f096f0 Chris
    // Check that the mean frequency is the mean of the frequencies
2485 38:944898c2e14e Chris
    NoteHypothesis h;
2486
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1);
2487
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1);
2488
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1);
2489
    BOOST_CHECK(h.accept(e1));
2490
    BOOST_CHECK(h.accept(e2));
2491
    BOOST_CHECK(h.accept(e3));
2492
    BOOST_CHECK_EQUAL(h.getMeanFrequency(), 444.0);
2493
}
2494
2495
BOOST_AUTO_TEST_CASE(averagedNote)
2496
{
2497 45:8db4a1f096f0 Chris
    // Check that getAveragedNote returns something sane
2498 38:944898c2e14e Chris
    NoteHypothesis h;
2499
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(10), 1);
2500
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(20), 1);
2501
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(30), 1);
2502
    BOOST_CHECK(h.accept(e1));
2503
    BOOST_CHECK(h.accept(e2));
2504
    BOOST_CHECK(h.accept(e3));
2505
    BOOST_CHECK_EQUAL(h.getAveragedNote(), NoteHypothesis::Note
2506
                      (444,
2507
                       RealTime::fromMilliseconds(10),
2508
                       RealTime::fromMilliseconds(20)));
2509
}
2510
2511 45:8db4a1f096f0 Chris
//!!! Not yet tested: Confidence scores
2512
2513 38:944898c2e14e Chris
BOOST_AUTO_TEST_SUITE_END()
2514
2515 39:822cf7b8e070 Chris
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2516
/*
2517
    This file is Copyright (c) 2012 Chris Cannam
2518
2519
    Permission is hereby granted, free of charge, to any person
2520
    obtaining a copy of this software and associated documentation
2521
    files (the "Software"), to deal in the Software without
2522
    restriction, including without limitation the rights to use, copy,
2523
    modify, merge, publish, distribute, sublicense, and/or sell copies
2524
    of the Software, and to permit persons to whom the Software is
2525
    furnished to do so, subject to the following conditions:
2526
2527
    The above copyright notice and this permission notice shall be
2528
    included in all copies or substantial portions of the Software.
2529
2530
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2531
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2532
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2533
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
2534
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
2535
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2536
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2537
*/
2538
2539
#include "PeakInterpolator.h"
2540
2541
#define BOOST_TEST_DYN_LINK
2542
#define BOOST_TEST_MAIN
2543
2544
#include <boost/test/unit_test.hpp>
2545
2546
BOOST_AUTO_TEST_SUITE(TestPeakInterpolator)
2547
2548
BOOST_AUTO_TEST_CASE(peakAtSample_N3)
2549
{
2550 46:c666067fb8da Chris
    // Peak exactly at sample index
2551
    double data[] = { 0.0, 10.0, 0.0 };
2552 39:822cf7b8e070 Chris
    PeakInterpolator p;
2553 46:c666067fb8da Chris
    // Asked to find peak at index 1, should return index 1
2554 39:822cf7b8e070 Chris
    double result = p.findPeakLocation(data, 3, 1);
2555
    BOOST_CHECK_EQUAL(result, 1.0);
2556 46:c666067fb8da Chris
    // Asked to find any peak, should return index 1
2557 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 3);
2558
    BOOST_CHECK_EQUAL(result, 1.0);
2559 39:822cf7b8e070 Chris
}
2560
2561
BOOST_AUTO_TEST_CASE(peakAtSample_N5)
2562
{
2563 46:c666067fb8da Chris
    // Peak exactly at sample index
2564
    double data[] = { 0.0, 10.0, 20.0, 10.0, 0.0 };
2565 39:822cf7b8e070 Chris
    PeakInterpolator p;
2566 46:c666067fb8da Chris
    // Asked to find peak at index 2, should return index 2
2567 39:822cf7b8e070 Chris
    double result = p.findPeakLocation(data, 5, 2);
2568
    BOOST_CHECK_EQUAL(result, 2.0);
2569 46:c666067fb8da Chris
    // Asked to find any peak, should return index 2
2570 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 5);
2571
    BOOST_CHECK_EQUAL(result, 2.0);
2572 39:822cf7b8e070 Chris
}
2573
2574
BOOST_AUTO_TEST_CASE(flat)
2575
{
2576 46:c666067fb8da Chris
    // No peak
2577 39:822cf7b8e070 Chris
    double data[] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
2578
    PeakInterpolator p;
2579 46:c666067fb8da Chris
    // Asked to find peak at index N, should return N (no superior neighbours)
2580 39:822cf7b8e070 Chris
    double result = p.findPeakLocation(data, 5, 2);
2581
    BOOST_CHECK_EQUAL(result, 2.0);
2582 46:c666067fb8da Chris
    // Asked to find any peak, should return 0 (first value as good as any)
2583 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 5);
2584
    BOOST_CHECK_EQUAL(result, 0.0);
2585 39:822cf7b8e070 Chris
}
2586
2587
BOOST_AUTO_TEST_CASE(multiPeak)
2588
{
2589 46:c666067fb8da Chris
    // More than one peak
2590 39:822cf7b8e070 Chris
    double data[] = { 1.0, 2.0, 1.0, 2.0, 1.0 };
2591
    PeakInterpolator p;
2592 46:c666067fb8da Chris
    // Asked to find peak at index 3, should return index 3
2593 39:822cf7b8e070 Chris
    double result = p.findPeakLocation(data, 5, 3);
2594
    BOOST_CHECK_EQUAL(result, 3.0);
2595 46:c666067fb8da Chris
    // But asked to find any peak, should return 1 (first peak)
2596 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 5);
2597
    BOOST_CHECK_EQUAL(result, 1.0);
2598 39:822cf7b8e070 Chris
}
2599
2600 40:8f56ef28b0b1 Chris
BOOST_AUTO_TEST_CASE(start)
2601
{
2602
    // Can't meaningfully interpolate if we're identifying element 0
2603 46:c666067fb8da Chris
    // as the peak (nothing to its left)
2604 40:8f56ef28b0b1 Chris
    double data[] = { 1.0, 1.0, 0.0, 0.0 };
2605
    PeakInterpolator p;
2606
    double result = p.findPeakLocation(data, 4, 0);
2607
    BOOST_CHECK_EQUAL(result, 0.0);
2608
}
2609
2610
BOOST_AUTO_TEST_CASE(end)
2611
{
2612
    // Likewise for the final element
2613
    double data[] = { 0.0, 0.0, 1.0, 1.0 };
2614
    PeakInterpolator p;
2615
    double result = p.findPeakLocation(data, 4, 3);
2616
    BOOST_CHECK_EQUAL(result, 3.0);
2617 46:c666067fb8da Chris
    // But when asked to find any peak, we expect idx 2 to be picked,
2618
    // not idx 3, so that will result in interpolation
2619 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 4);
2620
    BOOST_CHECK(result > 2.0 && result < 3.0);
2621 40:8f56ef28b0b1 Chris
}
2622
2623
BOOST_AUTO_TEST_CASE(longHalfway)
2624
{
2625 46:c666067fb8da Chris
    // Peak is exactly half-way between indices
2626 40:8f56ef28b0b1 Chris
    double data[] = { 1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0 };
2627
    PeakInterpolator p;
2628 46:c666067fb8da Chris
    // Asked to find peak for either index 3 or 4, should return 3.5
2629
    double result = p.findPeakLocation(data, 8, 3);
2630 40:8f56ef28b0b1 Chris
    BOOST_CHECK_EQUAL(result, 3.5);
2631 46:c666067fb8da Chris
    result = p.findPeakLocation(data, 8, 4);
2632
    BOOST_CHECK_EQUAL(result, 3.5);
2633
    // Likewise if asked to find any peak
2634 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 8);
2635
    BOOST_CHECK_EQUAL(result, 3.5);
2636 40:8f56ef28b0b1 Chris
}
2637
2638
BOOST_AUTO_TEST_CASE(shortHalfway)
2639
{
2640 46:c666067fb8da Chris
    // As longHalfway, but with fewer points
2641 40:8f56ef28b0b1 Chris
    double data[] = { 1.0, 2.0, 2.0, 1.0 };
2642
    PeakInterpolator p;
2643
    double result = p.findPeakLocation(data, 4, 1);
2644
    BOOST_CHECK_EQUAL(result, 1.5);
2645 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 4);
2646
    BOOST_CHECK_EQUAL(result, 1.5);
2647 40:8f56ef28b0b1 Chris
}
2648
2649 41:16908c2bd781 Chris
BOOST_AUTO_TEST_CASE(aboveHalfway)
2650
{
2651 46:c666067fb8da Chris
    // Peak is nearer to one index than its neighbour. (Exact position
2652
    // depends on the peak interpolation method in use; we only know
2653
    // that it must be beyond the half way point)
2654 41:16908c2bd781 Chris
    double data[] = { 1.0, 1.5, 2.0, 1.0 };
2655
    PeakInterpolator p;
2656
    double result = p.findPeakLocation(data, 4, 2);
2657
    BOOST_CHECK(result > 1.5 && result < 2.0);
2658 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 4);
2659
    BOOST_CHECK(result > 1.5 && result < 2.0);
2660 41:16908c2bd781 Chris
}
2661
2662
BOOST_AUTO_TEST_CASE(belowHalfway)
2663
{
2664 46:c666067fb8da Chris
    // Peak is nearer to one index than its neighbour. (Exact position
2665
    // depends on the peak interpolation method in use; we only know
2666
    // that it must be before the half way point)
2667 41:16908c2bd781 Chris
    double data[] = { 1.0, 2.0, 1.5, 1.0 };
2668
    PeakInterpolator p;
2669
    double result = p.findPeakLocation(data, 4, 1);
2670
    BOOST_CHECK(result > 1.0 && result < 1.5);
2671 42:45b4401136f6 Chris
    result = p.findPeakLocation(data, 4);
2672
    BOOST_CHECK(result > 1.0 && result < 1.5);
2673 41:16908c2bd781 Chris
}
2674
2675 39:822cf7b8e070 Chris
BOOST_AUTO_TEST_SUITE_END()
2676
2677 63:686ef2976366 Chris
_vampGetPluginDescriptor
2678
{
2679
	global: vampGetPluginDescriptor;
2680
	local: *;
2681
};