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