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