Chris@181: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@181: Chris@181: /* Chris@181: Silvet Chris@181: Chris@181: A Vamp plugin for note transcription. Chris@181: Centre for Digital Music, Queen Mary University of London. Chris@181: This file Copyright 2012 Chris Cannam. Chris@181: Chris@181: This program is free software; you can redistribute it and/or Chris@181: modify it under the terms of the GNU General Public License as Chris@181: published by the Free Software Foundation; either version 2 of the Chris@181: License, or (at your option) any later version. See the file Chris@181: COPYING included with this distribution for more information. Chris@181: */ Chris@181: Chris@181: #include "NoteHypothesis.h" Chris@181: #include "AgentFeederPoly.h" Chris@181: Chris@181: #include Chris@181: #include Chris@181: Chris@181: #include Chris@184: #include Chris@181: Chris@181: using Vamp::RealTime; Chris@181: Chris@184: using std::cerr; Chris@184: using std::endl; Chris@184: Chris@188: //#define DEBUG_NOTE_HYPOTHESIS 1 Chris@181: Chris@181: NoteHypothesis::NoteHypothesis() Chris@181: { Chris@181: m_state = New; Chris@181: } Chris@181: Chris@181: NoteHypothesis::~NoteHypothesis() Chris@181: { Chris@181: } Chris@181: Chris@181: bool Chris@181: NoteHypothesis::isWithinTolerance(Observation s) const Chris@181: { Chris@181: if (m_pending.empty()) { Chris@181: return true; Chris@181: } Chris@181: Chris@181: // check we are within a relatively close tolerance of the last Chris@181: // candidate Chris@181: Observations::const_iterator i = m_pending.end(); Chris@181: --i; Chris@181: Observation last = *i; Chris@181: double r = s.value / last.value; Chris@181: int cents = lrint(1200.0 * (log(r) / log(2.0))); Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "isWithinTolerance: this " << s.value << " is " << cents Chris@184: << " cents from prior " << last.value << endl; Chris@181: #endif Chris@181: if (cents < -60 || cents > 60) return false; Chris@181: Chris@181: // and within a slightly bigger tolerance of the current mean Chris@181: double meanFreq = getMeanFrequency(); Chris@181: r = s.value / meanFreq; Chris@181: cents = lrint(1200.0 * (log(r) / log(2.0))); Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "isWithinTolerance: this " << s.value << " is " << cents Chris@184: << " cents from mean " << meanFreq << endl; Chris@181: #endif Chris@181: if (cents < -80 || cents > 80) return false; Chris@181: Chris@181: return true; Chris@181: } Chris@181: Chris@181: bool Chris@181: NoteHypothesis::isOutOfDateFor(Observation s) const Chris@181: { Chris@181: if (m_pending.empty()) return false; Chris@181: Chris@181: Observations::const_iterator i = m_pending.end(); Chris@181: --i; Chris@181: Observation last = *i; Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "isOutOfDateFor: this " << s.time << " is " Chris@181: << (s.time - last.time) << " from last " << last.time Chris@181: << " (threshold " << RealTime::fromMilliseconds(40) << ")" Chris@184: << endl; Chris@181: #endif Chris@181: Chris@181: return ((s.time - last.time) > RealTime::fromMilliseconds(40)); Chris@181: } Chris@181: Chris@181: bool Chris@181: NoteHypothesis::isSatisfied() const Chris@181: { Chris@181: if (m_pending.empty()) return false; Chris@181: Chris@181: double meanConfidence = 0.0; Chris@181: for (Observations::const_iterator i = m_pending.begin(); Chris@181: i != m_pending.end(); ++i) { Chris@181: meanConfidence += i->confidence; Chris@181: } Chris@181: meanConfidence /= m_pending.size(); Chris@181: Chris@184: //!!! surely this depends on the hop size? Chris@181: int lengthRequired = 100; Chris@181: if (meanConfidence > 0.0) { Chris@181: lengthRequired = int(2.0 / meanConfidence + 0.5); Chris@181: } Chris@184: //!!! Chris@184: lengthRequired = lengthRequired / 2; Chris@184: if (lengthRequired < 1) lengthRequired = 1; Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << endl; Chris@181: #endif Chris@181: Chris@181: return ((int)m_pending.size() > lengthRequired); Chris@181: } Chris@181: Chris@188: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: static void printState(NoteHypothesis::State s) Chris@184: { Chris@184: switch (s) { Chris@184: case NoteHypothesis::New: cerr << "New"; break; Chris@184: case NoteHypothesis::Provisional: cerr << "Provisional"; break; Chris@184: case NoteHypothesis::Rejected: cerr << "Rejected"; break; Chris@184: case NoteHypothesis::Satisfied: cerr << "Satisfied"; break; Chris@184: case NoteHypothesis::Expired: cerr << "Expired"; break; Chris@184: } Chris@184: } Chris@188: #endif Chris@184: Chris@181: bool Chris@181: NoteHypothesis::accept(Observation s) Chris@181: { Chris@181: bool accept = false; Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "NoteHypothesis[" << this << "]::accept (value " << s.value << ", time " << s.time << ", confidence " << s.confidence << "): state "; Chris@184: printState(m_state); Chris@184: cerr << "..." << endl; Chris@181: #endif Chris@181: Chris@181: static double negligibleConfidence = 0.0001; Chris@181: Chris@181: if (s.confidence < negligibleConfidence) { Chris@181: // avoid piling up a lengthy sequence of estimates that are Chris@181: // all acceptable but are in total not enough to cause us to Chris@181: // be satisfied Chris@181: if (m_state == New) { Chris@181: m_state = Rejected; Chris@181: } Chris@181: return false; Chris@181: } Chris@181: Chris@181: switch (m_state) { Chris@181: Chris@181: case New: Chris@181: m_state = Provisional; Chris@181: accept = true; Chris@181: break; Chris@181: Chris@181: case Provisional: Chris@181: if (isOutOfDateFor(s)) { Chris@181: m_state = Rejected; Chris@181: } else if (isWithinTolerance(s)) { Chris@181: accept = true; Chris@181: } Chris@181: break; Chris@181: Chris@181: case Satisfied: Chris@181: if (isOutOfDateFor(s)) { Chris@181: m_state = Expired; Chris@181: } else if (isWithinTolerance(s)) { Chris@181: accept = true; Chris@181: } Chris@181: break; Chris@181: Chris@181: case Rejected: Chris@181: break; Chris@181: Chris@181: case Expired: Chris@181: break; Chris@181: } Chris@181: Chris@181: if (accept) { Chris@184: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "... accepting" << endl; Chris@184: #endif Chris@181: m_pending.insert(s); Chris@181: if (m_state == Provisional && isSatisfied()) { Chris@181: m_state = Satisfied; Chris@181: } Chris@184: } else { Chris@184: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "... not accepting" << endl; Chris@184: #endif Chris@181: } Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@184: cerr << "... -> "; Chris@184: printState(m_state); Chris@184: cerr << " (pending: " << m_pending.size() << ")" << endl; Chris@181: #endif Chris@181: Chris@181: return accept; Chris@181: } Chris@181: Chris@181: NoteHypothesis::State Chris@181: NoteHypothesis::getState() const Chris@181: { Chris@181: return m_state; Chris@181: } Chris@181: Chris@181: NoteHypothesis::Observations Chris@181: NoteHypothesis::getAcceptedObservations() const Chris@181: { Chris@181: if (m_state == Satisfied || m_state == Expired) { Chris@181: return m_pending; Chris@181: } else { Chris@181: return Observations(); Chris@181: } Chris@181: } Chris@181: Chris@181: double Chris@184: NoteHypothesis::getMedianFrequency() const Chris@184: { Chris@184: if (m_pending.empty()) return 0.0; Chris@184: std::vector freqs; Chris@184: for (Observations::const_iterator i = m_pending.begin(); Chris@184: i != m_pending.end(); ++i) { Chris@184: freqs.push_back(i->value); Chris@184: } Chris@184: std::sort(freqs.begin(), freqs.end()); Chris@184: return freqs[freqs.size()/2]; Chris@184: } Chris@184: Chris@184: double Chris@181: NoteHypothesis::getMeanFrequency() const Chris@181: { Chris@181: double acc = 0.0; Chris@181: if (m_pending.empty()) return acc; Chris@181: for (Observations::const_iterator i = m_pending.begin(); Chris@181: i != m_pending.end(); ++i) { Chris@181: acc += i->value; Chris@181: } Chris@181: acc /= m_pending.size(); Chris@181: return acc; Chris@181: } Chris@181: Chris@184: double Chris@184: NoteHypothesis::getMedianConfidence() const Chris@184: { Chris@184: if (m_pending.empty()) return 0.0; Chris@184: std::vector confs; Chris@184: for (Observations::const_iterator i = m_pending.begin(); Chris@184: i != m_pending.end(); ++i) { Chris@184: confs.push_back(i->confidence); Chris@184: } Chris@184: std::sort(confs.begin(), confs.end()); Chris@184: return confs[confs.size()/2]; Chris@184: } Chris@184: Chris@181: NoteHypothesis::Note Chris@181: NoteHypothesis::getAveragedNote() const Chris@181: { Chris@181: Note n; Chris@181: Chris@181: n.time = getStartTime(); Chris@181: n.duration = getDuration(); Chris@184: n.freq = getMedianFrequency(); Chris@184: n.confidence = getMedianConfidence(); Chris@181: Chris@181: return n; Chris@181: } Chris@181: Chris@181: RealTime Chris@181: NoteHypothesis::getStartTime() const Chris@181: { Chris@181: if (!(m_state == Satisfied || m_state == Expired)) { Chris@181: return RealTime::zeroTime; Chris@181: } else { Chris@181: return m_pending.begin()->time; Chris@181: } Chris@181: } Chris@181: Chris@181: RealTime Chris@181: NoteHypothesis::getDuration() const Chris@181: { Chris@181: //!!! test this! it is wrong Chris@181: if (!(m_state == Satisfied || m_state == Expired)) { Chris@181: return RealTime::zeroTime; Chris@181: } else { Chris@181: RealTime start = m_pending.begin()->time; Chris@181: Observations::const_iterator i = m_pending.end(); Chris@181: --i; Chris@181: return i->time - start; Chris@181: } Chris@181: }