view 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 source
/* -*- 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> &notes,
                       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);
}