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
|