Mercurial > hg > beatroot-vamp
diff AgentList.h @ 8:f04f87b5e643
Add agent list class, and continue plodding through
author | Chris Cannam |
---|---|
date | Fri, 30 Sep 2011 11:37:25 +0100 |
parents | |
children | 1c1e98cd1b2e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AgentList.h Fri Sep 30 11:37:25 2011 +0100 @@ -0,0 +1,186 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp feature extraction plugin for the BeatRoot beat tracker. + + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2011 Simon Dixon, Chris Cannam and QMUL. + + 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. +*/ + +#ifndef _AGENT_LIST_H_ +#define _AGENT_LIST_H_ + +#include "Agent.h" +#include "Event.h" + +#include <algorithm> + +/** Class for maintaining the set of all Agents involved in beat tracking a piece of music. + */ +class AgentList +{ +public: + typedef std::vector<Agent> Container; + typedef Container::iterator iterator; + +protected: + Container list; + +public: + // expose some vector methods + //!!! can we remove these again once the rest of AgentList is implemented? + bool empty() const { return list.empty(); } + Container::iterator begin() { return list.begin(); } + Container::iterator end() { return list.end(); } + void push_back(const Agent &a) { list.push_back(a); } + + /** Flag for choice between sum and average beat salience values for Agent scores. + * The use of summed saliences favours faster tempi or lower metrical levels. */ + static bool useAverageSalience; + + /** For the purpose of removing duplicate agents, the default JND of IBI */ + static const double DEFAULT_BI; + + /** For the purpose of removing duplicate agents, the default JND of phase */ + static const double DEFAULT_BT; + + /** Inserts newAgent into the list in ascending order of beatInterval */ + void add(Agent a) { + add(a, true); + } // add()/1 + + /** Appends newAgent to list (sort==false), or inserts newAgent into the list + * in ascending order of beatInterval + * @param newAgent The agent to be added to the list + * @param sort Flag indicating whether the list is sorted or not + */ + void add(Agent newAgent, bool sort){ + list.push_back(newAgent); + if (sort) this->sort(); + } // add()/2 + + /** Sorts the AgentList by increasing beatInterval. */ + void sort() { + std::sort(list.begin(), list.end()); + } // sort() + + /** Removes the given item from the list. + * @param ptr Points to the Agent which is removed from the list + */ + void remove(iterator itr) { + list.erase(itr); + } // remove() + +protected: + /** Removes Agents from the list which are duplicates of other Agents. + * A duplicate is defined by the tempo and phase thresholds + * thresholdBI and thresholdBT respectively. + */ + void removeDuplicates() { + sort(); + for (iterator itr = begin(); itr != end(); ++itr) { + if (itr->phaseScore < 0.0) // already flagged for deletion + continue; + iterator itr2 = itr; + for (++itr2; itr != end(); ++itr) { + if (itr2->beatInterval - itr->beatInterval > DEFAULT_BI) + break; + if (fabs(itr->beatTime - itr2->beatTime) > DEFAULT_BT) + continue; + if (itr->phaseScore < itr2->phaseScore) { + itr->phaseScore = -1.0; // flag for deletion + if (itr2->topScoreTime < itr->topScoreTime) + itr2->topScoreTime = itr->topScoreTime; + break; + } else { + itr2->phaseScore = -1.0; // flag for deletion + if (itr->topScoreTime < itr2->topScoreTime) + itr->topScoreTime = itr2->topScoreTime; + } + } + } + for (iterator itr = begin(); itr != end(); ) { + if (itr->phaseScore < 0.0) { + list.erase(itr); + } else { + ++itr; + } + } + } // removeDuplicates() + +public: + /** Perform beat tracking on a list of events (onsets). + * @param el The list of onsets (or events or peaks) to beat track + */ + void beatTrack(EventList el) { + beatTrack(el, -1.0); + } // beatTrack()/1 + + /** Perform beat tracking on a list of events (onsets). + * @param el The list of onsets (or events or peaks) to beat track. + * @param stop Do not find beats after <code>stop</code> seconds. + */ + void beatTrack(EventList el, double stop) { + EventList::iterator ei = el.begin(); + bool phaseGiven = !empty() && (begin()->beatTime >= 0); // if given for one, assume given for others + while (ei != el.end()) { + Event ev = *ei; + ++ei; + if ((stop > 0) && (ev.time > stop)) + break; + bool created = phaseGiven; + double prevBeatInterval = -1.0; + // cc: Duplicate our list of agents, and scan through + // the (now immutable) copy. This means we can safely + // add agents to our own list while scanning without + // disrupting our scan. + Container currentAgents = list; + for (Container::iterator ai = currentAgents.begin(); + ai != currentAgents.end(); ++ai) { + Agent currentAgent = *ai; + if (currentAgent.beatInterval != prevBeatInterval) { + if ((prevBeatInterval>=0) && !created && (ev.time<5.0)) { + // Create new agent with different phase + Agent newAgent(prevBeatInterval); + // This may add an agent to our list + newAgent.considerAsBeat(ev, *this); + } + prevBeatInterval = currentAgent.beatInterval; + created = phaseGiven; + } + if (currentAgent.considerAsBeat(ev, *this)) + created = true; + } // loop for each agent + removeDuplicates(); + } // loop for each event + } // beatTrack() + + /** Finds the Agent with the highest score in the list, or NULL if beat tracking has failed. + * @return The Agent with the highest score + */ + Agent *bestAgent() { + double best = -1.0; + Agent *bestAg = 0; + for (iterator itr = begin(); itr != end(); ++itr) { + double startTime = itr->events.begin()->time; + double conf = (itr->phaseScore + itr->tempoScore) / + (useAverageSalience? (double)itr->beatCount: 1.0); + if (conf > best) { + bestAg = &(*itr); + best = conf; + } + } + return bestAg; + } // bestAgent() + + +}; // class AgentList + +#endif +