Chris@6
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@6
|
2
|
Chris@6
|
3 /*
|
Chris@6
|
4 Vamp feature extraction plugin for the BeatRoot beat tracker.
|
Chris@6
|
5
|
Chris@6
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@6
|
7 This file copyright 2011 Simon Dixon, Chris Cannam and QMUL.
|
Chris@6
|
8
|
Chris@6
|
9 This program is free software; you can redistribute it and/or
|
Chris@6
|
10 modify it under the terms of the GNU General Public License as
|
Chris@6
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@6
|
12 License, or (at your option) any later version. See the file
|
Chris@6
|
13 COPYING included with this distribution for more information.
|
Chris@6
|
14 */
|
Chris@6
|
15
|
Chris@6
|
16 #ifndef _AGENT_H_
|
Chris@6
|
17 #define _AGENT_H_
|
Chris@6
|
18
|
Chris@6
|
19 #include "Event.h"
|
Chris@6
|
20
|
Chris@6
|
21 /** Agent is the central class for beat tracking.
|
Chris@6
|
22 * Each Agent object has a tempo hypothesis, a history of tracked beats, and
|
Chris@6
|
23 * a score evaluating the continuity, regularity and salience of its beat track.
|
Chris@6
|
24 */
|
Chris@6
|
25 class Agent
|
Chris@6
|
26 {
|
Chris@6
|
27 public:
|
Chris@6
|
28
|
Chris@6
|
29 typedef std::vector<Agent> AgentList;
|
Chris@6
|
30
|
Chris@6
|
31 /** The maximum amount by which a beat can be later than the predicted beat time,
|
Chris@6
|
32 * expressed as a fraction of the beat period. */
|
Chris@6
|
33 static double POST_MARGIN_FACTOR;
|
Chris@6
|
34
|
Chris@6
|
35 /** The maximum amount by which a beat can be earlier than the predicted beat time,
|
Chris@6
|
36 * expressed as a fraction of the beat period. */
|
Chris@6
|
37 static double PRE_MARGIN_FACTOR;
|
Chris@6
|
38
|
Chris@6
|
39 /** The default value of innerMargin, which is the maximum time (in seconds) that a
|
Chris@6
|
40 * beat can deviate from the predicted beat time without a fork occurring. */
|
Chris@6
|
41 static const double INNER_MARGIN;
|
Chris@6
|
42
|
Chris@6
|
43 /** The maximum allowed deviation from the initial tempo, expressed as a fraction of the initial beat period. */
|
Chris@6
|
44 static double MAX_CHANGE;
|
Chris@6
|
45
|
Chris@6
|
46 /** The slope of the penalty function for onsets which do not coincide precisely with predicted beat times. */
|
Chris@6
|
47 static double CONF_FACTOR;
|
Chris@6
|
48
|
Chris@6
|
49 /** The reactiveness/inertia balance, i.e. degree of change in the tempo, is controlled by the correctionFactor
|
Chris@6
|
50 * variable. This constant defines its default value, which currently is not subsequently changed. The
|
Chris@6
|
51 * beat period is updated by the reciprocal of the correctionFactor multiplied by the difference between the
|
Chris@6
|
52 * predicted beat time and matching onset. */
|
Chris@6
|
53 static const double DEFAULT_CORRECTION_FACTOR;
|
Chris@6
|
54
|
Chris@6
|
55 /** The default value of expiryTime, which is the time (in seconds) after which an Agent that
|
Chris@6
|
56 * has no Event matching its beat predictions will be destroyed. */
|
Chris@6
|
57 static const double DEFAULT_EXPIRY_TIME;
|
Chris@6
|
58
|
Chris@6
|
59 protected:
|
Chris@6
|
60 /** The identity number of the next created Agent */
|
Chris@6
|
61 static int idCounter;
|
Chris@6
|
62
|
Chris@6
|
63 /** The maximum time (in seconds) that a beat can deviate from the predicted beat time
|
Chris@6
|
64 * without a fork occurring (i.e. a 2nd Agent being created). */
|
Chris@6
|
65 static double innerMargin;
|
Chris@6
|
66
|
Chris@6
|
67 /** Controls the reactiveness/inertia balance, i.e. degree of change in the tempo. The
|
Chris@6
|
68 * beat period is updated by the reciprocal of the correctionFactor multiplied by the difference between the
|
Chris@6
|
69 * predicted beat time and matching onset. */
|
Chris@6
|
70 static double correctionFactor;
|
Chris@6
|
71
|
Chris@6
|
72 /** The time (in seconds) after which an Agent that
|
Chris@6
|
73 * has no Event matching its beat predictions will be destroyed. */
|
Chris@6
|
74 static double expiryTime;
|
Chris@6
|
75
|
Chris@6
|
76 /** For scoring Agents in a (non-existent) real-time version (otherwise not used). */
|
Chris@6
|
77 static double decayFactor;
|
Chris@6
|
78
|
Chris@6
|
79 public:
|
Chris@6
|
80 /** The size of the outer half-window before the predicted beat time. */
|
Chris@6
|
81 double preMargin;
|
Chris@6
|
82
|
Chris@6
|
83 /** The size of the outer half-window after the predicted beat time. */
|
Chris@6
|
84 double postMargin;
|
Chris@6
|
85
|
Chris@6
|
86 /** The Agent's unique identity number. */
|
Chris@6
|
87 int idNumber;
|
Chris@6
|
88
|
Chris@6
|
89 /** To be used in real-time version?? */
|
Chris@6
|
90 double tempoScore;
|
Chris@6
|
91
|
Chris@6
|
92 /** Sum of salience values of the Events which have been interpreted
|
Chris@6
|
93 * as beats by this Agent, weighted by their nearness to the predicted beat times. */
|
Chris@6
|
94 double phaseScore;
|
Chris@6
|
95
|
Chris@6
|
96 /** How long has this agent been the best? For real-time version; otherwise not used. */
|
Chris@6
|
97 double topScoreTime;
|
Chris@6
|
98
|
Chris@6
|
99 /** The number of beats found by this Agent, including interpolated beats. */
|
Chris@6
|
100 int beatCount;
|
Chris@6
|
101
|
Chris@6
|
102 /** The current tempo hypothesis of the Agent, expressed as the beat period in seconds. */
|
Chris@6
|
103 double beatInterval;
|
Chris@6
|
104
|
Chris@6
|
105 /** The initial tempo hypothesis of the Agent, expressed as the beat period in seconds. */
|
Chris@6
|
106 double initialBeatInterval;
|
Chris@6
|
107
|
Chris@6
|
108 /** The time of the most recent beat accepted by this Agent. */
|
Chris@6
|
109 double beatTime;
|
Chris@6
|
110
|
Chris@6
|
111 /** The list of Events (onsets) accepted by this Agent as beats, plus interpolated beats. */
|
Chris@6
|
112 EventList events;
|
Chris@6
|
113
|
Chris@6
|
114 /** Constructor: the work is performed by init()
|
Chris@6
|
115 * @param ibi The beat period (inter-beat interval) of the Agent's tempo hypothesis.
|
Chris@6
|
116 */
|
Chris@6
|
117 Agent(double ibi) {
|
Chris@6
|
118 init(ibi);
|
Chris@6
|
119 } // constructor
|
Chris@6
|
120
|
Chris@6
|
121 /** Copy constructor.
|
Chris@6
|
122 * @param clone The Agent to duplicate. */
|
Chris@6
|
123 Agent(const Agent &clone) {
|
Chris@6
|
124 idNumber = idCounter++;
|
Chris@6
|
125 phaseScore = clone.phaseScore;
|
Chris@6
|
126 tempoScore = clone.tempoScore;
|
Chris@6
|
127 topScoreTime = clone.topScoreTime;
|
Chris@6
|
128 beatCount = clone.beatCount;
|
Chris@6
|
129 beatInterval = clone.beatInterval;
|
Chris@6
|
130 initialBeatInterval = clone.initialBeatInterval;
|
Chris@6
|
131 beatTime = clone.beatTime;
|
Chris@6
|
132 events = EventList(clone.events);
|
Chris@6
|
133 postMargin = clone.postMargin;
|
Chris@6
|
134 preMargin = clone.preMargin;
|
Chris@6
|
135 } // copy constructor
|
Chris@6
|
136
|
Chris@6
|
137 protected:
|
Chris@6
|
138 /** Initialise all the fields of this Agent.
|
Chris@6
|
139 * @param ibi The initial tempo hypothesis of the Agent.
|
Chris@6
|
140 */
|
Chris@6
|
141 void init(double ibi) {
|
Chris@6
|
142 innerMargin = INNER_MARGIN;
|
Chris@6
|
143 correctionFactor = DEFAULT_CORRECTION_FACTOR;
|
Chris@6
|
144 expiryTime = DEFAULT_EXPIRY_TIME;
|
Chris@6
|
145 decayFactor = 0;
|
Chris@6
|
146 beatInterval = ibi;
|
Chris@6
|
147 initialBeatInterval = ibi;
|
Chris@6
|
148 postMargin = ibi * POST_MARGIN_FACTOR;
|
Chris@6
|
149 preMargin = ibi * PRE_MARGIN_FACTOR;
|
Chris@6
|
150 idNumber = idCounter++;
|
Chris@6
|
151 phaseScore = 0.0;
|
Chris@6
|
152 tempoScore = 0.0;
|
Chris@6
|
153 topScoreTime = 0.0;
|
Chris@6
|
154 beatCount = 0;
|
Chris@6
|
155 beatTime = -1.0;
|
Chris@6
|
156 events.clear();
|
Chris@6
|
157 } // init()
|
Chris@6
|
158
|
Chris@6
|
159
|
Chris@6
|
160 double threshold(double value, double min, double max) {
|
Chris@6
|
161 if (value < min)
|
Chris@6
|
162 return min;
|
Chris@6
|
163 if (value > max)
|
Chris@6
|
164 return max;
|
Chris@6
|
165 return value;
|
Chris@6
|
166 }
|
Chris@6
|
167
|
Chris@6
|
168 /** Accept a new Event as a beat time, and update the state of the Agent accordingly.
|
Chris@6
|
169 * @param e The Event which is accepted as being on the beat.
|
Chris@6
|
170 * @param err The difference between the predicted and actual beat times.
|
Chris@6
|
171 * @param beats The number of beats since the last beat that matched an Event.
|
Chris@6
|
172 */
|
Chris@6
|
173 void accept(Event e, double err, int beats) {
|
Chris@6
|
174 beatTime = e.time;
|
Chris@6
|
175 events.push_back(e);
|
Chris@6
|
176 if (fabs(initialBeatInterval - beatInterval -
|
Chris@6
|
177 err / correctionFactor) < MAX_CHANGE * initialBeatInterval)
|
Chris@6
|
178 beatInterval += err / correctionFactor;// Adjust tempo
|
Chris@6
|
179 beatCount += beats;
|
Chris@6
|
180 double conFactor = 1.0 - CONF_FACTOR * err /
|
Chris@6
|
181 (err>0? postMargin: -preMargin);
|
Chris@6
|
182 if (decayFactor > 0) {
|
Chris@6
|
183 double memFactor = 1. - 1. / threshold((double)beatCount,1,decayFactor);
|
Chris@6
|
184 phaseScore = memFactor * phaseScore +
|
Chris@6
|
185 (1.0 - memFactor) * conFactor * e.salience;
|
Chris@6
|
186 } else
|
Chris@6
|
187 phaseScore += conFactor * e.salience;
|
Chris@6
|
188 } // accept()
|
Chris@6
|
189
|
Chris@6
|
190 /** The given Event is tested for a possible beat time. The following situations can occur:
|
Chris@6
|
191 * 1) The Agent has no beats yet; the Event is accepted as the first beat.
|
Chris@6
|
192 * 2) The Event is beyond expiryTime seconds after the Agent's last 'confirming' beat; the Agent is terminated.
|
Chris@6
|
193 * 3) The Event is within the innerMargin of the beat prediction; it is accepted as a beat.
|
Chris@6
|
194 * 4) The Event is within the outerMargin's of the beat prediction; it is accepted as a beat by this Agent,
|
Chris@6
|
195 * and a new Agent is created which doesn't accept it as a beat.
|
Chris@6
|
196 * 5) The Event is ignored because it is outside the windows around the Agent's predicted beat time.
|
Chris@6
|
197 * @param e The Event to be tested
|
Chris@6
|
198 * @param a The list of all agents, which is updated if a new agent is created.
|
Chris@6
|
199 * @return Indicate whether the given Event was accepted as a beat by this Agent.
|
Chris@6
|
200 */
|
Chris@6
|
201 bool considerAsBeat(Event e, const AgentList &a);
|
Chris@6
|
202
|
Chris@6
|
203 /** Interpolates missing beats in the Agent's beat track, starting from the beginning of the piece. */
|
Chris@6
|
204 void fillBeats() {
|
Chris@6
|
205 fillBeats(-1.0);
|
Chris@6
|
206 } // fillBeats()/0
|
Chris@6
|
207
|
Chris@6
|
208 /** Interpolates missing beats in the Agent's beat track.
|
Chris@6
|
209 * @param start Ignore beats earlier than this start time
|
Chris@6
|
210 */
|
Chris@6
|
211 void fillBeats(double start);
|
Chris@6
|
212
|
Chris@6
|
213 }; // class Agent
|
Chris@6
|
214
|
Chris@6
|
215 typedef Agent::AgentList AgentList;
|
Chris@6
|
216
|
Chris@6
|
217 #endif
|