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