annotate src/BTrack.cpp @ 117:ca2d83d29814 tip master

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