Revision 0:89c5d5998cda

View differences:

.hgignore
1
syntax: glob
2
*.o
3
*.so
4
*.bak
5
test/test-*
.hgtags
1
f31b2da9258dfc05000ab2741d2ce63aa45637ba v1.0
AgentFeeder.cpp
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
/*
3
    This file is Copyright (c) 2012 Chris Cannam
4
  
5
    Permission is hereby granted, free of charge, to any person
6
    obtaining a copy of this software and associated documentation
7
    files (the "Software"), to deal in the Software without
8
    restriction, including without limitation the rights to use, copy,
9
    modify, merge, publish, distribute, sublicense, and/or sell copies
10
    of the Software, and to permit persons to whom the Software is
11
    furnished to do so, subject to the following conditions:
12

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

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

  
25
#include "AgentFeeder.h"
26

  
27
void AgentFeeder::feed(NoteHypothesis::Estimate e)
28
{
29
    if (m_haveCurrent) {
30
        if (m_current.accept(e)) {
31
            return;
32
        }
33
        if (m_current.getState() == NoteHypothesis::Expired) {
34
            m_accepted.push_back(m_current);
35
            m_haveCurrent = false;
36
        }
37
    }
38

  
39
    bool swallowed = false;
40

  
41
    Hypotheses newCandidates;
42

  
43
    for (Hypotheses::iterator i = m_candidates.begin();
44
         i != m_candidates.end(); ++i) {
45
        
46
        NoteHypothesis h = *i;
47
        
48
        if (swallowed) {
49
            
50
            // don't offer: each observation can only belong to one
51
            // satisfied hypothesis
52
            newCandidates.push_back(h);
53
            
54
        } else {
55
            
56
            if (h.accept(e)) {
57
                
58
                if (h.getState() == NoteHypothesis::Satisfied) {
59
                    
60
                    swallowed = true;
61
                    
62
                    if (!m_haveCurrent ||
63
                        m_current.getState() == NoteHypothesis::Expired ||
64
                        m_current.getState() == NoteHypothesis::Rejected) {
65
                        m_current = h;
66
                        m_haveCurrent = true;
67
                    } else {
68
                        newCandidates.push_back(h);
69
                    }
70
                    
71
                } else {
72
                    newCandidates.push_back(h);
73
                }
74
            }
75
        }
76
    }
77
    
78
    if (!swallowed) {
79
        NoteHypothesis h;
80
        if (h.accept(e)) {
81
            newCandidates.push_back(h);
82
        }
83
    }
84
    
85
    m_candidates = reap(newCandidates);
86
}
87

  
88
AgentFeeder::Hypotheses
89
AgentFeeder::reap(Hypotheses candidates)
90
{
91
    // reap rejected/expired hypotheses from list of candidates
92

  
93
    Hypotheses survived;
94
    for (Hypotheses::const_iterator i = candidates.begin();
95
         i != candidates.end(); ++i) {
96
        NoteHypothesis h = *i;
97
        if (h.getState() != NoteHypothesis::Rejected && 
98
            h.getState() != NoteHypothesis::Expired) {
99
            survived.push_back(h);
100
        }
101
    }
102

  
103
    return survived;
104
}
105
    
106
void
107
AgentFeeder::finish()
108
{
109
    if (m_current.getState() == NoteHypothesis::Satisfied) {
110
	m_accepted.push_back(m_current);
111
    }
112
}
113

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

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

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

  
25
#ifndef _AGENT_FEEDER_H_
26
#define _AGENT_FEEDER_H_
27

  
28
#include "NoteHypothesis.h"
29

  
30
#include <vector>
31

  
32
/**
33
 * Take a series of estimates (one at a time) and feed them to a set
34
 * of note hypotheses, creating a new candidate hypothesis for each
35
 * observation and also testing the observation against the existing
36
 * set of hypotheses.
37
 *
38
 * One satisfied hypothesis is considered to be "accepted" at any
39
 * moment (that is, the earliest contemporary hypothesis to have
40
 * become satisfied). The series of accepted and completed hypotheses
41
 * from construction to the present time can be queried through
42
 * getAcceptedHypotheses().
43
 *
44
 * Call feed() to provide a new observation. Call finish() when all
45
 * observations have been provided. The set of hypotheses returned by
46
 * getAcceptedHypotheses() will not be complete unless finish() has
47
 * been called.
48
 */
49
class AgentFeeder
50
{
51
public:
52
    AgentFeeder() : m_haveCurrent(false) { }
53

  
54
    void feed(NoteHypothesis::Estimate);
55
    void finish();
56

  
57
    typedef std::vector<NoteHypothesis> Hypotheses;
58

  
59
    const Hypotheses &getAcceptedHypotheses() const {
60
        return m_accepted;
61
    }
62

  
63
    Hypotheses reap(Hypotheses);
64

  
65
private:
66
    Hypotheses m_candidates;
67
    NoteHypothesis m_current;
68
    bool m_haveCurrent;
69
    Hypotheses m_accepted;
70
};
71

  
72

  
73
#endif
74

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

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

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

  
25
#include "CepstralPitchTracker.h"
26
#include "Cepstrum.h"
27
#include "MeanFilter.h"
28
#include "PeakInterpolator.h"
29
#include "AgentFeeder.h"
30

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

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

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

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

  
44

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

  
61
CepstralPitchTracker::~CepstralPitchTracker()
62
{
63
    delete m_feeder;
64
}
65

  
66
string
67
CepstralPitchTracker::getIdentifier() const
68
{
69
    return "cepstral-pitchtracker";
70
}
71

  
72
string
73
CepstralPitchTracker::getName() const
74
{
75
    return "Cepstral Pitch Tracker";
76
}
77

  
78
string
79
CepstralPitchTracker::getDescription() const
80
{
81
    return "Estimate f0 of monophonic material using a cepstrum method.";
82
}
83

  
84
string
85
CepstralPitchTracker::getMaker() const
86
{
87
    return "Chris Cannam";
88
}
89

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

  
98
string
99
CepstralPitchTracker::getCopyright() const
100
{
101
    return "Freely redistributable (BSD license)";
102
}
103

  
104
CepstralPitchTracker::InputDomain
105
CepstralPitchTracker::getInputDomain() const
106
{
107
    return FrequencyDomain;
108
}
109

  
110
size_t
111
CepstralPitchTracker::getPreferredBlockSize() const
112
{
113
    return 1024;
114
}
115

  
116
size_t 
117
CepstralPitchTracker::getPreferredStepSize() const
118
{
119
    return 256;
120
}
121

  
122
size_t
123
CepstralPitchTracker::getMinChannelCount() const
124
{
125
    return 1;
126
}
127

  
128
size_t
129
CepstralPitchTracker::getMaxChannelCount() const
130
{
131
    return 1;
132
}
133

  
134
CepstralPitchTracker::ParameterList
135
CepstralPitchTracker::getParameterDescriptors() const
136
{
137
    ParameterList list;
138
    return list;
139
}
140

  
141
float
142
CepstralPitchTracker::getParameter(string identifier) const
143
{
144
    return 0.f;
145
}
146

  
147
void
148
CepstralPitchTracker::setParameter(string identifier, float value) 
149
{
150
}
151

  
152
CepstralPitchTracker::ProgramList
153
CepstralPitchTracker::getPrograms() const
154
{
155
    ProgramList list;
156
    return list;
157
}
158

  
159
string
160
CepstralPitchTracker::getCurrentProgram() const
161
{
162
    return ""; // no programs
163
}
164

  
165
void
166
CepstralPitchTracker::selectProgram(string name)
167
{
168
}
169

  
170
CepstralPitchTracker::OutputList
171
CepstralPitchTracker::getOutputDescriptors() const
172
{
173
    OutputList outputs;
174

  
175
    OutputDescriptor d;
176

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

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

  
207
    return outputs;
208
}
209

  
210
bool
211
CepstralPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
212
{
213
    if (channels < getMinChannelCount() ||
214
	channels > getMaxChannelCount()) return false;
215

  
216
//    std::cerr << "CepstralPitchTracker::initialise: channels = " << channels
217
//	      << ", stepSize = " << stepSize << ", blockSize = " << blockSize
218
//	      << std::endl;
219

  
220
    m_channels = channels;
221
    m_stepSize = stepSize;
222
    m_blockSize = blockSize;
223

  
224
    m_binFrom = int(m_inputSampleRate / m_fmax);
225
    m_binTo = int(m_inputSampleRate / m_fmin); 
226

  
227
    if (m_binTo >= (int)m_blockSize / 2) {
228
        m_binTo = m_blockSize / 2 - 1;
229
    }
230
    if (m_binFrom >= m_binTo) {
231
        // shouldn't happen except for degenerate samplerate / blocksize combos
232
        m_binFrom = m_binTo - 1;
233
    }
234

  
235
    m_bins = (m_binTo - m_binFrom) + 1;
236

  
237
    reset();
238

  
239
    return true;
240
}
241

  
242
void
243
CepstralPitchTracker::reset()
244
{
245
    delete m_feeder;
246
    m_feeder = new AgentFeeder();
247
    m_nAccepted = 0;
248
}
249

  
250
void
251
CepstralPitchTracker::addFeaturesFrom(NoteHypothesis h, FeatureSet &fs)
252
{
253
    NoteHypothesis::Estimates es = h.getAcceptedEstimates();
254

  
255
    for (int i = 0; i < (int)es.size(); ++i) {
256
	Feature f;
257
	f.hasTimestamp = true;
258
	f.timestamp = es[i].time;
259
	f.values.push_back(es[i].freq);
260
	fs[0].push_back(f);
261
    }
262

  
263
    Feature nf;
264
    nf.hasTimestamp = true;
265
    nf.hasDuration = true;
266
    NoteHypothesis::Note n = h.getAveragedNote();
267
    nf.timestamp = n.time;
268
    nf.duration = n.duration;
269
    nf.values.push_back(n.freq);
270
    fs[1].push_back(nf);
271
}
272

  
273
void
274
CepstralPitchTracker::addNewFeatures(FeatureSet &fs)
275
{
276
    int n = m_feeder->getAcceptedHypotheses().size();
277
    if (n == m_nAccepted) return;
278

  
279
    AgentFeeder::Hypotheses accepted = m_feeder->getAcceptedHypotheses();
280

  
281
    for (int i = m_nAccepted; i < n; ++i) {
282
        addFeaturesFrom(accepted[i], fs);
283
    }
284

  
285
    m_nAccepted = n;
286
}
287

  
288
CepstralPitchTracker::FeatureSet
289
CepstralPitchTracker::process(const float *const *inputBuffers, RealTime timestamp)
290
{
291
    double *rawcep = new double[m_blockSize];
292
    double magmean = Cepstrum(m_blockSize).process(inputBuffers[0], rawcep);
293

  
294
    int n = m_bins;
295
    double *data = new double[n];
296
    MeanFilter(m_vflen).filterSubsequence
297
        (rawcep, data, m_blockSize, n, m_binFrom);
298

  
299
    delete[] rawcep;
300

  
301
    double maxval = 0.0;
302
    int maxbin = -1;
303

  
304
    for (int i = 0; i < n; ++i) {
305
        if (data[i] > maxval) {
306
            maxval = data[i];
307
            maxbin = i;
308
        }
309
    }
310

  
311
    if (maxbin < 0) {
312
        delete[] data;
313
        return FeatureSet();
314
    }
315

  
316
    double nextPeakVal = 0.0;
317
    for (int i = 1; i+1 < n; ++i) {
318
        if (data[i] > data[i-1] &&
319
            data[i] > data[i+1] &&
320
            i != maxbin &&
321
            data[i] > nextPeakVal) {
322
            nextPeakVal = data[i];
323
        }
324
    }
325

  
326
    PeakInterpolator pi;
327
    double cimax = pi.findPeakLocation(data, m_bins, maxbin);
328
    double peakfreq = m_inputSampleRate / (cimax + m_binFrom);
329

  
330
    double confidence = 0.0;
331
    double threshold = 0.1; // for magmean
332

  
333
    if (nextPeakVal != 0.0) {
334
        confidence = (maxval - nextPeakVal) * 10.0;
335
        if (magmean < threshold) confidence = 0.0;
336
    }
337

  
338
    delete[] data;
339

  
340
    NoteHypothesis::Estimate e;
341
    e.freq = peakfreq;
342
    e.time = timestamp;
343
    e.confidence = confidence;
344

  
345
    m_feeder->feed(e);
346

  
347
    FeatureSet fs;
348
    addNewFeatures(fs);
349
    return fs;
350
}
351

  
352
CepstralPitchTracker::FeatureSet
353
CepstralPitchTracker::getRemainingFeatures()
354
{
355
    m_feeder->finish();
356

  
357
    FeatureSet fs;
358
    addNewFeatures(fs);
359
    return fs;
360
}
CepstralPitchTracker.h
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
/*
3
    This file is Copyright (c) 2012 Chris Cannam
4
  
5
    Permission is hereby granted, free of charge, to any person
6
    obtaining a copy of this software and associated documentation
7
    files (the "Software"), to deal in the Software without
8
    restriction, including without limitation the rights to use, copy,
9
    modify, merge, publish, distribute, sublicense, and/or sell copies
10
    of the Software, and to permit persons to whom the Software is
11
    furnished to do so, subject to the following conditions:
12

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

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

  
25
#ifndef _CEPSTRAL_PITCH_H_
26
#define _CEPSTRAL_PITCH_H_
27

  
28
#include <vamp-sdk/Plugin.h>
29

  
30
#include "NoteHypothesis.h"
31

  
32
class AgentFeeder;
33

  
34
class CepstralPitchTracker : public Vamp::Plugin
35
{
36
public:
37
    CepstralPitchTracker(float inputSampleRate);
38
    virtual ~CepstralPitchTracker();
39

  
40
    std::string getIdentifier() const;
41
    std::string getName() const;
42
    std::string getDescription() const;
43
    std::string getMaker() const;
44
    int getPluginVersion() const;
45
    std::string getCopyright() const;
46

  
47
    InputDomain getInputDomain() const;
48
    size_t getPreferredBlockSize() const;
49
    size_t getPreferredStepSize() const;
50
    size_t getMinChannelCount() const;
51
    size_t getMaxChannelCount() const;
52

  
53
    ParameterList getParameterDescriptors() const;
54
    float getParameter(std::string identifier) const;
55
    void setParameter(std::string identifier, float value);
56

  
57
    ProgramList getPrograms() const;
58
    std::string getCurrentProgram() const;
59
    void selectProgram(std::string name);
60

  
61
    OutputList getOutputDescriptors() const;
62

  
63
    bool initialise(size_t channels, size_t stepSize, size_t blockSize);
64
    void reset();
65

  
66
    FeatureSet process(const float *const *inputBuffers,
67
                       Vamp::RealTime timestamp);
68

  
69
    FeatureSet getRemainingFeatures();
70

  
71
protected:
72
    size_t m_channels;
73
    size_t m_stepSize;
74
    size_t m_blockSize;
75
    float m_fmin;
76
    float m_fmax;
77
    int m_vflen;
78

  
79
    int m_binFrom;
80
    int m_binTo;
81
    int m_bins; // count of "interesting" bins, those returned in m_cepOutput
82

  
83
    int m_nAccepted;
84

  
85
    AgentFeeder *m_feeder;
86
    void addFeaturesFrom(NoteHypothesis h, FeatureSet &fs);
87
    void addNewFeatures(FeatureSet &fs);
88
};
89

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

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

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

  
25
#ifndef CEPSTRUM_H
26
#define CEPSTRUM_H
27

  
28
#include "vamp-sdk/FFT.h"
29
#include <cmath>
30

  
31
#include <iostream>
32
#include <exception>
33

  
34
class Cepstrum
35
{
36
public:
37
    /**
38
     * Construct a cepstrum converter based on an n-point FFT.
39
     */
