Mercurial > hg > bayesian-drum-tracker
diff src/BayesDrumTracker.cpp @ 0:0f9165f96bdb
started drum tracker project svn
author | Andrew N Robertson <andrew.robertson@eecs.qmul.ac.uk> |
---|---|
date | Tue, 30 Aug 2011 20:16:35 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/BayesDrumTracker.cpp Tue Aug 30 20:16:35 2011 +0100 @@ -0,0 +1,625 @@ +/* + * BayesDrumTracker.cpp + * bayesianTempoInitialiser5 + * + * Created by Andrew on 14/07/2011. + * Copyright 2011 QMUL. All rights reserved. + * + */ + +#include "BayesDrumTracker.h" +#define OUTPORT 12346 +#define HOST "localhost" + + +//beatCorrection process indicates how the phase is changing from max + + +BayesDrumTracker::BayesDrumTracker(){ + + initialiseTracker(); + sender.setup( HOST, OUTPORT ); +} + + +BayesDrumTracker::~BayesDrumTracker(){} + +void BayesDrumTracker::initialiseTracker(){ + + beatDistribution.initialiseArray(); + tempoDistribution.initialiseArray(); + beatTimes.lastBeatTime = 0; + correctionFactor = 0.5; + + tempoDistribution.likelihoodStdDev = ARRAY_SIZE / 32; + // tempoDistribution.likelihoodNoise = 0.96; + tempoDistribution.likelihoodNoise = 0.7; + tempoDistribution.setGaussianPrior(ARRAY_SIZE/2, ARRAY_SIZE/1);//wide + + beatDistribution.likelihoodStdDev = ARRAY_SIZE / 32; + beatDistribution.likelihoodNoise = 0.56; + beatDistribution.setGaussianPrior(ARRAY_SIZE/2, ARRAY_SIZE/1); + + + tempoMinimum = 180; + tempoMaximum = 400; + posteriorMaximum = 0.1; + + adaptiveStandardDeviationMode = false; + setDistributionOnStartTempo = true; + + setBeatToNowTime = ofGetElapsedTimeMillis(); + recentClickTime = ofGetElapsedTimeMillis(); + + resetParameters(); + //check what we can delete above SINCE RESET CALLED + +} + + +void BayesDrumTracker::resetParameters(){ + + beatTimes.startIndex = 0; + beatTimes.lastBeatTime = 0; + maxPhase = 0; + posteriorMaximum = 0.1; + + accompanimentStarted = false; + + tempoDistribution.likelihoodNoise = 0.8; + tempoDistribution.setGaussianPrior(ARRAY_SIZE/2, ARRAY_SIZE/2);//wide + + beatDistribution.initialiseArray(); + tempoDistribution.initialiseArray(); + + tempoDistribution.calculateStandardDeviation(); + beatDistribution.calculateStandardDeviation(); + + tempoStdDev = tempoDistribution.standardDeviation; + + beatTimes.resetBeatTimeArray(); + +} + + + +void BayesDrumTracker::decayDistributions(){ + + if (accompanimentStarted){ + tempoDistribution.decayPosteriorWithGaussianNoise (); + beatDistribution.decayPosteriorWithGaussianNoise(); + } + else{ + if (tempoStdDev < 0.8 && beatDistribution.standardDeviation < 5) + accompanimentStarted = true; + + } +} + + +void BayesDrumTracker::setBeatDistribution(int beatPosition){ + switch (beatPosition){ + //early sixteenth is that the beat is a sixteenth earlier + case 0: + case 1: + case 11: + //i.e. these zones are interpreted as "on the beat" + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0; + beatDistribution.lateSixteenthNoteProportion = 0; + break; + //10 and 2 were here + + case 2: + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0.25;//was 0.3 in Bayesian8 + //i.e. a 25% chance it is early sixteenth - 75% that the beat actually lies here + beatDistribution.lateSixteenthNoteProportion = 0; + break; + + case 3: + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0.3;//was 0.4 in Bayesian8 //half chance it is early + beatDistribution.lateSixteenthNoteProportion = 0; + break; + + case 5: + case 6: + case 7: + beatDistribution.eighthNoteProportion = 0.3;//i.e. nearly half a chance we are on the 8th note + beatDistribution.earlySixteenthNoteProportion = 0; + beatDistribution.lateSixteenthNoteProportion = 0; + break; + + case 4: + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0.25;//was 0.3 in Bayesian8 + beatDistribution.lateSixteenthNoteProportion = 0.05;//was 0.2 in Bayesian8 + //chsanged to 0.2 and 0.1 then back + break; + + case 8: + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0.05;//was 0.2 in Bayesian8 + beatDistribution.lateSixteenthNoteProportion = 0.25;//was 0.3 in Bayesian8 + break; + + case 9: + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0; + beatDistribution.lateSixteenthNoteProportion = 0.35;//was 0.4 in Bayesian8 + break; + + case 10: + beatDistribution.eighthNoteProportion = 0; + beatDistribution.earlySixteenthNoteProportion = 0; + beatDistribution.lateSixteenthNoteProportion = 0.25;//was 0.2 in Bayesian8 + break; + + } + +} + +void BayesDrumTracker::newKickError(const float& error, const double& cpuEventTime, const string& onsetTypeString){ + + onsetType = onsetTypeString; + cpuBeatTime = cpuEventTime; + kickError = error; + + //printf("beat errror %f time %f\n", kickError, cpuBeatTime); + + while (kickError > 0.5){ + kickError -= 1; + } + + if (paused != true){ + updateTempoProcess(cpuBeatTime, onsetType); + //this also cross updates the distributions + beatTimes.beatMapTimeDifferences[beatTimes.beatSegment] = kickError*beatTimes.tatum; + }//end if paused + + + + if (onsetType == "kick"){ + if (accompanimentStarted) + beatDistribution.likelihoodNoise = 0.5; + else + beatDistribution.likelihoodNoise = 0.5; + // printf("kick %f ", cpuBeatTime); + } + else{ + //snare + if (accompanimentStarted) + beatDistribution.likelihoodNoise = 0.7; + else + beatDistribution.likelihoodNoise = 0.85; + // printf("snare %f ", cpuBeatTime); + } + + + setBeatDistribution(beatTimes.beatSegment%12); + + if (kickError <= 0.5 && kickError >= -0.5) + { + float beatStandardDeviation; + if (adaptiveStandardDeviationMode) + beatStandardDeviation = min((double)beatDistribution.likelihoodStdDev, beatDistribution.standardDeviation); + else + beatStandardDeviation = beatDistribution.likelihoodStdDev; + + + beatDistribution.resetPrior(); + beatDistribution.setGaussianLikelihoodForBeats((ARRAY_SIZE/2)+(kickError*ARRAY_SIZE), beatStandardDeviation); + beatDistribution.calculatePosterior(); + beatDistribution.renormalisePosterior(); + + sendMaxPhase(); + + beatDistribution.calculateStandardDeviation(); + + }//end if error < 0.5 + + + + if (beatTimes.beatSegment % 12 == 6){ + kickString = "Kick "; + kickString += ofToString(kickError); + kickString += " ERROR "; + kickString += ofToString(kickError, 2); + kickString += " at time diff "; + kickString += ofToString(cpuBeatTime - beatTimes.lastClickTime, 2); + kickString += " index "; + kickString += ofToString(beatTimes.lastClickIndex, 2); + kickString += " TYPE "; + kickString += ofToString(beatTimes.beatSegment%12); + kickString += " Time diff "; + kickString += ofToString(beatTimes.timeDifference, 2); + } + + +} + + + +void BayesDrumTracker::startTatum(const float& startTatum){ + beatTimes.tatum = startTatum; + + if (setDistributionOnStartTempo){ + beatDistribution.setGaussianPosterior(ARRAY_SIZE/2, 8); + tempoDistribution.setGaussianPosterior(ARRAY_SIZE/2, 12); + float tmpIndex; + tmpIndex = ( (beatTimes.tatum - ((tempoMinimum+tempoMaximum)/2) ) * ARRAY_SIZE)/(tempoMaximum - tempoMinimum); + tempoDistribution.translateDistribution(tmpIndex); + + sendMaxTempo(); + } +} + + +void BayesDrumTracker::setUniformTempo(){ + for (int i = 0;i < ARRAY_SIZE;i++) + tempoDistribution.posterior[i] = (float)1/ARRAY_SIZE; +} + + +void BayesDrumTracker::setUniformPhase(){ + for (int i = 0;i < ARRAY_SIZE;i++) + beatDistribution.posterior[i] = (float)1/ARRAY_SIZE; +} + +void BayesDrumTracker::setBeatNow(const double& beatTime){ + for (int i = 0;i < ARRAY_SIZE;i++) + beatDistribution.prior[i] = (float)1/ARRAY_SIZE; + + setBeatToNowTime = ofGetElapsedTimeMillis(); + double difference = (setBeatToNowTime - recentClickTime); + printf("SET BEAT TO NOW %f vs %f :: diff %f tatum %f :: ", setBeatToNowTime, recentClickTime, difference, beatTimes.tatum ); + + double beatTimeToUse = 0; + + if (difference < beatTimes.tatum)//tatum is the eighth note time + beatTimeToUse = difference/ (2*beatTimes.tatum); + else + beatTimeToUse = -1*(2*beatTimes.tatum - difference) / (2*beatTimes.tatum); + + printf("sending %f \n", beatTimeToUse); + + beatDistribution.setGaussianLikelihoodForBeats((ARRAY_SIZE/2)+(beatTimeToUse*ARRAY_SIZE), 2); + beatDistribution.calculatePosterior(); + beatDistribution.renormalisePosterior(); + + sendMaxPhase(); + + +} + + +void BayesDrumTracker::newBeat(int& beatIndex){ + ofxOscMessage m; + m.setAddress( "/beatInfo" ); + + m.addFloatArg(beatTimes.tatum); + m.addFloatArg(maxPhase); + + beatTimes.tatum = maxTempo; + printf("BEAT INFO %f, %f\n", beatTimes.tatum, maxPhase); + + sender.sendMessage( m ); + +} + +void BayesDrumTracker::sendMaxTempo(){ + ofxOscMessage m; + m.setAddress( "/tempo" ); + + //maxTempo = tempoDistribution.maximumIndex * (tempoMaximum - tempoMinimum) / ARRAY_SIZE; + //would be introduced new in bayesian8 + maxTempo = tempoDistribution.getIntegratedEstimateIndex() * (tempoMaximum - tempoMinimum) / ARRAY_SIZE; + maxTempo += tempoMinimum; + + beatTimes.tatum = maxTempo; + printf("SEND TATUM %f\n", beatTimes.tatum); + + m.addFloatArg( maxTempo ); + sender.sendMessage( m ); + + //printf("max tempo %f\n", maxTempo); + +} + +void BayesDrumTracker::sendMaxPhase(){ + + + // maxPhase = (beatDistribution.maximumIndex - (ARRAY_SIZE/2)) / ARRAY_SIZE; + maxPhase = (beatDistribution.getIntegratedEstimateIndex() - (ARRAY_SIZE/2)) / ARRAY_SIZE; +// printf("\nphase index %f :: %f\n", (float) beatDistribution.integratedEstimate , maxPhase); + ofxOscMessage m; + m.setAddress( "/phase" ); + m.addFloatArg( maxPhase ); + sender.sendMessage( m ); + + //beatCorrection = maxPhase * beatTimes.tatum / 4; +} + + +void BayesDrumTracker::setNewClickIndex(const int& clickIndex, const float& clickTime){ + + beatTimes.lastClickIndex = clickIndex; + beatTimes.lastClickTime = clickTime; + + int clickIndexToUse = clickIndex % 16; + beatTimes.clickIndex = clickIndex; + beatTimes.clickNumber[clickIndexToUse] = clickIndex; + beatTimes.clickTimes[clickIndexToUse] = clickTime; + + recentClickTime = ofGetElapsedTimeMillis(); + +} + + +void BayesDrumTracker::doBeatCorrection(const float& beatCorrFloat){ + beatCorrection = beatCorrFloat; + correctBeatBy = round(correctionFactor * beatCorrection * ARRAY_SIZE / (2 * beatTimes.tatum)); + beatDistribution.translateDistribution(-1 * correctBeatBy); +} + + +bool BayesDrumTracker::filterBeatTime(double newBeatTime){ + bool newBeatFound = false; + if ((newBeatTime - beatTimes.lastBeatTime) > 20 || beatTimes.lastBeatTime == 0){ + + crossUpdateArrays((float)(newBeatTime - beatTimes.lastBeatTime)); + beatTimes.lastBeatTime = newBeatTime; + newBeatFound = true; + } + return newBeatFound; +} + +void BayesDrumTracker::crossUpdateArrays(float timeInterval){ + + int finalBeatIndex, tmpTempoIndex, startBeatIndex; + //finalBeat has contribution from BEAT[finalBeat + INT.k] * TEMPO[Max_tempo + k] where INT = INTERVAL + float interval; + interval = timeInterval / maxTempo;//beatTimes.tatum; + tempoDistribution.resetMaximumPosterior(); + beatDistribution.resetMaximumPosterior(); + + + int tmpBeatIndex; + //&& interval > 0.8 idea? + if (timeInterval > 0 && timeInterval < 12000 ){//need between 0 and 12 seconds only to update + + for (tmpBeatIndex = 0;tmpBeatIndex < ARRAY_SIZE;tmpBeatIndex++){ + + tmpArray[tmpBeatIndex] = 0; + float minusMsecToMakeUp = beatIndexToMsec(tmpBeatIndex) / interval; + float plusMsecToMakeUp = beatIndexToMsec(ARRAY_SIZE - tmpBeatIndex) / interval; + float convertMsecToTempoIndex = ARRAY_SIZE / (tempoMaximum - tempoMinimum) ; + + + int minTempoIndex = -1 * (int)(minusMsecToMakeUp * convertMsecToTempoIndex); + int maxTempoIndex = (int)(plusMsecToMakeUp * convertMsecToTempoIndex); + + + if (tmpBeatIndex == beatDistribution.maximumIndex){ + // minTmpDebug = tempoDistribution.maximumIndex + minTempoIndex; + // maxTmpDebug = tempoDistribution.maximumIndex + maxTempoIndex; + debugArray[0] = beatDistribution.maximumIndex;// + debugArray[1] = timeInterval; + debugArray[2] = interval;//beatDistribution.maximumIndex; + debugArray[3] = tempoDistribution.maximumIndex; + } + + for (tmpTempoIndex = minTempoIndex;tmpTempoIndex <= maxTempoIndex;tmpTempoIndex++){ + + if ((tempoDistribution.maximumIndex + tmpTempoIndex) >= 0 + && (tempoDistribution.maximumIndex + tmpTempoIndex) < ARRAY_SIZE + && (tmpBeatIndex - (int)(interval*tmpTempoIndex)) >= 0 + && (tmpBeatIndex - (int)(interval*tmpTempoIndex))< ARRAY_SIZE){ + tmpArray[tmpBeatIndex] += beatDistribution.posterior[tmpBeatIndex - (int)(interval*tmpTempoIndex)] * tempoDistribution.posterior[(int)tempoDistribution.maximumIndex + tmpTempoIndex]; + } + }//end for tmpTmepo + + + + } + + float tmpFloat; + for (tmpBeatIndex = 0;tmpBeatIndex < ARRAY_SIZE;tmpBeatIndex++){ + //debug - dont actually update:: + + tmpFloat = beatDistribution.posterior[tmpBeatIndex]; + beatDistribution.posterior[tmpBeatIndex] = tmpArray[tmpBeatIndex]; + tmpArray[tmpBeatIndex] = tmpFloat; + } + beatDistribution.renormaliseArray(&beatDistribution.posterior[0], ARRAY_SIZE); + + } //end if + + +} + + +void BayesDrumTracker::updateTempoProcess(const double& cpuTime, const string& onsetDescription){ + + if (filterBeatTime(cpuTime) == true){ + //checks for no repeat + + if (onsetDescription == "kick") + beatTimes.addBeatTime(cpuTime, 1); + else + beatTimes.addBeatTime(cpuTime, 2); + + + //recalculate the distribution + int altIndex = 0; + + tempoDataString = "Tatum :"; + tempoDataString += ofToString(beatTimes.tatum, 2); + tempoDataString += " BPM "; + tempoDataString += ofToString((double)30000/beatTimes.tatum, 2); + + timeString = "Last BEAT "; + timeString += ofToString(beatTimes.lastBeatTime); + timeString += " CLICK "; + timeString += ofToString(beatTimes.lastClickTime); + timeString += " DIFDF "; + timeString += ofToString(beatTimes.timeDifference); + timeString += " segment "; + timeString += ofToString(beatTimes.beatSegment); + + + for (altIndex = 0;altIndex< 16;altIndex++){ + tempoInterval = beatTimes.intervalDifferences[beatTimes.index][altIndex]; + integerMultipleOfTatum = beatTimes.relativeIntervals[altIndex][1]; + + + ///NEW VERSION + tempoUpdateStrings[altIndex] = ""; + double timeInterval = beatTimes.beatTimes[beatTimes.index] - beatTimes.beatTimes[altIndex]; + //raw time difference + beatTimes.intervalDifferences[beatTimes.index][altIndex] = 0; + beatTimes.intervalUsed[beatTimes.index][altIndex] = false; + + if (onsetType == "kick") + beatTimes.OnsetIsKick[beatTimes.index] = true; + else + beatTimes.OnsetIsKick[beatTimes.index] = false; + + + + if (!accompanimentStarted){ + //if we need to find tempo and start use this method + //we have 'started' once std dev is sufficiently low + + updateTempoIfWithinRange(timeInterval);//taken as being the tatum interval + + + + for (int i = 1;i <= 4;i++){ + //we test the main beats and the two bar (16 tatum intervals) + + double testInterval = timeInterval / 2*i;//pow(2, i);//pow(2.0, i); + + if (updateTempoIfWithinRange(testInterval)){ + //printf("test time %f, beats %i\n", testInterval, i); + + beatTimes.intervalUsed[beatTimes.index][altIndex] = true; + beatTimes.intervalDifferences[beatTimes.index][altIndex] = testInterval; + //xx what if two within range here? + + tempoUpdateStrings[altIndex] = "Tempo Updates ("; + tempoUpdateStrings[altIndex] += ofToString(beatTimes.index, 0); + tempoUpdateStrings[altIndex] += ") : ["; + tempoUpdateStrings[altIndex] += ofToString(altIndex); + tempoUpdateStrings[altIndex] += "]] : "; + tempoUpdateStrings[altIndex] += ofToString(timeInterval); + tempoUpdateStrings[altIndex] += ", ioi:"; + tempoUpdateStrings[altIndex] += ofToString(i); + //tempoUpdateStrings[altIndex] += ""; + + } + + } + + double testInterval = timeInterval / 16;//pow(2, i);//pow(2.0, i); + if (updateTempoIfWithinRange(testInterval)){ + beatTimes.intervalUsed[beatTimes.index][altIndex] = true; + beatTimes.intervalDifferences[beatTimes.index][altIndex] = testInterval; + } + + }else{ + //OLD VERSON + //THIS USES THE CURRENT TEMPO ESTIMATE TO DECIDE WHAT THE BEST INTERVAL IS + //&& integerMultipleOfTatum % 2 == 0 removed below XXX put back + if (altIndex != beatTimes.index && integerMultipleOfTatum < 17 + && integerMultipleOfTatum > 0 && beatTimes.startIndex > 8//beattimes.index > 8 - the start + && integerMultipleOfTatum%2 == 0){//mod 2 - i.e. proper beat intervals only + + double testInterval = timeInterval / integerMultipleOfTatum; + + if (updateTempoIfWithinRange(testInterval)){ + + beatTimes.intervalUsed[beatTimes.index][altIndex] = true; + beatTimes.intervalDifferences[beatTimes.index][altIndex] = testInterval; + + if (paused == false){ + tempoUpdateStrings[altIndex] = "Tempo Updates : ("; + tempoUpdateStrings[altIndex] += ofToString(beatTimes.index, 0); + tempoUpdateStrings[altIndex] += ") : ["; + tempoUpdateStrings[altIndex] += ofToString(altIndex, 0); + tempoUpdateStrings[altIndex] += "] :: "; + tempoUpdateStrings[altIndex] += ofToString(integerMultipleOfTatum); + tempoUpdateStrings[altIndex] += " intervals :: "; + tempoUpdateStrings[altIndex] += ofToString(tempoInterval); + tempoUpdateStrings[altIndex] += " ms."; + // tempoUpdateStrings[altIndex] += ", ioi:"; + + // tempoUpdateStrings[altIndex] += ofToString(integerMultipleOfTatum); + + + + + }//end if not paused + + + }//end if good interval to update + + }//end if not same index etc + + + } + + + + }//end for all intervals + + sendMaxTempo(); + }//end if new beat time + double tempoEstimate = tempoDistribution.getIntegratedEstimateIndex(); + tempoDistribution.calculateStandardDeviation(); + tempoStdDev = tempoDistribution.standardDeviation; + +} + + +bool BayesDrumTracker::updateTempoIfWithinRange(double timeInterval){ + + bool updated = false; + + if (timeInterval > tempoMinimum && timeInterval < tempoMaximum ){ + calculateTempoUpdate(timeInterval); + updated = true; + } + + return updated; +} + + +void BayesDrumTracker::calculateTempoUpdate(double tempoInterval){ + + + tempoDistribution.resetPrior(); + //need to relook at likelihood for the tempo distribution - not the same as.... + tempoDistribution.setGaussianLikelihood(ARRAY_SIZE * (tempoInterval-tempoMinimum)/(tempoMaximum - tempoMinimum), tempoDistribution.likelihoodStdDev); + tempoDistribution.calculatePosterior(); + tempoDistribution.renormalisePosterior(); + + //did take pic of screen here - see initialiser4 +} + + +float BayesDrumTracker::tempoIndexToMsec(const int& index){ + float msec; + msec = index * (tempoMaximum - tempoMinimum) / ARRAY_SIZE; + msec += tempoMinimum; + return msec; +} + +float BayesDrumTracker::beatIndexToMsec(const int& index){ + float msec; + msec = index * maxTempo / ARRAY_SIZE; + msec += tempoMinimum; + return msec; +} +