annotate src/BTrack.cpp @ 108:a6701b3f636f

Tidying up the interface to some functions
author Adam Stark <adamstark.uk@gmail.com>
date Tue, 12 Sep 2017 08:40:16 +0100
parents 6f5ba0250afd
children 8fb1610c9192
rev   line source
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 }