Mercurial > hg > btrack
view src/BTrack.cpp @ 20:baf35f208814 develop
Replaced switch statements in OnsetDetectionFunction with enums. Renamed lots of functions so that they have better names, in camel case. Added some unit tests for initialisation of BTrack.
author | Adam <adamstark.uk@gmail.com> |
---|---|
date | Thu, 23 Jan 2014 15:31:11 +0000 |
parents | 88c8d3862eee |
children | ef4721e7466c |
line wrap: on
line source
//======================================================================= /** @file BTrack.cpp * @brief BTrack - a real-time beat tracker * @author Adam Stark * @copyright Copyright (C) 2008-2014 Queen Mary University of London * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ //======================================================================= #include <cmath> #include <algorithm> #include "BTrack.h" #include "samplerate.h" //======================================================================= BTrack::BTrack() : odf(512,1024,ComplexSpectralDifferenceHWR,HanningWindow) { initialise(512, 1024); } //======================================================================= BTrack::BTrack(int hopSize_) : odf(hopSize_,2*hopSize_,ComplexSpectralDifferenceHWR,HanningWindow) { initialise(hopSize_, 2*hopSize_); } //======================================================================= BTrack::BTrack(int hopSize_,int frameSize_) : odf(hopSize_,frameSize_,ComplexSpectralDifferenceHWR,HanningWindow) { initialise(hopSize_, frameSize_); } //======================================================================= double BTrack::getBeatTimeInSeconds(long frameNumber,int hopSize,int fs) { double hop = (double) hopSize; double samplingFrequency = (double) fs; double frameNum = (double) frameNumber; return ((hop / samplingFrequency) * frameNum); } //======================================================================= double BTrack::getBeatTimeInSeconds(int frameNumber,int hopSize,int fs) { long frameNum = (long) frameNumber; return getBeatTimeInSeconds(frameNum, hopSize, fs); } //======================================================================= void BTrack::initialise(int hopSize_, int frameSize_) { double rayparam = 43; double pi = 3.14159265; // initialise parameters tightness = 5; alpha = 0.9; tempo = 120; est_tempo = 120; p_fact = 60.*44100./512.; m0 = 10; beat = -1; beatDueInFrame = false; // create rayleigh weighting vector for (int n = 0;n < 128;n++) { wv[n] = ((double) n / pow(rayparam,2)) * exp((-1*pow((double)-n,2)) / (2*pow(rayparam,2))); } // initialise prev_delta for (int i = 0;i < 41;i++) { prev_delta[i] = 1; } double t_mu = 41/2; double m_sig; double x; // create tempo transition matrix m_sig = 41/8; for (int i = 0;i < 41;i++) { for (int j = 0;j < 41;j++) { x = j+1; t_mu = i+1; t_tmat[i][j] = (1 / (m_sig * sqrt(2*pi))) * exp( (-1*pow((x-t_mu),2)) / (2*pow(m_sig,2)) ); } } // tempo is not fixed tempofix = 0; // initialise algorithm given the hopsize setHopSize(hopSize_); } //======================================================================= void BTrack::setHopSize(int hopSize_) { hopSize = hopSize_; dfbuffer_size = (512*512)/hopSize; // calculate df buffer size beatPeriod = round(60/((((double) hopSize)/44100)*tempo)); dfbuffer = new double[dfbuffer_size]; // create df_buffer cumscore = new double[dfbuffer_size]; // create cumscore // initialise df_buffer to zeros for (int i = 0;i < dfbuffer_size;i++) { dfbuffer[i] = 0; cumscore[i] = 0; if ((i % ((int) round(beatPeriod))) == 0) { dfbuffer[i] = 1; } } } //======================================================================= bool BTrack::beatDueInCurrentFrame() { return beatDueInFrame; } //======================================================================= int BTrack::getHopSize() { return hopSize; } //======================================================================= void BTrack::processAudioFrame(double *frame) { // calculate the onset detection function sample for the frame double sample = odf.getDFsample(frame); // process the new onset detection function sample in the beat tracking algorithm processOnsetDetectionFunctionSample(sample); } //======================================================================= void BTrack::processOnsetDetectionFunctionSample(double newSample) { // we need to ensure that the onset // detection function sample is positive newSample = fabs(newSample); // add a tiny constant to the sample to stop it from ever going // to zero. this is to avoid problems further down the line newSample = newSample + 0.0001; m0--; beat--; beatDueInFrame = false; // move all samples back one step for (int i=0;i < (dfbuffer_size-1);i++) { dfbuffer[i] = dfbuffer[i+1]; } // add new sample at the end dfbuffer[dfbuffer_size-1] = newSample; // update cumulative score updateCumulativeScore(newSample); // if we are halfway between beats if (m0 == 0) { predictBeat(); } // if we are at a beat if (beat == 0) { beatDueInFrame = true; // indicate a beat should be output // recalculate the tempo resampleOnsetDetectionFunction(); calculateTempo(); } } //======================================================================= void BTrack::setTempo(double tempo) { /////////// TEMPO INDICATION RESET ////////////////// // firstly make sure tempo is between 80 and 160 bpm.. while (tempo > 160) { tempo = tempo/2; } while (tempo < 80) { tempo = tempo * 2; } // convert tempo from bpm value to integer index of tempo probability int tempo_index = (int) round((tempo - 80)/2); // now set previous tempo observations to zero for (int i=0;i < 41;i++) { prev_delta[i] = 0; } // set desired tempo index to 1 prev_delta[tempo_index] = 1; /////////// CUMULATIVE SCORE ARTIFICAL TEMPO UPDATE ////////////////// // calculate new beat period int new_bperiod = (int) round(60/((((double) hopSize)/44100)*tempo)); int bcounter = 1; // initialise df_buffer to zeros for (int i = (dfbuffer_size-1);i >= 0;i--) { if (bcounter == 1) { cumscore[i] = 150; dfbuffer[i] = 150; } else { cumscore[i] = 10; dfbuffer[i] = 10; } bcounter++; if (bcounter > new_bperiod) { bcounter = 1; } } /////////// INDICATE THAT THIS IS A BEAT ////////////////// // beat is now beat = 0; // offbeat is half of new beat period away m0 = (int) round(((double) new_bperiod)/2); } //======================================================================= void BTrack::fixTempo(double tempo) { // firstly make sure tempo is between 80 and 160 bpm.. while (tempo > 160) { tempo = tempo/2; } while (tempo < 80) { tempo = tempo * 2; } // convert tempo from bpm value to integer index of tempo probability int tempo_index = (int) round((tempo - 80)/2); // now set previous fixed previous tempo observation values to zero for (int i=0;i < 41;i++) { prev_delta_fix[i] = 0; } // set desired tempo index to 1 prev_delta_fix[tempo_index] = 1; // set the tempo fix flag tempofix = 1; } //======================================================================= void BTrack::doNotFixTempo() { // set the tempo fix flag tempofix = 0; } //======================================================================= void BTrack::resampleOnsetDetectionFunction() { float output[512]; float input[dfbuffer_size]; for (int i = 0;i < dfbuffer_size;i++) { input[i] = (float) dfbuffer[i]; } double src_ratio = 512.0/((double) dfbuffer_size); int BUFFER_LEN = dfbuffer_size; int output_len; SRC_DATA src_data ; //output_len = (int) floor (((double) BUFFER_LEN) * src_ratio) ; output_len = 512; src_data.data_in = input; src_data.input_frames = BUFFER_LEN; src_data.src_ratio = src_ratio; src_data.data_out = output; src_data.output_frames = output_len; src_simple (&src_data, SRC_SINC_BEST_QUALITY, 1); for (int i = 0;i < output_len;i++) { df512[i] = (double) src_data.data_out[i]; } } //======================================================================= void BTrack::calculateTempo() { // adaptive threshold on input adaptiveThreshold(df512,512); // calculate auto-correlation function of detection function calculateBalancedACF(df512); // calculate output of comb filterbank calculateOutputOfCombFilterBank(); // adaptive threshold on rcf adaptiveThreshold(rcf,128); int t_index; int t_index2; // calculate tempo observation vector from bperiod observation vector for (int i = 0;i < 41;i++) { t_index = (int) round(p_fact / ((double) ((2*i)+80))); t_index2 = (int) round(p_fact / ((double) ((4*i)+160))); t_obs[i] = rcf[t_index-1] + rcf[t_index2-1]; } double maxval; double maxind; double curval; // if tempo is fixed then always use a fixed set of tempi as the previous observation probability function if (tempofix == 1) { for (int k = 0;k < 41;k++) { prev_delta[k] = prev_delta_fix[k]; } } for (int j=0;j < 41;j++) { maxval = -1; for (int i = 0;i < 41;i++) { curval = prev_delta[i]*t_tmat[i][j]; if (curval > maxval) { maxval = curval; } } delta[j] = maxval*t_obs[j]; } normaliseArray(delta,41); maxind = -1; maxval = -1; for (int j=0;j < 41;j++) { if (delta[j] > maxval) { maxval = delta[j]; maxind = j; } prev_delta[j] = delta[j]; } beatPeriod = round((60.0*44100.0)/(((2*maxind)+80)*((double) hopSize))); if (beatPeriod > 0) { est_tempo = 60.0/((((double) hopSize) / 44100.0)*beatPeriod); } } //======================================================================= void BTrack::adaptiveThreshold(double *x,int N) { //int N = 512; // length of df int i = 0; int k,t = 0; double x_thresh[N]; int p_post = 7; int p_pre = 8; t = std::min(N,p_post); // what is smaller, p_post of df size. This is to avoid accessing outside of arrays // find threshold for first 't' samples, where a full average cannot be computed yet for (i = 0;i <= t;i++) { k = std::min((i+p_pre),N); x_thresh[i] = calculateMeanOfArray(x,1,k); } // find threshold for bulk of samples across a moving average from [i-p_pre,i+p_post] for (i = t+1;i < N-p_post;i++) { x_thresh[i] = calculateMeanOfArray(x,i-p_pre,i+p_post); } // for last few samples calculate threshold, again, not enough samples to do as above for (i = N-p_post;i < N;i++) { k = std::max((i-p_post),1); x_thresh[i] = calculateMeanOfArray(x,k,N); } // subtract the threshold from the detection function and check that it is not less than 0 for (i = 0;i < N;i++) { x[i] = x[i] - x_thresh[i]; if (x[i] < 0) { x[i] = 0; } } } //======================================================================= void BTrack::calculateOutputOfCombFilterBank() { int numelem; for (int i = 0;i < 128;i++) { rcf[i] = 0; } numelem = 4; for (int i = 2;i <= 127;i++) // max beat period { for (int a = 1;a <= numelem;a++) // number of comb elements { for (int b = 1-a;b <= a-1;b++) // general state using normalisation of comb elements { rcf[i-1] = rcf[i-1] + (acf[(a*i+b)-1]*wv[i-1])/(2*a-1); // calculate value for comb filter row } } } } //======================================================================= void BTrack::calculateBalancedACF(double *df_thresh) { int l, n = 0; double sum, tmp; // for l lags from 0-511 for (l = 0;l < 512;l++) { sum = 0; // for n samples from 0 - (512-lag) for (n = 0;n < (512-l);n++) { tmp = df_thresh[n] * df_thresh[n+l]; // multiply current sample n by sample (n+l) sum = sum + tmp; // add to sum } acf[l] = sum / (512-l); // weight by number of mults and add to acf buffer } } //======================================================================= double BTrack::calculateMeanOfArray(double *array,int start,int end) { int i; double sum = 0; int length = end - start; // find sum for (i = start;i < end;i++) { sum = sum + array[i]; } if (length > 0) { return sum / length; // average and return } else { return 0; } } //======================================================================= void BTrack::normaliseArray(double *array,int N) { double sum = 0; for (int i = 0;i < N;i++) { if (array[i] > 0) { sum = sum + array[i]; } } if (sum > 0) { for (int i = 0;i < N;i++) { array[i] = array[i] / sum; } } } //======================================================================= void BTrack::updateCumulativeScore(double df_sample) { int start, end, winsize; double max; start = dfbuffer_size - round(2*beatPeriod); end = dfbuffer_size - round(beatPeriod/2); winsize = end-start+1; double w1[winsize]; double v = -2*beatPeriod; double wcumscore; // create window for (int i = 0;i < winsize;i++) { w1[i] = exp((-1*pow(tightness*log(-v/beatPeriod),2))/2); v = v+1; } // calculate new cumulative score value max = 0; int n = 0; for (int i=start;i <= end;i++) { wcumscore = cumscore[i]*w1[n]; if (wcumscore > max) { max = wcumscore; } n++; } // shift cumulative score back one for (int i = 0;i < (dfbuffer_size-1);i++) { cumscore[i] = cumscore[i+1]; } // add new value to cumulative score cumscore[dfbuffer_size-1] = ((1-alpha)*df_sample) + (alpha*max); cscoreval = cumscore[dfbuffer_size-1]; //cout << cumscore[dfbuffer_size-1] << endl; } //======================================================================= void BTrack::predictBeat() { int winsize = (int) beatPeriod; double fcumscore[dfbuffer_size + winsize]; double w2[winsize]; // copy cumscore to first part of fcumscore for (int i = 0;i < dfbuffer_size;i++) { fcumscore[i] = cumscore[i]; } // create future window double v = 1; for (int i = 0;i < winsize;i++) { w2[i] = exp((-1*pow((v - (beatPeriod/2)),2)) / (2*pow((beatPeriod/2) ,2))); v++; } // create past window v = -2*beatPeriod; int start = dfbuffer_size - round(2*beatPeriod); int end = dfbuffer_size - round(beatPeriod/2); int pastwinsize = end-start+1; double w1[pastwinsize]; for (int i = 0;i < pastwinsize;i++) { w1[i] = exp((-1*pow(tightness*log(-v/beatPeriod),2))/2); v = v+1; } // calculate future cumulative score double max; int n; double wcumscore; for (int i = dfbuffer_size;i < (dfbuffer_size+winsize);i++) { start = i - round(2*beatPeriod); end = i - round(beatPeriod/2); max = 0; n = 0; for (int k=start;k <= end;k++) { wcumscore = fcumscore[k]*w1[n]; if (wcumscore > max) { max = wcumscore; } n++; } fcumscore[i] = max; } // predict beat max = 0; n = 0; for (int i = dfbuffer_size;i < (dfbuffer_size+winsize);i++) { wcumscore = fcumscore[i]*w2[n]; if (wcumscore > max) { max = wcumscore; beat = n; } n++; } // set next prediction time m0 = beat+round(beatPeriod/2); }