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