40
    Cepstrum(int n) : m_n(n) {
41
	if (n & (n-1)) {
42
	    throw "N must be a power of two";
43
	}
44
    }
45
    ~Cepstrum() { }
46
    
47
    /**
48
     * Convert the given frequency-domain data to the cepstral domain.
49
     *
50
     * The input must be in the format used for FrequencyDomain data
51
     * in the Vamp SDK: n/2+1 consecutive pairs of real and imaginary
52
     * component floats corresponding to bins 0..(n/2) of the FFT
53
     * output. Thus, n+2 values in total.
54
     *
55
     * The output consists of the raw cepstrum of length n.
56
     *
57
     * The cepstrum is calculated as the inverse FFT of a
58
     * synthetically symmetrical base-10 log magnitude spectrum.
59
     *
60
     * Returns the mean magnitude of the input spectrum.
61
     */
62
    double process(const float *in, double *out) {
63

  
64
	int hs = m_n/2 + 1;
65
	double *io = new double[m_n];
66
	double *logmag = new double[m_n];
67
	double epsilon = 1e-10;
68

  
69
	double magmean = 0.0;
70

  
71
	for (int i = 0; i < hs; ++i) {
72

  
73
            double re = in[i*2];
74
            double im = in[i*2+1];
75
            double power = re * re + im * im;
76
	    double mag = sqrt(power);
77
	    magmean += mag;
78

  
79
	    logmag[i] = log10(mag + epsilon);
80

  
81
	    if (i > 0) {
82
		// make the log magnitude spectrum symmetrical
83
		logmag[m_n - i] = logmag[i];
84
	    }
85
	}
86
	
87
	magmean /= hs;
88
	/*
89
	std::cerr << "logmags:" << std::endl;
90
	for (int i = 0; i < m_n; ++i) {
91
	    std::cerr << logmag[i] << " ";
92
	}
93
	std::cerr << std::endl;
94
	*/
95
	Vamp::FFT::inverse(m_n, logmag, 0, out, io);
96
    
97
	delete[] logmag;
98
	delete[] io;
99

  
100
	return magmean;
101
    }
