Mercurial > hg > beatroot-vamp
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: