changeset 23:633ec097fa56

Expose the processing parameters Simon suggests
author Chris Cannam
date Tue, 03 Sep 2013 17:32:09 +0100
parents 6afcb5edd7ab
children 3a5840de4d5f
files Agent.cpp Agent.h AgentList.cpp AgentList.h BeatRootProcessor.cpp BeatRootProcessor.h BeatRootVampPlugin.cpp BeatRootVampPlugin.h BeatTracker.cpp BeatTracker.h Induction.cpp Induction.h
diffstat 12 files changed, 236 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/Agent.cpp	Wed Aug 28 16:50:40 2013 +0100
+++ b/Agent.cpp	Tue Sep 03 17:32:09 2013 +0100
@@ -16,26 +16,22 @@
 #include "Agent.h"
 #include "BeatTracker.h"
 
-double Agent::POST_MARGIN_FACTOR = 0.3;
-double Agent::PRE_MARGIN_FACTOR = 0.15;
+const double AgentParameters::DEFAULT_POST_MARGIN_FACTOR = 0.3;
+const double AgentParameters::DEFAULT_PRE_MARGIN_FACTOR = 0.15;
+const double AgentParameters::DEFAULT_MAX_CHANGE = 0.2;
+const double AgentParameters::DEFAULT_EXPIRY_TIME = 10.0;
+
 const double Agent::INNER_MARGIN = 0.040;
-double Agent::MAX_CHANGE = 0.2;
-double Agent::CONF_FACTOR = 0.5;
+const double Agent::CONF_FACTOR = 0.5;
 const double Agent::DEFAULT_CORRECTION_FACTOR = 50.0;
-const double Agent::DEFAULT_EXPIRY_TIME = 10.0;
 
 int Agent::idCounter = 0;
 
-double Agent::innerMargin = 0.0;
-double Agent::correctionFactor = 0.0;
-double Agent::expiryTime = 0.0;
-double Agent::decayFactor = 0.0;
-
 void Agent::accept(Event e, double err, int beats) {
     beatTime = e.time;
     events.push_back(e);
     if (fabs(initialBeatInterval - beatInterval -
-             err / correctionFactor) < MAX_CHANGE * initialBeatInterval)
+             err / correctionFactor) < maxChange * initialBeatInterval)
         beatInterval += err / correctionFactor;// Adjust tempo
     beatCount += beats;
     double conFactor = 1.0 - CONF_FACTOR * err /
--- a/Agent.h	Wed Aug 28 16:50:40 2013 +0100
+++ b/Agent.h	Tue Sep 03 17:32:09 2013 +0100
@@ -26,6 +26,40 @@
 
 class AgentList;
 
+class AgentParameters
+{
+public:
+    static const double DEFAULT_POST_MARGIN_FACTOR;
+    static const double DEFAULT_PRE_MARGIN_FACTOR;
+    static const double DEFAULT_MAX_CHANGE;
+    static const double DEFAULT_EXPIRY_TIME;
+
+    AgentParameters() :
+        postMarginFactor(DEFAULT_POST_MARGIN_FACTOR),
+        preMarginFactor(DEFAULT_PRE_MARGIN_FACTOR),
+        maxChange(DEFAULT_MAX_CHANGE),
+        expiryTime(DEFAULT_EXPIRY_TIME) { }
+
+    /** The maximum amount by which a beat can be later than the
+     *  predicted beat time, expressed as a fraction of the beat
+     *  period. */
+    double postMarginFactor;
+
+    /** The maximum amount by which a beat can be earlier than the
+     *  predicted beat time, expressed as a fraction of the beat
+     *  period. */
+    double preMarginFactor;
+
+    /** The maximum allowed deviation from the initial tempo,
+     * expressed as a fraction of the initial beat period. */
+    double maxChange;
+
+    /** The default value of expiryTime, which is the time (in
+     *  seconds) after which an Agent that has no Event matching its
+     *  beat predictions will be destroyed. */
+    double expiryTime;
+};
+
 /** Agent is the central class for beat tracking.
  *  Each Agent object has a tempo hypothesis, a history of tracked beats, and
  *  a score evaluating the continuity, regularity and salience of its beat track.
@@ -33,53 +67,47 @@
 class Agent
 {
 public:
-    /** The maximum amount by which a beat can be later than the predicted beat time,
-     *  expressed as a fraction of the beat period. */
-    static double POST_MARGIN_FACTOR;
-
-    /** The maximum amount by which a beat can be earlier than the predicted beat time,
-     *  expressed as a fraction of the beat period. */
-    static double PRE_MARGIN_FACTOR;
-	
-    /** The default value of innerMargin, which is the maximum time (in seconds) that a
-     * 	beat can deviate from the predicted beat time without a fork occurring. */
+    /** The default value of innerMargin, which is the maximum time
+     * 	(in seconds) that a beat can deviate from the predicted beat
+     * 	time without a fork occurring. */
     static const double INNER_MARGIN;
 	
