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