# HG changeset patch # User Chris Cannam # Date 1318413352 -3600 # Node ID 887c629502a9135f2d0f86a66b78810c622badca # Parent f1252b6a7cf5a4a94be6a46747d3a75110347ee9 refactor: pull method implementations into .cpp files diff -r f1252b6a7cf5 -r 887c629502a9 Agent.cpp --- a/Agent.cpp Fri Oct 07 14:07:42 2011 +0100 +++ b/Agent.cpp Wed Oct 12 10:55:52 2011 +0100 @@ -31,6 +31,30 @@ 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) + beatInterval += err / correctionFactor;// Adjust tempo + beatCount += beats; + double conFactor = 1.0 - CONF_FACTOR * err / + (err>0? postMargin: -preMargin); + if (decayFactor > 0) { + double memFactor = 1. - 1. / threshold((double)beatCount,1,decayFactor); + phaseScore = memFactor * phaseScore + + (1.0 - memFactor) * conFactor * e.salience; + } else + phaseScore += conFactor * e.salience; + +#ifdef DEBUG_BEATROOT + std::cerr << "Ag#" << idNumber << ": " << beatInterval << std::endl; + std::cerr << " Beat" << beatCount << " Time=" << beatTime + << " Score=" << tempoScore << ":P" << phaseScore << ":" + << topScoreTime << std::endl; +#endif +} // accept() + bool Agent::considerAsBeat(Event e, AgentList &a) { double err; if (beatTime < 0) { // first event diff -r f1252b6a7cf5 -r 887c629502a9 Agent.h --- a/Agent.h Fri Oct 07 14:07:42 2011 +0100 +++ b/Agent.h Wed Oct 12 10:55:52 2011 +0100 @@ -157,28 +157,7 @@ * @param err The difference between the predicted and actual beat times. * @param beats The number of beats since the last beat that matched an Event. */ - void accept(Event e, double err, int beats) { - beatTime = e.time; - events.push_back(e); - if (fabs(initialBeatInterval - beatInterval - - err / correctionFactor) < MAX_CHANGE * initialBeatInterval) - beatInterval += err / correctionFactor;// Adjust tempo - beatCount += beats; - double conFactor = 1.0 - CONF_FACTOR * err / - (err>0? postMargin: -preMargin); - if (decayFactor > 0) { - double memFactor = 1. - 1. / threshold((double)beatCount,1,decayFactor); - phaseScore = memFactor * phaseScore + - (1.0 - memFactor) * conFactor * e.salience; - } else - phaseScore += conFactor * e.salience; -#ifdef DEBUG_BEATROOT - std::cerr << "Ag#" << idNumber << ": " << beatInterval << std::endl; - std::cerr << " Beat" << beatCount << " Time=" << beatTime - << " Score=" << tempoScore << ":P" << phaseScore << ":" - << topScoreTime << std::endl; -#endif - } // accept() + void accept(Event e, double err, int beats); /** The given Event is tested for a possible beat time. The following situations can occur: * 1) The Agent has no beats yet; the Event is accepted as the first beat. diff -r f1252b6a7cf5 -r 887c629502a9 AgentList.cpp --- a/AgentList.cpp Fri Oct 07 14:07:42 2011 +0100 +++ b/AgentList.cpp Wed Oct 12 10:55:52 2011 +0100 @@ -19,3 +19,117 @@ const double AgentList::DEFAULT_BI = 0.02; const double AgentList::DEFAULT_BT = 0.04; +void AgentList::removeDuplicates() +{ + sort(); + for (iterator itr = begin(); itr != end(); ++itr) { + if (itr->phaseScore < 0.0) // already flagged for deletion + continue; + iterator itr2 = itr; + for (++itr2; itr2 != end(); ++itr2) { + 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; + } + } + } + int removed = 0; + for (iterator itr = begin(); itr != end(); ) { + if (itr->phaseScore < 0.0) { + ++removed; + list.erase(itr); + } else { + ++itr; + } + } +#ifdef DEBUG_BEATROOT + if (removed > 0) { + std::cerr << "removeDuplicates: removed " << removed << ", have " + << list.size() << " agent(s) remaining" << std::endl; + } + int n = 0; + for (Container::iterator i = list.begin(); i != list.end(); ++i) { + std::cerr << "agent " << n++ << ": time " << i->beatTime << std::endl; + } +#endif +} // removeDuplicates() + + +void AgentList::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 + // copy. This means we can safely add agents to our own + // list while scanning without disrupting our scan. Each + // agent needs to be re-added to our own list explicitly + // (since it is modified by e.g. considerAsBeat) + Container currentAgents = list; + list.clear(); + 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)) { +#ifdef DEBUG_BEATROOT + std::cerr << "Creating a new agent" << std::endl; +#endif + // Create new agent with different phase + Agent newAgent(prevBeatInterval); + // This may add another agent to our list as well + newAgent.considerAsBeat(ev, *this); + add(newAgent); + } + prevBeatInterval = currentAgent.beatInterval; + created = phaseGiven; + } + if (currentAgent.considerAsBeat(ev, *this)) + created = true; + add(currentAgent); + } // loop for each agent + removeDuplicates(); + } // loop for each event +} // beatTrack() + +Agent *AgentList::bestAgent() +{ + double best = -1.0; + Agent *bestAg = 0; + for (iterator itr = begin(); itr != end(); ++itr) { + if (itr->events.empty()) continue; + 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; + } + } +#ifdef DEBUG_BEATROOT + if (bestAg) { + std::cerr << "Best agent: Ag#" << bestAg->idNumber << std::endl; + std::cerr << " Av-salience = " << best << std::endl; + } else { + std::cerr << "No surviving agent - beat tracking failed" << std::endl; + } +#endif + return bestAg; +} // bestAgent() + diff -r f1252b6a7cf5 -r 887c629502a9 AgentList.h --- a/AgentList.h Fri Oct 07 14:07:42 2011 +0100 +++ b/AgentList.h Wed Oct 12 10:55:52 2011 +0100 @@ -93,49 +93,7 @@ * 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; itr2 != end(); ++itr2) { - 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; - } - } - } - int removed = 0; - for (iterator itr = begin(); itr != end(); ) { - if (itr->phaseScore < 0.0) { - ++removed; - list.erase(itr); - } else { - ++itr; - } - } -#ifdef DEBUG_BEATROOT - if (removed > 0) { - std::cerr << "removeDuplicates: removed " << removed << ", have " - << list.size() << " agent(s) remaining" << std::endl; - } - int n = 0; - for (Container::iterator i = list.begin(); i != list.end(); ++i) { - std::cerr << "agent " << n++ << ": time " << i->beatTime << std::endl; - } -#endif - } // removeDuplicates() + void removeDuplicates(); public: /** Perform beat tracking on a list of events (onsets). @@ -149,75 +107,12 @@ * @param el The list of onsets (or events or peaks) to beat track. * @param stop Do not find beats after stop 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 - // copy. This means we can safely add agents to our own - // list while scanning without disrupting our scan. Each - // agent needs to be re-added to our own list explicitly - // (since it is modified by e.g. considerAsBeat) - Container currentAgents = list; - list.clear(); - 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)) { -#ifdef DEBUG_BEATROOT - std::cerr << "Creating a new agent" << std::endl; -#endif - // Create new agent with different phase - Agent newAgent(prevBeatInterval); - // This may add another agent to our list as well - newAgent.considerAsBeat(ev, *this); - add(newAgent); - } - prevBeatInterval = currentAgent.beatInterval; - created = phaseGiven; - } - if (currentAgent.considerAsBeat(ev, *this)) - created = true; - add(currentAgent); - } // loop for each agent - removeDuplicates(); - } // loop for each event - } // beatTrack() + void beatTrack(EventList el, 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 */ - Agent *bestAgent() { - double best = -1.0; - Agent *bestAg = 0; - for (iterator itr = begin(); itr != end(); ++itr) { - if (itr->events.empty()) continue; - 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; - } - } -#ifdef DEBUG_BEATROOT - if (bestAg) { - std::cerr << "Best agent: Ag#" << bestAg->idNumber << std::endl; - std::cerr << " Av-salience = " << best << std::endl; - } else { - std::cerr << "No surviving agent - beat tracking failed" << std::endl; - } -#endif - return bestAg; - } // bestAgent() - + Agent *bestAgent(); }; // class AgentList diff -r f1252b6a7cf5 -r 887c629502a9 BeatRootProcessor.cpp --- a/BeatRootProcessor.cpp Fri Oct 07 14:07:42 2011 +0100 +++ b/BeatRootProcessor.cpp Wed Oct 12 10:55:52 2011 +0100 @@ -18,3 +18,56 @@ bool BeatRootProcessor::silent = true; +void BeatRootProcessor::processFrame(const float *const *inputBuffers) { + double flux = 0; + for (int i = 0; i <= fftSize/2; i++) { + double mag = sqrt(inputBuffers[0][i*2] * inputBuffers[0][i*2] + + inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1]); + if (mag > prevFrame[i]) flux += mag - prevFrame[i]; + prevFrame[i] = mag; + } + + spectralFlux.push_back(flux); + +} // processFrame() + +EventList BeatRootProcessor::beatTrack() { + +#ifdef DEBUG_BEATROOT + std::cerr << "Spectral flux:" << std::endl; + for (int i = 0; i < spectralFlux.size(); ++i) { + if ((i % 8) == 0) std::cerr << "\n"; + std::cerr << spectralFlux[i] << " "; + } +#endif + + double hop = hopTime; + Peaks::normalise(spectralFlux); + vector peaks = Peaks::findPeaks(spectralFlux, (int)lrint(0.06 / hop), 0.35, 0.84, true); + onsets.clear(); + onsets.resize(peaks.size(), 0); + vector::iterator it = peaks.begin(); + onsetList.clear(); + double minSalience = Peaks::min(spectralFlux); + for (int i = 0; i < onsets.size(); i++) { + int index = *it; + ++it; + onsets[i] = index * hop; + Event e = BeatTracker::newBeat(onsets[i], 0); +// if (debug) +// System.err.printf("Onset: %8.3f %8.3f %8.3f\n", +// onsets[i], energy[index], slope[index]); +// e.salience = slope[index]; // or combination of energy + slope?? + // Note that salience must be non-negative or the beat tracking system fails! + e.salience = spectralFlux[index] - minSalience; + onsetList.push_back(e); + } + +#ifdef DEBUG_BEATROOT + std::cerr << "Onsets: " << onsetList.size() << std::endl; +#endif + + return BeatTracker::beatTrack(onsetList); + +} // processFile() + diff -r f1252b6a7cf5 -r 887c629502a9 BeatRootProcessor.h --- a/BeatRootProcessor.h Fri Oct 07 14:07:42 2011 +0100 +++ b/BeatRootProcessor.h Wed Oct 12 10:55:52 2011 +0100 @@ -108,57 +108,11 @@ * then computing the spectral flux then (optionally) normalising * and calculating onsets. */ - void processFrame(const float *const *inputBuffers) { - double flux = 0; - for (int i = 0; i <= fftSize/2; i++) { - double mag = sqrt(inputBuffers[0][i*2] * inputBuffers[0][i*2] + - inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1]); - if (mag > prevFrame[i]) flux += mag - prevFrame[i]; - prevFrame[i] = mag; - } - - spectralFlux.push_back(flux); - - } // processFrame() + void processFrame(const float *const *inputBuffers); /** Tracks beats once all frames have been processed by processFrame */ - EventList beatTrack() { - - for (int i = 0; i < spectralFlux.size(); ++i) { - if ((i % 8) == 0) std::cerr << "\n"; - std::cerr << spectralFlux[i] << " "; - } - - double hop = hopTime; - Peaks::normalise(spectralFlux); - vector peaks = Peaks::findPeaks(spectralFlux, (int)lrint(0.06 / hop), 0.35, 0.84, true); - onsets.clear(); - onsets.resize(peaks.size(), 0); - vector::iterator it = peaks.begin(); - onsetList.clear(); - double minSalience = Peaks::min(spectralFlux); - for (int i = 0; i < onsets.size(); i++) { - int index = *it; - ++it; - onsets[i] = index * hop; - Event e = BeatTracker::newBeat(onsets[i], 0); -// if (debug) -// System.err.printf("Onset: %8.3f %8.3f %8.3f\n", -// onsets[i], energy[index], slope[index]); -// e.salience = slope[index]; // or combination of energy + slope?? - // Note that salience must be non-negative or the beat tracking system fails! - e.salience = spectralFlux[index] - minSalience; - onsetList.push_back(e); - } - -#ifdef DEBUG_BEATROOT - std::cerr << "Onsets: " << onsetList.size() << std::endl; -#endif - - return BeatTracker::beatTrack(onsetList); - - } // processFile() + EventList beatTrack(); protected: /** Allocates memory for arrays, based on parameter settings */ diff -r f1252b6a7cf5 -r 887c629502a9 BeatTracker.h --- a/BeatTracker.h Fri Oct 07 14:07:42 2011 +0100 +++ b/BeatTracker.h Wed Oct 12 10:55:52 2011 +0100 @@ -65,64 +65,7 @@ * @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) { - AgentList agents; - int count = 0; - double beatTime = -1; - if (!beats.empty()) { - count = beats.size() - 1; - EventList::iterator itr = beats.end(); - --itr; - beatTime = itr->time; - } - if (count > 0) { // tempo given by mean of initial 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) { - best->fillBeats(beatTime); - return best->events; - } - return EventList(); - } // beatTrack()/1 - - /** Finds the mean tempo (as inter-beat interval) from an array of beat times - * @param d An array of beat times - * @return The average inter-beat interval - */ - static double getAverageIBI(vector d) { - if (d.size() < 2) - return -1.0; - 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 - * @param d An array of beat times - * @return The median inter-beat interval - */ - static double getMedianIBI(vector d) { - if (d.size() < 2) - return -1.0; - vector ibi; - ibi.resize(d.size()-1); - for (int i = 1; i < d.size(); i++) - ibi[i-1] = d[i] - d[i-1]; - 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.size() / 2]; - } // getAverageIBI() + static EventList beatTrack(EventList events, EventList beats); // Various get and set methods diff -r f1252b6a7cf5 -r 887c629502a9 Induction.cpp --- a/Induction.cpp Fri Oct 07 14:07:42 2011 +0100 +++ b/Induction.cpp Wed Oct 12 10:55:52 2011 +0100 @@ -22,3 +22,173 @@ double Induction::maxIBI = 1.0; int Induction::topN = 10; + +AgentList Induction::beatInduction(EventList events) { + int i, j, b, bestCount; + bool submult; + int intervals = 0; // number of interval clusters + vector bestn;// count of high-scoring clusters + bestn.resize(topN); + + double ratio, err; + int degree; + int maxClusterCount = (int) ceil((maxIOI - minIOI) / clusterWidth); + vector clusterMean; + clusterMean.resize(maxClusterCount); + vector clusterSize; + clusterSize.resize(maxClusterCount); + vector clusterScore; + clusterScore.resize(maxClusterCount); + + EventList::iterator ptr1, ptr2; + Event e1, e2; + ptr1 = events.begin(); + while (ptr1 != events.end()) { + e1 = *ptr1; + ++ptr1; + ptr2 = events.begin(); + e2 = *ptr2; + ++ptr2; + while (e2 != e1 && ptr2 != events.end()) { + e2 = *ptr2; + ++ptr2; + } + while (ptr2 != events.end()) { + e2 = *ptr2; + ++ptr2; + double ioi = e2.time - e1.time; + if (ioi < minIOI) // skip short intervals + continue; + if (ioi > maxIOI) // ioi too long + break; + for (b = 0; b < intervals; b++) // assign to nearest cluster + if (fabs(clusterMean[b] - ioi) < clusterWidth) { + if ((b < intervals - 1) && ( + fabs(clusterMean[b+1] - ioi) < + fabs(clusterMean[b] - ioi))) + b++; // next cluster is closer + clusterMean[b] = (clusterMean[b] * clusterSize[b] +ioi)/ + (clusterSize[b] + 1); + clusterSize[b]++; + break; + } + if (b == intervals) { // no suitable cluster; create new one + if (intervals == maxClusterCount) { +// System.err.println("Warning: Too many clusters"); + continue; // ignore this IOI + } + intervals++; + for ( ; (b>0) && (clusterMean[b-1] > ioi); b--) { + clusterMean[b] = clusterMean[b-1]; + clusterSize[b] = clusterSize[b-1]; + } + clusterMean[b] = ioi; + clusterSize[b] = 1; + } + } + } + for (b = 0; b < intervals; b++) // merge similar intervals + // TODO: they are now in order, so don't need the 2nd loop + // TODO: check BOTH sides before averaging or upper gps don't work + for (i = b+1; i < intervals; i++) + if (fabs(clusterMean[b] - clusterMean[i]) < clusterWidth) { + clusterMean[b] = (clusterMean[b] * clusterSize[b] + + clusterMean[i] * clusterSize[i]) / + (clusterSize[b] + clusterSize[i]); + clusterSize[b] = clusterSize[b] + clusterSize[i]; + --intervals; + for (j = i+1; j <= intervals; j++) { + clusterMean[j-1] = clusterMean[j]; + clusterSize[j-1] = clusterSize[j]; + } + } + if (intervals == 0) + return AgentList(); + for (b = 0; b < intervals; b++) + clusterScore[b] = 10 * clusterSize[b]; + bestn[0] = 0; + bestCount = 1; + for (b = 0; b < intervals; b++) + for (i = 0; i <= bestCount; i++) + if ((i < topN) && ((i == bestCount) || + (clusterScore[b] > clusterScore[bestn[i]]))){ + if (bestCount < topN) + bestCount++; + for (j = bestCount - 1; j > i; j--) + bestn[j] = bestn[j-1]; + bestn[i] = b; + break; + } + for (b = 0; b < intervals; b++) // score intervals + for (i = b+1; i < intervals; i++) { + ratio = clusterMean[b] / clusterMean[i]; + submult = ratio < 1; + if (submult) + degree = (int) nearbyint(1/ratio); + else + degree = (int) nearbyint(ratio); + if ((degree >= 2) && (degree <= 8)) { + if (submult) + err = fabs(clusterMean[b]*degree - clusterMean[i]); + else + err = fabs(clusterMean[b] - clusterMean[i]*degree); + if (err < (submult? clusterWidth : clusterWidth * degree)) { + if (degree >= 5) + degree = 1; + else + degree = 6 - degree; + clusterScore[b] += degree * clusterSize[i]; + clusterScore[i] += degree * clusterSize[b]; + } + } + } + + AgentList a; + for (int index = 0; index < bestCount; index++) { + b = bestn[index]; + // Adjust it, using the size of super- and sub-intervals + double newSum = clusterMean[b] * clusterScore[b]; + int newCount = clusterSize[b]; + int newWeight = clusterScore[b]; + for (i = 0; i < intervals; i++) { + if (i == b) + continue; + ratio = clusterMean[b] / clusterMean[i]; + if (ratio < 1) { + degree = (int) nearbyint(1 / ratio); + if ((degree >= 2) && (degree <= 8)) { + err = fabs(clusterMean[b]*degree - clusterMean[i]); + if (err < clusterWidth) { + newSum += clusterMean[i] / degree * clusterScore[i]; + newCount += clusterSize[i]; + newWeight += clusterScore[i]; + } + } + } else { + degree = (int) nearbyint(ratio); + if ((degree >= 2) && (degree <= 8)) { + err = fabs(clusterMean[b] - degree*clusterMean[i]); + if (err < clusterWidth * degree) { + newSum += clusterMean[i] * degree * clusterScore[i]; + newCount += clusterSize[i]; + newWeight += clusterScore[i]; + } + } + } + } + double beat = newSum / newWeight; + // Scale within range ... hope the grouping isn't ternary :( + while (beat < minIBI) // Maximum speed + beat *= 2.0; + while (beat > maxIBI) // Minimum speed + beat /= 2.0; + if (beat >= minIBI) { + a.push_back(Agent(beat)); + } + } +#ifdef DEBUG_BEATROOT + std::cerr << "Induction complete, returning " << a.size() << " agent(s)" << std::endl; +#endif + return a; +} // beatInduction() + diff -r f1252b6a7cf5 -r 887c629502a9 Induction.h --- a/Induction.h Fri Oct 07 14:07:42 2011 +0100 +++ b/Induction.h Wed Oct 12 10:55:52 2011 +0100 @@ -68,174 +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) { - int i, j, b, bestCount; - bool submult; - int intervals = 0; // number of interval clusters - vector bestn;// count of high-scoring clusters - bestn.resize(topN); - - double ratio, err; - int degree; - int maxClusterCount = (int) ceil((maxIOI - minIOI) / clusterWidth); - vector clusterMean; - clusterMean.resize(maxClusterCount); - vector clusterSize; - clusterSize.resize(maxClusterCount); - vector clusterScore; - clusterScore.resize(maxClusterCount); - - EventList::iterator ptr1, ptr2; - Event e1, e2; - ptr1 = events.begin(); - while (ptr1 != events.end()) { - e1 = *ptr1; - ++ptr1; - ptr2 = events.begin(); - e2 = *ptr2; - ++ptr2; - while (e2 != e1 && ptr2 != events.end()) { - e2 = *ptr2; - ++ptr2; - } - while (ptr2 != events.end()) { - e2 = *ptr2; - ++ptr2; - double ioi = e2.time - e1.time; - if (ioi < minIOI) // skip short intervals - continue; - if (ioi > maxIOI) // ioi too long - break; - for (b = 0; b < intervals; b++) // assign to nearest cluster - if (fabs(clusterMean[b] - ioi) < clusterWidth) { - if ((b < intervals - 1) && ( - fabs(clusterMean[b+1] - ioi) < - fabs(clusterMean[b] - ioi))) - b++; // next cluster is closer - clusterMean[b] = (clusterMean[b] * clusterSize[b] +ioi)/ - (clusterSize[b] + 1); - clusterSize[b]++; - break; - } - if (b == intervals) { // no suitable cluster; create new one - if (intervals == maxClusterCount) { -// System.err.println("Warning: Too many clusters"); - continue; // ignore this IOI - } - intervals++; - for ( ; (b>0) && (clusterMean[b-1] > ioi); b--) { - clusterMean[b] = clusterMean[b-1]; - clusterSize[b] = clusterSize[b-1]; - } - clusterMean[b] = ioi; - clusterSize[b] = 1; - } - } - } - for (b = 0; b < intervals; b++) // merge similar intervals - // TODO: they are now in order, so don't need the 2nd loop - // TODO: check BOTH sides before averaging or upper gps don't work - for (i = b+1; i < intervals; i++) - if (fabs(clusterMean[b] - clusterMean[i]) < clusterWidth) { - clusterMean[b] = (clusterMean[b] * clusterSize[b] + - clusterMean[i] * clusterSize[i]) / - (clusterSize[b] + clusterSize[i]); - clusterSize[b] = clusterSize[b] + clusterSize[i]; - --intervals; - for (j = i+1; j <= intervals; j++) { - clusterMean[j-1] = clusterMean[j]; - clusterSize[j-1] = clusterSize[j]; - } - } - if (intervals == 0) - return AgentList(); - for (b = 0; b < intervals; b++) - clusterScore[b] = 10 * clusterSize[b]; - bestn[0] = 0; - bestCount = 1; - for (b = 0; b < intervals; b++) - for (i = 0; i <= bestCount; i++) - if ((i < topN) && ((i == bestCount) || - (clusterScore[b] > clusterScore[bestn[i]]))){ - if (bestCount < topN) - bestCount++; - for (j = bestCount - 1; j > i; j--) - bestn[j] = bestn[j-1]; - bestn[i] = b; - break; - } - for (b = 0; b < intervals; b++) // score intervals - for (i = b+1; i < intervals; i++) { - ratio = clusterMean[b] / clusterMean[i]; - submult = ratio < 1; - if (submult) - degree = (int) nearbyint(1/ratio); - else - degree = (int) nearbyint(ratio); - if ((degree >= 2) && (degree <= 8)) { - if (submult) - err = fabs(clusterMean[b]*degree - clusterMean[i]); - else - err = fabs(clusterMean[b] - clusterMean[i]*degree); - if (err < (submult? clusterWidth : clusterWidth * degree)) { - if (degree >= 5) - degree = 1; - else - degree = 6 - degree; - clusterScore[b] += degree * clusterSize[i]; - clusterScore[i] += degree * clusterSize[b]; - } - } - } - - AgentList a; - for (int index = 0; index < bestCount; index++) { - b = bestn[index]; - // Adjust it, using the size of super- and sub-intervals - double newSum = clusterMean[b] * clusterScore[b]; - int newCount = clusterSize[b]; - int newWeight = clusterScore[b]; - for (i = 0; i < intervals; i++) { - if (i == b) - continue; - ratio = clusterMean[b] / clusterMean[i]; - if (ratio < 1) { - degree = (int) nearbyint(1 / ratio); - if ((degree >= 2) && (degree <= 8)) { - err = fabs(clusterMean[b]*degree - clusterMean[i]); - if (err < clusterWidth) { - newSum += clusterMean[i] / degree * clusterScore[i]; - newCount += clusterSize[i]; - newWeight += clusterScore[i]; - } - } - } else { - degree = (int) nearbyint(ratio); - if ((degree >= 2) && (degree <= 8)) { - err = fabs(clusterMean[b] - degree*clusterMean[i]); - if (err < clusterWidth * degree) { - newSum += clusterMean[i] * degree * clusterScore[i]; - newCount += clusterSize[i]; - newWeight += clusterScore[i]; - } - } - } - } - double beat = newSum / newWeight; - // Scale within range ... hope the grouping isn't ternary :( - while (beat < minIBI) // Maximum speed - beat *= 2.0; - while (beat > maxIBI) // Minimum speed - beat /= 2.0; - if (beat >= minIBI) { - a.push_back(Agent(beat)); - } - } -#ifdef DEBUG_BEATROOT - std::cerr << "Induction complete, returning " << a.size() << " agent(s)" << std::endl; -#endif - return a; - } // beatInduction() + static AgentList beatInduction(EventList events); protected: /** For variable cluster widths in newInduction(). diff -r f1252b6a7cf5 -r 887c629502a9 Makefile --- a/Makefile Fri Oct 07 14:07:42 2011 +0100 +++ b/Makefile Wed Oct 12 10:55:52 2011 +0100 @@ -1,7 +1,8 @@ -CXXFLAGS := -g -DDEBUG_BEATROOT +#CXXFLAGS := -fPIC -g -DDEBUG_BEATROOT +CXXFLAGS := -fPIC -O3 -OBJECTS := BeatRootProcessor.o BeatRootVampPlugin.o Peaks.o Agent.o AgentList.o Induction.o +OBJECTS := BeatRootProcessor.o BeatRootVampPlugin.o Peaks.o Agent.o AgentList.o Induction.o BeatTracker.o HEADERS := Agent.h AgentList.h BeatRootProcessor.h BeatRootVampPlugin.h BeatTracker.h Event.h Induction.h Peaks.h @@ -19,5 +20,12 @@ BeatRootProcessor.o: Agent.h AgentList.h Induction.h BeatRootVampPlugin.o: BeatRootVampPlugin.h BeatRootProcessor.h Peaks.h BeatRootVampPlugin.o: Event.h BeatTracker.h Agent.h AgentList.h Induction.h +BeatTracker.o: BeatTracker.h Event.h Agent.h AgentList.h Induction.h Induction.o: Induction.h Agent.h Event.h AgentList.h Peaks.o: Peaks.h +Agent.o: Event.h +AgentList.o: Agent.h Event.h +BeatRootProcessor.o: Peaks.h Event.h BeatTracker.h Agent.h AgentList.h +BeatRootProcessor.o: Induction.h +BeatTracker.o: Event.h Agent.h AgentList.h Induction.h +Induction.o: Agent.h Event.h AgentList.h diff -r f1252b6a7cf5 -r 887c629502a9 Peaks.cpp --- a/Peaks.cpp Fri Oct 07 14:07:42 2011 +0100 +++ b/Peaks.cpp Wed Oct 12 10:55:52 2011 +0100 @@ -18,3 +18,163 @@ int Peaks::pre = 3; int Peaks::post = 1; + +int Peaks::findPeaks(const vector &data, vector peaks, int width) { + int peakCount = 0; + int maxp = 0; + int mid = 0; + int end = data.size(); + while (mid < end) { + int i = mid - width; + if (i < 0) + i = 0; + int stop = mid + width + 1; + if (stop > data.size()) + stop = data.size(); + maxp = i; + for (i++; i < stop; i++) + if (data[i] > data[maxp]) + maxp = i; + if (maxp == mid) { + int j; + for (j = peakCount; j > 0; j--) { + if (data[maxp] <= data[peaks[j-1]]) + break; + else if (j < peaks.size()) + peaks[j] = peaks[j-1]; + } + if (j != peaks.size()) + peaks[j] = maxp; + if (peakCount != peaks.size()) + peakCount++; + } + mid++; + } + return peakCount; +} // findPeaks() + +vector Peaks::findPeaks(const vector &data, int width, + double threshold, double decayRate, bool isRelative) { + vector peaks; + int maxp = 0; + int mid = 0; + int end = data.size(); + double av = data[0]; + while (mid < end) { + av = decayRate * av + (1 - decayRate) * data[mid]; + if (av < data[mid]) + av = data[mid]; + int i = mid - width; + if (i < 0) + i = 0; + int stop = mid + width + 1; + if (stop > data.size()) + stop = data.size(); + maxp = i; + for (i++; i < stop; i++) + if (data[i] > data[maxp]) + maxp = i; + if (maxp == mid) { + if (overThreshold(data, maxp, width, threshold, isRelative,av)){ + peaks.push_back(maxp); + } + } + mid++; + } + return peaks; +} // findPeaks() + +double Peaks::expDecayWithHold(double av, double decayRate, + const vector &data, int start, int stop) { + while (start < stop) { + av = decayRate * av + (1 - decayRate) * data[start]; + if (av < data[start]) + av = data[start]; + start++; + } + return av; +} // expDecayWithHold() + +bool Peaks::overThreshold(const vector &data, int index, int width, + double threshold, bool isRelative, + double av) { + if (data[index] < av) + return false; + if (isRelative) { + int iStart = index - pre * width; + if (iStart < 0) + iStart = 0; + int iStop = index + post * width; + if (iStop > data.size()) + iStop = data.size(); + double sum = 0; + int count = iStop - iStart; + while (iStart < iStop) + sum += data[iStart++]; + return (data[index] > sum / count + threshold); + } else + return (data[index] > threshold); +} // overThreshold() + +void Peaks::normalise(vector &data) { + double sx = 0; + double sxx = 0; + for (int i = 0; i < data.size(); i++) { + sx += data[i]; + sxx += data[i] * data[i]; + } + double mean = sx / data.size(); + double sd = sqrt((sxx - sx * mean) / data.size()); + if (sd == 0) + sd = 1; // all data[i] == mean -> 0; avoids div by 0 + for (int i = 0; i < data.size(); i++) { + data[i] = (data[i] - mean) / sd; + } +} // normalise() + +/** Uses an n-point linear regression to estimate the slope of data. + * @param data input data + * @param hop spacing of data points + * @param n length of linear regression + * @param slope output data + */ +void Peaks::getSlope(const vector &data, double hop, int n, + vector &slope) { + int i = 0, j = 0; + double t; + double sx = 0, sxx = 0, sy = 0, sxy = 0; + for ( ; i < n; i++) { + t = i * hop; + sx += t; + sxx += t * t; + sy += data[i]; + sxy += t * data[i]; + } + double delta = n * sxx - sx * sx; + for ( ; j < n / 2; j++) + slope[j] = (n * sxy - sx * sy) / delta; + for ( ; j < data.size() - (n + 1) / 2; j++, i++) { + slope[j] = (n * sxy - sx * sy) / delta; + sy += data[i] - data[i - n]; + sxy += hop * (n * data[i] - sy); + } + for ( ; j < data.size(); j++) + slope[j] = (n * sxy - sx * sy) / delta; +} // getSlope() + +int Peaks::imin(const vector &arr) { + int i = 0; + for (int j = 1; j < arr.size(); j++) + if (arr[j] < arr[i]) + i = j; + return i; +} // imin() + +int Peaks::imax(const vector &arr) { + int i = 0; + for (int j = 1; j < arr.size(); j++) + if (arr[j] > arr[i]) + i = j; + return i; +} // imax() + diff -r f1252b6a7cf5 -r 887c629502a9 Peaks.h --- a/Peaks.h Fri Oct 07 14:07:42 2011 +0100 +++ b/Peaks.h Wed Oct 12 10:55:52 2011 +0100 @@ -33,39 +33,7 @@ * @param peaks list of peak indexes * @param width minimum distance between peaks */ - static int findPeaks(const vector &data, vector peaks, int width) { - int peakCount = 0; - int maxp = 0; - int mid = 0; - int end = data.size(); - while (mid < end) { - int i = mid - width; - if (i < 0) - i = 0; - int stop = mid + width + 1; - if (stop > data.size()) - stop = data.size(); - maxp = i; - for (i++; i < stop; i++) - if (data[i] > data[maxp]) - maxp = i; - if (maxp == mid) { - int j; - for (j = peakCount; j > 0; j--) { - if (data[maxp] <= data[peaks[j-1]]) - break; - else if (j < peaks.size()) - peaks[j] = peaks[j-1]; - } - if (j != peaks.size()) - peaks[j] = maxp; - if (peakCount != peaks.size()) - peakCount++; - } - mid++; - } - return peakCount; - } // findPeaks() + static int findPeaks(const vector &data, vector peaks, int width); /** General peak picking method for finding local maxima in an array * @param data input data @@ -87,83 +55,16 @@ * @return list of peak indexes */ static vector findPeaks(const vector &data, int width, - double threshold, double decayRate, bool isRelative) { - vector peaks; - int maxp = 0; - int mid = 0; - int end = data.size(); - double av = data[0]; - while (mid < end) { - av = decayRate * av + (1 - decayRate) * data[mid]; - if (av < data[mid]) - av = data[mid]; - int i = mid - width; - if (i < 0) - i = 0; - int stop = mid + width + 1; - if (stop > data.size()) - stop = data.size(); - maxp = i; - for (i++; i < stop; i++) - if (data[i] > data[maxp]) - maxp = i; - if (maxp == mid) { - if (overThreshold(data, maxp, width, threshold, isRelative,av)){ - peaks.push_back(maxp); - } - } - mid++; - } - return peaks; - } // findPeaks() + double threshold, double decayRate, bool isRelative); static double expDecayWithHold(double av, double decayRate, - const vector &data, int start, int stop) { - while (start < stop) { - av = decayRate * av + (1 - decayRate) * data[start]; - if (av < data[start]) - av = data[start]; - start++; - } - return av; - } // expDecayWithHold() + const vector &data, int start, int stop); static bool overThreshold(const vector &data, int index, int width, - double threshold, bool isRelative, - double av) { - if (data[index] < av) - return false; - if (isRelative) { - int iStart = index - pre * width; - if (iStart < 0) - iStart = 0; - int iStop = index + post * width; - if (iStop > data.size()) - iStop = data.size(); - double sum = 0; - int count = iStop - iStart; - while (iStart < iStop) - sum += data[iStart++]; - return (data[index] > sum / count + threshold); - } else - return (data[index] > threshold); - } // overThreshold() + double threshold, bool isRelative, + double av); - static void normalise(vector &data) { - double sx = 0; - double sxx = 0; - for (int i = 0; i < data.size(); i++) { - sx += data[i]; - sxx += data[i] * data[i]; - } - double mean = sx / data.size(); - double sd = sqrt((sxx - sx * mean) / data.size()); - if (sd == 0) - sd = 1; // all data[i] == mean -> 0; avoids div by 0 - for (int i = 0; i < data.size(); i++) { - data[i] = (data[i] - mean) / sd; - } - } // normalise() + static void normalise(vector &data); /** Uses an n-point linear regression to estimate the slope of data. * @param data input data @@ -172,48 +73,13 @@ * @param slope output data */ static void getSlope(const vector &data, double hop, int n, - vector &slope) { - int i = 0, j = 0; - double t; - double sx = 0, sxx = 0, sy = 0, sxy = 0; - for ( ; i < n; i++) { - t = i * hop; - sx += t; - sxx += t * t; - sy += data[i]; - sxy += t * data[i]; - } - double delta = n * sxx - sx * sx; - for ( ; j < n / 2; j++) - slope[j] = (n * sxy - sx * sy) / delta; - for ( ; j < data.size() - (n + 1) / 2; j++, i++) { - slope[j] = (n * sxy - sx * sy) / delta; - sy += data[i] - data[i - n]; - sxy += hop * (n * data[i] - sy); - } - for ( ; j < data.size(); j++) - slope[j] = (n * sxy - sx * sy) / delta; - } // getSlope() + vector &slope); static double min(const vector &arr) { return arr[imin(arr)]; } - static double max(const vector &arr) { return arr[imax(arr)]; } - static int imin(const vector &arr) { - int i = 0; - for (int j = 1; j < arr.size(); j++) - if (arr[j] < arr[i]) - i = j; - return i; - } // imin() - - static int imax(const vector &arr) { - int i = 0; - for (int j = 1; j < arr.size(); j++) - if (arr[j] > arr[i]) - i = j; - return i; - } // imax() + static int imin(const vector &arr); + static int imax(const vector &arr); }; // class Peaks