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