102

  
103
private:
104
    int m_n;
105
};
106

  
107
#endif
Makefile.inc
1 1

  
2 2
PLUGIN_EXT	?= .so
3

  
3
PLUGIN	?= simple-cepstrum$(PLUGIN_EXT)
4 4
CXX	?= g++
5 5
CC	?= gcc
6 6

  
7
CFLAGS := $(CFLAGS) 
8
CXXFLAGS := -I. $(CXXFLAGS)
7
CFLAGS		:= $(CFLAGS) 
8
CXXFLAGS	:= $(CXXFLAGS) 
9
LDFLAGS		:= $(LDFLAGS)
9 10

  
10
LDFLAGS := $(LDFLAGS) -lvamp-sdk
11
PLUGIN_LDFLAGS := $(LDFLAGS) $(PLUGIN_LDFLAGS)
12
TEST_LDFLAGS := $(LDFLAGS) -lboost_unit_test_framework
11
HEADERS := SimpleCepstrum.h
13 12

  
14
PLUGIN := cepstral-pitchtracker$(PLUGIN_EXT)
13
SOURCES := SimpleCepstrum.cpp \
14
           libmain.cpp
15 15

  
16
HEADERS := CepstralPitchTracker.h \
17
           AgentFeeder.h \
18
           MeanFilter.h \
19
	   NoteHypothesis.h \
20
	   PeakInterpolator.h
21

  
22
SOURCES := CepstralPitchTracker.cpp \
23
           AgentFeeder.cpp \
24
	   NoteHypothesis.cpp \
25
	   PeakInterpolator.cpp
26

  
27
PLUGIN_MAIN := libmain.cpp
28

  
29
TESTS ?= test/test-meanfilter \
30
         test/test-fft \
31
	 test/test-cepstrum \
32
         test/test-peakinterpolator \
33
	 test/test-notehypothesis \
34
	 test/test-agentfeeder
35
         
36 16
OBJECTS := $(SOURCES:.cpp=.o)
37 17
OBJECTS := $(OBJECTS:.c=.o)
38 18

  
39
PLUGIN_OBJECTS := $(OBJECTS) $(PLUGIN_MAIN:.cpp=.o)
40

  
41
all: $(PLUGIN) $(TESTS)
42
	for t in $(TESTS); do echo "Running $$t"; ./"$$t" || exit 1; done
43

  
44
$(PLUGIN): $(PLUGIN_OBJECTS)
45
	$(CXX) -o $@ $^ $(PLUGIN_LDFLAGS)
46

  
47
test/test-notehypothesis: test/TestNoteHypothesis.o $(OBJECTS)
48
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
49

  
50
test/test-agentfeeder: test/TestAgentFeeder.o $(OBJECTS)
51
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
52

  
53
test/test-meanfilter: test/TestMeanFilter.o $(OBJECTS)
54
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
55

  
56
test/test-cepstrum: test/TestCepstrum.o $(OBJECTS)
57
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
58

  
59
test/test-fft: test/TestFFT.o $(OBJECTS)
60
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
61

  
62
test/test-peakinterpolator: test/TestPeakInterpolator.o $(OBJECTS)
63
	$(CXX) -o $@ $^ $(TEST_LDFLAGS)
19
$(PLUGIN):	$(OBJECTS)
20
		$(CXX) -o $@ $^ $(LDFLAGS)
