Chris@32: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@37: /* Chris@37: This file is Copyright (c) 2012 Chris Cannam Chris@37: Chris@37: Permission is hereby granted, free of charge, to any person Chris@37: obtaining a copy of this software and associated documentation Chris@37: files (the "Software"), to deal in the Software without Chris@37: restriction, including without limitation the rights to use, copy, Chris@37: modify, merge, publish, distribute, sublicense, and/or sell copies Chris@37: of the Software, and to permit persons to whom the Software is Chris@37: furnished to do so, subject to the following conditions: Chris@37: Chris@37: The above copyright notice and this permission notice shall be Chris@37: included in all copies or substantial portions of the Software. Chris@37: Chris@37: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@37: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@37: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@37: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR Chris@37: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@37: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@37: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@37: */ Chris@32: Chris@32: #include "NoteHypothesis.h" Chris@32: Chris@32: #include Chris@32: Chris@35: using Vamp::RealTime; Chris@32: Chris@32: NoteHypothesis::NoteHypothesis() Chris@32: { Chris@32: m_state = New; Chris@32: } Chris@32: Chris@32: NoteHypothesis::~NoteHypothesis() Chris@32: { Chris@32: } Chris@32: Chris@32: bool Chris@32: NoteHypothesis::isWithinTolerance(Estimate s) const Chris@32: { Chris@32: if (m_pending.empty()) { Chris@32: return true; Chris@32: } Chris@32: Chris@32: // check we are within a relatively close tolerance of the last Chris@32: // candidate Chris@32: Estimate last = m_pending[m_pending.size()-1]; Chris@32: double r = s.freq / last.freq; Chris@32: int cents = lrint(1200.0 * (log(r) / log(2.0))); Chris@32: if (cents < -60 || cents > 60) return false; Chris@32: Chris@32: // and within a slightly bigger tolerance of the current mean Chris@32: double meanFreq = getMeanFrequency(); Chris@32: r = s.freq / meanFreq; Chris@32: cents = lrint(1200.0 * (log(r) / log(2.0))); Chris@32: if (cents < -80 || cents > 80) return false; Chris@32: Chris@32: return true; Chris@32: } Chris@32: Chris@32: bool Chris@32: NoteHypothesis::isOutOfDateFor(Estimate s) const Chris@32: { Chris@32: if (m_pending.empty()) return false; Chris@32: return ((s.time - m_pending[m_pending.size()-1].time) > Chris@32: RealTime::fromMilliseconds(40)); Chris@32: } Chris@32: Chris@32: bool Chris@32: NoteHypothesis::isSatisfied() const Chris@32: { Chris@32: if (m_pending.empty()) return false; Chris@32: Chris@32: double meanConfidence = 0.0; Chris@32: for (int i = 0; i < (int)m_pending.size(); ++i) { Chris@32: meanConfidence += m_pending[i].confidence; Chris@32: } Chris@32: meanConfidence /= m_pending.size(); Chris@32: Chris@59: int lengthRequired = 100; Chris@32: if (meanConfidence > 0.0) { Chris@32: lengthRequired = int(2.0 / meanConfidence + 0.5); Chris@32: } Chris@32: Chris@32: return ((int)m_pending.size() > lengthRequired); Chris@32: } Chris@32: Chris@32: bool Chris@32: NoteHypothesis::accept(Estimate s) Chris@32: { Chris@32: bool accept = false; Chris@32: Chris@58: static double negligibleConfidence = 0.0001; Chris@58: Chris@58: if (s.confidence < negligibleConfidence) { Chris@58: // avoid piling up a lengthy sequence of estimates that are Chris@58: // all acceptable but are in total not enough to cause us to Chris@58: // be satisfied Chris@60: if (m_pending.empty()) { Chris@60: m_state = Rejected; Chris@60: } Chris@58: return false; Chris@58: } Chris@58: Chris@32: switch (m_state) { Chris@32: Chris@32: case New: Chris@32: m_state = Provisional; Chris@32: accept = true; Chris@32: break; Chris@32: Chris@32: case Provisional: Chris@32: if (isOutOfDateFor(s)) { Chris@32: m_state = Rejected; Chris@32: } else if (isWithinTolerance(s)) { Chris@32: accept = true; Chris@32: } Chris@32: break; Chris@32: Chris@32: case Satisfied: Chris@32: if (isOutOfDateFor(s)) { Chris@32: m_state = Expired; Chris@32: } else if (isWithinTolerance(s)) { Chris@32: accept = true; Chris@32: } Chris@32: break; Chris@32: Chris@32: case Rejected: Chris@32: break; Chris@32: Chris@32: case Expired: Chris@32: break; Chris@32: } Chris@32: Chris@32: if (accept) { Chris@32: m_pending.push_back(s); Chris@32: if (m_state == Provisional && isSatisfied()) { Chris@32: m_state = Satisfied; Chris@32: } Chris@32: } Chris@32: Chris@32: return accept; Chris@32: } Chris@32: Chris@32: NoteHypothesis::State Chris@32: NoteHypothesis::getState() const Chris@32: { Chris@32: return m_state; Chris@32: } Chris@32: Chris@32: NoteHypothesis::Estimates Chris@32: NoteHypothesis::getAcceptedEstimates() const Chris@32: { Chris@32: if (m_state == Satisfied || m_state == Expired) { Chris@32: return m_pending; Chris@32: } else { Chris@32: return Estimates(); Chris@32: } Chris@32: } Chris@32: Chris@52: RealTime Chris@52: NoteHypothesis::getStartTime() const Chris@52: { Chris@52: if (!(m_state == Satisfied || m_state == Expired)) { Chris@52: return RealTime::zeroTime; Chris@52: } else { Chris@52: return m_pending.begin()->time; Chris@52: } Chris@52: } Chris@52: Chris@32: double Chris@32: NoteHypothesis::getMeanFrequency() const Chris@32: { Chris@32: double acc = 0.0; Chris@34: if (m_pending.empty()) return acc; Chris@32: for (int i = 0; i < (int)m_pending.size(); ++i) { Chris@32: acc += m_pending[i].freq; Chris@32: } Chris@32: acc /= m_pending.size(); Chris@32: return acc; Chris@32: } Chris@32: Chris@32: NoteHypothesis::Note Chris@32: NoteHypothesis::getAveragedNote() const Chris@32: { Chris@32: Note n; Chris@32: Chris@32: if (!(m_state == Satisfied || m_state == Expired)) { Chris@32: n.freq = 0.0; Chris@32: n.time = RealTime::zeroTime; Chris@32: n.duration = RealTime::zeroTime; Chris@32: return n; Chris@32: } Chris@32: Chris@32: n.time = m_pending.begin()->time; Chris@32: Chris@32: Estimates::const_iterator i = m_pending.end(); Chris@32: --i; Chris@32: n.duration = i->time - n.time; Chris@32: Chris@32: // just mean frequency for now, but this isn't at all right perceptually Chris@32: n.freq = getMeanFrequency(); Chris@32: Chris@32: return n; Chris@32: } Chris@32: