Revision 35:2f5b169e4a3b CepstralPitchTracker.cpp
| CepstralPitchTracker.cpp | ||
|---|---|---|
| 37 | 37 |
using std::vector; |
| 38 | 38 |
using Vamp::RealTime; |
| 39 | 39 |
|
| 40 |
CepstralPitchTracker::Hypothesis::Hypothesis() |
|
| 41 |
{
|
|
| 42 |
m_state = New; |
|
| 43 |
} |
|
| 44 |
|
|
| 45 |
CepstralPitchTracker::Hypothesis::~Hypothesis() |
|
| 46 |
{
|
|
| 47 |
} |
|
| 48 |
|
|
| 49 |
bool |
|
| 50 |
CepstralPitchTracker::Hypothesis::isWithinTolerance(Estimate s) const |
|
| 51 |
{
|
|
| 52 |
if (m_pending.empty()) {
|
|
| 53 |
return true; |
|
| 54 |
} |
|
| 55 |
|
|
| 56 |
// check we are within a relatively close tolerance of the last |
|
| 57 |
// candidate |
|
| 58 |
Estimate last = m_pending[m_pending.size()-1]; |
|
| 59 |
double r = s.freq / last.freq; |
|
| 60 |
int cents = lrint(1200.0 * (log(r) / log(2.0))); |
|
| 61 |
if (cents < -60 || cents > 60) return false; |
|
| 62 |
|
|
| 63 |
// and within a slightly bigger tolerance of the current mean |
|
| 64 |
double meanFreq = getMeanFrequency(); |
|
| 65 |
r = s.freq / meanFreq; |
|
| 66 |
cents = lrint(1200.0 * (log(r) / log(2.0))); |
|
| 67 |
if (cents < -80 || cents > 80) return false; |
|
| 68 |
|
|
| 69 |
return true; |
|
| 70 |
} |
|
| 71 |
|
|
| 72 |
bool |
|
| 73 |
CepstralPitchTracker::Hypothesis::isOutOfDateFor(Estimate s) const |
|
| 74 |
{
|
|
| 75 |
if (m_pending.empty()) return false; |
|
| 76 |
|
|
| 77 |
return ((s.time - m_pending[m_pending.size()-1].time) > |
|
| 78 |
RealTime::fromMilliseconds(40)); |
|
| 79 |
} |
|
| 80 |
|
|
| 81 |
bool |
|
| 82 |
CepstralPitchTracker::Hypothesis::isSatisfied() const |
|
| 83 |
{
|
|
| 84 |
if (m_pending.empty()) return false; |
|
| 85 |
|
|
| 86 |
double meanConfidence = 0.0; |
|
| 87 |
for (int i = 0; i < m_pending.size(); ++i) {
|
|
| 88 |
meanConfidence += m_pending[i].confidence; |
|
| 89 |
} |
|
| 90 |
meanConfidence /= m_pending.size(); |
|
| 91 |
|
|
| 92 |
int lengthRequired = 10000; |
|
| 93 |
if (meanConfidence > 0.0) {
|
|
| 94 |
lengthRequired = int(2.0 / meanConfidence + 0.5); |
|
| 95 |
} |
|
| 96 |
|
|
| 97 |
return (m_pending.size() > lengthRequired); |
|
| 98 |
} |
|
| 99 |
|
|
| 100 |
bool |
|
| 101 |
CepstralPitchTracker::Hypothesis::accept(Estimate s) |
|
| 102 |
{
|
|
| 103 |
bool accept = false; |
|
| 104 |
|
|
| 105 |
switch (m_state) {
|
|
| 106 |
|
|
| 107 |
case New: |
|
| 108 |
m_state = Provisional; |
|
| 109 |
accept = true; |
|
| 110 |
break; |
|
| 111 |
|
|
| 112 |
case Provisional: |
|
| 113 |
if (isOutOfDateFor(s)) {
|
|
| 114 |
m_state = Rejected; |
|
| 115 |
} else if (isWithinTolerance(s)) {
|
|
| 116 |
accept = true; |
|
| 117 |
} |
|
| 118 |
break; |
|
| 119 |
|
|
| 120 |
case Satisfied: |
|
| 121 |
if (isOutOfDateFor(s)) {
|
|
| 122 |
m_state = Expired; |
|
| 123 |
} else if (isWithinTolerance(s)) {
|
|
| 124 |
accept = true; |
|
| 125 |
} |
|
| 126 |
break; |
|
| 127 |
|
|
| 128 |
case Rejected: |
|
| 129 |
break; |
|
| 130 |
|
|
| 131 |
case Expired: |
|
| 132 |
break; |
|
| 133 |
} |
|
| 134 |
|
|
| 135 |
if (accept) {
|
|
| 136 |
m_pending.push_back(s); |
|
| 137 |
if (m_state == Provisional && isSatisfied()) {
|
|
| 138 |
m_state = Satisfied; |
|
| 139 |
} |
|
| 140 |
} |
|
| 141 |
|
|
| 142 |
return accept; |
|
| 143 |
} |
|
| 144 |
|
|
| 145 |
CepstralPitchTracker::Hypothesis::State |
|
| 146 |
CepstralPitchTracker::Hypothesis::getState() const |
|
| 147 |
{
|
|
| 148 |
return m_state; |
|
| 149 |
} |
|
| 150 |
|
|
| 151 |
CepstralPitchTracker::Hypothesis::Estimates |
|
| 152 |
CepstralPitchTracker::Hypothesis::getAcceptedEstimates() const |
|
| 153 |
{
|
|
| 154 |
if (m_state == Satisfied || m_state == Expired) {
|
|
| 155 |
return m_pending; |
|
| 156 |
} else {
|
|
| 157 |
return Estimates(); |
|
| 158 |
} |
|
| 159 |
} |
|
| 160 |
|
|
| 161 |
double |
|
| 162 |
CepstralPitchTracker::Hypothesis::getMeanFrequency() const |
|
| 163 |
{
|
|
| 164 |
double acc = 0.0; |
|
| 165 |
for (int i = 0; i < m_pending.size(); ++i) {
|
|
| 166 |
acc += m_pending[i].freq; |
|
| 167 |
} |
|
| 168 |
acc /= m_pending.size(); |
|
| 169 |
return acc; |
|
| 170 |
} |
|
| 171 |
|
|
| 172 |
CepstralPitchTracker::Hypothesis::Note |
|
| 173 |
CepstralPitchTracker::Hypothesis::getAveragedNote() const |
|
| 174 |
{
|
|
| 175 |
Note n; |
|
| 176 |
|
|
| 177 |
if (!(m_state == Satisfied || m_state == Expired)) {
|
|
| 178 |
n.freq = 0.0; |
|
| 179 |
n.time = RealTime::zeroTime; |
|
| 180 |
n.duration = RealTime::zeroTime; |
|
| 181 |
return n; |
|
| 182 |
} |
|
| 183 |
|
|
| 184 |
n.time = m_pending.begin()->time; |
|
| 185 |
|
|
| 186 |
Estimates::const_iterator i = m_pending.end(); |
|
| 187 |
--i; |
|
| 188 |
n.duration = i->time - n.time; |
|
| 189 |
|
|
| 190 |
// just mean frequency for now, but this isn't at all right perceptually |
|
| 191 |
n.freq = getMeanFrequency(); |
|
| 192 |
|
|
| 193 |
return n; |
|
| 194 |
} |
|
| 195 | 40 |
|
| 196 | 41 |
CepstralPitchTracker::CepstralPitchTracker(float inputSampleRate) : |
| 197 | 42 |
Plugin(inputSampleRate), |
| ... | ... | |
| 320 | 165 |
{
|
| 321 | 166 |
OutputList outputs; |
| 322 | 167 |
|
| 323 |
int n = 0; |
|
| 324 |
|
|
| 325 | 168 |
OutputDescriptor d; |
| 326 | 169 |
|
| 327 | 170 |
d.identifier = "f0"; |
| ... | ... | |
| 391 | 234 |
} |
| 392 | 235 |
|
| 393 | 236 |
void |
| 394 |
CepstralPitchTracker::addFeaturesFrom(Hypothesis h, FeatureSet &fs) |
|
| 237 |
CepstralPitchTracker::addFeaturesFrom(NoteHypothesis h, FeatureSet &fs)
|
|
| 395 | 238 |
{
|
| 396 |
Hypothesis::Estimates es = h.getAcceptedEstimates(); |
|
| 239 |
NoteHypothesis::Estimates es = h.getAcceptedEstimates();
|
|
| 397 | 240 |
|
| 398 |
for (int i = 0; i < es.size(); ++i) {
|
|
| 241 |
for (int i = 0; i < (int)es.size(); ++i) {
|
|
| 399 | 242 |
Feature f; |
| 400 | 243 |
f.hasTimestamp = true; |
| 401 | 244 |
f.timestamp = es[i].time; |
| ... | ... | |
| 406 | 249 |
Feature nf; |
| 407 | 250 |
nf.hasTimestamp = true; |
| 408 | 251 |
nf.hasDuration = true; |
| 409 |
Hypothesis::Note n = h.getAveragedNote(); |
|
| 252 |
NoteHypothesis::Note n = h.getAveragedNote();
|
|
| 410 | 253 |
nf.timestamp = n.time; |
| 411 | 254 |
nf.duration = n.duration; |
| 412 | 255 |
nf.values.push_back(n.freq); |
| ... | ... | |
| 422 | 265 |
// average according to the vertical filter length |
| 423 | 266 |
for (int j = -m_vflen/2; j <= m_vflen/2; ++j) {
|
| 424 | 267 |
int ix = i + m_binFrom + j; |
| 425 |
if (ix >= 0 && ix < m_blockSize) {
|
|
| 268 |
if (ix >= 0 && ix < (int)m_blockSize) {
|
|
| 426 | 269 |
v += cep[ix]; |
| 427 | 270 |
++n; |
| 428 | 271 |
} |
| ... | ... | |
| 575 | 418 |
std::cerr << "magmean = " << magmean << ", confidence = " << confidence << std::endl; |
| 576 | 419 |
} |
| 577 | 420 |
|
| 578 |
Hypothesis::Estimate e; |
|
| 421 |
NoteHypothesis::Estimate e;
|
|
| 579 | 422 |
e.freq = peakfreq; |
| 580 | 423 |
e.time = timestamp; |
| 581 | 424 |
e.confidence = confidence; |
| 582 | 425 |
|
| 583 |
// m_good.advanceTime(); |
|
| 584 |
for (int i = 0; i < m_possible.size(); ++i) {
|
|
| 585 |
// m_possible[i].advanceTime(); |
|
| 586 |
} |
|
| 587 |
|
|
| 588 | 426 |
if (!m_good.accept(e)) {
|
| 589 | 427 |
|
| 590 | 428 |
int candidate = -1; |
| 591 | 429 |
bool accepted = false; |
| 592 | 430 |
|
| 593 |
for (int i = 0; i < m_possible.size(); ++i) {
|
|
| 431 |
for (int i = 0; i < (int)m_possible.size(); ++i) {
|
|
| 594 | 432 |
if (m_possible[i].accept(e)) {
|
| 595 |
if (m_possible[i].getState() == Hypothesis::Satisfied) {
|
|
| 433 |
if (m_possible[i].getState() == NoteHypothesis::Satisfied) {
|
|
| 596 | 434 |
accepted = true; |
| 597 | 435 |
candidate = i; |
| 598 | 436 |
} |
| ... | ... | |
| 601 | 439 |
} |
| 602 | 440 |
|
| 603 | 441 |
if (!accepted) {
|
| 604 |
Hypothesis h; |
|
| 442 |
NoteHypothesis h;
|
|
| 605 | 443 |
h.accept(e); //!!! must succeed as h is new, so perhaps there should be a ctor for this |
| 606 | 444 |
m_possible.push_back(h); |
| 607 | 445 |
} |
| 608 | 446 |
|
| 609 |
if (m_good.getState() == Hypothesis::Expired) {
|
|
| 447 |
if (m_good.getState() == NoteHypothesis::Expired) {
|
|
| 610 | 448 |
addFeaturesFrom(m_good, fs); |
| 611 | 449 |
} |
| 612 | 450 |
|
| 613 |
if (m_good.getState() == Hypothesis::Expired || |
|
| 614 |
m_good.getState() == Hypothesis::Rejected) {
|
|
| 451 |
if (m_good.getState() == NoteHypothesis::Expired ||
|
|
| 452 |
m_good.getState() == NoteHypothesis::Rejected) {
|
|
| 615 | 453 |
if (candidate >= 0) {
|
| 616 | 454 |
m_good = m_possible[candidate]; |
| 617 | 455 |
} else {
|
| 618 |
m_good = Hypothesis(); |
|
| 456 |
m_good = NoteHypothesis();
|
|
| 619 | 457 |
} |
| 620 | 458 |
} |
| 621 | 459 |
|
| 622 | 460 |
// reap rejected/expired hypotheses from possible list |
| 623 | 461 |
Hypotheses toReap = m_possible; |
| 624 | 462 |
m_possible.clear(); |
| 625 |
for (int i = 0; i < toReap.size(); ++i) {
|
|
| 626 |
Hypothesis h = toReap[i]; |
|
| 627 |
if (h.getState() != Hypothesis::Rejected && |
|
| 628 |
h.getState() != Hypothesis::Expired) {
|
|
| 463 |
for (int i = 0; i < (int)toReap.size(); ++i) {
|
|
| 464 |
NoteHypothesis h = toReap[i];
|
|
| 465 |
if (h.getState() != NoteHypothesis::Rejected &&
|
|
| 466 |
h.getState() != NoteHypothesis::Expired) {
|
|
| 629 | 467 |
m_possible.push_back(h); |
| 630 | 468 |
} |
| 631 | 469 |
} |
| ... | ... | |
| 639 | 477 |
CepstralPitchTracker::getRemainingFeatures() |
| 640 | 478 |
{
|
| 641 | 479 |
FeatureSet fs; |
| 642 |
if (m_good.getState() == Hypothesis::Satisfied) {
|
|
| 480 |
if (m_good.getState() == NoteHypothesis::Satisfied) {
|
|
| 643 | 481 |
addFeaturesFrom(m_good, fs); |
| 644 | 482 |
} |
| 645 | 483 |
return fs; |
Also available in: Unified diff