annotate Induction.h @ 9:4f6626f9ffac

Many fixes. This now compiles and passes the plugin tester, but I don't expect it produces any results yet.
author Chris Cannam
date Fri, 30 Sep 2011 15:39:17 +0100
parents 3c11becfc81a
children 59520cd6abac
rev   line source
Chris@7 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@7 2
Chris@7 3 /*
Chris@7 4 Vamp feature extraction plugin for the BeatRoot beat tracker.
Chris@7 5
Chris@7 6 Centre for Digital Music, Queen Mary, University of London.
Chris@7 7 This file copyright 2011 Simon Dixon, Chris Cannam and QMUL.
Chris@7 8
Chris@7 9 This program is free software; you can redistribute it and/or
Chris@7 10 modify it under the terms of the GNU General Public License as
Chris@7 11 published by the Free Software Foundation; either version 2 of the
Chris@7 12 License, or (at your option) any later version. See the file
Chris@7 13 COPYING included with this distribution for more information.
Chris@7 14 */
Chris@7 15
Chris@7 16 #ifndef _INDUCTION_H_
Chris@7 17 #define _INDUCTION_H_
Chris@7 18
Chris@7 19 #include "Agent.h"
Chris@9 20 #include "AgentList.h"
Chris@7 21 #include "Event.h"
Chris@7 22
Chris@7 23 #include <vector>
Chris@7 24
Chris@7 25 using std::vector;
Chris@7 26
Chris@7 27 /** Performs tempo induction by finding clusters of similar
Chris@7 28 * inter-onset intervals (IOIs), ranking them according to the number
Chris@7 29 * of intervals and relationships between them, and returning a set
Chris@7 30 * of tempo hypotheses for initialising the beat tracking agents.
Chris@7 31 */
Chris@7 32 class Induction
Chris@7 33 {
Chris@7 34 public:
Chris@7 35 /** The maximum difference in IOIs which are in the same cluster */
Chris@7 36 static double clusterWidth;
Chris@7 37
Chris@7 38 /** The minimum IOI for inclusion in a cluster */
Chris@7 39 static double minIOI;
Chris@7 40
Chris@7 41 /** The maximum IOI for inclusion in a cluster */
Chris@7 42 static double maxIOI;
Chris@7 43
Chris@7 44 /** The minimum inter-beat interval (IBI), i.e. the maximum tempo
Chris@7 45 * hypothesis that can be returned.
Chris@7 46 * 0.30 seconds == 200 BPM
Chris@7 47 * 0.25 seconds == 240 BPM
Chris@7 48 */
Chris@7 49 static double minIBI;
Chris@7 50
Chris@7 51 /** The maximum inter-beat interval (IBI), i.e. the minimum tempo
Chris@7 52 * hypothesis that can be returned.
Chris@7 53 * 1.00 seconds == 60 BPM
Chris@7 54 * 0.75 seconds == 80 BPM
Chris@7 55 * 0.60 seconds == 100 BPM
Chris@7 56 */
Chris@7 57 static double maxIBI; // 60BPM // was 0.75 => 80
Chris@7 58
Chris@7 59 /** The maximum number of tempo hypotheses to return */
Chris@7 60 static int topN;
Chris@7 61
Chris@7 62 /** Performs tempo induction (see JNMR 2001 paper by Simon Dixon for details).
Chris@7 63 * @param events The onsets (or other events) from which the tempo is induced
Chris@7 64 * @return A list of beat tracking agents, where each is initialised with one
Chris@7 65 * of the top tempo hypotheses but no beats
Chris@7 66 */
Chris@7 67 static AgentList beatInduction(EventList events) {
Chris@7 68 int i, j, b, bestCount;
Chris@7 69 bool submult;
Chris@7 70 int intervals = 0; // number of interval clusters
Chris@7 71 vector<int> bestn;// count of high-scoring clusters
Chris@7 72 bestn.resize(topN);
Chris@7 73
Chris@7 74 double ratio, err;
Chris@7 75 int degree;
Chris@7 76 int maxClusterCount = (int) ceil((maxIOI - minIOI) / clusterWidth);
Chris@7 77 vector<double> clusterMean;
Chris@7 78 clusterMean.resize(maxClusterCount);
Chris@7 79 vector<int> clusterSize;
Chris@7 80 clusterSize.resize(maxClusterCount);
Chris@7 81 vector<int> clusterScore;
Chris@7 82 clusterScore.resize(maxClusterCount);
Chris@7 83
Chris@7 84 EventList::iterator ptr1, ptr2;
Chris@7 85 Event e1, e2;
Chris@7 86 ptr1 = events.begin();
Chris@7 87 while (ptr1 != events.end()) {
Chris@7 88 e1 = *ptr1;
Chris@7 89 ++ptr1;
Chris@7 90 ptr2 = events.begin();
Chris@7 91 e2 = *ptr2;
Chris@7 92 ++ptr2;
Chris@7 93 while (e2 != e1 && ptr2 != events.end()) {
Chris@7 94 e2 = *ptr2;
Chris@7 95 ++ptr2;
Chris@7 96 }
Chris@7 97 while (ptr2 != events.end()) {
Chris@7 98 e2 = *ptr2;
Chris@7 99 ++ptr2;
Chris@7 100 double ioi = e2.time - e1.time;
Chris@7 101 if (ioi < minIOI) // skip short intervals
Chris@7 102 continue;
Chris@7 103 if (ioi > maxIOI) // ioi too long
Chris@7 104 break;
Chris@7 105 for (b = 0; b < intervals; b++) // assign to nearest cluster
Chris@7 106 if (fabs(clusterMean[b] - ioi) < clusterWidth) {
Chris@7 107 if ((b < intervals - 1) && (
Chris@7 108 fabs(clusterMean[b+1] - ioi) <
Chris@7 109 fabs(clusterMean[b] - ioi)))
Chris@7 110 b++; // next cluster is closer
Chris@7 111 clusterMean[b] = (clusterMean[b] * clusterSize[b] +ioi)/
Chris@7 112 (clusterSize[b] + 1);
Chris@7 113 clusterSize[b]++;
Chris@7 114 break;
Chris@7 115 }
Chris@7 116 if (b == intervals) { // no suitable cluster; create new one
Chris@7 117 if (intervals == maxClusterCount) {
Chris@7 118 // System.err.println("Warning: Too many clusters");
Chris@7 119 continue; // ignore this IOI
Chris@7 120 }
Chris@7 121 intervals++;
Chris@7 122 for ( ; (b>0) && (clusterMean[b-1] > ioi); b--) {
Chris@7 123 clusterMean[b] = clusterMean[b-1];
Chris@7 124 clusterSize[b] = clusterSize[b-1];
Chris@7 125 }
Chris@7 126 clusterMean[b] = ioi;
Chris@7 127 clusterSize[b] = 1;
Chris@7 128 }
Chris@7 129 }
Chris@7 130 }
Chris@7 131 for (b = 0; b < intervals; b++) // merge similar intervals
Chris@7 132 // TODO: they are now in order, so don't need the 2nd loop
Chris@7 133 // TODO: check BOTH sides before averaging or upper gps don't work
Chris@7 134 for (i = b+1; i < intervals; i++)
Chris@7 135 if (fabs(clusterMean[b] - clusterMean[i]) < clusterWidth) {
Chris@7 136 clusterMean[b] = (clusterMean[b] * clusterSize[b] +
Chris@7 137 clusterMean[i] * clusterSize[i]) /
Chris@7 138 (clusterSize[b] + clusterSize[i]);
Chris@7 139 clusterSize[b] = clusterSize[b] + clusterSize[i];
Chris@7 140 --intervals;
Chris@7 141 for (j = i+1; j <= intervals; j++) {
Chris@7 142 clusterMean[j-1] = clusterMean[j];
Chris@7 143 clusterSize[j-1] = clusterSize[j];
Chris@7 144 }
Chris@7 145 }
Chris@7 146 if (intervals == 0)
Chris@7 147 return AgentList();
Chris@7 148 for (b = 0; b < intervals; b++)
Chris@7 149 clusterScore[b] = 10 * clusterSize[b];
Chris@7 150 bestn[0] = 0;
Chris@7 151 bestCount = 1;
Chris@7 152 for (b = 0; b < intervals; b++)
Chris@7 153 for (i = 0; i <= bestCount; i++)
Chris@7 154 if ((i < topN) && ((i == bestCount) ||
Chris@7 155 (clusterScore[b] > clusterScore[bestn[i]]))){
Chris@7 156 if (bestCount < topN)
Chris@7 157 bestCount++;
Chris@7 158 for (j = bestCount - 1; j > i; j--)
Chris@7 159 bestn[j] = bestn[j-1];
Chris@7 160 bestn[i] = b;
Chris@7 161 break;
Chris@7 162 }
Chris@7 163 for (b = 0; b < intervals; b++) // score intervals
Chris@7 164 for (i = b+1; i < intervals; i++) {
Chris@7 165 ratio = clusterMean[b] / clusterMean[i];
Chris@7 166 submult = ratio < 1;
Chris@7 167 if (submult)
Chris@7 168 degree = (int) nearbyint(1/ratio);
Chris@7 169 else
Chris@7 170 degree = (int) nearbyint(ratio);
Chris@7 171 if ((degree >= 2) && (degree <= 8)) {
Chris@7 172 if (submult)
Chris@7 173 err = fabs(clusterMean[b]*degree - clusterMean[i]);
Chris@7 174 else
Chris@7 175 err = fabs(clusterMean[b] - clusterMean[i]*degree);
Chris@7 176 if (err < (submult? clusterWidth : clusterWidth * degree)) {
Chris@7 177 if (degree >= 5)
Chris@7 178 degree = 1;
Chris@7 179 else
Chris@7 180 degree = 6 - degree;
Chris@7 181 clusterScore[b] += degree * clusterSize[i];
Chris@7 182 clusterScore[i] += degree * clusterSize[b];
Chris@7 183 }
Chris@7 184 }
Chris@7 185 }
Chris@7 186
Chris@7 187 AgentList a;
Chris@7 188 for (int index = 0; index < bestCount; index++) {
Chris@7 189 b = bestn[index];
Chris@7 190 // Adjust it, using the size of super- and sub-intervals
Chris@7 191 double newSum = clusterMean[b] * clusterScore[b];
Chris@7 192 int newCount = clusterSize[b];
Chris@7 193 int newWeight = clusterScore[b];
Chris@7 194 for (i = 0; i < intervals; i++) {
Chris@7 195 if (i == b)
Chris@7 196 continue;
Chris@7 197 ratio = clusterMean[b] / clusterMean[i];
Chris@7 198 if (ratio < 1) {
Chris@7 199 degree = (int) nearbyint(1 / ratio);
Chris@7 200 if ((degree >= 2) && (degree <= 8)) {
Chris@7 201 err = fabs(clusterMean[b]*degree - clusterMean[i]);
Chris@7 202 if (err < clusterWidth) {
Chris@7 203 newSum += clusterMean[i] / degree * clusterScore[i];
Chris@7 204 newCount += clusterSize[i];
Chris@7 205 newWeight += clusterScore[i];
Chris@7 206 }
Chris@7 207 }
Chris@7 208 } else {
Chris@7 209 degree = (int) nearbyint(ratio);
Chris@7 210 if ((degree >= 2) && (degree <= 8)) {
Chris@7 211 err = fabs(clusterMean[b] - degree*clusterMean[i]);
Chris@7 212 if (err < clusterWidth * degree) {
Chris@7 213 newSum += clusterMean[i] * degree * clusterScore[i];
Chris@7 214 newCount += clusterSize[i];
Chris@7 215 newWeight += clusterScore[i];
Chris@7 216 }
Chris@7 217 }
Chris@7 218 }
Chris@7 219 }
Chris@7 220 double beat = newSum / newWeight;
Chris@7 221 // Scale within range ... hope the grouping isn't ternary :(
Chris@7 222 while (beat < minIBI) // Maximum speed
Chris@7 223 beat *= 2.0;
Chris@7 224 while (beat > maxIBI) // Minimum speed
Chris@7 225 beat /= 2.0;
Chris@7 226 if (beat >= minIBI) {
Chris@7 227 a.push_back(Agent(beat));
Chris@7 228 }
Chris@7 229 }
Chris@7 230 return a;
Chris@7 231 } // beatInduction()
Chris@7 232
Chris@7 233 protected:
Chris@7 234 /** For variable cluster widths in newInduction().
Chris@7 235 * @param low The lowest IOI allowed in the cluster
Chris@7 236 * @return The highest IOI allowed in the cluster
Chris@7 237 */
Chris@7 238 static int top(int low) {
Chris@7 239 return low + 25; // low/10;
Chris@7 240 } // top()
Chris@7 241
Chris@7 242 }; // class Induction
Chris@7 243
Chris@7 244 #endif