Mercurial > hg > beatroot-vamp
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 |