annotate Agent.h @ 8:f04f87b5e643

Add agent list class, and continue plodding through
author Chris Cannam
date Fri, 30 Sep 2011 11:37:25 +0100
parents 02d388f98c23
children 4f6626f9ffac
rev   line source
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@8 21 class AgentList;
Chris@8 22
Chris@6 23 /** Agent is the central class for beat tracking.
Chris@6 24 * Each Agent object has a tempo hypothesis, a history of tracked beats, and
Chris@6 25 * a score evaluating the continuity, regularity and salience of its beat track.
Chris@6 26 */
Chris@6 27 class Agent
Chris@6 28 {
Chris@6 29 public:
Chris@6 30 /** The maximum amount by which a beat can be later than the predicted beat time,
Chris@6 31 * expressed as a fraction of the beat period. */
Chris@6 32 static double POST_MARGIN_FACTOR;
Chris@6 33
Chris@6 34 /** The maximum amount by which a beat can be earlier than the predicted beat time,
Chris@6 35 * expressed as a fraction of the beat period. */
Chris@6 36 static double PRE_MARGIN_FACTOR;
Chris@6 37
Chris@6 38 /** The default value of innerMargin, which is the maximum time (in seconds) that a
Chris@6 39 * beat can deviate from the predicted beat time without a fork occurring. */
Chris@6 40 static const double INNER_MARGIN;
Chris@6 41
Chris@6 42 /** The maximum allowed deviation from the initial tempo, expressed as a fraction of the initial beat period. */
Chris@6 43 static double MAX_CHANGE;
Chris@6 44
Chris@6 45 /** The slope of the penalty function for onsets which do not coincide precisely with predicted beat times. */
Chris@6 46 static double CONF_FACTOR;
Chris@6 47
Chris@6 48 /** The reactiveness/inertia balance, i.e. degree of change in the tempo, is controlled by the correctionFactor
Chris@6 49 * variable. This constant defines its default value, which currently is not subsequently changed. The
Chris@6 50 * beat period is updated by the reciprocal of the correctionFactor multiplied by the difference between the
Chris@6 51 * predicted beat time and matching onset. */
Chris@6 52 static const double DEFAULT_CORRECTION_FACTOR;
Chris@6 53
Chris@6 54 /** The default value of expiryTime, which is the time (in seconds) after which an Agent that
Chris@6 55 * has no Event matching its beat predictions will be destroyed. */
Chris@6 56 static const double DEFAULT_EXPIRY_TIME;
Chris@6 57
Chris@6 58 protected:
Chris@6 59 /** The identity number of the next created Agent */
Chris@6 60 static int idCounter;
Chris@6 61
Chris@6 62 /** The maximum time (in seconds) that a beat can deviate from the predicted beat time
Chris@6 63 * without a fork occurring (i.e. a 2nd Agent being created). */
Chris@6 64 static double innerMargin;
Chris@6 65
Chris@6 66 /** Controls the reactiveness/inertia balance, i.e. degree of change in the tempo. The
Chris@6 67 * beat period is updated by the reciprocal of the correctionFactor multiplied by the difference between the
Chris@6 68 * predicted beat time and matching onset. */
Chris@6 69 static double correctionFactor;
Chris@6 70
Chris@6 71 /** The time (in seconds) after which an Agent that
Chris@6 72 * has no Event matching its beat predictions will be destroyed. */
Chris@6 73 static double expiryTime;
Chris@6 74
Chris@6 75 /** For scoring Agents in a (non-existent) real-time version (otherwise not used). */
Chris@6 76 static double decayFactor;
Chris@6 77
Chris@6 78 public:
Chris@6 79 /** The size of the outer half-window before the predicted beat time. */
Chris@6 80 double preMargin;
Chris@6 81
Chris@6 82 /** The size of the outer half-window after the predicted beat time. */
Chris@6 83 double postMargin;
Chris@6 84
Chris@6 85 /** The Agent's unique identity number. */
Chris@6 86 int idNumber;
Chris@6 87
Chris@6 88 /** To be used in real-time version?? */
Chris@6 89 double tempoScore;
Chris@6 90
Chris@6 91 /** Sum of salience values of the Events which have been interpreted
Chris@6 92 * as beats by this Agent, weighted by their nearness to the predicted beat times. */
Chris@6 93 double phaseScore;
Chris@6 94
Chris@6 95 /** How long has this agent been the best? For real-time version; otherwise not used. */
Chris@6 96 double topScoreTime;
Chris@6 97
Chris@6 98 /** The number of beats found by this Agent, including interpolated beats. */
Chris@6 99 int beatCount;
Chris@6 100
Chris@6 101 /** The current tempo hypothesis of the Agent, expressed as the beat period in seconds. */
Chris@6 102 double beatInterval;
Chris@6 103
Chris@6 104 /** The initial tempo hypothesis of the Agent, expressed as the beat period in seconds. */
Chris@6 105 double initialBeatInterval;
Chris@6 106
Chris@6 107 /** The time of the most recent beat accepted by this Agent. */
Chris@6 108 double beatTime;
Chris@6 109
Chris@6 110 /** The list of Events (onsets) accepted by this Agent as beats, plus interpolated beats. */
Chris@6 111 EventList events;
Chris@6 112
Chris@6 113 /** Constructor: the work is performed by init()
Chris@6 114 * @param ibi The beat period (inter-beat interval) of the Agent's tempo hypothesis.
Chris@6 115 */
Chris@6 116 Agent(double ibi) {
Chris@6 117 init(ibi);
Chris@6 118 } // constructor
Chris@6 119
Chris@6 120 /** Copy constructor.
Chris@6 121 * @param clone The Agent to duplicate. */
Chris@6 122 Agent(const Agent &clone) {
Chris@6 123 idNumber = idCounter++;
Chris@6 124 phaseScore = clone.phaseScore;
Chris@6 125 tempoScore = clone.tempoScore;
Chris@6 126 topScoreTime = clone.topScoreTime;
Chris@6 127 beatCount = clone.beatCount;
Chris@6 128 beatInterval = clone.beatInterval;
Chris@6 129 initialBeatInterval = clone.initialBeatInterval;
Chris@6 130 beatTime = clone.beatTime;
Chris@6 131 events = EventList(clone.events);
Chris@6 132 postMargin = clone.postMargin;
Chris@6 133 preMargin = clone.preMargin;
Chris@6 134 } // copy constructor
Chris@6 135
Chris@6 136 protected:
Chris@6 137 /** Initialise all the fields of this Agent.
Chris@6 138 * @param ibi The initial tempo hypothesis of the Agent.
Chris@6 139 */
Chris@6 140 void init(double ibi) {
Chris@6 141 innerMargin = INNER_MARGIN;
Chris@6 142 correctionFactor = DEFAULT_CORRECTION_FACTOR;
Chris@6 143 expiryTime = DEFAULT_EXPIRY_TIME;
Chris@6 144 decayFactor = 0;
Chris@6 145 beatInterval = ibi;
Chris@6 146 initialBeatInterval = ibi;
Chris@6 147 postMargin = ibi * POST_MARGIN_FACTOR;
Chris@6 148 preMargin = ibi * PRE_MARGIN_FACTOR;
Chris@6 149 idNumber = idCounter++;
Chris@6 150 phaseScore = 0.0;
Chris@6 151 tempoScore = 0.0;
Chris@6 152 topScoreTime = 0.0;
Chris@6 153 beatCount = 0;
Chris@6 154 beatTime = -1.0;
Chris@6 155 events.clear();
Chris@6 156 } // init()
Chris@6 157
Chris@6 158
Chris@6 159 double threshold(double value, double min, double max) {
Chris@6 160 if (value < min)
Chris@6 161 return min;
Chris@6 162 if (value > max)
Chris@6 163 return max;
Chris@6 164 return value;
Chris@6 165 }
Chris@6 166
Chris@8 167 public:
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@8 201 bool considerAsBeat(Event e, 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@8 213 // for sorting AgentList
Chris@8 214 bool operator<(const Agent &a) const {
Chris@8 215 return beatInterval < a.beatInterval;
Chris@8 216 }
Chris@8 217
Chris@6 218 }; // class Agent
Chris@6 219
Chris@6 220 #endif