annotate 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
rev   line source
Chris@8 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@8 2
Chris@8 3 /*
Chris@8 4 Vamp feature extraction plugin for the BeatRoot beat tracker.
Chris@8 5
Chris@8 6 Centre for Digital Music, Queen Mary, University of London.
Chris@8 7 This file copyright 2011 Simon Dixon, Chris Cannam and QMUL.
Chris@8 8
Chris@8 9 This program is free software; you can redistribute it and/or
Chris@8 10 modify it under the terms of the GNU General Public License as
Chris@8 11 published by the Free Software Foundation; either version 2 of the
Chris@8 12 License, or (at your option) any later version. See the file
Chris@8 13 COPYING included with this distribution for more information.
Chris@8 14 */
Chris@8 15
Chris@8 16 #ifndef _AGENT_LIST_H_
Chris@8 17 #define _AGENT_LIST_H_
Chris@8 18
Chris@8 19 #include "Agent.h"
Chris@8 20 #include "Event.h"
Chris@8 21
Chris@8 22 #include <algorithm>
Chris@8 23
Chris@8 24 /** Class for maintaining the set of all Agents involved in beat tracking a piece of music.
Chris@8 25 */
Chris@8 26 class AgentList
Chris@8 27 {
Chris@8 28 public:
Chris@8 29 typedef std::vector<Agent> Container;
Chris@8 30 typedef Container::iterator iterator;
Chris@8 31
Chris@8 32 protected:
Chris@8 33 Container list;
Chris@8 34
Chris@8 35 public:
Chris@8 36 // expose some vector methods
Chris@8 37 //!!! can we remove these again once the rest of AgentList is implemented?
Chris@8 38 bool empty() const { return list.empty(); }
Chris@8 39 Container::iterator begin() { return list.begin(); }
Chris@8 40 Container::iterator end() { return list.end(); }
Chris@8 41 void push_back(const Agent &a) { list.push_back(a); }
Chris@8 42
Chris@8 43 /** Flag for choice between sum and average beat salience values for Agent scores.
Chris@8 44 * The use of summed saliences favours faster tempi or lower metrical levels. */
Chris@8 45 static bool useAverageSalience;
Chris@8 46
Chris@8 47 /** For the purpose of removing duplicate agents, the default JND of IBI */
Chris@8 48 static const double DEFAULT_BI;
Chris@8 49
Chris@8 50 /** For the purpose of removing duplicate agents, the default JND of phase */
Chris@8 51 static const double DEFAULT_BT;
Chris@8 52
Chris@8 53 /** Inserts newAgent into the list in ascending order of beatInterval */
Chris@8 54 void add(Agent a) {
Chris@8 55 add(a, true);
Chris@8 56 } // add()/1
Chris@8 57
Chris@8 58 /** Appends newAgent to list (sort==false), or inserts newAgent into the list
Chris@8 59 * in ascending order of beatInterval
Chris@8 60 * @param newAgent The agent to be added to the list
Chris@8 61 * @param sort Flag indicating whether the list is sorted or not
Chris@8 62 */
Chris@8 63 void add(Agent newAgent, bool sort){
Chris@8 64 list.push_back(newAgent);
Chris@8 65 if (sort) this->sort();
Chris@8 66 } // add()/2
Chris@8 67
Chris@8 68 /** Sorts the AgentList by increasing beatInterval. */
Chris@8 69 void sort() {
Chris@8 70 std::sort(list.begin(), list.end());
Chris@8 71 } // sort()
Chris@8 72
Chris@8 73 /** Removes the given item from the list.
Chris@8 74 * @param ptr Points to the Agent which is removed from the list
Chris@8 75 */
Chris@8 76 void remove(iterator itr) {
Chris@8 77 list.erase(itr);
Chris@8 78 } // remove()
Chris@8 79
Chris@8 80 protected:
Chris@8 81 /** Removes Agents from the list which are duplicates of other Agents.
Chris@8 82 * A duplicate is defined by the tempo and phase thresholds
Chris@8 83 * thresholdBI and thresholdBT respectively.
Chris@8 84 */
Chris@8 85 void removeDuplicates() {
Chris@8 86 sort();
Chris@8 87 for (iterator itr = begin(); itr != end(); ++itr) {
Chris@8 88 if (itr->phaseScore < 0.0) // already flagged for deletion
Chris@8 89 continue;
Chris@8 90 iterator itr2 = itr;
Chris@8 91 for (++itr2; itr != end(); ++itr) {
Chris@8 92 if (itr2->beatInterval - itr->beatInterval > DEFAULT_BI)
Chris@8 93 break;
Chris@8 94 if (fabs(itr->beatTime - itr2->beatTime) > DEFAULT_BT)
Chris@8 95 continue;
Chris@8 96 if (itr->phaseScore < itr2->phaseScore) {
Chris@8 97 itr->phaseScore = -1.0; // flag for deletion
Chris@8 98 if (itr2->topScoreTime < itr->topScoreTime)
Chris@8 99 itr2->topScoreTime = itr->topScoreTime;
Chris@8 100 break;
Chris@8 101 } else {
Chris@8 102 itr2->phaseScore = -1.0; // flag for deletion
Chris@8 103 if (itr->topScoreTime < itr2->topScoreTime)
Chris@8 104 itr->topScoreTime = itr2->topScoreTime;
Chris@8 105 }
Chris@8 106 }
Chris@8 107 }
Chris@8 108 for (iterator itr = begin(); itr != end(); ) {
Chris@8 109 if (itr->phaseScore < 0.0) {
Chris@8 110 list.erase(itr);
Chris@8 111 } else {
Chris@8 112 ++itr;
Chris@8 113 }
Chris@8 114 }
Chris@8 115 } // removeDuplicates()
Chris@8 116
Chris@8 117 public:
Chris@8 118 /** Perform beat tracking on a list of events (onsets).
Chris@8 119 * @param el The list of onsets (or events or peaks) to beat track
Chris@8 120 */
Chris@8 121 void beatTrack(EventList el) {
Chris@8 122 beatTrack(el, -1.0);
Chris@8 123 } // beatTrack()/1
Chris@8 124
Chris@8 125 /** Perform beat tracking on a list of events (onsets).
Chris@8 126 * @param el The list of onsets (or events or peaks) to beat track.
Chris@8 127 * @param stop Do not find beats after <code>stop</code> seconds.
Chris@8 128 */
Chris@8 129 void beatTrack(EventList el, double stop) {
Chris@8 130 EventList::iterator ei = el.begin();
Chris@8 131 bool phaseGiven = !empty() && (begin()->beatTime >= 0); // if given for one, assume given for others
Chris@8 132 while (ei != el.end()) {
Chris@8 133 Event ev = *ei;
Chris@8 134 ++ei;
Chris@8 135 if ((stop > 0) && (ev.time > stop))
Chris@8 136 break;
Chris@8 137 bool created = phaseGiven;
Chris@8 138 double prevBeatInterval = -1.0;
Chris@8 139 // cc: Duplicate our list of agents, and scan through
Chris@8 140 // the (now immutable) copy. This means we can safely
Chris@8 141 // add agents to our own list while scanning without
Chris@8 142 // disrupting our scan.
Chris@8 143 Container currentAgents = list;
Chris@8 144 for (Container::iterator ai = currentAgents.begin();
Chris@8 145 ai != currentAgents.end(); ++ai) {
Chris@8 146 Agent currentAgent = *ai;
Chris@8 147 if (currentAgent.beatInterval != prevBeatInterval) {
Chris@8 148 if ((prevBeatInterval>=0) && !created && (ev.time<5.0)) {
Chris@8 149 // Create new agent with different phase
Chris@8 150 Agent newAgent(prevBeatInterval);
Chris@8 151 // This may add an agent to our list
Chris@8 152 newAgent.considerAsBeat(ev, *this);
Chris@8 153 }
Chris@8 154 prevBeatInterval = currentAgent.beatInterval;
Chris@8 155 created = phaseGiven;
Chris@8 156 }
Chris@8 157 if (currentAgent.considerAsBeat(ev, *this))
Chris@8 158 created = true;
Chris@8 159 } // loop for each agent
Chris@8 160 removeDuplicates();
Chris@8 161 } // loop for each event
Chris@8 162 } // beatTrack()
Chris@8 163
Chris@8 164 /** Finds the Agent with the highest score in the list, or NULL if beat tracking has failed.
Chris@8 165 * @return The Agent with the highest score
Chris@8 166 */
Chris@8 167 Agent *bestAgent() {
Chris@8 168 double best = -1.0;
Chris@8 169 Agent *bestAg = 0;
Chris@8 170 for (iterator itr = begin(); itr != end(); ++itr) {
Chris@8 171 double startTime = itr->events.begin()->time;
Chris@8 172 double conf = (itr->phaseScore + itr->tempoScore) /
Chris@8 173 (useAverageSalience? (double)itr->beatCount: 1.0);
Chris@8 174 if (conf > best) {
Chris@8 175 bestAg = &(*itr);
Chris@8 176 best = conf;
Chris@8 177 }
Chris@8 178 }
Chris@8 179 return bestAg;
Chris@8 180 } // bestAgent()
Chris@8 181
Chris@8 182
Chris@8 183 }; // class AgentList
Chris@8 184
Chris@8 185 #endif
Chris@8 186