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@181: Chris@181: using Vamp::RealTime; Chris@181: Chris@181: //#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@181: std::cerr << "isWithinTolerance: this " << s.value << " is " << cents Chris@181: << " cents from prior " << last.value << std::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@181: std::cerr << "isWithinTolerance: this " << s.value << " is " << cents Chris@181: << " cents from mean " << meanFreq << std::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@181: std::cerr << "isOutOfDateFor: this " << s.time << " is " Chris@181: << (s.time - last.time) << " from last " << last.time Chris@181: << " (threshold " << RealTime::fromMilliseconds(40) << ")" Chris@181: << std::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@181: int lengthRequired = 100; Chris@181: if (meanConfidence > 0.0) { Chris@181: lengthRequired = int(2.0 / meanConfidence + 0.5); Chris@181: } Chris@181: // if (lengthRequired < 1) lengthRequired = 1; Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@181: std::cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << std::endl; Chris@181: #endif Chris@181: Chris@181: return ((int)m_pending.size() > lengthRequired); Chris@181: } Chris@181: 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@181: std::cerr << "NoteHypothesis[" << this << "]::accept: state " << m_state << "..." << std::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@181: m_pending.insert(s); Chris@181: if (m_state == Provisional && isSatisfied()) { Chris@181: m_state = Satisfied; Chris@181: } Chris@181: } Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@181: std::cerr << "... -> " << m_state << " (pending: " << m_pending.size() << ")" << std::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@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@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@181: Chris@181: // just mean frequency for now, but this isn't at all right perceptually Chris@181: n.freq = getMeanFrequency(); 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: } Chris@181: Chris@181: std::vector Chris@181: NoteHypothesis::sample(const std::set ¬es, Chris@181: RealTime startTime, Chris@181: RealTime endTime, Chris@181: RealTime interval) Chris@181: { Chris@181: //!!! where should this live? in AgentHypothesis? a Feeder class? Chris@181: Chris@181: assert(interval > RealTime::zeroTime); Chris@181: Chris@181: Observations obs = flatten(notes); Chris@181: Observations::const_iterator oi = obs.begin(); Chris@181: Chris@181: // std::cerr << "sample: start " << startTime << " end " << endTime << " interval " << interval << std::endl; Chris@181: Chris@181: // std::cerr << "sample: flatten gives " << obs.size() << " observations" << std::endl; Chris@181: Chris@181: std::vector samples; Chris@181: Chris@181: RealTime obsInterval; Chris@181: Chris@181: RealTime t = startTime; Chris@181: Chris@181: while (oi != obs.end()) { Chris@181: Chris@181: Observation o = *oi; Chris@181: Chris@181: if (obsInterval == RealTime()) { Chris@181: //!!! should pull out a function to establish this from the list Chris@181: Observations::const_iterator oj = oi; Chris@181: ++oj; Chris@181: if (oj != obs.end()) { Chris@181: obsInterval = oj->time - o.time; Chris@181: } Chris@181: } Chris@181: Chris@181: // std::cerr << "t = " << t << ", o.time = " << o.time << ", interval = " << interval << ", obsInterval = " << obsInterval << std::endl; Chris@181: Chris@181: if (t > endTime) { Chris@181: break; Chris@181: } else if (o.time > t) { Chris@181: samples.push_back(0.0); Chris@181: t = t + interval; Chris@181: } else if (o.time + obsInterval <= t) { Chris@181: ++oi; Chris@181: } else { Chris@181: samples.push_back(o.value); Chris@181: t = t + interval; Chris@181: } Chris@181: } Chris@181: Chris@181: while (1) { Chris@181: // std::cerr << "t = " << t << std::endl; Chris@181: if (t > endTime) { Chris@181: break; Chris@181: } else { Chris@181: samples.push_back(0.0); Chris@181: t = t + interval; Chris@181: } Chris@181: } Chris@181: Chris@181: return samples; Chris@181: } Chris@181: Chris@181: std::vector Chris@181: NoteHypothesis::winnow(const Observations &obs, Chris@181: RealTime startTime, Chris@181: RealTime endTime, Chris@181: RealTime interval) Chris@181: { Chris@181: AgentFeederPoly feeder; Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@181: std::cerr << "winnow: " << obs.size() << " input observations" Chris@181: << std::endl; Chris@181: int nonzero = 0; Chris@181: #endif Chris@181: Chris@181: for (Observations::const_iterator i = obs.begin(); Chris@181: i != obs.end(); ++i) { Chris@181: if (i->value != 0.0) { // 0.0 is a special unvoiced value Chris@181: feeder.feed(*i); Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@181: std::cerr << i->value << " "; Chris@181: ++nonzero; Chris@181: if (nonzero % 6 == 0) { Chris@181: std::cerr << std::endl; Chris@181: } Chris@181: #endif Chris@181: } Chris@181: } Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@181: std::cerr << "winnow: " << nonzero << " non-zero" Chris@181: << std::endl; Chris@181: #endif Chris@181: Chris@181: feeder.finish(); Chris@181: Chris@181: AgentFeederPoly::Hypotheses accepted = Chris@181: feeder.getAcceptedHypotheses(); Chris@181: Chris@181: #ifdef DEBUG_NOTE_HYPOTHESIS Chris@181: std::cerr << "winnow: " << accepted.size() << " accepted hypotheses" Chris@181: << std::endl; Chris@181: #endif Chris@181: Chris@181: return NoteHypothesis::sample(accepted, startTime, endTime, interval); Chris@181: } Chris@181: