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
|