annotate Induction.h @ 7:3c11becfc81a

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