64 21

  
65 22
clean:		
66
		rm -f $(OBJECTS) test/*.o
23
		rm $(OBJECTS)
67 24

  
68 25
distclean:	clean
69
		rm -f $(PLUGIN) $(TESTS)
70

  
71
depend:
72
		makedepend -Y -fMakefile.inc *.cpp test/*.cpp *.h test/*.h
73

  
74
# DO NOT DELETE
75

  
76
CepstralPitchTracker.o: CepstralPitchTracker.h NoteHypothesis.h Cepstrum.h
77
CepstralPitchTracker.o: MeanFilter.h PeakInterpolator.h
78
libmain.o: CepstralPitchTracker.h NoteHypothesis.h
79
NoteHypothesis.o: NoteHypothesis.h
80
PeakInterpolator.o: PeakInterpolator.h
81
test/TestCepstrum.o: Cepstrum.h
82
test/TestMeanFilter.o: MeanFilter.h
83
test/TestNoteHypothesis.o: NoteHypothesis.h
84
test/TestPeakInterpolator.o: PeakInterpolator.h
85
CepstralPitchTracker.o: NoteHypothesis.h
26
		rm $(PLUGIN)
Makefile.linux64
1 1

  
2
CFLAGS := -Wall -O2 -fPIC 
2
CFLAGS := -Wall -g -fPIC 
3 3
CXXFLAGS := $(CFLAGS)
4 4

  
5
PLUGIN_LDFLAGS := -shared -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -Wl,-Bsymbolic -Wl,-z,defs -Wl,--version-script=vamp-plugin.map
5
LDFLAGS := -shared -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic 
6 6

  
7 7
PLUGIN_EXT := .so
8 8

  
Makefile.mingw32
1

  
2
CXX		= i486-mingw32-c++
3
CC		= i486-mingw32-gcc
4
LD            	= i486-mingw32-c++
5
AR            	= i486-mingw32-ar
6
RANLIB          = i486-mingw32-ranlib
7

  
8
TESTS		= test/null
9

  
10
CFLAGS := -Wall -O2 -I../include 
11
CXXFLAGS := $(CFLAGS)
12

  
13
LDFLAGS	 := -L../lib
14
PLUGIN_LDFLAGS := -shared -Wl,-Bstatic -static-libgcc -Wl,--version-script=vamp-plugin.map
15

  
16
PLUGIN_EXT := .dll
17

  
18
include Makefile.inc
19

  
20
test/null:	
21
		ln -s /bin/true test/null
Makefile.osx
1

  
2
CFLAGS := -I../inst/include -I/usr/local/boost -Wall -g -fPIC 
3
CXXFLAGS := $(CFLAGS)
4

  
5
LDFLAGS := -L../inst/lib -lvamp-sdk -L/usr/local/boost/stage/lib 
6
PLUGIN_LDFLAGS := -dynamiclib -exported_symbols_list=vamp-plugin.list
7
PLUGIN_EXT := .dylib
8

  
9
include Makefile.inc
10

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

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

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

  
25
#ifndef MEAN_FILTER_H
26
#define MEAN_FILTER_H
27

  
28
class MeanFilter
29
{
30
public:
31
    /**
32
     * Construct a non-causal mean filter with filter length flen,
33
     * that replaces each sample N with the mean of samples
34
     * [N-floor(F/2) .. N+floor(F/2)] where F is the filter length.
35
     * Only odd F are supported.
36
     */
37
    MeanFilter(int flen) : m_flen(flen) { }
38
    ~MeanFilter() { }
39

  
40
    /**
41
     * Filter the n samples in "in" and place the results in "out"
42
     */
43
    void filter(const double *in, double *out, const int n) {
44
	filterSubsequence(in, out, n, n, 0);
45
    }
46

  
47
    /**
48
     * Filter the n samples starting at the given offset in the
49
     * m-element array "in" and place the results in the n-element
50
     * array "out"
51
     */
52
    void filterSubsequence(const double *in, double *out,
53
			   const int m, const int n,
54
			   const int offset) {
55
	int half = m_flen/2;
56
	for (int i = 0; i < n; ++i) {
57
	    double v = 0;
58
	    int n = 0;
59
	    for (int j = -half; j <= half; ++j) {
60
		int ix = i + j + offset;
61
		if (ix >= 0 && ix < m) {
62
                    double value = in[ix];
63
                    if (value == value) { // i.e. not NaN
64
                        v += value;
65
                    }
66
                    ++n;
67
		}
68
	    }
69
            if (n > 0) {
70
                out[i] = v / n;
71
            } else {
72
                out[i] = 0.0;
73
            }
74
	}
75
    }
76

  
77
private:
78
    int m_flen;
79
};
80

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

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

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

  
25
#include "NoteHypothesis.h"
26

  
27
#include <cmath>
28

  
29
using Vamp::RealTime;
30

  
31
NoteHypothesis::NoteHypothesis()
32
{
33
    m_state = New;
34
}
35

  
36
NoteHypothesis::~NoteHypothesis()
37
{
38
}
39

  
40
bool
41
NoteHypothesis::isWithinTolerance(Estimate s) const
42
{
43
    if (m_pending.empty()) {
44
        return true;
45
    }
46

  
47
    // check we are within a relatively close tolerance of the last
48
    // candidate
49
    Estimate last = m_pending[m_pending.size()-1];
50
    double r = s.freq / last.freq;
51
    int cents = lrint(1200.0 * (log(r) / log(2.0)));
52
    if (cents < -60 || cents > 60) return false;
53

  
54
    // and within a slightly bigger tolerance of the current mean
55
    double meanFreq = getMeanFrequency();
56
    r = s.freq / meanFreq;
57
    cents = lrint(1200.0 * (log(r) / log(2.0)));
58
    if (cents < -80 || cents > 80) return false;
59
    
60
    return true;
61
}
62

  
63
bool
64
NoteHypothesis::isOutOfDateFor(Estimate s) const
65
{
66
    if (m_pending.empty()) return false;
67
    return ((s.time - m_pending[m_pending.size()-1].time) > 
68
            RealTime::fromMilliseconds(40));
69
}
70

  
71
bool 
72
NoteHypothesis::isSatisfied() const
73
{
74
    if (m_pending.empty()) return false;
75
    
76
    double meanConfidence = 0.0;
77
    for (int i = 0; i < (int)m_pending.size(); ++i) {
78
        meanConfidence += m_pending[i].confidence;
79
    }
80
    meanConfidence /= m_pending.size();
81

  
82
    int lengthRequired = 100;
83
    if (meanConfidence > 0.0) {
84
        lengthRequired = int(2.0 / meanConfidence + 0.5);
85
    }
86

  
87
    return ((int)m_pending.size() > lengthRequired);
88
}
89

  
90
bool
91
NoteHypothesis::accept(Estimate s)
92
{
93
    bool accept = false;
94

  
95
    static double negligibleConfidence = 0.0001;
96

  
97
    if (s.confidence < negligibleConfidence) {
98
        // avoid piling up a lengthy sequence of estimates that are
99
        // all acceptable but are in total not enough to cause us to
100
        // be satisfied
101
        if (m_pending.empty()) {
102
            m_state = Rejected;
103
        }
104
        return false;
105
    }
106

  
107
    switch (m_state) {
108

  
109
    case New:
110
        m_state = Provisional;
111
        accept = true;
112
        break;
113

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

  
130
    case Rejected:
131
        break;
132

  
133
    case Expired:
134
        break;
135
    }
136

  
137
    if (accept) {
138
        m_pending.push_back(s);
139
        if (m_state == Provisional && isSatisfied()) {
140
            m_state = Satisfied;
141
        }
142
    }
143

  
144
    return accept;
145
}        
146

  
147
NoteHypothesis::State
148
NoteHypothesis::getState() const
149
{
150
    return m_state;
151
}
152

  
153
NoteHypothesis::Estimates
154
NoteHypothesis::getAcceptedEstimates() const
155
{
156
    if (m_state == Satisfied || m_state == Expired) {
157
        return m_pending;
158
    } else {
159
        return Estimates();
160
    }
161
}
162

  
163
RealTime
164
NoteHypothesis::getStartTime() const
165
{
166
    if (!(m_state == Satisfied || m_state == Expired)) {
167
        return RealTime::zeroTime;
168
    } else {
169
        return m_pending.begin()->time;
170
    }
171
}
172

  
173
double
174
NoteHypothesis::getMeanFrequency() const
175
{
176
    double acc = 0.0;
177
    if (m_pending.empty()) return acc;
178
    for (int i = 0; i < (int)m_pending.size(); ++i) {
179
        acc += m_pending[i].freq;
180
    }
181
    acc /= m_pending.size();
182
    return acc;
183
}
184

  
185
NoteHypothesis::Note
186
NoteHypothesis::getAveragedNote() const
187
{
188
    Note n;
189

  
190
    if (!(m_state == Satisfied || m_state == Expired)) {
191
        n.freq = 0.0;
192
        n.time = RealTime::zeroTime;
193
        n.duration = RealTime::zeroTime;
194
        return n;
195
    }
196

  
197
    n.time = m_pending.begin()->time;
198

  
199
    Estimates::const_iterator i = m_pending.end();
200
    --i;
201
    n.duration = i->time - n.time;
202

  
203
    // just mean frequency for now, but this isn't at all right perceptually
204
    n.freq = getMeanFrequency();
205
    
206
    return n;
207
}
208

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

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

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

  
25
#ifndef _NOTE_HYPOTHESIS_H_
26
#define _NOTE_HYPOTHESIS_H_
27

  
28
#include "vamp-sdk/RealTime.h"
29
#include <vector>
30

  
31
/**
32
 * An agent used to test an incoming series of instantaneous pitch
33
 * estimates to see whether they fit a consistent single-note
34
 * relationship. Contains rules specific to testing note pitch and
35
 * timing.
36
 */
