Mercurial > hg > btrack
diff src/BTrack.cpp @ 5:bd2c405d4a06 develop
Added all source code
author | Adam <adamstark.uk@gmail.com> |
---|---|
date | Tue, 21 Jan 2014 00:10:11 +0000 |
parents | |
children | 46971d477183 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/BTrack.cpp Tue Jan 21 00:10:11 2014 +0000 @@ -0,0 +1,665 @@ +//======================================================================= +/** @file BTrack.cpp + * @brief Implementation file for the BTrack 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 <iostream> +#include <cmath> +#include "BTrack.h" +#include "samplerate.h" +using namespace std; + + + + +//------------------------------------------------------------------------------- +// Constructor +BTrack :: BTrack() +{ + float rayparam = 43; + float pi = 3.14159265; + + + // initialise parameters + tightness = 5; + alpha = 0.9; + tempo = 120; + est_tempo = 120; + p_fact = 60.*44100./512.; + + m0 = 10; + beat = -1; + + playbeat = 0; + + + + + // create rayleigh weighting vector + for (int n = 0;n < 128;n++) + { + wv[n] = ((float) n / pow(rayparam,2)) * exp((-1*pow((float)-n,2)) / (2*pow(rayparam,2))); + } + + // initialise prev_delta + for (int i = 0;i < 41;i++) + { + prev_delta[i] = 1; + } + + float t_mu = 41/2; + float m_sig; + float 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; +} + +//------------------------------------------------------------------------------- +// Destructor +BTrack :: ~BTrack() +{ + +} + +//------------------------------------------------------------------------------- +// Initialise with frame size and set all frame sizes accordingly +void BTrack :: initialise(int fsize) +{ + framesize = fsize; + dfbuffer_size = (512*512)/fsize; // calculate df buffer size + + bperiod = round(60/((((float) fsize)/44100)*tempo)); + + dfbuffer = new float[dfbuffer_size]; // create df_buffer + cumscore = new float[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(bperiod))) == 0) + { + dfbuffer[i] = 1; + } + } +} + +//------------------------------------------------------------------------------- +// Add new sample to buffer and apply beat tracking +void BTrack :: process(float df_sample) +{ + m0--; + beat--; + playbeat = 0; + + // 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] = df_sample; + + // update cumulative score + updatecumscore(df_sample); + + // if we are halfway between beats + if (m0 == 0) + { + predictbeat(); + } + + // if we are at a beat + if (beat == 0) + { + playbeat = 1; // indicate a beat should be output + + // recalculate the tempo + dfconvert(); + calcTempo(); + } +} + +//------------------------------------------------------------------------------- +// Set the tempo of the beat tracker +void BTrack :: settempo(float 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/((((float) framesize)/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(((float) new_bperiod)/2); +} + + +//------------------------------------------------------------------------------- +// fix tempo to roughly around some value +void BTrack :: fixtempo(float 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; +} + +//------------------------------------------------------------------------------- +// do not fix the tempo anymore +void BTrack :: unfixtempo() +{ + // set the tempo fix flag + tempofix = 0; +} + +//------------------------------------------------------------------------------- +// Convert detection function from N samples to 512 +void BTrack :: dfconvert() +{ + float output[512]; + + 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 = dfbuffer; + 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] = src_data.data_out[i]; + } +} + +//------------------------------------------------------------------------------- +// To calculate the current tempo expressed as the beat period in detection function samples +void BTrack :: calcTempo() +{ + // adaptive threshold on input + adapt_thresh(df512,512); + + // calculate auto-correlation function of detection function + acf_bal(df512); + + // calculate output of comb filterbank + getrcfoutput(); + + + // adaptive threshold on rcf + adapt_thresh(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 / ((float) ((2*i)+80))); + t_index2 = (int) round(p_fact / ((float) ((4*i)+160))); + + + t_obs[i] = rcf[t_index-1] + rcf[t_index2-1]; + } + + + float maxval; + float maxind; + float 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]; + } + + + normalise(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]; + } + + bperiod = round((60.0*44100.0)/(((2*maxind)+80)*((float) framesize))); + + if (bperiod > 0) + { + est_tempo = 60.0/((((float) framesize) / 44100.0)*bperiod); + } + + //cout << bperiod << endl; +} + +//------------------------------------------------------------------------------- +// calculates an adaptive threshold which is used to remove low level energy from detection function and emphasise peaks +void BTrack :: adapt_thresh(float x[],int N) +{ + //int N = 512; // length of df + int i = 0; + int k,t = 0; + float x_thresh[N]; + + int p_post = 7; + int p_pre = 8; + + t = 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 = min((i+p_pre),N); + x_thresh[i] = mean_array(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] = mean_array(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 = max((i-p_post),1); + x_thresh[i] = mean_array(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; + } + } +} + +//------------------------------------------------------------------------------- +// returns the output of the comb filter +void BTrack :: getrcfoutput() +{ + 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 + } + } + } +} + +//------------------------------------------------------------------------------- +// calculates the balanced autocorrelation of the smoothed detection function +void BTrack :: acf_bal(float df_thresh[]) +{ + int l, n = 0; + float 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 + } +} + + +//------------------------------------------------------------------------------- +// calculates the mean of values in an array from index locations [start,end] +float BTrack :: mean_array(float array[],int start,int end) +{ + int i; + float sum = 0; + int length = end - start + 1; + + // find sum + for (i = start;i < end+1;i++) + { + sum = sum + array[i]; + } + + return sum / length; // average and return +} + +//------------------------------------------------------------------------------- +// normalise the array +void BTrack :: normalise(float 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; + } + } +} + +//------------------------------------------------------------------------------- +// plot contents of detection function buffer +void BTrack :: plotdfbuffer() +{ + for (int i=0;i < dfbuffer_size;i++) + { + cout << dfbuffer[i] << endl; + } + + cout << "--------------------------------" << endl; +} + +//------------------------------------------------------------------------------- +// update the cumulative score +void BTrack :: updatecumscore(float df_sample) +{ + int start, end, winsize; + float max; + + start = dfbuffer_size - round(2*bperiod); + end = dfbuffer_size - round(bperiod/2); + winsize = end-start+1; + + float w1[winsize]; + float v = -2*bperiod; + float wcumscore; + + + // create window + for (int i = 0;i < winsize;i++) + { + w1[i] = exp((-1*pow(tightness*log(-v/bperiod),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; + +} + +//------------------------------------------------------------------------------- +// plot contents of detection function buffer +void BTrack :: predictbeat() +{ + int winsize = (int) bperiod; + float fcumscore[dfbuffer_size + winsize]; + float w2[winsize]; + // copy cumscore to first part of fcumscore + for (int i = 0;i < dfbuffer_size;i++) + { + fcumscore[i] = cumscore[i]; + } + + // create future window + float v = 1; + for (int i = 0;i < winsize;i++) + { + w2[i] = exp((-1*pow((v - (bperiod/2)),2)) / (2*pow((bperiod/2) ,2))); + v++; + } + + // create past window + v = -2*bperiod; + int start = dfbuffer_size - round(2*bperiod); + int end = dfbuffer_size - round(bperiod/2); + int pastwinsize = end-start+1; + float w1[pastwinsize]; + + for (int i = 0;i < pastwinsize;i++) + { + w1[i] = exp((-1*pow(tightness*log(-v/bperiod),2))/2); + v = v+1; + } + + + + // calculate future cumulative score + float max; + int n; + float wcumscore; + for (int i = dfbuffer_size;i < (dfbuffer_size+winsize);i++) + { + start = i - round(2*bperiod); + end = i - round(bperiod/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 beat + beat = beat; + + // set next prediction time + m0 = beat+round(bperiod/2); + + +} \ No newline at end of file