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