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