adamstark@46
|
1 //=======================================================================
|
adamstark@46
|
2 /** @file BTrack.cpp
|
adamstark@47
|
3 * @brief BTrack - a real-time beat tracker
|
adamstark@46
|
4 * @author Adam Stark
|
adamstark@46
|
5 * @copyright Copyright (C) 2008-2014 Queen Mary University of London
|
adamstark@46
|
6 *
|
adamstark@46
|
7 * This program is free software: you can redistribute it and/or modify
|
adamstark@46
|
8 * it under the terms of the GNU General Public License as published by
|
adamstark@46
|
9 * the Free Software Foundation, either version 3 of the License, or
|
adamstark@46
|
10 * (at your option) any later version.
|
adamstark@46
|
11 *
|
adamstark@46
|
12 * This program is distributed in the hope that it will be useful,
|
adamstark@46
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
adamstark@46
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
adamstark@46
|
15 * GNU General Public License for more details.
|
adamstark@46
|
16 *
|
adamstark@46
|
17 * You should have received a copy of the GNU General Public License
|
adamstark@46
|
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
adamstark@46
|
19 */
|
adamstark@46
|
20 //=======================================================================
|
adamstark@46
|
21
|
adamstark@46
|
22 #include <cmath>
|
adamstark@52
|
23 #include <algorithm>
|
adamstark@97
|
24 #include <numeric>
|
adamstark@46
|
25 #include "BTrack.h"
|
adamstark@46
|
26 #include "samplerate.h"
|
adamstark@89
|
27 #include <iostream>
|
adamstark@46
|
28
|
adamstark@55
|
29 //=======================================================================
|
adamstark@91
|
30 BTrack::BTrack()
|
adamstark@91
|
31 : odf (512, 1024, ComplexSpectralDifferenceHWR, HanningWindow)
|
adamstark@55
|
32 {
|
adamstark@108
|
33 initialise (512);
|
adamstark@55
|
34 }
|
adamstark@46
|
35
|
adamstark@51
|
36 //=======================================================================
|
adamstark@108
|
37 BTrack::BTrack (int hop)
|
adamstark@108
|
38 : odf (hop, 2 * hop, ComplexSpectralDifferenceHWR, HanningWindow)
|
adamstark@111
|
39 {
|
adamstark@108
|
40 initialise (hop);
|
adamstark@55
|
41 }
|
adamstark@55
|
42
|
adamstark@55
|
43 //=======================================================================
|
adamstark@108
|
44 BTrack::BTrack (int hop, int frame)
|
adamstark@108
|
45 : odf (hop, frame, ComplexSpectralDifferenceHWR, HanningWindow)
|
adamstark@55
|
46 {
|
adamstark@108
|
47 initialise (hop);
|
adamstark@55
|
48 }
|
adamstark@55
|
49
|
adamstark@55
|
50 //=======================================================================
|
adamstark@88
|
51 BTrack::~BTrack()
|
adamstark@88
|
52 {
|
adamstark@93
|
53 #ifdef USE_FFTW
|
adamstark@88
|
54 // destroy fft plan
|
adamstark@91
|
55 fftw_destroy_plan (acfForwardFFT);
|
adamstark@91
|
56 fftw_destroy_plan (acfBackwardFFT);
|
adamstark@91
|
57 fftw_free (complexIn);
|
adamstark@91
|
58 fftw_free (complexOut);
|
adamstark@93
|
59 #endif
|
adamstark@93
|
60
|
adamstark@93
|
61 #ifdef USE_KISS_FFT
|
adamstark@93
|
62 free (cfgForwards);
|
adamstark@93
|
63 free (cfgBackwards);
|
adamstark@93
|
64 delete [] fftIn;
|
adamstark@93
|
65 delete [] fftOut;
|
adamstark@93
|
66 #endif
|
adamstark@88
|
67 }
|
adamstark@88
|
68
|
adamstark@88
|
69 //=======================================================================
|
adamstark@108
|
70 double BTrack::getBeatTimeInSeconds (long frameNumber, int hopSize, int samplingFrequency)
|
adamstark@55
|
71 {
|
adamstark@108
|
72 return ((static_cast<double> (hopSize) / static_cast<double> (samplingFrequency)) * static_cast<double> (frameNumber));
|
adamstark@55
|
73 }
|
adamstark@55
|
74
|
adamstark@55
|
75 //=======================================================================
|
adamstark@108
|
76 void BTrack::initialise (int hop)
|
adamstark@55
|
77 {
|
adamstark@97
|
78 // set vector sizes
|
adamstark@97
|
79 resampledOnsetDF.resize (512);
|
adamstark@97
|
80 acf.resize (512);
|
adamstark@97
|
81 weightingVector.resize (128);
|
adamstark@97
|
82 combFilterBankOutput.resize (128);
|
adamstark@97
|
83 tempoObservationVector.resize (41);
|
adamstark@97
|
84 delta.resize (41);
|
adamstark@97
|
85 prevDelta.resize (41);
|
adamstark@97
|
86 prevDeltaFixed.resize (41);
|
adamstark@97
|
87
|
adamstark@98
|
88 double rayleighParameter = 43;
|
adamstark@46
|
89
|
adamstark@46
|
90 // initialise parameters
|
adamstark@46
|
91 tightness = 5;
|
adamstark@46
|
92 alpha = 0.9;
|
adamstark@58
|
93 estimatedTempo = 120.0;
|
adamstark@46
|
94
|
adamstark@105
|
95 timeToNextPrediction = 10;
|
adamstark@105
|
96 timeToNextBeat = -1;
|
adamstark@46
|
97
|
adamstark@57
|
98 beatDueInFrame = false;
|
adamstark@46
|
99
|
adamstark@58
|
100
|
adamstark@46
|
101 // create rayleigh weighting vector
|
adamstark@91
|
102 for (int n = 0; n < 128; n++)
|
adamstark@98
|
103 weightingVector[n] = ((double) n / pow (rayleighParameter, 2)) * exp((-1 * pow((double) - n, 2)) / (2 * pow (rayleighParameter, 2)));
|
adamstark@46
|
104
|
adamstark@100
|
105 // initialise prevDelta
|
adamstark@97
|
106 std::fill (prevDelta.begin(), prevDelta.end(), 1);
|
adamstark@97
|
107
|
adamstark@54
|
108 double t_mu = 41/2;
|
adamstark@54
|
109 double m_sig;
|
adamstark@54
|
110 double x;
|
adamstark@46
|
111 // create tempo transition matrix
|
adamstark@46
|
112 m_sig = 41/8;
|
adamstark@111
|
113
|
adamstark@111
|
114 for (int i = 0; i < 41; i++)
|
adamstark@46
|
115 {
|
adamstark@111
|
116 for (int j = 0; j < 41; j++)
|
adamstark@46
|
117 {
|
adamstark@111
|
118 x = j + 1;
|
adamstark@111
|
119 t_mu = i + 1;
|
adamstark@111
|
120 tempoTransitionMatrix[i][j] = (1 / (m_sig * sqrt (2 * M_PI))) * exp((-1 * pow ((x - t_mu), 2)) / (2 * pow (m_sig, 2)) );
|
adamstark@46
|
121 }
|
adamstark@55
|
122 }
|
adamstark@46
|
123
|
adamstark@46
|
124 // tempo is not fixed
|
adamstark@58
|
125 tempoFixed = false;
|
adamstark@58
|
126
|
adamstark@55
|
127 // initialise algorithm given the hopsize
|
adamstark@108
|
128 setHopSize (hop);
|
adamstark@88
|
129
|
adamstark@88
|
130 // Set up FFT for calculating the auto-correlation function
|
adamstark@88
|
131 FFTLengthForACFCalculation = 1024;
|
adamstark@88
|
132
|
adamstark@93
|
133 #ifdef USE_FFTW
|
adamstark@91
|
134 complexIn = (fftw_complex*) fftw_malloc (sizeof(fftw_complex) * FFTLengthForACFCalculation); // complex array to hold fft data
|
adamstark@91
|
135 complexOut = (fftw_complex*) fftw_malloc (sizeof(fftw_complex) * FFTLengthForACFCalculation); // complex array to hold fft data
|
adamstark@88
|
136
|
adamstark@91
|
137 acfForwardFFT = fftw_plan_dft_1d (FFTLengthForACFCalculation, complexIn, complexOut, FFTW_FORWARD, FFTW_ESTIMATE); // FFT plan initialisation
|
adamstark@91
|
138 acfBackwardFFT = fftw_plan_dft_1d (FFTLengthForACFCalculation, complexOut, complexIn, FFTW_BACKWARD, FFTW_ESTIMATE); // FFT plan initialisation
|
adamstark@93
|
139 #endif
|
adamstark@93
|
140
|
adamstark@93
|
141 #ifdef USE_KISS_FFT
|
adamstark@93
|
142 fftIn = new kiss_fft_cpx[FFTLengthForACFCalculation];
|
adamstark@93
|
143 fftOut = new kiss_fft_cpx[FFTLengthForACFCalculation];
|
adamstark@93
|
144 cfgForwards = kiss_fft_alloc (FFTLengthForACFCalculation, 0, 0, 0);
|
adamstark@93
|
145 cfgBackwards = kiss_fft_alloc (FFTLengthForACFCalculation, 1, 0, 0);
|
adamstark@93
|
146 #endif
|
adamstark@46
|
147 }
|
adamstark@46
|
148
|
adamstark@51
|
149 //=======================================================================
|
adamstark@108
|
150 void BTrack::setHopSize (int hop)
|
adamstark@46
|
151 {
|
adamstark@108
|
152 hopSize = hop;
|
adamstark@97
|
153 onsetDFBufferSize = (512 * 512) / hopSize; // calculate df buffer size
|
adamstark@115
|
154 beatPeriod = round (60 / ((((double) hopSize) / 44100) * 120.));
|
adamstark@63
|
155
|
adamstark@63
|
156 // set size of onset detection function buffer
|
adamstark@91
|
157 onsetDF.resize (onsetDFBufferSize);
|
adamstark@63
|
158
|
adamstark@63
|
159 // set size of cumulative score buffer
|
adamstark@91
|
160 cumulativeScore.resize (onsetDFBufferSize);
|
adamstark@46
|
161
|
adamstark@46
|
162 // initialise df_buffer to zeros
|
adamstark@91
|
163 for (int i = 0; i < onsetDFBufferSize; i++)
|
adamstark@46
|
164 {
|
adamstark@58
|
165 onsetDF[i] = 0;
|
adamstark@58
|
166 cumulativeScore[i] = 0;
|
adamstark@46
|
167
|
adamstark@57
|
168 if ((i % ((int) round(beatPeriod))) == 0)
|
adamstark@46
|
169 {
|
adamstark@58
|
170 onsetDF[i] = 1;
|
adamstark@46
|
171 }
|
adamstark@46
|
172 }
|
adamstark@46
|
173 }
|
adamstark@46
|
174
|
adamstark@51
|
175 //=======================================================================
|
adamstark@108
|
176 void BTrack::updateHopAndFrameSize (int hop, int frame)
|
adamstark@65
|
177 {
|
adamstark@65
|
178 // update the onset detection function object
|
adamstark@108
|
179 odf.initialise (hop, frame);
|
adamstark@65
|
180
|
adamstark@65
|
181 // update the hop size being used by the beat tracker
|
adamstark@108
|
182 setHopSize (hop);
|
adamstark@65
|
183 }
|
adamstark@65
|
184
|
adamstark@65
|
185 //=======================================================================
|
adamstark@57
|
186 bool BTrack::beatDueInCurrentFrame()
|
adamstark@57
|
187 {
|
adamstark@57
|
188 return beatDueInFrame;
|
adamstark@57
|
189 }
|
adamstark@57
|
190
|
adamstark@57
|
191 //=======================================================================
|
adamstark@78
|
192 double BTrack::getCurrentTempoEstimate()
|
adamstark@78
|
193 {
|
adamstark@78
|
194 return estimatedTempo;
|
adamstark@78
|
195 }
|
adamstark@78
|
196
|
adamstark@78
|
197 //=======================================================================
|
adamstark@57
|
198 int BTrack::getHopSize()
|
adamstark@57
|
199 {
|
adamstark@57
|
200 return hopSize;
|
adamstark@57
|
201 }
|
adamstark@57
|
202
|
adamstark@57
|
203 //=======================================================================
|
adamstark@58
|
204 double BTrack::getLatestCumulativeScoreValue()
|
adamstark@58
|
205 {
|
adamstark@105
|
206 return cumulativeScore[cumulativeScore.size() - 1];
|
adamstark@58
|
207 }
|
adamstark@58
|
208
|
adamstark@58
|
209 //=======================================================================
|
adamstark@91
|
210 void BTrack::processAudioFrame (double* frame)
|
adamstark@55
|
211 {
|
adamstark@55
|
212 // calculate the onset detection function sample for the frame
|
adamstark@91
|
213 double sample = odf.calculateOnsetDetectionFunctionSample (frame);
|
adamstark@55
|
214
|
adamstark@55
|
215 // process the new onset detection function sample in the beat tracking algorithm
|
adamstark@91
|
216 processOnsetDetectionFunctionSample (sample);
|
adamstark@55
|
217 }
|
adamstark@55
|
218
|
adamstark@55
|
219 //=======================================================================
|
adamstark@91
|
220 void BTrack::processOnsetDetectionFunctionSample (double newSample)
|
adamstark@56
|
221 {
|
adamstark@56
|
222 // we need to ensure that the onset
|
adamstark@56
|
223 // detection function sample is positive
|
adamstark@91
|
224 newSample = fabs (newSample);
|
adamstark@56
|
225
|
adamstark@56
|
226 // add a tiny constant to the sample to stop it from ever going
|
adamstark@56
|
227 // to zero. this is to avoid problems further down the line
|
adamstark@56
|
228 newSample = newSample + 0.0001;
|
adamstark@56
|
229
|
adamstark@105
|
230 timeToNextPrediction--;
|
adamstark@105
|
231 timeToNextBeat--;
|
adamstark@57
|
232 beatDueInFrame = false;
|
adamstark@90
|
233
|
adamstark@46
|
234 // add new sample at the end
|
adamstark@91
|
235 onsetDF.addSampleToEnd (newSample);
|
adamstark@46
|
236
|
adamstark@46
|
237 // update cumulative score
|
adamstark@91
|
238 updateCumulativeScore (newSample);
|
adamstark@46
|
239
|
adamstark@97
|
240 // if we are halfway between beats, predict a beat
|
adamstark@105
|
241 if (timeToNextPrediction == 0)
|
adamstark@97
|
242 predictBeat();
|
adamstark@46
|
243
|
adamstark@46
|
244 // if we are at a beat
|
adamstark@105
|
245 if (timeToNextBeat == 0)
|
adamstark@46
|
246 {
|
adamstark@57
|
247 beatDueInFrame = true; // indicate a beat should be output
|
adamstark@46
|
248
|
adamstark@46
|
249 // recalculate the tempo
|
adamstark@57
|
250 resampleOnsetDetectionFunction();
|
adamstark@57
|
251 calculateTempo();
|
adamstark@46
|
252 }
|
adamstark@46
|
253 }
|
adamstark@46
|
254
|
adamstark@51
|
255 //=======================================================================
|
adamstark@91
|
256 void BTrack::setTempo (double tempo)
|
adamstark@97
|
257 {
|
adamstark@46
|
258 /////////// TEMPO INDICATION RESET //////////////////
|
adamstark@46
|
259
|
adamstark@46
|
260 // firstly make sure tempo is between 80 and 160 bpm..
|
adamstark@46
|
261 while (tempo > 160)
|
adamstark@97
|
262 tempo = tempo / 2;
|
adamstark@46
|
263
|
adamstark@46
|
264 while (tempo < 80)
|
adamstark@97
|
265 tempo = tempo * 2;
|
adamstark@46
|
266
|
adamstark@46
|
267 // convert tempo from bpm value to integer index of tempo probability
|
adamstark@105
|
268 int tempoIndex = (int) round ((tempo - 80.) / 2);
|
adamstark@46
|
269
|
adamstark@97
|
270 // now set previous tempo observations to zero and set desired tempo index to 1
|
adamstark@97
|
271 std::fill (prevDelta.begin(), prevDelta.end(), 0);
|
adamstark@105
|
272 prevDelta[tempoIndex] = 1;
|
adamstark@46
|
273
|
adamstark@46
|
274 /////////// CUMULATIVE SCORE ARTIFICAL TEMPO UPDATE //////////////////
|
adamstark@46
|
275
|
adamstark@46
|
276 // calculate new beat period
|
adamstark@97
|
277 int newBeatPeriod = (int) round (60 / ((((double) hopSize) / 44100) * tempo));
|
adamstark@46
|
278
|
adamstark@97
|
279 int k = 1;
|
adamstark@97
|
280
|
adamstark@97
|
281 // initialise onset detection function with delta functions spaced
|
adamstark@97
|
282 // at the new beat period
|
adamstark@97
|
283 for (int i = onsetDFBufferSize - 1; i >= 0; i--)
|
adamstark@46
|
284 {
|
adamstark@97
|
285 if (k == 1)
|
adamstark@46
|
286 {
|
adamstark@58
|
287 cumulativeScore[i] = 150;
|
adamstark@58
|
288 onsetDF[i] = 150;
|
adamstark@46
|
289 }
|
adamstark@46
|
290 else
|
adamstark@46
|
291 {
|
adamstark@58
|
292 cumulativeScore[i] = 10;
|
adamstark@58
|
293 onsetDF[i] = 10;
|
adamstark@46
|
294 }
|
adamstark@46
|
295
|
adamstark@97
|
296 k++;
|
adamstark@46
|
297
|
adamstark@97
|
298 if (k > newBeatPeriod)
|
adamstark@46
|
299 {
|
adamstark@97
|
300 k = 1;
|
adamstark@46
|
301 }
|
adamstark@46
|
302 }
|
adamstark@46
|
303
|
adamstark@46
|
304 /////////// INDICATE THAT THIS IS A BEAT //////////////////
|
adamstark@46
|
305
|
adamstark@46
|
306 // beat is now
|
adamstark@105
|
307 timeToNextBeat = 0;
|
adamstark@46
|
308
|
adamstark@105
|
309 // next prediction is on the offbeat, so half of new beat period away
|
adamstark@105
|
310 timeToNextPrediction = (int) round (((double) newBeatPeriod) / 2);
|
adamstark@46
|
311 }
|
adamstark@46
|
312
|
adamstark@51
|
313 //=======================================================================
|
adamstark@91
|
314 void BTrack::fixTempo (double tempo)
|
adamstark@46
|
315 {
|
adamstark@46
|
316 // firstly make sure tempo is between 80 and 160 bpm..
|
adamstark@46
|
317 while (tempo > 160)
|
adamstark@100
|
318 tempo = tempo / 2;
|
adamstark@46
|
319
|
adamstark@46
|
320 while (tempo < 80)
|
adamstark@100
|
321 tempo = tempo * 2;
|
adamstark@46
|
322
|
adamstark@46
|
323 // convert tempo from bpm value to integer index of tempo probability
|
adamstark@100
|
324 int tempoIndex = (int) round((tempo - 80) / 2);
|
adamstark@46
|
325
|
adamstark@46
|
326 // now set previous fixed previous tempo observation values to zero
|
adamstark@115
|
327 for (int i = 0; i < 41; i++)
|
adamstark@46
|
328 {
|
adamstark@58
|
329 prevDeltaFixed[i] = 0;
|
adamstark@46
|
330 }
|
adamstark@46
|
331
|
adamstark@46
|
332 // set desired tempo index to 1
|
adamstark@100
|
333 prevDeltaFixed[tempoIndex] = 1;
|
adamstark@46
|
334
|
adamstark@46
|
335 // set the tempo fix flag
|
adamstark@58
|
336 tempoFixed = true;
|
adamstark@46
|
337 }
|
adamstark@46
|
338
|
adamstark@51
|
339 //=======================================================================
|
adamstark@57
|
340 void BTrack::doNotFixTempo()
|
adamstark@46
|
341 {
|
adamstark@46
|
342 // set the tempo fix flag
|
adamstark@58
|
343 tempoFixed = false;
|
adamstark@46
|
344 }
|
adamstark@46
|
345
|
adamstark@51
|
346 //=======================================================================
|
adamstark@57
|
347 void BTrack::resampleOnsetDetectionFunction()
|
adamstark@46
|
348 {
|
adamstark@46
|
349 float output[512];
|
adamstark@58
|
350 float input[onsetDFBufferSize];
|
adamstark@54
|
351
|
adamstark@115
|
352 for (int i = 0; i < onsetDFBufferSize; i++)
|
adamstark@58
|
353 input[i] = (float) onsetDF[i];
|
adamstark@89
|
354
|
adamstark@97
|
355 double ratio = 512.0 / ((double) onsetDFBufferSize);
|
adamstark@97
|
356 int bufferLength = onsetDFBufferSize;
|
adamstark@97
|
357 int outputLength = 512;
|
adamstark@89
|
358
|
adamstark@97
|
359 SRC_DATA src_data;
|
adamstark@89
|
360 src_data.data_in = input;
|
adamstark@97
|
361 src_data.input_frames = bufferLength;
|
adamstark@97
|
362 src_data.src_ratio = ratio;
|
adamstark@89
|
363 src_data.data_out = output;
|
adamstark@97
|
364 src_data.output_frames = outputLength;
|
adamstark@89
|
365
|
adamstark@89
|
366 src_simple (&src_data, SRC_SINC_BEST_QUALITY, 1);
|
adamstark@89
|
367
|
adamstark@97
|
368 for (int i = 0; i < outputLength; i++)
|
adamstark@89
|
369 resampledOnsetDF[i] = (double) src_data.data_out[i];
|
adamstark@46
|
370 }
|
adamstark@46
|
371
|
adamstark@51
|
372 //=======================================================================
|
adamstark@57
|
373 void BTrack::calculateTempo()
|
adamstark@46
|
374 {
|
adamstark@105
|
375 double tempoToLagFactor = 60. * 44100. / 512.;
|
adamstark@105
|
376
|
adamstark@46
|
377 // adaptive threshold on input
|
adamstark@100
|
378 adaptiveThreshold (resampledOnsetDF);
|
adamstark@46
|
379
|
adamstark@46
|
380 // calculate auto-correlation function of detection function
|
adamstark@100
|
381 calculateBalancedACF (resampledOnsetDF);
|
adamstark@46
|
382
|
adamstark@46
|
383 // calculate output of comb filterbank
|
adamstark@57
|
384 calculateOutputOfCombFilterBank();
|
adamstark@46
|
385
|
adamstark@46
|
386 // adaptive threshold on rcf
|
adamstark@100
|
387 adaptiveThreshold (combFilterBankOutput);
|
adamstark@46
|
388
|
adamstark@59
|
389 // calculate tempo observation vector from beat period observation vector
|
adamstark@100
|
390 for (int i = 0; i < 41; i++)
|
adamstark@46
|
391 {
|
adamstark@105
|
392 int tempoIndex1 = (int) round (tempoToLagFactor / ((double) ((2 * i) + 80)));
|
adamstark@105
|
393 int tempoIndex2 = (int) round (tempoToLagFactor / ((double) ((4 * i) + 160)));
|
adamstark@100
|
394 tempoObservationVector[i] = combFilterBankOutput[tempoIndex1 - 1] + combFilterBankOutput[tempoIndex2 - 1];
|
adamstark@46
|
395 }
|
adamstark@46
|
396
|
adamstark@46
|
397 // if tempo is fixed then always use a fixed set of tempi as the previous observation probability function
|
adamstark@58
|
398 if (tempoFixed)
|
adamstark@46
|
399 {
|
adamstark@100
|
400 for (int k = 0; k < 41; k++)
|
adamstark@100
|
401 prevDelta[k] = prevDeltaFixed[k];
|
adamstark@46
|
402 }
|
adamstark@46
|
403
|
adamstark@100
|
404 for (int j = 0; j < 41; j++)
|
adamstark@46
|
405 {
|
adamstark@100
|
406 double maxValue = -1;
|
adamstark@100
|
407
|
adamstark@100
|
408 for (int i = 0; i < 41; i++)
|
adamstark@46
|
409 {
|
adamstark@100
|
410 double currentValue = prevDelta[i] * tempoTransitionMatrix[i][j];
|
adamstark@46
|
411
|
adamstark@100
|
412 if (currentValue > maxValue)
|
adamstark@100
|
413 maxValue = currentValue;
|
adamstark@46
|
414 }
|
adamstark@46
|
415
|
adamstark@100
|
416 delta[j] = maxValue * tempoObservationVector[j];
|
adamstark@46
|
417 }
|
adamstark@46
|
418
|
adamstark@100
|
419 normaliseVector (delta);
|
adamstark@46
|
420
|
adamstark@100
|
421 double maxIndex = -1;
|
adamstark@100
|
422 double maxValue = -1;
|
adamstark@46
|
423
|
adamstark@100
|
424 for (int j = 0; j < 41; j++)
|
adamstark@46
|
425 {
|
adamstark@100
|
426 if (delta[j] > maxValue)
|
adamstark@46
|
427 {
|
adamstark@100
|
428 maxValue = delta[j];
|
adamstark@100
|
429 maxIndex = j;
|
adamstark@46
|
430 }
|
adamstark@46
|
431
|
adamstark@58
|
432 prevDelta[j] = delta[j];
|
adamstark@46
|
433 }
|
adamstark@46
|
434
|
adamstark@100
|
435 beatPeriod = round ((60.0 * 44100.0) / (((2 * maxIndex) + 80) * ((double) hopSize)));
|
adamstark@46
|
436
|
adamstark@57
|
437 if (beatPeriod > 0)
|
adamstark@115
|
438 estimatedTempo = 60.0 / ((((double) hopSize) / 44100.0) * beatPeriod);
|
adamstark@46
|
439 }
|
adamstark@46
|
440
|
adamstark@51
|
441 //=======================================================================
|
adamstark@100
|
442 void BTrack::adaptiveThreshold (std::vector<double>& x)
|
adamstark@46
|
443 {
|
adamstark@100
|
444 int N = static_cast<int> (x.size());
|
adamstark@100
|
445 double threshold[N];
|
adamstark@46
|
446
|
adamstark@46
|
447 int p_post = 7;
|
adamstark@46
|
448 int p_pre = 8;
|
adamstark@46
|
449
|
adamstark@100
|
450 int t = std::min (N, p_post); // what is smaller, p_post or df size. This is to avoid accessing outside of arrays
|
adamstark@46
|
451
|
adamstark@46
|
452 // find threshold for first 't' samples, where a full average cannot be computed yet
|
adamstark@100
|
453 for (int i = 0; i <= t; i++)
|
adamstark@46
|
454 {
|
adamstark@100
|
455 int k = std::min ((i + p_pre), N);
|
adamstark@100
|
456 threshold[i] = calculateMeanOfVector (x, 1, k);
|
adamstark@46
|
457 }
|
adamstark@100
|
458
|
adamstark@46
|
459 // find threshold for bulk of samples across a moving average from [i-p_pre,i+p_post]
|
adamstark@100
|
460 for (int i = t + 1; i < N - p_post; i++)
|
adamstark@46
|
461 {
|
adamstark@100
|
462 threshold[i] = calculateMeanOfVector (x, i - p_pre, i + p_post);
|
adamstark@46
|
463 }
|
adamstark@100
|
464
|
adamstark@46
|
465 // for last few samples calculate threshold, again, not enough samples to do as above
|
adamstark@100
|
466 for (int i = N - p_post; i < N; i++)
|
adamstark@46
|
467 {
|
adamstark@100
|
468 int k = std::max ((i - p_post), 1);
|
adamstark@100
|
469 threshold[i] = calculateMeanOfVector (x, k, N);
|
adamstark@46
|
470 }
|
adamstark@46
|
471
|
adamstark@46
|
472 // subtract the threshold from the detection function and check that it is not less than 0
|
adamstark@100
|
473 for (int i = 0; i < N; i++)
|
adamstark@46
|
474 {
|
adamstark@100
|
475 x[i] = x[i] - threshold[i];
|
adamstark@100
|
476
|
adamstark@46
|
477 if (x[i] < 0)
|
adamstark@100
|
478 x[i] = 0;
|
adamstark@46
|
479 }
|
adamstark@46
|
480 }
|
adamstark@46
|
481
|
adamstark@51
|
482 //=======================================================================
|
adamstark@57
|
483 void BTrack::calculateOutputOfCombFilterBank()
|
adamstark@46
|
484 {
|
adamstark@100
|
485 std::fill (combFilterBankOutput.begin(), combFilterBankOutput.end(), 0.0);
|
adamstark@100
|
486 int numCombElements = 4;
|
adamstark@46
|
487
|
adamstark@91
|
488 for (int i = 2; i <= 127; i++) // max beat period
|
adamstark@46
|
489 {
|
adamstark@100
|
490 for (int a = 1; a <= numCombElements; a++) // number of comb elements
|
adamstark@46
|
491 {
|
adamstark@100
|
492 for (int b = 1 - a; b <= a - 1; b++) // general state using normalisation of comb elements
|
adamstark@46
|
493 {
|
adamstark@115
|
494 combFilterBankOutput[i - 1] += (acf[(a * i + b) - 1] * weightingVector[i - 1]) / (2 * a - 1); // calculate value for comb filter row
|
adamstark@46
|
495 }
|
adamstark@46
|
496 }
|
adamstark@46
|
497 }
|
adamstark@46
|
498 }
|
adamstark@46
|
499
|
adamstark@51
|
500 //=======================================================================
|
adamstark@100
|
501 void BTrack::calculateBalancedACF (std::vector<double>& onsetDetectionFunction)
|
adamstark@46
|
502 {
|
adamstark@88
|
503 int onsetDetectionFunctionLength = 512;
|
adamstark@88
|
504
|
adamstark@93
|
505 #ifdef USE_FFTW
|
adamstark@88
|
506 // copy into complex array and zero pad
|
adamstark@111
|
507 for (int i = 0; i < FFTLengthForACFCalculation; i++)
|
adamstark@88
|
508 {
|
adamstark@88
|
509 if (i < onsetDetectionFunctionLength)
|
adamstark@88
|
510 {
|
adamstark@88
|
511 complexIn[i][0] = onsetDetectionFunction[i];
|
adamstark@88
|
512 complexIn[i][1] = 0.0;
|
adamstark@88
|
513 }
|
adamstark@88
|
514 else
|
adamstark@88
|
515 {
|
adamstark@88
|
516 complexIn[i][0] = 0.0;
|
adamstark@88
|
517 complexIn[i][1] = 0.0;
|
adamstark@88
|
518 }
|
adamstark@88
|
519 }
|
adamstark@88
|
520
|
adamstark@88
|
521 // perform the fft
|
adamstark@91
|
522 fftw_execute (acfForwardFFT);
|
adamstark@88
|
523
|
adamstark@88
|
524 // multiply by complex conjugate
|
adamstark@115
|
525 for (int i = 0; i < FFTLengthForACFCalculation; i++)
|
adamstark@88
|
526 {
|
adamstark@111
|
527 complexOut[i][0] = complexOut[i][0] * complexOut[i][0] + complexOut[i][1] * complexOut[i][1];
|
adamstark@88
|
528 complexOut[i][1] = 0.0;
|
adamstark@88
|
529 }
|
adamstark@88
|
530
|
adamstark@88
|
531 // perform the ifft
|
adamstark@91
|
532 fftw_execute (acfBackwardFFT);
|
adamstark@88
|
533
|
adamstark@93
|
534 #endif
|
adamstark@93
|
535
|
adamstark@93
|
536 #ifdef USE_KISS_FFT
|
adamstark@93
|
537 // copy into complex array and zero pad
|
adamstark@111
|
538 for (int i = 0; i < FFTLengthForACFCalculation; i++)
|
adamstark@93
|
539 {
|
adamstark@93
|
540 if (i < onsetDetectionFunctionLength)
|
adamstark@93
|
541 {
|
adamstark@93
|
542 fftIn[i].r = onsetDetectionFunction[i];
|
adamstark@93
|
543 fftIn[i].i = 0.0;
|
adamstark@93
|
544 }
|
adamstark@93
|
545 else
|
adamstark@93
|
546 {
|
adamstark@93
|
547 fftIn[i].r = 0.0;
|
adamstark@93
|
548 fftIn[i].i = 0.0;
|
adamstark@93
|
549 }
|
adamstark@93
|
550 }
|
adamstark@93
|
551
|
adamstark@93
|
552 // execute kiss fft
|
adamstark@93
|
553 kiss_fft (cfgForwards, fftIn, fftOut);
|
adamstark@93
|
554
|
adamstark@93
|
555 // multiply by complex conjugate
|
adamstark@115
|
556 for (int i = 0; i < FFTLengthForACFCalculation; i++)
|
adamstark@93
|
557 {
|
adamstark@93
|
558 fftOut[i].r = fftOut[i].r * fftOut[i].r + fftOut[i].i * fftOut[i].i;
|
adamstark@93
|
559 fftOut[i].i = 0.0;
|
adamstark@93
|
560 }
|
adamstark@93
|
561
|
adamstark@93
|
562 // perform the ifft
|
adamstark@93
|
563 kiss_fft (cfgBackwards, fftOut, fftIn);
|
adamstark@93
|
564
|
adamstark@93
|
565 #endif
|
adamstark@88
|
566
|
adamstark@88
|
567 double lag = 512;
|
adamstark@88
|
568
|
adamstark@91
|
569 for (int i = 0; i < 512; i++)
|
adamstark@88
|
570 {
|
adamstark@93
|
571 #ifdef USE_FFTW
|
adamstark@88
|
572 // calculate absolute value of result
|
adamstark@111
|
573 double absValue = sqrt (complexIn[i][0] * complexIn[i][0] + complexIn[i][1] * complexIn[i][1]);
|
adamstark@93
|
574 #endif
|
adamstark@88
|
575
|
adamstark@93
|
576 #ifdef USE_KISS_FFT
|
adamstark@93
|
577 // calculate absolute value of result
|
adamstark@93
|
578 double absValue = sqrt (fftIn[i].r * fftIn[i].r + fftIn[i].i * fftIn[i].i);
|
adamstark@93
|
579 #endif
|
adamstark@88
|
580 // divide by inverse lad to deal with scale bias towards small lags
|
adamstark@88
|
581 acf[i] = absValue / lag;
|
adamstark@88
|
582
|
adamstark@88
|
583 // this division by 1024 is technically unnecessary but it ensures the algorithm produces
|
adamstark@88
|
584 // exactly the same ACF output as the old time domain implementation. The time difference is
|
adamstark@88
|
585 // minimal so I decided to keep it
|
adamstark@88
|
586 acf[i] = acf[i] / 1024.;
|
adamstark@88
|
587
|
adamstark@88
|
588 lag = lag - 1.;
|
adamstark@88
|
589 }
|
adamstark@46
|
590 }
|
adamstark@46
|
591
|
adamstark@51
|
592 //=======================================================================
|
adamstark@100
|
593 double BTrack::calculateMeanOfVector (std::vector<double>& vector, int startIndex, int endIndex)
|
adamstark@46
|
594 {
|
adamstark@97
|
595 int length = endIndex - startIndex;
|
adamstark@100
|
596 double sum = std::accumulate (vector.begin() + startIndex, vector.begin() + endIndex, 0.0);
|
adamstark@47
|
597
|
adamstark@47
|
598 if (length > 0)
|
adamstark@97
|
599 return sum / static_cast<double> (length); // average and return
|
adamstark@47
|
600 else
|
adamstark@47
|
601 return 0;
|
adamstark@46
|
602 }
|
adamstark@46
|
603
|
adamstark@51
|
604 //=======================================================================
|
adamstark@100
|
605 void BTrack::normaliseVector (std::vector<double>& vector)
|
adamstark@46
|
606 {
|
adamstark@100
|
607 double sum = std::accumulate (vector.begin(), vector.end(), 0.0);
|
adamstark@46
|
608
|
adamstark@46
|
609 if (sum > 0)
|
adamstark@97
|
610 {
|
adamstark@100
|
611 for (int i = 0; i < vector.size(); i++)
|
adamstark@100
|
612 vector[i] = vector[i] / sum;
|
adamstark@97
|
613 }
|
adamstark@46
|
614 }
|
adamstark@46
|
615
|
adamstark@51
|
616 //=======================================================================
|
adamstark@100
|
617 void BTrack::updateCumulativeScore (double onsetDetectionFunctionSample)
|
adamstark@98
|
618 {
|
adamstark@100
|
619 int windowStart = onsetDFBufferSize - round (2. * beatPeriod);
|
adamstark@100
|
620 int windowEnd = onsetDFBufferSize - round (beatPeriod / 2.);
|
adamstark@100
|
621 int windowSize = windowEnd - windowStart + 1;
|
adamstark@46
|
622
|
adamstark@104
|
623 // create log gaussian transition window
|
adamstark@103
|
624 double logGaussianTransitionWeighting[windowSize];
|
adamstark@103
|
625 createLogGaussianTransitionWeighting (logGaussianTransitionWeighting, windowSize, beatPeriod);
|
adamstark@46
|
626
|
adamstark@104
|
627 // calculate the new cumulative score value
|
adamstark@105
|
628 double cumulativeScoreValue = calculateNewCumulativeScoreValue (cumulativeScore, logGaussianTransitionWeighting, windowStart, windowEnd, onsetDetectionFunctionSample, alpha);
|
adamstark@104
|
629
|
adamstark@104
|
630 // add the new cumulative score value to the buffer
|
adamstark@105
|
631 cumulativeScore.addSampleToEnd (cumulativeScoreValue);
|
adamstark@46
|
632 }
|
adamstark@46
|
633
|
adamstark@51
|
634 //=======================================================================
|
adamstark@57
|
635 void BTrack::predictBeat()
|
adamstark@46
|
636 {
|
adamstark@104
|
637 int beatExpectationWindowSize = static_cast<int> (beatPeriod);
|
adamstark@102
|
638 double futureCumulativeScore[onsetDFBufferSize + beatExpectationWindowSize];
|
adamstark@102
|
639 double beatExpectationWindow[beatExpectationWindowSize];
|
adamstark@93
|
640
|
adamstark@102
|
641 // copy cumulativeScore to first part of futureCumulativeScore
|
adamstark@115
|
642 for (int i = 0; i < onsetDFBufferSize; i++)
|
adamstark@102
|
643 futureCumulativeScore[i] = cumulativeScore[i];
|
adamstark@102
|
644
|
adamstark@102
|
645 // Create a beat expectation window for predicting future beats from the "future" of the cumulative score.
|
adamstark@102
|
646 // We are making this beat prediction at the midpoint between beats, and so we make a Gaussian
|
adamstark@102
|
647 // weighting centred on the most likely beat position (half a beat period into the future)
|
adamstark@102
|
648 // This is W2 in Adam Stark's PhD thesis, equation 3.6, page 62
|
adamstark@102
|
649
|
adamstark@102
|
650 double v = 1;
|
adamstark@102
|
651 for (int i = 0; i < beatExpectationWindowSize; i++)
|
adamstark@46
|
652 {
|
adamstark@102
|
653 beatExpectationWindow[i] = exp((-1 * pow ((v - (beatPeriod / 2)), 2)) / (2 * pow (beatPeriod / 2, 2)));
|
adamstark@46
|
654 v++;
|
adamstark@46
|
655 }
|
adamstark@46
|
656
|
adamstark@102
|
657 // Create window for "synthesizing" the cumulative score into the future
|
adamstark@102
|
658 // It is a log-Gaussian transition weighting running from from 2 beat periods
|
adamstark@102
|
659 // in the past to half a beat period in the past. It favours the time exactly
|
adamstark@102
|
660 // one beat period in the past
|
adamstark@102
|
661
|
adamstark@102
|
662 int startIndex = onsetDFBufferSize - round (2 * beatPeriod);
|
adamstark@102
|
663 int endIndex = onsetDFBufferSize - round (beatPeriod / 2);
|
adamstark@102
|
664 int pastWindowSize = endIndex - startIndex + 1;
|
adamstark@103
|
665
|
adamstark@102
|
666 double logGaussianTransitionWeighting[pastWindowSize];
|
adamstark@103
|
667 createLogGaussianTransitionWeighting (logGaussianTransitionWeighting, pastWindowSize, beatPeriod);
|
adamstark@46
|
668
|
adamstark@104
|
669 // Calculate the future cumulative score, by shifting the log Gaussian transition weighting from its
|
adamstark@106
|
670 // start position of [-2 beat periods, - 0.5 beat periods] forwards over the size of the beat
|
adamstark@104
|
671 // expectation window, calculating a new cumulative score where the onset detection function sample
|
adamstark@104
|
672 // is zero. This uses the "momentum" of the function to generate itself into the future.
|
adamstark@102
|
673 for (int i = onsetDFBufferSize; i < (onsetDFBufferSize + beatExpectationWindowSize); i++)
|
adamstark@46
|
674 {
|
adamstark@106
|
675 // note here that we pass 0.0 in for the onset detection function sample and 1.0 for the alpha weighting factor
|
adamstark@104
|
676 // see equation 3.4 and page 60 - 62 of Adam Stark's PhD thesis for details
|
adamstark@104
|
677 futureCumulativeScore[i] = calculateNewCumulativeScoreValue (futureCumulativeScore, logGaussianTransitionWeighting, startIndex, endIndex, 0.0, 1.0);
|
adamstark@104
|
678
|
adamstark@104
|
679 startIndex++;
|
adamstark@104
|
680 endIndex++;
|
adamstark@46
|
681 }
|
adamstark@46
|
682
|
adamstark@102
|
683 // Predict the next beat, finding the maximum point of the future cumulative score
|
adamstark@102
|
684 // over the next beat, after being weighted by the beat expectation window
|
adamstark@102
|
685
|
adamstark@102
|
686 double maxValue = 0;
|
adamstark@102
|
687 int n = 0;
|
adamstark@46
|
688
|
adamstark@102
|
689 for (int i = onsetDFBufferSize; i < (onsetDFBufferSize + beatExpectationWindowSize); i++)
|
adamstark@46
|
690 {
|
adamstark@102
|
691 double weightedCumulativeScore = futureCumulativeScore[i] * beatExpectationWindow[n];
|
adamstark@46
|
692
|
adamstark@102
|
693 if (weightedCumulativeScore > maxValue)
|
adamstark@46
|
694 {
|
adamstark@102
|
695 maxValue = weightedCumulativeScore;
|
adamstark@105
|
696 timeToNextBeat = n;
|
adamstark@46
|
697 }
|
adamstark@46
|
698
|
adamstark@46
|
699 n++;
|
adamstark@46
|
700 }
|
adamstark@46
|
701
|
adamstark@105
|
702 // set next prediction time as on the offbeat after the next beat
|
adamstark@105
|
703 timeToNextPrediction = timeToNextBeat + round (beatPeriod / 2);
|
adamstark@97
|
704 }
|
adamstark@103
|
705
|
adamstark@103
|
706 //=======================================================================
|
adamstark@103
|
707 void BTrack::createLogGaussianTransitionWeighting (double* weightingArray, int numSamples, double beatPeriod)
|
adamstark@103
|
708 {
|
adamstark@105
|
709 // (This is W1 in Adam Stark's PhD thesis, equation 3.2, page 60)
|
adamstark@105
|
710
|
adamstark@103
|
711 double v = -2. * beatPeriod;
|
adamstark@103
|
712
|
adamstark@103
|
713 for (int i = 0; i < numSamples; i++)
|
adamstark@103
|
714 {
|
adamstark@103
|
715 double a = tightness * log (-v / beatPeriod);
|
adamstark@103
|
716 weightingArray[i] = exp ((-1. * a * a) / 2.);
|
adamstark@103
|
717 v++;
|
adamstark@103
|
718 }
|
adamstark@103
|
719 }
|
adamstark@104
|
720
|
adamstark@104
|
721 //=======================================================================
|
adamstark@104
|
722 template <typename T>
|
adamstark@104
|
723 double BTrack::calculateNewCumulativeScoreValue (T cumulativeScoreArray, double* logGaussianTransitionWeighting, int startIndex, int endIndex, double onsetDetectionFunctionSample, double alphaWeightingFactor)
|
adamstark@104
|
724 {
|
adamstark@104
|
725 // calculate new cumulative score value by weighting the cumulative score between
|
adamstark@104
|
726 // startIndex and endIndex and finding the maximum value
|
adamstark@104
|
727 double maxValue = 0;
|
adamstark@104
|
728 int n = 0;
|
adamstark@104
|
729 for (int i = startIndex; i <= endIndex; i++)
|
adamstark@104
|
730 {
|
adamstark@104
|
731 double weightedCumulativeScore = cumulativeScoreArray[i] * logGaussianTransitionWeighting[n];
|
adamstark@104
|
732
|
adamstark@104
|
733 if (weightedCumulativeScore > maxValue)
|
adamstark@104
|
734 maxValue = weightedCumulativeScore;
|
adamstark@104
|
735
|
adamstark@104
|
736 n++;
|
adamstark@104
|
737 }
|
adamstark@104
|
738
|
adamstark@104
|
739 // now mix with the incoming onset detection function sample
|
adamstark@104
|
740 // (equation 3.4 on page 60 of Adam Stark's PhD thesis)
|
adamstark@104
|
741 double cumulativeScoreValue = ((1. - alphaWeightingFactor) * onsetDetectionFunctionSample) + (alphaWeightingFactor * maxValue);
|
adamstark@104
|
742
|
adamstark@104
|
743 return cumulativeScoreValue;
|
adamstark@104
|
744 }
|