-    /** The maximum allowed deviation from the initial tempo, expressed as a fraction of the initial beat period. */
-    static double MAX_CHANGE;
-		
-    /** The slope of the penalty function for onsets which do not coincide precisely with predicted beat times. */
-    static double CONF_FACTOR;
+    /** The slope of the penalty function for onsets which do not
+     * coincide precisely with predicted beat times. */
+    static const double CONF_FACTOR;
 	
-    /** The reactiveness/inertia balance, i.e. degree of change in the tempo, is controlled by the correctionFactor
-     *  variable.  This constant defines its default value, which currently is not subsequently changed. The
-     *  beat period is updated by the reciprocal of the correctionFactor multiplied by the difference between the
-     *  predicted beat time and matching onset. */
+    /** The reactiveness/inertia balance, i.e. degree of change in the
+     *  tempo, is controlled by the correctionFactor variable.  This
+     *  constant defines its default value, which currently is not
+     *  subsequently changed. The beat period is updated by the
+     *  reciprocal of the correctionFactor multiplied by the
+     *  difference between the predicted beat time and matching
+     *  onset. */
     static const double DEFAULT_CORRECTION_FACTOR;
 	
-    /** The default value of expiryTime, which is the time (in seconds) after which an Agent that
-     *  has no Event matching its beat predictions will be destroyed. */
-    static const double DEFAULT_EXPIRY_TIME;
-
 protected:
     /** The identity number of the next created Agent */
     static int idCounter;
 	
-    /** The maximum time (in seconds) that a beat can deviate from the predicted beat time
-     *  without a fork occurring (i.e. a 2nd Agent being created). */
-    static double innerMargin;
+    /** The maximum time (in seconds) that a beat can deviate from the
+     *  predicted beat time without a fork occurring (i.e. a 2nd Agent
+     *  being created). */
+    double innerMargin;
 
-    /** Controls the reactiveness/inertia balance, i.e. degree of change in the tempo.  The
-     *  beat period is updated by the reciprocal of the correctionFactor multiplied by the difference between the
-     *  predicted beat time and matching onset. */
-    static double correctionFactor;
+    /** Controls the reactiveness/inertia balance, i.e. degree of
+     *  change in the tempo.  The beat period is updated by the
+     *  reciprocal of the correctionFactor multiplied by the
+     *  difference between the predicted beat time and matching
+     *  onset. */
+    double correctionFactor;
 
-    /** The time (in seconds) after which an Agent that
-     *  has no Event matching its beat predictions will be destroyed. */
-    static double expiryTime;
+    /** The time (in seconds) after which an Agent that has no Event
+     *  matching its beat predictions will be destroyed. */
+    double expiryTime;
 	
-    /** For scoring Agents in a (non-existent) real-time version (otherwise not used). */
-    static double decayFactor;
+    /** For scoring Agents in a (non-existent) real-time version
+     * (otherwise not used). */
+    double decayFactor;
 
 public:
     /** The size of the outer half-window before the predicted beat time. */
@@ -94,46 +122,57 @@
     /** To be used in real-time version?? */
     double tempoScore;
 	
