changeset 8:f04f87b5e643

Add agent list class, and continue plodding through
author Chris Cannam
date Fri, 30 Sep 2011 11:37:25 +0100
parents 3c11becfc81a
children 4f6626f9ffac
files Agent.cpp Agent.h AgentList.cpp AgentList.h BeatRootProcessor.cpp BeatRootProcessor.h BeatTracker.h Makefile
diffstat 8 files changed, 253 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/Agent.cpp	Tue Sep 27 19:05:27 2011 +0100
+++ b/Agent.cpp	Fri Sep 30 11:37:25 2011 +0100
@@ -32,21 +32,21 @@
 double Agent::expiryTime = 0.0;
 double Agent::decayFactor = 0.0;
 
-bool Agent::considerAsBeat(Event e, const AgentList &a) {
+bool Agent::considerAsBeat(Event e, AgentList &a) {
     double err;
     if (beatTime < 0) {	// first event
 	accept(e, 0, 1);
 	return true;
     } else {			// subsequent events
-	if (e.keyDown - events.l.getLast().keyDown > expiryTime) {
+	if (e.time - events.l.getLast().keyDown > expiryTime) {
 	    phaseScore = -1.0;	// flag agent to be deleted
 	    return false;
 	}
-	double beats = Math.round((e.keyDown - beatTime) / beatInterval);
-	err = e.keyDown - beatTime - beats * beatInterval;
+	double beats = nearbyint((e.time - beatTime) / beatInterval);
+	err = e.time - beatTime - beats * beatInterval;
 	if ((beats > 0) && (-preMargin <= err) && (err <= postMargin)) {
-	    if (Math.abs(err) > innerMargin)	// Create new agent that skips this
-		a.add(new Agent(this));	//  event (avoids large phase jump)
+	    if (fabs(err) > innerMargin)	// Create new agent that skips this
+		a.push_back(Agent(*this));	//  event (avoids large phase jump)
 	    accept(e, err, (int)beats);
 	    return true;
 	}
--- a/Agent.h	Tue Sep 27 19:05:27 2011 +0100
+++ b/Agent.h	Fri Sep 30 11:37:25 2011 +0100
@@ -18,6 +18,8 @@
 
 #include "Event.h"
 
+class AgentList;
+
 /** 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.
@@ -25,9 +27,6 @@
 class Agent
 {
 public:
-
-    typedef std::vector<Agent> AgentList;
-
     /** 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;
@@ -165,6 +164,7 @@
 	return value;
     }
 
+public:
     /** Accept a new Event as a beat time, and update the state of the Agent accordingly.
      *  @param e The Event which is accepted as being on the beat.
      *  @param err The difference between the predicted and actual beat times.
@@ -198,7 +198,7 @@
      * @param a The list of all agents, which is updated if a new agent is created.
      * @return Indicate whether the given Event was accepted as a beat by this Agent.
      */
-    bool considerAsBeat(Event e, const AgentList &a);
+    bool considerAsBeat(Event e, AgentList &a);
 
     /** Interpolates missing beats in the Agent's beat track, starting from the beginning of the piece. */
     void fillBeats() {
@@ -210,8 +210,11 @@
      */
     void fillBeats(double start);
 
+    // for sorting AgentList
+    bool operator<(const Agent &a) const {
+        return beatInterval < a.beatInterval;
+    }
+
 }; // class Agent
 
-typedef Agent::AgentList AgentList;
-
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AgentList.cpp	Fri Sep 30 11:37:25 2011 +0100
@@ -0,0 +1,21 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+  Vamp feature extraction plugin for the BeatRoot beat tracker.
+
+  Centre for Digital Music, Queen Mary, University of London.
+  This file copyright 2011 Simon Dixon, Chris Cannam and QMUL.
+    
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.  See the file
+  COPYING included with this distribution for more information.
+*/
+
+#include "AgentList.h"
+
+bool AgentList::useAverageSalience = false;
+const double AgentList::DEFAULT_BI = 0.02;
+const double AgentList::DEFAULT_BT = 0.04;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AgentList.h	Fri Sep 30 11:37:25 2011 +0100
@@ -0,0 +1,186 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+  Vamp feature extraction plugin for the BeatRoot beat tracker.
+
+  Centre for Digital Music, Queen Mary, University of London.
+  This file copyright 2011 Simon Dixon, Chris Cannam and QMUL.
+    
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.  See the file
+  COPYING included with this distribution for more information.
+*/
+
+#ifndef _AGENT_LIST_H_
+#define _AGENT_LIST_H_
+
+#include "Agent.h"
+#include "Event.h"
+
+#include <algorithm>
+
+/** Class for maintaining the set of all Agents involved in beat tracking a piece of music.
+ */
+class AgentList
+{
+public:
+    typedef std::vector<Agent> Container;
+    typedef Container::iterator iterator;
+
+protected:
+    Container list;
+
+public:
+    // expose some vector methods
+    //!!! can we remove these again once the rest of AgentList is implemented?
+    bool empty() const { return list.empty(); }
+    Container::iterator begin() { return list.begin(); }
+    Container::iterator end() { return list.end(); }
+    void push_back(const Agent &a) { list.push_back(a); }
+
+   	/** Flag for choice between sum and average beat salience values for Agent scores.
+	 *  The use of summed saliences favours faster tempi or lower metrical levels. */
+	static bool useAverageSalience;
+	
+	/** For the purpose of removing duplicate agents, the default JND of IBI */
+	static const double DEFAULT_BI;
+	
+	/** For the purpose of removing duplicate agents, the default JND of phase */
+	static const double DEFAULT_BT;
+
+	/** Inserts newAgent into the list in ascending order of beatInterval */
+	void add(Agent a) {
+		add(a, true);
+	} // add()/1
+
+	/** Appends newAgent to list (sort==false), or inserts newAgent into the list
+	 *  in ascending order of beatInterval
+	 *  @param newAgent The agent to be added to the list
+	 *  @param sort Flag indicating whether the list is sorted or not
+	 */
+	void add(Agent newAgent, bool sort){
+	    list.push_back(newAgent);
+	    if (sort) this->sort();
+	} // add()/2
+
+	/** Sorts the AgentList by increasing beatInterval. */
+	void sort() {
+	    std::sort(list.begin(), list.end());
+	} // sort()
+
+	/** Removes the given item from the list.
+	 *  @param ptr Points to the Agent which is removed from the list
+	 */
+	void remove(iterator itr) {
+	    list.erase(itr);
+	} // remove()
+
+protected:
+	/** Removes Agents from the list which are duplicates of other Agents.
+	 *  A duplicate is defined by the tempo and phase thresholds
+	 *  thresholdBI and thresholdBT respectively.
+	 */
+ void removeDuplicates() {
+     sort();
+     for (iterator itr = begin(); itr != end(); ++itr) {
+	 if (itr->phaseScore < 0.0) // already flagged for deletion
+	     continue;
+	 iterator itr2 = itr;
+	 for (++itr2; itr != end(); ++itr) {
+	     if (itr2->beatInterval - itr->beatInterval > DEFAULT_BI)
+		 break;
+	     if (fabs(itr->beatTime - itr2->beatTime) > DEFAULT_BT)
+		 continue;
+	     if (itr->phaseScore < itr2->phaseScore) {
+		 itr->phaseScore = -1.0;	// flag for deletion
+		 if (itr2->topScoreTime < itr->topScoreTime)
+		     itr2->topScoreTime = itr->topScoreTime;
+		 break;
+	     } else {
+		 itr2->phaseScore = -1.0;	// flag for deletion
+		 if (itr->topScoreTime < itr2->topScoreTime)
+		     itr->topScoreTime = itr2->topScoreTime;
+	     }
+	 }
+     }
+     for (iterator itr = begin(); itr != end(); ) {
+	 if (itr->phaseScore < 0.0) {
+	     list.erase(itr);
+	 } else {
+	     ++itr;
+	 }
+     }
+ } // removeDuplicates()
+
+public:
+	/** 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);
+	} // 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) {
+	    EventList::iterator ei = el.begin();
+	    bool phaseGiven = !empty() && (begin()->beatTime >= 0); // if given for one, assume given for others
+	    while (ei != el.end()) {
+		Event ev = *ei;
+		++ei;
+		if ((stop > 0) && (ev.time > stop))
+		    break;
+		bool created = phaseGiven;
+		double prevBeatInterval = -1.0;
+		// cc: Duplicate our list of agents, and scan through
+		// the (now immutable) copy.  This means we can safely
+		// add agents to our own list while scanning without
+		// disrupting our scan.
+		Container currentAgents = list;
+		for (Container::iterator ai = currentAgents.begin();
+		     ai != currentAgents.end(); ++ai) {
+		    Agent currentAgent = *ai;
+		    if (currentAgent.beatInterval != prevBeatInterval) {
+			if ((prevBeatInterval>=0) && !created && (ev.time<5.0)) {
+			    // Create new agent with different phase
+			    Agent newAgent(prevBeatInterval);
+			    // This may add an agent to our list
+			    newAgent.considerAsBeat(ev, *this);
+			}
+			prevBeatInterval = currentAgent.beatInterval;
+			created = phaseGiven;
+		    }
+		    if (currentAgent.considerAsBeat(ev, *this))
+			created = true;
+		} // loop for each agent
+		removeDuplicates();
+	    } // loop for each event
+	} // beatTrack()
+
+	/** Finds the Agent with the highest score in the list, or NULL if beat tracking has failed.
+	 *  @return The Agent with the highest score
+	 */
+	Agent *bestAgent() {
+	    double best = -1.0;
+	    Agent *bestAg = 0;
+	    for (iterator itr = begin(); itr != end(); ++itr) {
+		double startTime = itr->events.begin()->time;
+		double conf = (itr->phaseScore + itr->tempoScore) /
+		    (useAverageSalience? (double)itr->beatCount: 1.0);
+		if (conf > best) {
+		    bestAg = &(*itr);
+		    best = conf;
+		}
+	    }
+	    return bestAg;
+	} // bestAgent()
+
+
+}; // class AgentList
+
+#endif
+
--- a/BeatRootProcessor.cpp	Tue Sep 27 19:05:27 2011 +0100
+++ b/BeatRootProcessor.cpp	Fri Sep 30 11:37:25 2011 +0100
@@ -30,6 +30,6 @@
 int
 BeatRootProcessor::normaliseMode = 2;
 
-int
-BeatRootProcessor::energyOversampleFactor = 2;
+//int
+//BeatRootProcessor::energyOversampleFactor = 2;
 
--- a/BeatRootProcessor.h	Tue Sep 27 19:05:27 2011 +0100
+++ b/BeatRootProcessor.h	Fri Sep 30 11:37:25 2011 +0100
@@ -128,7 +128,8 @@
 
     /** Constructor: note that streams are not opened until the input
      *  file is set (see <code>setInputFile()</code>). */
-    BeatRootProcessor() {
+    BeatRootProcessor(float sr) :
+        sampleRate(sr) {
         frameRMS = 0;
         ltAverage = 0;
         frameCount = 0;
--- a/BeatTracker.h	Tue Sep 27 19:05:27 2011 +0100
+++ b/BeatTracker.h	Fri Sep 30 11:37:25 2011 +0100
@@ -18,6 +18,7 @@
 
 #include "Event.h"
 #include "Agent.h"
+#include "AgentList.h"
 #include "Induction.h"
 
 using std::vector;
@@ -33,12 +34,6 @@
 	
     /** the times of onsets (in seconds) */
     vector<double> onsets;
-	
-    /** the times corresponding to each point in the <code>magnitudes</code> array */
-    vector<double> env;
-	
-    /** smoothed amplitude envelope of audio signal */ 
-    vector<int> magnitudes;
 
 public:
     /** Constructor:
@@ -81,23 +76,24 @@
 	    beatTime = itr->time;
 	}
 	if (count > 0) { // tempo given by mean of initial beats
-	    double ioi = (beatTime - beats.l.getFirst().keyDown) / count;
-	    agents = new AgentList(new Agent(ioi), null);
-	} else									// tempo not given; use tempo induction
-	    agents = Induction.beatInduction(events);
-	if (beats != null)
-	    for (AgentList ptr = agents; ptr.ag != null; ptr = ptr.next) {
-		ptr.ag.beatTime = beatTime;
-		ptr.ag.beatCount = count;
-		ptr.ag.events = new EventList(beats);
+	    double ioi = (beatTime - beats.begin()->time) / count;
+	    agents.push_back(Agent(ioi));
+	} else // tempo not given; use tempo induction
+	    agents = Induction::beatInduction(events);
+	if (!beats.empty())
+	    for (AgentList::iterator itr = agents.begin(); itr != agents.end();
+                 ++itr) {
+		itr->beatTime = beatTime;
+		itr->beatCount = count;
+		itr->events = beats;
 	    }
 	agents.beatTrack(events, -1);
-	Agent best = agents.bestAgent();
-	if (best != null) {
-	    best.fillBeats(beatTime);
-	    return best.events;
+	Agent *best = agents.bestAgent();
+	if (best) {
+	    best->fillBeats(beatTime);
+	    return best->events;
 	}
-	return new EventList();
+	return EventList();
     } // beatTrack()/1
 	
     /** Finds the mean tempo (as inter-beat interval) from an array of beat times
@@ -105,9 +101,9 @@
      *  @return The average inter-beat interval
      */
     static double getAverageIBI(vector<double> d) {
-	if ((d == null) || (d.length < 2))
+	if (d.size() < 2)
 	    return -1.0;
-	return (d[d.length - 1] - d[0]) / (d.length - 1);
+	return (d[d.size() - 1] - d[0]) / (d.size() - 1);
     } // getAverageIBI()
 	
     /** Finds the median tempo (as inter-beat interval) from an array of beat times
@@ -115,16 +111,17 @@
      *  @return The median inter-beat interval
      */
     static double getMedianIBI(vector<double> d) {
-	if ((d == null) || (d.length < 2))
+	if (d.size() < 2)
 	    return -1.0;
-	vector<double> ibi = new double[d.length-1];
-	for (int i = 1; i < d.length; i++)
+	vector<double> ibi;
+        ibi.resize(d.size()-1);
+	for (int i = 1; i < d.size(); i++)
 	    ibi[i-1] = d[i] - d[i-1];
-	Arrays.sort(ibi);
-	if (ibi.length % 2 == 0)
-	    return (ibi[ibi.length / 2] + ibi[ibi.length / 2 - 1]) / 2;
+        std::sort(ibi.begin(), ibi.end());
+	if (ibi.size() % 2 == 0)
+	    return (ibi[ibi.size() / 2] + ibi[ibi.size() / 2 - 1]) / 2;
 	else
-	    return ibi[ibi.length / 2];
+	    return ibi[ibi.size() / 2];
     } // getAverageIBI()
 	
 	
@@ -140,16 +137,6 @@
 	return onsets;
     } // getOnsets()
 
-    /** @return the array of offset times */
-    vector<double> getOffsets() {
-	return offsets;
-    } // getOffsets()
-
-    /** @return the array of MIDI pitches */
-    vector<int> getPitches() {
-	return pitches;
-    } // getPitches()
-
     /** Sets the onset times as a list of Events, for use by the beat tracking methods. 
      *  @param on The times of onsets in seconds
      */
@@ -164,43 +151,11 @@
 	onsets = on;
     } // setOnsets()
 
-    /** Sets the array of offset times, for displaying MIDI input data.
-     *  @param off The array of MIDI offset times
-     */
-    void setOffsets(vector<double> off) {
-	offsets = off;
-	// setMode(SHOW_MIDI, SHOW_AUDIO | SHOW_SPECTRO);
-    } // setOffsets()
-
-    /** Sets the array of times of amplitude envelope points, for displaying.
-     *  @param envTimes The array of times in seconds corresponding to the values in <code>magnitudes</code>
-     */
-    void setEnvTimes(vector<double> envTimes) {
-	env = envTimes;
-	setMode(SHOW_AUDIO, SHOW_MIDI);
-    } // setEnvTimes()
-
-    /** Sets the array of magnitude values, for displaying.
-     *  @param mag The array of amplitude envelope values
-     */
-    void setMagnitudes(vector<int> mag) {
-	magnitudes = mag;
-    } // setMagnitudes()
-
-    /** Sets the array of pitch values, for displaying MIDI input data.
-     *  @param p The array of MIDI pitch values
-     */
-    void setPitches(vector<int> p) {
-	pitches = p;
-    } // setPitches()
-
     /** Sets the list of beats.
      * @param b The list of beats
      */
     void setBeats(EventList b) {
 	beats = b;
-	selectedBeat = null;
-	beatPtr = beats.listIterator();
     } // setBeats()
 
 }; // class BeatTrackDisplay
--- a/Makefile	Tue Sep 27 19:05:27 2011 +0100
+++ b/Makefile	Fri Sep 30 11:37:25 2011 +0100
@@ -1,7 +1,7 @@
 
 CXXFLAGS	:= -g
 
-beatroot-vamp.so:	BeatRootProcessor.o BeatRootVampPlugin.o BeatTracker.o Peaks.o Agent.o Induction.o
+beatroot-vamp.so:	BeatRootProcessor.o BeatRootVampPlugin.o BeatTracker.o Peaks.o Agent.o AgentList.o Induction.o
 	g++ -shared $^ -o $@ -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -lpthread -Wl,--version-script=vamp-plugin.map
 
 clean: