annotate AgentList.h @ 13:0d4048bfadbb

Fixes to beat insertion in Agent. We get plausible results now, but there's probably quite a lot still to do.
author Chris Cannam
date Thu, 06 Oct 2011 18:37:50 +0100
parents 59520cd6abac
children f1252b6a7cf5
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@13 22 #include <vector>
Chris@8 23 #include <algorithm>
Chris@8 24
Chris@12 25 #ifdef DEBUG_BEATROOT
Chris@12 26 #include <iostream>
Chris@12 27 #endif
Chris@12 28
Chris@8 29 /** Class for maintaining the set of all Agents involved in beat tracking a piece of music.
Chris@8 30 */
Chris@8 31 class AgentList
Chris@8 32 {
Chris@8 33 public:
Chris@8 34 typedef std::vector<Agent> Container;
Chris@8 35 typedef Container::iterator iterator;
Chris@8 36
Chris@8 37 protected:
Chris@8 38 Container list;
Chris@8 39
Chris@8 40 public:
Chris@8 41 // expose some vector methods
Chris@8 42 //!!! can we remove these again once the rest of AgentList is implemented?
Chris@8 43 bool empty() const { return list.empty(); }
Chris@8 44 Container::iterator begin() { return list.begin(); }
Chris@8 45 Container::iterator end() { return list.end(); }
Chris@12 46 size_t size() { return list.size(); }
Chris@12 47 void push_back(const Agent &a) {
Chris@12 48 list.push_back(a);
Chris@12 49 #ifdef DEBUG_BEATROOT
Chris@12 50 std::cerr << " Added Ag#" << a.idNumber << ", have " << list.size() << " agent(s)" << std::endl;
Chris@12 51 #endif
Chris@12 52 }
Chris@8 53
Chris@11 54 /** Flag for choice between sum and average beat salience values for Agent scores.
Chris@11 55 * The use of summed saliences favours faster tempi or lower metrical levels. */
Chris@11 56 static bool useAverageSalience;
Chris@8 57
Chris@11 58 /** For the purpose of removing duplicate agents, the default JND of IBI */
Chris@11 59 static const double DEFAULT_BI;
Chris@8 60
Chris@11 61 /** For the purpose of removing duplicate agents, the default JND of phase */
Chris@11 62 static const double DEFAULT_BT;
Chris@8 63
Chris@11 64 /** Inserts newAgent into the list in ascending order of beatInterval */
Chris@11 65 void add(Agent a) {
Chris@11 66 add(a, true);
Chris@11 67 } // add()/1
Chris@8 68
Chris@11 69 /** Appends newAgent to list (sort==false), or inserts newAgent into the list
Chris@11 70 * in ascending order of beatInterval
Chris@11 71 * @param newAgent The agent to be added to the list
Chris@11 72 * @param sort Flag indicating whether the list is sorted or not
Chris@11 73 */
Chris@11 74 void add(Agent newAgent, bool sort){
Chris@12 75 push_back(newAgent);
Chris@11 76 if (sort) this->sort();
Chris@11 77 } // add()/2
Chris@8 78
Chris@11 79 /** Sorts the AgentList by increasing beatInterval. */
Chris@11 80 void sort() {
Chris@11 81 std::sort(list.begin(), list.end());
Chris@11 82 } // sort()
Chris@8 83
Chris@11 84 /** Removes the given item from the list.
Chris@11 85 * @param ptr Points to the Agent which is removed from the list
Chris@11 86 */
Chris@11 87 void remove(iterator itr) {
Chris@11 88 list.erase(itr);
Chris@11 89 } // remove()
Chris@8 90
Chris@8 91 protected:
Chris@11 92 /** Removes Agents from the list which are duplicates of other Agents.
Chris@11 93 * A duplicate is defined by the tempo and phase thresholds
Chris@11 94 * thresholdBI and thresholdBT respectively.
Chris@11 95 */
Chris@11 96 void removeDuplicates() {
Chris@11 97 sort();
Chris@11 98 for (iterator itr = begin(); itr != end(); ++itr) {
Chris@11 99 if (itr->phaseScore < 0.0) // already flagged for deletion
Chris@11 100 continue;
Chris@11 101 iterator itr2 = itr;
Chris@11 102 for (++itr2; itr2 != end(); ++itr2) {
Chris@11 103 if (itr2->beatInterval - itr->beatInterval > DEFAULT_BI)
Chris@11 104 break;
Chris@11 105 if (fabs(itr->beatTime - itr2->beatTime) > DEFAULT_BT)
Chris@11 106 continue;
Chris@11 107 if (itr->phaseScore < itr2->phaseScore) {
Chris@11 108 itr->phaseScore = -1.0; // flag for deletion
Chris@11 109 if (itr2->topScoreTime < itr->topScoreTime)
Chris@11 110 itr2->topScoreTime = itr->topScoreTime;
Chris@11 111 break;
Chris@11 112 } else {
Chris@11 113 itr2->phaseScore = -1.0; // flag for deletion
Chris@11 114 if (itr->topScoreTime < itr2->topScoreTime)
Chris@11 115 itr->topScoreTime = itr2->topScoreTime;
Chris@11 116 }
Chris@11 117 }
Chris@11 118 }
Chris@12 119 int removed = 0;
Chris@11 120 for (iterator itr = begin(); itr != end(); ) {
Chris@11 121 if (itr->phaseScore < 0.0) {
Chris@12 122 ++removed;
Chris@11 123 list.erase(itr);
Chris@11 124 } else {
Chris@11 125 ++itr;
Chris@11 126 }
Chris@11 127 }
Chris@12 128 #ifdef DEBUG_BEATROOT
Chris@12 129 if (removed > 0) {
Chris@12 130 std::cerr << "removeDuplicates: removed " << removed << ", have "
Chris@12 131 << list.size() << " agent(s) remaining" << std::endl;
Chris@12 132 }
Chris@12 133 for (int i = 0; i <list.size(); ++i) {
Chris@12 134 std::cerr << "agent " << i << ": time " << list[i].beatTime << std::endl;
Chris@12 135 }
Chris@12 136 #endif
Chris@11 137 } // removeDuplicates()
Chris@8 138
Chris@8 139 public:
Chris@11 140 /** Perform beat tracking on a list of events (onsets).
Chris@11 141 * @param el The list of onsets (or events or peaks) to beat track
Chris@11 142 */
Chris@11 143 void beatTrack(EventList el) {
Chris@11 144 beatTrack(el, -1.0);
Chris@11 145 } // beatTrack()/1
Chris@8 146
Chris@11 147 /** Perform beat tracking on a list of events (onsets).
Chris@11 148 * @param el The list of onsets (or events or peaks) to beat track.
Chris@11 149 * @param stop Do not find beats after <code>stop</code> seconds.
Chris@11 150 */
Chris@11 151 void beatTrack(EventList el, double stop) {
Chris@11 152 EventList::iterator ei = el.begin();
Chris@11 153 bool phaseGiven = !empty() && (begin()->beatTime >= 0); // if given for one, assume given for others
Chris@11 154 while (ei != el.end()) {
Chris@11 155 Event ev = *ei;
Chris@11 156 ++ei;
Chris@11 157 if ((stop > 0) && (ev.time > stop))
Chris@11 158 break;
Chris@11 159 bool created = phaseGiven;
Chris@11 160 double prevBeatInterval = -1.0;
Chris@12 161 // cc: Duplicate our list of agents, and scan through the
Chris@12 162 // copy. This means we can safely add agents to our own
Chris@12 163 // list while scanning without disrupting our scan. Each
Chris@12 164 // agent needs to be re-added to our own list explicitly
Chris@12 165 // (since it is modified by e.g. considerAsBeat)
Chris@11 166 Container currentAgents = list;
Chris@12 167 list.clear();
Chris@11 168 for (Container::iterator ai = currentAgents.begin();
Chris@11 169 ai != currentAgents.end(); ++ai) {
Chris@11 170 Agent currentAgent = *ai;
Chris@11 171 if (currentAgent.beatInterval != prevBeatInterval) {
Chris@11 172 if ((prevBeatInterval>=0) && !created && (ev.time<5.0)) {
Chris@12 173 #ifdef DEBUG_BEATROOT
Chris@12 174 std::cerr << "Creating a new agent" << std::endl;
Chris@12 175 #endif
Chris@11 176 // Create new agent with different phase
Chris@11 177 Agent newAgent(prevBeatInterval);
Chris@12 178 // This may add another agent to our list as well
Chris@11 179 newAgent.considerAsBeat(ev, *this);
Chris@12 180 add(newAgent);
Chris@8 181 }
Chris@11 182 prevBeatInterval = currentAgent.beatInterval;
Chris@11 183 created = phaseGiven;
Chris@11 184 }
Chris@11 185 if (currentAgent.considerAsBeat(ev, *this))
Chris@11 186 created = true;
Chris@12 187 add(currentAgent);
Chris@11 188 } // loop for each agent
Chris@11 189 removeDuplicates();
Chris@11 190 } // loop for each event
Chris@11 191 } // beatTrack()
Chris@8 192
Chris@11 193 /** Finds the Agent with the highest score in the list, or NULL if beat tracking has failed.
Chris@11 194 * @return The Agent with the highest score
Chris@11 195 */
Chris@11 196 Agent *bestAgent() {
Chris@11 197 double best = -1.0;
Chris@11 198 Agent *bestAg = 0;
Chris@11 199 for (iterator itr = begin(); itr != end(); ++itr) {
Chris@11 200 if (itr->events.empty()) continue;
Chris@11 201 double startTime = itr->events.begin()->time;
Chris@11 202 double conf = (itr->phaseScore + itr->tempoScore) /
Chris@11 203 (useAverageSalience? (double)itr->beatCount: 1.0);
Chris@11 204 if (conf > best) {
Chris@11 205 bestAg = &(*itr);
Chris@11 206 best = conf;
Chris@8 207 }
Chris@11 208 }
Chris@12 209 #ifdef DEBUG_BEATROOT
Chris@12 210 if (bestAg) {
Chris@12 211 std::cerr << "Best agent: Ag#" << bestAg->idNumber << std::endl;
Chris@12 212 std::cerr << " Av-salience = " << best << std::endl;
Chris@12 213 } else {
Chris@12 214 std::cerr << "No surviving agent - beat tracking failed" << std::endl;
Chris@12 215 }
Chris@12 216 #endif
Chris@11 217 return bestAg;
Chris@11 218 } // bestAgent()
Chris@8 219
Chris@8 220
Chris@8 221 }; // class AgentList
Chris@8 222
Chris@8 223 #endif
Chris@8 224