-    /** Sum of salience values of the Events which have been interpreted
-     *  as beats by this Agent, weighted by their nearness to the predicted beat times. */
+    /** Sum of salience values of the Events which have been
+     *  interpreted as beats by this Agent, weighted by their nearness
+     *  to the predicted beat times. */
     double phaseScore;
 	
-    /** How long has this agent been the best?  For real-time version; otherwise not used. */
+    /** How long has this agent been the best?  For real-time version;
+     * otherwise not used. */
     double topScoreTime;
 	
-    /** The number of beats found by this Agent, including interpolated beats. */
+    /** The number of beats found by this Agent, including
+     * interpolated beats. */
     int beatCount;
 	
-    /** The current tempo hypothesis of the Agent, expressed as the beat period in seconds. */
+    /** The current tempo hypothesis of the Agent, expressed as the
+     * beat period in seconds. */
     double beatInterval;
 
-    /** The initial tempo hypothesis of the Agent, expressed as the beat period in seconds. */
+    /** The initial tempo hypothesis of the Agent, expressed as the
+     * beat period in seconds. */
     double initialBeatInterval;
 	
     /** The time of the most recent beat accepted by this Agent. */
     double beatTime;
+
+    /** The maximum allowed deviation from the initial tempo,
+     * expressed as a fraction of the initial beat period. */
+    double maxChange;
 	
-    /** The list of Events (onsets) accepted by this Agent as beats, plus interpolated beats. */
+    /** The list of Events (onsets) accepted by this Agent as beats,
+     * plus interpolated beats. */
     EventList events;
 
     /** Constructor: the work is performed by init()
      *  @param ibi The beat period (inter-beat interval) of the Agent's tempo hypothesis.
      */
-    Agent(double ibi) {
-	innerMargin = INNER_MARGIN;
-	correctionFactor = DEFAULT_CORRECTION_FACTOR;
-	expiryTime = DEFAULT_EXPIRY_TIME;
-	decayFactor = 0;
-	beatInterval = ibi;
-	initialBeatInterval = ibi;
-	postMargin = ibi * POST_MARGIN_FACTOR;
-	preMargin = ibi * PRE_MARGIN_FACTOR;
-	idNumber = idCounter++;
-	phaseScore = 0.0;
-	tempoScore = 0.0;
-	topScoreTime = 0.0;
-	beatCount = 0;
-	beatTime = -1.0;
+    Agent(AgentParameters params, double ibi) :
+	innerMargin(INNER_MARGIN),
+	correctionFactor(DEFAULT_CORRECTION_FACTOR),
+	expiryTime(params.expiryTime),
+	decayFactor(0),
+	preMargin(ibi * params.preMarginFactor),
+	postMargin(ibi * params.postMarginFactor),
+	idNumber(idCounter++),
+	tempoScore(0.0),
+	phaseScore(0.0),
+	topScoreTime(0.0),
+	beatCount(0),
+	beatInterval(ibi),
+	initialBeatInterval(ibi),
+	beatTime(-1.0),
+        maxChange(params.maxChange) {
     } // constructor
 
     Agent *clone() const {
--- a/AgentList.cpp	Wed Aug 28 16:50:40 2013 +0100
+++ b/AgentList.cpp	Tue Sep 03 17:32:09 2013 +0100
@@ -75,7 +75,7 @@
 } // removeDuplicates()
 
 
-void AgentList::beatTrack(EventList el, double stop)
+void AgentList::beatTrack(EventList el, AgentParameters params, double stop)
 {
     EventList::iterator ei = el.begin();
     bool phaseGiven = !empty() && ((*begin())->beatTime >= 0); // if given for one, assume given for others
@@ -102,7 +102,7 @@
                     std::cerr << "Creating a new agent" << std::endl;
 #endif
                     // Create new agent with different phase
-                    Agent *newAgent = new Agent(prevBeatInterval);
+                    Agent *newAgent = new Agent(params, prevBeatInterval);
                     // This may add another agent to our list as well
                     newAgent->considerAsBeat(ev, *this);
                     add(newAgent);
--- a/AgentList.h	Wed Aug 28 16:50:40 2013 +0100
+++ b/AgentList.h	Tue Sep 03 17:32:09 2013 +0100
@@ -121,15 +121,15 @@
     /** Perform beat tracking on a list of events (onsets).
      *  @param el The list of onsets (or events or peaks) to beat track
      */
-    void beatTrack(EventList el) {
-	beatTrack(el, -1.0);
+    void beatTrack(EventList el, AgentParameters params) {
+	beatTrack(el, params, -1.0);
     } // beatTrack()/1
 	
     /** Perform beat tracking on a list of events (onsets).
      *  @param el The list of onsets (or events or peaks) to beat track.
      *  @param stop Do not find beats after <code>stop</code> seconds.
      */
-    void beatTrack(EventList el, double stop);
+    void beatTrack(EventList el, AgentParameters params, double stop);
 
     /** Finds the Agent with the highest score in the list, or NULL if beat tracking has failed.
      *  @return The Agent with the highest score
--- a/BeatRootProcessor.cpp	Wed Aug 28 16:50:40 2013 +0100
+++ b/BeatRootProcessor.cpp	Tue Sep 03 17:32:09 2013 +0100
@@ -67,7 +67,7 @@
     std::cerr << "Onsets: " << onsetList.size() << std::endl;
 #endif
 
-    return BeatTracker::beatTrack(onsetList);
+    return BeatTracker::beatTrack(agentParameters, onsetList);
 
 } // processFile()
 
--- a/BeatRootProcessor.h	Wed Aug 28 16:50:40 2013 +0100
+++ b/BeatRootProcessor.h	Tue Sep 03 17:32:09 2013 +0100
@@ -81,6 +81,9 @@
 	
     /** The estimated onset times and their saliences. */	
     EventList onsetList;
+    
+    /** User-specifiable processing parameters. */
+    AgentParameters agentParameters;
 	
     /** Flag for suppressing all standard output messages except results. */
     static bool silent;
@@ -89,12 +92,14 @@
 
     /** Constructor: note that streams are not opened until the input
      *  file is set (see <code>setInputFile()</code>). */
-    BeatRootProcessor(float sr) :
-        sampleRate(sr) {
-        hopSize = 0;
-        fftSize = 0;
-        hopTime = 0.010;
-        fftTime = 0.04644;
+    BeatRootProcessor(float sr, AgentParameters parameters) :
+        sampleRate(sr),
+        hopSize(0),
+        fftSize(0),
+        hopTime(0.010),
+        fftTime(0.04644),
+        agentParameters(parameters)
+    {
         hopSize = lrint(sampleRate * hopTime);
         fftSize = lrint(pow(2, lrint( log(fftTime * sampleRate) / log(2))));
     } // constructor
--- a/BeatRootVampPlugin.cpp	Wed Aug 28 16:50:40 2013 +0100
+++ b/BeatRootVampPlugin.cpp	Tue Sep 03 17:32:09 2013 +0100
@@ -24,7 +24,7 @@
 BeatRootVampPlugin::BeatRootVampPlugin(float inputSampleRate) :
     Plugin(inputSampleRate)
 {
-    m_processor = new BeatRootProcessor(inputSampleRate);
+    m_processor = new BeatRootProcessor(inputSampleRate, AgentParameters());
 }
 
 BeatRootVampPlugin::~BeatRootVampPlugin()
@@ -104,18 +104,113 @@
 BeatRootVampPlugin::getParameterDescriptors() const
 {
     ParameterList list;
+
+    ParameterDescriptor desc;
+
+    double postMarginFactor;
+
+    /** The maximum amount by which a beat can be earlier than the
+     *  predicted beat time, expressed as a fraction of the beat
+     *  period. */
+    double preMarginFactor;
+
+    /** The maximum allowed deviation from the initial tempo,
+     * expressed as a fraction of the initial beat period. */
+    double maxChange;
+
+    /** The default value of expiryTime, which is the time (in
+     *  seconds) after which an Agent that has no Event matching its
+     *  beat predictions will be destroyed. */
+    
+    desc.identifier = "preMarginFactor";
+    desc.name = "Pre-Margin Factor";
+    desc.description = "The maximum amount by which a beat can be earlier than the predicted beat time, expressed as a fraction of the beat period.";
+    desc.minValue = 0;
+    desc.maxValue = 1;
+    desc.defaultValue = AgentParameters::DEFAULT_PRE_MARGIN_FACTOR;
+    desc.isQuantized = false;
+    list.push_back(desc);
+    
+    desc.identifier = "postMarginFactor";
+    desc.name = "Post-Margin Factor";
+    desc.description = "The maximum amount by which a beat can be later than the predicted beat time, expressed as a fraction of the beat period.";
+    desc.minValue = 0;
+    desc.maxValue = 1;
+    desc.defaultValue = AgentParameters::DEFAULT_POST_MARGIN_FACTOR;
+    desc.isQuantized = false;
+    list.push_back(desc);
+    
+    desc.identifier = "maxChange";
+    desc.name = "Maximum Change";
+    desc.description = "The maximum allowed deviation from the initial tempo, expressed as a fraction of the initial beat period.";
+    desc.minValue = 0;
+    desc.maxValue = 1;
+    desc.defaultValue = AgentParameters::DEFAULT_MAX_CHANGE;
+    desc.isQuantized = false;
+    list.push_back(desc);
+    
+    desc.identifier = "expiryTime";
+    desc.name = "Expiry Time";
+    desc.description = "The default value of expiryTime, which is the time (in seconds) after which an Agent that has no Event matching its beat predictions will be destroyed.";
+    desc.minValue = 2;
+    desc.maxValue = 120;
+    desc.defaultValue = AgentParameters::DEFAULT_EXPIRY_TIME;
+    desc.isQuantized = false;
+    list.push_back(desc);
+
+    // Simon says...
+
+    // These are the parameters that should be exposed (Agent.cpp):
+
+    // If Pop, both margins should be lower (0.1).  If classical
+    // music, post margin can be increased
+    //
+    // double Agent::POST_MARGIN_FACTOR = 0.3;
+    // double Agent::PRE_MARGIN_FACTOR = 0.15;
+    //
+    // Max Change tells us how much tempo can change - so for
+    // classical we should make it higher
+    // 
+    // double Agent::MAX_CHANGE = 0.2;
+    // 
+    // The EXPIRY TIME default should be defaulted to 100 (usual cause
+    // of agents dying....)  it should also be exposed in order to
+    // troubleshoot eventual problems in songs with big silences in
+    // the beggining/end.
+    // 
+    // const double Agent::DEFAULT_EXPIRY_TIME = 10.0;
+
     return list;
 }
 
 float
 BeatRootVampPlugin::getParameter(string identifier) const
 {
+    if (identifier == "preMarginFactor") {
+        return m_parameters.preMarginFactor;
+    } else if (identifier == "postMarginFactor") {
+        return m_parameters.postMarginFactor;
+    } else if (identifier == "maxChange") {
+        return m_parameters.maxChange;
+    } else if (identifier == "expiryTime") {
+        return m_parameters.expiryTime;
+    }
+    
     return 0;
 }
 
 void
 BeatRootVampPlugin::setParameter(string identifier, float value) 
 {
+    if (identifier == "preMarginFactor") {
+        m_parameters.preMarginFactor = value;
+    } else if (identifier == "postMarginFactor") {
+        m_parameters.postMarginFactor = value;
+    } else if (identifier == "maxChange") {
+        m_parameters.maxChange = value;
+    } else if (identifier == "expiryTime") {
+        m_parameters.expiryTime = value;
+    }
 }
 
 BeatRootVampPlugin::ProgramList
@@ -187,7 +282,11 @@
 	return false;
     }
 
-    m_processor->reset();
+    // Delete the processor that was created with default parameters
+    // and used to determine the expected step and block size; replace
+    // with one using the actual parameters we have
+    delete m_processor;
+    m_processor = new BeatRootProcessor(m_inputSampleRate, m_parameters);
 
     return true;
 }
--- a/BeatRootVampPlugin.h	Wed Aug 28 16:50:40 2013 +0100
+++ b/BeatRootVampPlugin.h	Tue Sep 03 17:32:09 2013 +0100
@@ -16,6 +16,8 @@
 #ifndef _BEATROOT_VAMP_PLUGIN_H_
 #define _BEATROOT_VAMP_PLUGIN_H_
 
+#include "Agent.h"
+
 #include <vamp-sdk/Plugin.h>
 
 using std::string;
@@ -61,6 +63,7 @@
 
 protected:
     BeatRootProcessor *m_processor;
+    AgentParameters m_parameters;
 };
 
 
--- a/BeatTracker.cpp	Wed Aug 28 16:50:40 2013 +0100
+++ b/BeatTracker.cpp	Tue Sep 03 17:32:09 2013 +0100
@@ -15,7 +15,8 @@
 
 #include "BeatTracker.h"
 
-EventList BeatTracker::beatTrack(EventList events, EventList beats)
+EventList BeatTracker::beatTrack(AgentParameters params,
+                                 EventList events, EventList beats)
 {
     AgentList agents;
     int count = 0;
@@ -28,9 +29,9 @@
     }
     if (count > 0) { // tempo given by mean of initial beats
 	double ioi = (beatTime - beats.begin()->time) / count;
-	agents.push_back(new Agent(ioi));
+	agents.push_back(new Agent(params, ioi));
     } else // tempo not given; use tempo induction
-	agents = Induction::beatInduction(events);
+	agents = Induction::beatInduction(params, events);
     if (!beats.empty())
 	for (AgentList::iterator itr = agents.begin(); itr != agents.end();
 	     ++itr) {
@@ -38,7 +39,7 @@
 	    (*itr)->beatCount = count;
 	    (*itr)->events = beats;
 	}
-    agents.beatTrack(events, -1);
+    agents.beatTrack(events, params, -1);
     Agent *best = agents.bestAgent();
     EventList results;
     if (best) {
--- a/BeatTracker.h	Wed Aug 28 16:50:40 2013 +0100
+++ b/BeatTracker.h	Tue Sep 03 17:32:09 2013 +0100
@@ -56,8 +56,8 @@
      *  @param events The onsets or peaks in a feature list
      *  @return The list of beats, or an empty list if beat tracking fails
      */
-    static EventList beatTrack(EventList events) {
-	return beatTrack(events, EventList());
+    static EventList beatTrack(AgentParameters params, EventList events) {
+	return beatTrack(params, events, EventList());
     }
 	
     /** Perform beat tracking.
@@ -65,7 +65,8 @@
      *  @param beats The initial beats which are given, if any
      *  @return The list of beats, or an empty list if beat tracking fails
      */
-    static EventList beatTrack(EventList events, EventList beats);
+    static EventList beatTrack(AgentParameters params,
+                               EventList events, EventList beats);
 	
 	
     // Various get and set methods
--- a/Induction.cpp	Wed Aug 28 16:50:40 2013 +0100
+++ b/Induction.cpp	Tue Sep 03 17:32:09 2013 +0100
@@ -23,7 +23,7 @@
 int Induction::topN = 10;
 
 
-AgentList Induction::beatInduction(EventList events) {
+AgentList Induction::beatInduction(AgentParameters params, EventList events) {
     int i, j, b, bestCount;
     bool submult;
     int intervals = 0;			// number of interval clusters
@@ -183,7 +183,7 @@
         while (beat > maxIBI)		// Minimum speed
             beat /= 2.0;
         if (beat >= minIBI) {
-            a.push_back(new Agent(beat));
+            a.push_back(new Agent(params, beat));
         }
     }
 #ifdef DEBUG_BEATROOT
--- a/Induction.h	Wed Aug 28 16:50:40 2013 +0100
+++ b/Induction.h	Tue Sep 03 17:32:09 2013 +0100
@@ -68,7 +68,7 @@
      *  @return A list of beat tracking agents, where each is initialised with one
      *          of the top tempo hypotheses but no beats
      */
-    static AgentList beatInduction(EventList events);
+    static AgentList beatInduction(AgentParameters params, EventList events);
 
 protected:
     /** For variable cluster widths in newInduction().