Chris@8: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@8: Chris@8: /* Chris@8: Vamp feature extraction plugin for the BeatRoot beat tracker. Chris@8: Chris@8: Centre for Digital Music, Queen Mary, University of London. Chris@8: This file copyright 2011 Simon Dixon, Chris Cannam and QMUL. Chris@8: Chris@8: This program is free software; you can redistribute it and/or Chris@8: modify it under the terms of the GNU General Public License as Chris@8: published by the Free Software Foundation; either version 2 of the Chris@8: License, or (at your option) any later version. See the file Chris@8: COPYING included with this distribution for more information. Chris@8: */ Chris@8: Chris@8: #ifndef _AGENT_LIST_H_ Chris@8: #define _AGENT_LIST_H_ Chris@8: Chris@8: #include "Agent.h" Chris@8: #include "Event.h" Chris@8: Chris@8: #include Chris@8: Chris@8: /** Class for maintaining the set of all Agents involved in beat tracking a piece of music. Chris@8: */ Chris@8: class AgentList Chris@8: { Chris@8: public: Chris@8: typedef std::vector Container; Chris@8: typedef Container::iterator iterator; Chris@8: Chris@8: protected: Chris@8: Container list; Chris@8: Chris@8: public: Chris@8: // expose some vector methods Chris@8: //!!! can we remove these again once the rest of AgentList is implemented? Chris@8: bool empty() const { return list.empty(); } Chris@8: Container::iterator begin() { return list.begin(); } Chris@8: Container::iterator end() { return list.end(); } Chris@8: void push_back(const Agent &a) { list.push_back(a); } Chris@8: Chris@8: /** Flag for choice between sum and average beat salience values for Agent scores. Chris@8: * The use of summed saliences favours faster tempi or lower metrical levels. */ Chris@8: static bool useAverageSalience; Chris@8: Chris@8: /** For the purpose of removing duplicate agents, the default JND of IBI */ Chris@8: static const double DEFAULT_BI; Chris@8: Chris@8: /** For the purpose of removing duplicate agents, the default JND of phase */ Chris@8: static const double DEFAULT_BT; Chris@8: Chris@8: /** Inserts newAgent into the list in ascending order of beatInterval */ Chris@8: void add(Agent a) { Chris@8: add(a, true); Chris@8: } // add()/1 Chris@8: Chris@8: /** Appends newAgent to list (sort==false), or inserts newAgent into the list Chris@8: * in ascending order of beatInterval Chris@8: * @param newAgent The agent to be added to the list Chris@8: * @param sort Flag indicating whether the list is sorted or not Chris@8: */ Chris@8: void add(Agent newAgent, bool sort){ Chris@8: list.push_back(newAgent); Chris@8: if (sort) this->sort(); Chris@8: } // add()/2 Chris@8: Chris@8: /** Sorts the AgentList by increasing beatInterval. */ Chris@8: void sort() { Chris@8: std::sort(list.begin(), list.end()); Chris@8: } // sort() Chris@8: Chris@8: /** Removes the given item from the list. Chris@8: * @param ptr Points to the Agent which is removed from the list Chris@8: */ Chris@8: void remove(iterator itr) { Chris@8: list.erase(itr); Chris@8: } // remove() Chris@8: Chris@8: protected: Chris@8: /** Removes Agents from the list which are duplicates of other Agents. Chris@8: * A duplicate is defined by the tempo and phase thresholds Chris@8: * thresholdBI and thresholdBT respectively. Chris@8: */ Chris@8: void removeDuplicates() { Chris@8: sort(); Chris@8: for (iterator itr = begin(); itr != end(); ++itr) { Chris@8: if (itr->phaseScore < 0.0) // already flagged for deletion Chris@8: continue; Chris@8: iterator itr2 = itr; Chris@8: for (++itr2; itr != end(); ++itr) { Chris@8: if (itr2->beatInterval - itr->beatInterval > DEFAULT_BI) Chris@8: break; Chris@8: if (fabs(itr->beatTime - itr2->beatTime) > DEFAULT_BT) Chris@8: continue; Chris@8: if (itr->phaseScore < itr2->phaseScore) { Chris@8: itr->phaseScore = -1.0; // flag for deletion Chris@8: if (itr2->topScoreTime < itr->topScoreTime) Chris@8: itr2->topScoreTime = itr->topScoreTime; Chris@8: break; Chris@8: } else { Chris@8: itr2->phaseScore = -1.0; // flag for deletion Chris@8: if (itr->topScoreTime < itr2->topScoreTime) Chris@8: itr->topScoreTime = itr2->topScoreTime; Chris@8: } Chris@8: } Chris@8: } Chris@8: for (iterator itr = begin(); itr != end(); ) { Chris@8: if (itr->phaseScore < 0.0) { Chris@8: list.erase(itr); Chris@8: } else { Chris@8: ++itr; Chris@8: } Chris@8: } Chris@8: } // removeDuplicates() Chris@8: Chris@8: public: Chris@8: /** Perform beat tracking on a list of events (onsets). Chris@8: * @param el The list of onsets (or events or peaks) to beat track Chris@8: */ Chris@8: void beatTrack(EventList el) { Chris@8: beatTrack(el, -1.0); Chris@8: } // beatTrack()/1 Chris@8: Chris@8: /** Perform beat tracking on a list of events (onsets). Chris@8: * @param el The list of onsets (or events or peaks) to beat track. Chris@8: * @param stop Do not find beats after stop seconds. Chris@8: */ Chris@8: void beatTrack(EventList el, double stop) { Chris@8: EventList::iterator ei = el.begin(); Chris@8: bool phaseGiven = !empty() && (begin()->beatTime >= 0); // if given for one, assume given for others Chris@8: while (ei != el.end()) { Chris@8: Event ev = *ei; Chris@8: ++ei; Chris@8: if ((stop > 0) && (ev.time > stop)) Chris@8: break; Chris@8: bool created = phaseGiven; Chris@8: double prevBeatInterval = -1.0; Chris@8: // cc: Duplicate our list of agents, and scan through Chris@8: // the (now immutable) copy. This means we can safely Chris@8: // add agents to our own list while scanning without Chris@8: // disrupting our scan. Chris@8: Container currentAgents = list; Chris@8: for (Container::iterator ai = currentAgents.begin(); Chris@8: ai != currentAgents.end(); ++ai) { Chris@8: Agent currentAgent = *ai; Chris@8: if (currentAgent.beatInterval != prevBeatInterval) { Chris@8: if ((prevBeatInterval>=0) && !created && (ev.time<5.0)) { Chris@8: // Create new agent with different phase Chris@8: Agent newAgent(prevBeatInterval); Chris@8: // This may add an agent to our list Chris@8: newAgent.considerAsBeat(ev, *this); Chris@8: } Chris@8: prevBeatInterval = currentAgent.beatInterval; Chris@8: created = phaseGiven; Chris@8: } Chris@8: if (currentAgent.considerAsBeat(ev, *this)) Chris@8: created = true; Chris@8: } // loop for each agent Chris@8: removeDuplicates(); Chris@8: } // loop for each event Chris@8: } // beatTrack() Chris@8: Chris@8: /** Finds the Agent with the highest score in the list, or NULL if beat tracking has failed. Chris@8: * @return The Agent with the highest score Chris@8: */ Chris@8: Agent *bestAgent() { Chris@8: double best = -1.0; Chris@8: Agent *bestAg = 0; Chris@8: for (iterator itr = begin(); itr != end(); ++itr) { Chris@8: double startTime = itr->events.begin()->time; Chris@8: double conf = (itr->phaseScore + itr->tempoScore) / Chris@8: (useAverageSalience? (double)itr->beatCount: 1.0); Chris@8: if (conf > best) { Chris@8: bestAg = &(*itr); Chris@8: best = conf; Chris@8: } Chris@8: } Chris@8: return bestAg; Chris@8: } // bestAgent() Chris@8: Chris@8: Chris@8: }; // class AgentList Chris@8: Chris@8: #endif Chris@8: