Revision 66:7ad142c710c6

View differences:

AgentFeeder.cpp
76 76
    }
77 77
    
78 78
    if (!swallowed) {
79
        NoteHypothesis h;
79
        NoteHypothesis h(m_slack);
80 80
        if (h.accept(e)) {
81 81
            newCandidates.push_back(h);
82 82
        }
AgentFeeder.h
49 49
class AgentFeeder
50 50
{
51 51
public:
52
    AgentFeeder() : m_haveCurrent(false) { }
52
    AgentFeeder(float slack) : 
53
        m_slack(slack),
54
        m_current(slack), 
55
        m_haveCurrent(false)
56
    { }
53 57

  
54 58
    void feed(NoteHypothesis::Estimate);
55 59
    void finish();
......
63 67
    Hypotheses reap(Hypotheses);
64 68

  
65 69
private:
70
    float m_slack;
66 71
    Hypotheses m_candidates;
67 72
    NoteHypothesis m_current;
68 73
    bool m_haveCurrent;
CepstralPitchTracker.cpp
50 50
    m_fmin(50),
51 51
    m_fmax(900),
52 52
    m_vflen(1),
53
    m_slack(40),
54
    m_sensitivity(10),
55
    m_threshold(0.1),
53 56
    m_binFrom(0),
54 57
    m_binTo(0),
55 58
    m_bins(0),
......
92 95
{
93 96
    // Increment this each time you release a version that behaves
94 97
    // differently from the previous one
95
    return 1;
98
    return 2;
96 99
}
97 100

  
98 101
string
......
135 138
CepstralPitchTracker::getParameterDescriptors() const
136 139
{
137 140
    ParameterList list;
141

  
142
    ParameterDescriptor d;
143
    d.identifier = "sensitivity";
144
    d.name = "Sensitivity";
145
    d.description = "Sensitivity of the voicing detector";
146
    d.unit = "";
147
    d.minValue = 0;
148
    d.maxValue = 100;
149
    d.defaultValue = 10;
150
    d.isQuantized = true;
151
    d.quantizeStep = 1;
152
    list.push_back(d);
153

  
154
    d.identifier = "slack";
155
    d.name = "Slack";
156
    d.description = "Maximum permissible length of voicing gap for a continuous note";
157
    d.unit = "ms";
158
    d.minValue = 0;
159
    d.maxValue = 200;
160
    d.defaultValue = 40;
161
    d.isQuantized = true;
162
    d.quantizeStep = 1;
163
    list.push_back(d);
164
    
165
    d.identifier = "threshold";
166
    d.name = "Silence threshold";
167
    d.description = "Threshold for silence detection";
168
    d.unit = ""; //!!! todo: convert this threshold to a meaningful unit!
169
    d.minValue = 0;
170
    d.maxValue = 0.5;
171
    d.defaultValue = 0.1;
172
    d.isQuantized = false;
173
    list.push_back(d);
174
    
138 175
    return list;
139 176
}
140 177

  
141 178
float
142 179
CepstralPitchTracker::getParameter(string identifier) const
143 180
{
181
    if (identifier == "sensitivity") return m_sensitivity;
182
    else if (identifier == "slack") return m_slack;
183
    else if (identifier == "threshold") return m_threshold;
144 184
    return 0.f;
145 185
}
146 186

  
147 187
void
148 188
CepstralPitchTracker::setParameter(string identifier, float value) 
149 189
{
190
    if (identifier == "sensitivity") m_sensitivity = value;
191
    else if (identifier == "slack") m_slack = value;
192
    else if (identifier == "threshold") m_threshold = value;
150 193
}
151 194

  
152 195
CepstralPitchTracker::ProgramList
......
204 247
    d.hasDuration = true;
205 248
    outputs.push_back(d);
206 249

  
250
    d.identifier = "raw";
251
    d.name = "Raw frequencies";
252
    d.description = "Raw peak frequencies from cepstrum, including unvoiced segments";
253
    d.unit = "Hz";
254
    d.hasFixedBinCount = true;
255
    d.binCount = 1;
256
    d.hasKnownExtents = true;
257
    d.minValue = m_fmin;
258
    d.maxValue = m_fmax;
259
    d.isQuantized = false;
260
    d.sampleType = OutputDescriptor::OneSamplePerStep;
261
    d.hasDuration = false;
262
    outputs.push_back(d);
263

  
207 264
    return outputs;
208 265
}
209 266

  
......
243 300
CepstralPitchTracker::reset()
244 301
{
245 302
    delete m_feeder;
246
    m_feeder = new AgentFeeder();
303
    m_feeder = new AgentFeeder(m_slack);
247 304
    m_nAccepted = 0;
248 305
}
249 306

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

  
387
    FeatureSet fs;
388
    Feature rawf;
389
    rawf.hasTimestamp = false;
390
    rawf.hasDuration = false;
391
    rawf.values.push_back(peakfreq);
392
    fs[2].push_back(rawf);
393

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

  
333 396
    if (nextPeakVal != 0.0) {
334
        confidence = (maxval - nextPeakVal) * 10.0;
335
        if (magmean < threshold) confidence = 0.0;
397
        confidence = (maxval - nextPeakVal) * m_sensitivity;
398
        if (magmean < m_threshold) confidence = 0.0;
336 399
    }
337 400

  
338 401
    delete[] data;
......
344 407

  
345 408
    m_feeder->feed(e);
346 409

  
347
    FeatureSet fs;
348 410
    addNewFeatures(fs);
349 411
    return fs;
350 412
}
CepstralPitchTracker.h
76 76
    float m_fmax;
77 77
    int m_vflen;
78 78

  
79
    float m_slack;
80
    float m_sensitivity;
81
    float m_threshold;
82

  
79 83
    int m_binFrom;
80 84
    int m_binTo;
81 85
    int m_bins; // count of "interesting" bins, those returned in m_cepOutput
Makefile.inc
73 73

  
74 74
# DO NOT DELETE
75 75

  
76
AgentFeeder.o: AgentFeeder.h NoteHypothesis.h
76 77
CepstralPitchTracker.o: CepstralPitchTracker.h NoteHypothesis.h Cepstrum.h
77
CepstralPitchTracker.o: MeanFilter.h PeakInterpolator.h
78
libmain.o: CepstralPitchTracker.h NoteHypothesis.h
78
CepstralPitchTracker.o: MeanFilter.h PeakInterpolator.h AgentFeeder.h
79 79
NoteHypothesis.o: NoteHypothesis.h
80 80
PeakInterpolator.o: PeakInterpolator.h
81
libmain.o: CepstralPitchTracker.h NoteHypothesis.h
82
test/TestAgentFeeder.o: AgentFeeder.h NoteHypothesis.h
81 83
test/TestCepstrum.o: Cepstrum.h
82 84
test/TestMeanFilter.o: MeanFilter.h
83 85
test/TestNoteHypothesis.o: NoteHypothesis.h
84 86
test/TestPeakInterpolator.o: PeakInterpolator.h
87
AgentFeeder.o: NoteHypothesis.h
85 88
CepstralPitchTracker.o: NoteHypothesis.h
NoteHypothesis.cpp
28 28

  
29 29
using Vamp::RealTime;
30 30

  
31
NoteHypothesis::NoteHypothesis()
31
NoteHypothesis::NoteHypothesis(float slack) :
32
    m_state(New),
33
    m_slack(slack)
32 34
{
33
    m_state = New;
34 35
}
35 36

  
36 37
NoteHypothesis::~NoteHypothesis()
......
65 66
{
66 67
    if (m_pending.empty()) return false;
67 68
    return ((s.time - m_pending[m_pending.size()-1].time) > 
68
            RealTime::fromMilliseconds(40));
69
            RealTime::fromMilliseconds(m_slack));
69 70
}
70 71

  
71 72
bool 
NoteHypothesis.h
57 57
    };
58 58
    
59 59
    /**
60
     * Construct an empty hypothesis. This will be in New state and
61
     * will provisionally accept any estimate.
60
     * Construct an empty hypothesis. The given slack (in
61
     * milliseconds) determines how long the hypothesis is prepared to
62
     * tolerate unacceptable estimates in between accepted estimates
63
     * before it becomes rejected. A reasonable default is 40ms.
64
     *
65
     * This hypothesis will be in New state and will provisionally
66
     * accept any estimate.
62 67
     */
63
    NoteHypothesis();
68
    NoteHypothesis(float slack);
64 69

  
65 70
    /**
66 71
     * Destroy the hypothesis
......
134 139
    
135 140
    State m_state;
136 141
    Estimates m_pending;
142
    float m_slack;
137 143
};
138 144

  
139 145
#endif
test/TestAgentFeeder.cpp
35 35

  
36 36
typedef NoteHypothesis::Estimate Est;
37 37

  
38
#define DEFAULT_SLACK_MS 40
39

  
38 40
BOOST_AUTO_TEST_SUITE(TestAgentFeeder)
39 41

  
40 42
BOOST_AUTO_TEST_CASE(feederEmpty)
41 43
{
42
    AgentFeeder f;
44
    AgentFeeder f(DEFAULT_SLACK_MS);
43 45
    f.finish();
44 46
    AgentFeeder::Hypotheses accepted = f.getAcceptedHypotheses();
45 47
    BOOST_CHECK(accepted.empty());
......
52 54
    Est e20(low, ms(20), 1);
53 55
    Est e30(low, ms(30), 1);
54 56

  
55
    AgentFeeder f;
57
    AgentFeeder f(DEFAULT_SLACK_MS);
56 58
    f.feed(e0);
57 59
    f.feed(e10);
58 60
    f.feed(e20);
......
76 78
    Est f20(high, ms(2020), 1);
77 79
    Est f30(high, ms(2030), 1);
78 80

  
79
    AgentFeeder f;
81
    AgentFeeder f(DEFAULT_SLACK_MS);
80 82
    f.feed(e0);
81 83
    f.feed(e10);
82 84
    f.feed(e20);
......
118 120
    Est f43(high, ms(43), 1);
119 121
    Est f44(high, ms(44), 1);
120 122

  
121
    AgentFeeder f;
123
    AgentFeeder f(DEFAULT_SLACK_MS);
122 124
    f.feed(e0);
123 125
    f.feed(e10);
124 126
    f.feed(e20);
......
171 173
    Est f70(high, ms(70), 1);
172 174
    Est f80(high, ms(80), 1);
173 175

  
174
    AgentFeeder f;
176
    AgentFeeder f(DEFAULT_SLACK_MS);
175 177
    f.feed(e0);
176 178
    f.feed(e10);
177 179
    f.feed(e20);
......
225 227
    Est f40(high, ms(40), 1);
226 228
    Est f50(high, ms(50), 1);
227 229

  
228
    AgentFeeder f;
230
    AgentFeeder f(DEFAULT_SLACK_MS);
229 231

  
230 232
    f.feed(e0);
231 233
    f.feed(e10);
test/TestNoteHypothesis.cpp
49 49

  
50 50
using Vamp::RealTime;
51 51

  
52
#define DEFAULT_SLACK_MS 40
53

  
52 54
BOOST_AUTO_TEST_SUITE(TestNoteHypothesis)
53 55

  
54 56
BOOST_AUTO_TEST_CASE(emptyAccept)
55 57
{
56 58
    // An empty hypothesis should accept any estimate with a
57 59
    // non-negligible confidence, and enter provisional state
58
    NoteHypothesis h;
60
    NoteHypothesis h(DEFAULT_SLACK_MS);
59 61
    NoteHypothesis::Estimate e; // default estimate has confidence 1
60 62
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
61 63
    BOOST_CHECK(h.accept(e));
......
66 68
{
67 69
    // A hypothesis should reject any estimate that has a negligible
68 70
    // confidence
69
    NoteHypothesis h;
71
    NoteHypothesis h(DEFAULT_SLACK_MS);
70 72
    NoteHypothesis::Estimate e;
71 73
    e.confidence = 0;
72 74
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
......
78 80
{
79 81
    // But if we're already in process we don't go to rejected state,
80 82
    // we just ignore this hypothesis
81
    NoteHypothesis h;
83
    NoteHypothesis h(DEFAULT_SLACK_MS);
82 84
    NoteHypothesis::Estimate e;
83 85
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
84 86
    BOOST_CHECK(h.accept(e));
......
92 94
{
93 95
    // Having accepted a first estimate, a hypothesis should reject a
94 96
    // second (and enter rejected state) if there is too long a gap
95
    // between them for them to belong to a single note
96
    NoteHypothesis h;
97
    // between them for them to belong to a single note. Test this
98
    // with more than one slack parameter, since that's what
99
    // determines how long the gap can be.
100

  
101
    {
102
    NoteHypothesis h(40);
97 103
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
98 104
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(50), 1);
99 105
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
......
101 107
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
102 108
    BOOST_CHECK(!h.accept(e2));
103 109
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Rejected);
110
    }
111
    {
112
    NoteHypothesis h(100);
113
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
114
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(50), 1);
115
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New);
116
    BOOST_CHECK(h.accept(e1));
117
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
118
    BOOST_CHECK(h.accept(e2));
119
    BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional);
120
    }
104 121
}
105 122

  
106 123
BOOST_AUTO_TEST_CASE(simpleSatisfy)
......
108 125
    // A hypothesis should enter satisfied state after accepting three
109 126
    // consistent estimates, and then remain satisfied while accepting
110 127
    // further consistent estimates
111
    NoteHypothesis h;
128
    NoteHypothesis h(DEFAULT_SLACK_MS);
112 129
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
113 130
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1);
114 131
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
......
130 147
    // offered an estimate that follows too long a gap, should enter
131 148
    // expired state rather than rejected state (showing that it has a
132 149
    // valid note but that the note has apparently finished)
133
    NoteHypothesis h;
150
    NoteHypothesis h(DEFAULT_SLACK_MS);
134 151
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
135 152
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1);
136 153
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
......
156 173
{
157 174
    // A wildly different frequency occurring in the middle of a
158 175
    // provisionally accepted note should be ignored
159
    NoteHypothesis h;
176
    NoteHypothesis h(DEFAULT_SLACK_MS);
160 177
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
161 178
    NoteHypothesis::Estimate e2(1000, RealTime::fromMilliseconds(10), 1);
162 179
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
......
176 193
{
177 194
    // A wildly different frequency occurring in the middle of a
178 195
    // satisfied note should be ignored
179
    NoteHypothesis h;
196
    NoteHypothesis h(DEFAULT_SLACK_MS);
180 197
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1);
181 198
    NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1);
182 199
    NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1);
......
199 216
{
200 217
    // Behaviour with slightly varying frequencies should be as for
201 218
    // that with fixed frequency
202
    NoteHypothesis h;
219
    NoteHypothesis h(DEFAULT_SLACK_MS);
203 220
    NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 0.5);
204 221
    NoteHypothesis::Estimate e2(502, RealTime::fromMilliseconds(10), 0.5);
205 222
    NoteHypothesis::Estimate e3(504, RealTime::fromMilliseconds(20), 0.5);
......
225 242
{
226 243
    // But there's a limit: outside a certain range we should reject
227 244
    //!!! (but what is this range? is it part of the spec?)
228
    NoteHypothesis h;
245
    NoteHypothesis h(DEFAULT_SLACK_MS);
229 246
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1);
230 247
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1);
231 248
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1);
......
244 261
BOOST_AUTO_TEST_CASE(acceptedEstimates)
245 262
{
246 263
    // Check that getAcceptedEstimates() returns the right result
247
    NoteHypothesis h;
264
    NoteHypothesis h(DEFAULT_SLACK_MS);
248 265
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1);
249 266
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1);
250 267
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1);
......
276 293
BOOST_AUTO_TEST_CASE(meanFrequency)
277 294
{
278 295
    // Check that the mean frequency is the mean of the frequencies
279
    NoteHypothesis h;
296
    NoteHypothesis h(DEFAULT_SLACK_MS);
280 297
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1);
281 298
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1);
282 299
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1);
......
289 306
BOOST_AUTO_TEST_CASE(averagedNote)
290 307
{
291 308
    // Check that getAveragedNote returns something sane
292
    NoteHypothesis h;
309
    NoteHypothesis h(DEFAULT_SLACK_MS);
293 310
    NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(10), 1);
294 311
    NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(20), 1);
295 312
    NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(30), 1);

Also available in: Unified diff