37

  
38
class NoteHypothesis
39
{
40
public:
41
    enum State {
42

  
43
        /// Just constructed, will provisionally accept any estimate
44
        New,
45

  
46
        /// Accepted at least one estimate, but not enough evidence to satisfy
47
        Provisional,
48

  
49
        /// Could not find enough consistency in offered estimates
50
        Rejected,
51

  
52
        /// Have accepted enough consistent estimates to satisfy hypothesis
53
        Satisfied,
54

  
55
        /// Have been satisfied, but evidence has now changed: we're done
56
        Expired
57
    };
58
    
59
    /**
60
     * Construct an empty hypothesis. This will be in New state and
61
     * will provisionally accept any estimate.
62
     */
63
    NoteHypothesis();
64

  
65
    /**
66
     * Destroy the hypothesis
67
     */
68
    ~NoteHypothesis();
69

  
70
    struct Estimate {
71
        Estimate() : freq(0), time(), confidence(1) { }
72
        Estimate(double _f, Vamp::RealTime _t, double _c) :
73
            freq(_f), time(_t), confidence(_c) { }
74
        bool operator==(const Estimate &e) const {
75
            return e.freq == freq && e.time == time && e.confidence == confidence;
76
        }
77
        double freq;
78
        Vamp::RealTime time;
79
        double confidence;
80
    };
81
    typedef std::vector<Estimate> Estimates;
82

  
83
    /**
84
     * Test the given estimate to see whether it is consistent with
85
     * this hypothesis, and adjust the hypothesis' internal state
86
     * accordingly. If the estimate is not inconsistent with the
87
     * hypothesis, return true.
88
     */
89
    bool accept(Estimate);
90

  
91
    /**
92
     * Return the current state of this hypothesis.
93
     */
94
    State getState() const;
95

  
96
    /**
97
     * If the hypothesis has been satisfied (i.e. is in Satisfied or
98
     * Expired state), return the series of estimates that it
99
     * accepted. Otherwise return an empty list
100
     */
101
    Estimates getAcceptedEstimates() const;
102

  
103
    struct Note {
104
        Note() : freq(0), time(), duration() { }
105
        Note(double _f, Vamp::RealTime _t, Vamp::RealTime _d) :
106
            freq(_f), time(_t), duration(_d) { }
107
        bool operator==(const Note &e) const {
108
            return e.freq == freq && e.time == time && e.duration == duration;
109
        }
110
        double freq;
111
        Vamp::RealTime time;
112
        Vamp::RealTime duration;
113
    };
114
    
115
    /**
116
     * Return the time of the first accepted estimate
117
     */
118
    Vamp::RealTime getStartTime() const;
119

  
120
    /**
121
     * Return the mean frequency of the accepted estimates
122
     */
123
    double getMeanFrequency() const;
124

  
125
    /**
126
     * Return a single note roughly matching this hypothesis
127
     */
128
    Note getAveragedNote() const;
129
    
130
private:
131
    bool isWithinTolerance(Estimate) const;
132
    bool isOutOfDateFor(Estimate) const;
133
    bool isSatisfied() const;
134
    
135
    State m_state;
136
    Estimates m_pending;
137
};
138

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

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

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

  
25
#include "PeakInterpolator.h"
26

  
27
#include <iostream>
28

  
29
double
30
PeakInterpolator::findPeakLocation(const double *data, int size)
31
{
32
    double maxval;
33
    int maxidx = 0;
34
    int i;
35
    for (i = 0; i < size; ++i) {
36
        if (i == 0 || data[i] > maxval) {
37
            maxval = data[i];
38
            maxidx = i;
39
        }
40
    }
41
    return findPeakLocation(data, size, maxidx);
42
}
43

  
44
double
45
PeakInterpolator::findPeakLocation(const double *data, int size, int peakIndex)
46
{
47
    // after jos, 
48
    // https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
49

  
50
    if (peakIndex < 1 || peakIndex > size - 2) {
51
        return peakIndex;
52
    }
53

  
54
    double alpha = data[peakIndex-1];
55
    double beta  = data[peakIndex];
56
    double gamma = data[peakIndex+1];
57

  
58
    double denom = (alpha - 2*beta + gamma);
59

  
60
    if (denom == 0) {
61
        // flat
62
        return peakIndex;
63
    }
64

  
65
    double p = ((alpha - gamma) / denom) / 2.0;
66

  
67
    return double(peakIndex) + p;
68
}
69

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

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

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

  
25
#ifndef _PEAK_INTERPOLATOR_H_
26
#define _PEAK_INTERPOLATOR_H_
27

  
28
class PeakInterpolator
29
{
30
public:
31
    PeakInterpolator() { } 
32
    ~PeakInterpolator() { }
33

  
34
    /**
35
     * Return the interpolated location (i.e. possibly between sample
36
     * point indices) of the peak in the given sampled range.
37
     *
38
     * "The peak" is defined as the (approximate) location of the
39
     * maximum of a function interpolating between the points
40
     * neighbouring the sample index with the maximum value in the
41
     * range.
42
     *
43
     * If multiple local peak samples in the input range are equal,
44
     * i.e. there is more than one apparent peak in the range, the one
45
     * with the lowest index will be used. This is the case even if a
46
     * later peak would be of superior height after interpolation.
47
     */
48
    double findPeakLocation(const double *data, int size);
49

  
50
    /**
51
     * Return the interpolated location (i.e. between sample point
52
     * indices) of the peak whose nearest sample is found at peakIndex
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff