Mercurial > hg > silvet
diff src/NoteHypothesis.cpp @ 181:10e7c3ff575e noteagent
Experimental branch toward note-agent stuff (not actually plumbed in yet)
author | Chris Cannam |
---|---|
date | Fri, 23 May 2014 12:40:18 +0100 |
parents | |
children | e1718e64a921 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/NoteHypothesis.cpp Fri May 23 12:40:18 2014 +0100 @@ -0,0 +1,356 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Silvet + + A Vamp plugin for note transcription. + Centre for Digital Music, Queen Mary University of London. + This file Copyright 2012 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NoteHypothesis.h" +#include "AgentFeederPoly.h" + +#include <cmath> +#include <cassert> + +#include <map> + +using Vamp::RealTime; + +//#define DEBUG_NOTE_HYPOTHESIS 1 + +NoteHypothesis::NoteHypothesis() +{ + m_state = New; +} + +NoteHypothesis::~NoteHypothesis() +{ +} + +bool +NoteHypothesis::isWithinTolerance(Observation s) const +{ + if (m_pending.empty()) { + return true; + } + + // check we are within a relatively close tolerance of the last + // candidate + Observations::const_iterator i = m_pending.end(); + --i; + Observation last = *i; + double r = s.value / last.value; + int cents = lrint(1200.0 * (log(r) / log(2.0))); +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "isWithinTolerance: this " << s.value << " is " << cents + << " cents from prior " << last.value << std::endl; +#endif + if (cents < -60 || cents > 60) return false; + + // and within a slightly bigger tolerance of the current mean + double meanFreq = getMeanFrequency(); + r = s.value / meanFreq; + cents = lrint(1200.0 * (log(r) / log(2.0))); +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "isWithinTolerance: this " << s.value << " is " << cents + << " cents from mean " << meanFreq << std::endl; +#endif + if (cents < -80 || cents > 80) return false; + + return true; +} + +bool +NoteHypothesis::isOutOfDateFor(Observation s) const +{ + if (m_pending.empty()) return false; + + Observations::const_iterator i = m_pending.end(); + --i; + Observation last = *i; + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "isOutOfDateFor: this " << s.time << " is " + << (s.time - last.time) << " from last " << last.time + << " (threshold " << RealTime::fromMilliseconds(40) << ")" + << std::endl; +#endif + + return ((s.time - last.time) > RealTime::fromMilliseconds(40)); +} + +bool +NoteHypothesis::isSatisfied() const +{ + if (m_pending.empty()) return false; + + double meanConfidence = 0.0; + for (Observations::const_iterator i = m_pending.begin(); + i != m_pending.end(); ++i) { + meanConfidence += i->confidence; + } + meanConfidence /= m_pending.size(); + + int lengthRequired = 100; + if (meanConfidence > 0.0) { + lengthRequired = int(2.0 / meanConfidence + 0.5); + } +// if (lengthRequired < 1) lengthRequired = 1; + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << std::endl; +#endif + + return ((int)m_pending.size() > lengthRequired); +} + +bool +NoteHypothesis::accept(Observation s) +{ + bool accept = false; + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "NoteHypothesis[" << this << "]::accept: state " << m_state << "..." << std::endl; +#endif + + static double negligibleConfidence = 0.0001; + + if (s.confidence < negligibleConfidence) { + // avoid piling up a lengthy sequence of estimates that are + // all acceptable but are in total not enough to cause us to + // be satisfied + if (m_state == New) { + m_state = Rejected; + } + return false; + } + + switch (m_state) { + + case New: + m_state = Provisional; + accept = true; + break; + + case Provisional: + if (isOutOfDateFor(s)) { + m_state = Rejected; + } else if (isWithinTolerance(s)) { + accept = true; + } + break; + + case Satisfied: + if (isOutOfDateFor(s)) { + m_state = Expired; + } else if (isWithinTolerance(s)) { + accept = true; + } + break; + + case Rejected: + break; + + case Expired: + break; + } + + if (accept) { + m_pending.insert(s); + if (m_state == Provisional && isSatisfied()) { + m_state = Satisfied; + } + } + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "... -> " << m_state << " (pending: " << m_pending.size() << ")" << std::endl; +#endif + + return accept; +} + +NoteHypothesis::State +NoteHypothesis::getState() const +{ + return m_state; +} + +NoteHypothesis::Observations +NoteHypothesis::getAcceptedObservations() const +{ + if (m_state == Satisfied || m_state == Expired) { + return m_pending; + } else { + return Observations(); + } +} + +double +NoteHypothesis::getMeanFrequency() const +{ + double acc = 0.0; + if (m_pending.empty()) return acc; + for (Observations::const_iterator i = m_pending.begin(); + i != m_pending.end(); ++i) { + acc += i->value; + } + acc /= m_pending.size(); + return acc; +} + +NoteHypothesis::Note +NoteHypothesis::getAveragedNote() const +{ + Note n; + + n.time = getStartTime(); + n.duration = getDuration(); + + // just mean frequency for now, but this isn't at all right perceptually + n.freq = getMeanFrequency(); + + return n; +} + +RealTime +NoteHypothesis::getStartTime() const +{ + if (!(m_state == Satisfied || m_state == Expired)) { + return RealTime::zeroTime; + } else { + return m_pending.begin()->time; + } +} + +RealTime +NoteHypothesis::getDuration() const +{ +//!!! test this! it is wrong + if (!(m_state == Satisfied || m_state == Expired)) { + return RealTime::zeroTime; + } else { + RealTime start = m_pending.begin()->time; + Observations::const_iterator i = m_pending.end(); + --i; + return i->time - start; + } +} + +std::vector<double> +NoteHypothesis::sample(const std::set<NoteHypothesis> ¬es, + RealTime startTime, + RealTime endTime, + RealTime interval) +{ + //!!! where should this live? in AgentHypothesis? a Feeder class? + + assert(interval > RealTime::zeroTime); + + Observations obs = flatten(notes); + Observations::const_iterator oi = obs.begin(); + +// std::cerr << "sample: start " << startTime << " end " << endTime << " interval " << interval << std::endl; + +// std::cerr << "sample: flatten gives " << obs.size() << " observations" << std::endl; + + std::vector<double> samples; + + RealTime obsInterval; + + RealTime t = startTime; + + while (oi != obs.end()) { + + Observation o = *oi; + + if (obsInterval == RealTime()) { + //!!! should pull out a function to establish this from the list + Observations::const_iterator oj = oi; + ++oj; + if (oj != obs.end()) { + obsInterval = oj->time - o.time; + } + } + +// std::cerr << "t = " << t << ", o.time = " << o.time << ", interval = " << interval << ", obsInterval = " << obsInterval << std::endl; + + if (t > endTime) { + break; + } else if (o.time > t) { + samples.push_back(0.0); + t = t + interval; + } else if (o.time + obsInterval <= t) { + ++oi; + } else { + samples.push_back(o.value); + t = t + interval; + } + } + + while (1) { +// std::cerr << "t = " << t << std::endl; + if (t > endTime) { + break; + } else { + samples.push_back(0.0); + t = t + interval; + } + } + + return samples; +} + +std::vector<double> +NoteHypothesis::winnow(const Observations &obs, + RealTime startTime, + RealTime endTime, + RealTime interval) +{ + AgentFeederPoly<NoteHypothesis> feeder; + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "winnow: " << obs.size() << " input observations" + << std::endl; + int nonzero = 0; +#endif + + for (Observations::const_iterator i = obs.begin(); + i != obs.end(); ++i) { + if (i->value != 0.0) { // 0.0 is a special unvoiced value + feeder.feed(*i); +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << i->value << " "; + ++nonzero; + if (nonzero % 6 == 0) { + std::cerr << std::endl; + } +#endif + } + } + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "winnow: " << nonzero << " non-zero" + << std::endl; +#endif + + feeder.finish(); + + AgentFeederPoly<NoteHypothesis>::Hypotheses accepted = + feeder.getAcceptedHypotheses(); + +#ifdef DEBUG_NOTE_HYPOTHESIS + std::cerr << "winnow: " << accepted.size() << " accepted hypotheses" + << std::endl; +#endif + + return NoteHypothesis::sample(accepted, startTime, endTime, interval); +} +