view newOFsrc/BayesDrumTracker.cpp @ 11:23ff520d28ff

Changed the likelihood to noise - now 0.6 likelihood, added in a reste for the data output
author Andrew N Robertson <andrew.robertson@eecs.qmul.ac.uk>
date Fri, 09 Mar 2012 20:41:32 +0000
parents d880f7f29fbe
children
line wrap: on
line source
/*
 *  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 );
	generalLikelihoodToNoiseRatio = 0.6;
}


BayesDrumTracker::~BayesDrumTracker(){}

void BayesDrumTracker::initialiseTracker(){
	
	beatDistribution.initialiseArray();
	tempoDistribution.initialiseArray();
	beatTimes.lastBeatTime = 0;
	correctionFactor = 0.5;
	
	tempoDistribution.likelihoodStdDev =  arraySize / 32;
	//	tempoDistribution.likelihoodNoise = 0.96;
	tempoDistribution.likelihoodNoise = 0.7;	
	tempoDistribution.setGaussianPrior(arraySize/2, arraySize/1);//wide 
	
	beatDistribution.likelihoodStdDev = arraySize  / 32;
	beatDistribution.likelihoodNoise = 0.56;	
	beatDistribution.setGaussianPrior(arraySize/2, arraySize/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(arraySize/2, arraySize/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){
			//beat position is in twelfth divisions of a 'beat' so position 6 is an eighth note out etc
			
			//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
	
	
	beatDistribution.likelihoodNoise = 1 - generalLikelihoodToNoiseRatio;
/*
	if (onsetType == "kick"){
		if (accompanimentStarted)
			beatDistribution.likelihoodNoise = generalLikelihoodtoNoiseRatio;
		else
			beatDistribution.likelihoodNoise = 0.5;		
		//	printf("kick %f ", cpuBeatTime);
	}
	else{
		//snare
		if (accompanimentStarted)//was 0.7 and else 0.85
			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();//prior is old posterior	
		beatDistribution.setGaussianLikelihoodForBeats((arraySize/2)+(kickError*arraySize), 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(arraySize/2, 8);
			   tempoDistribution.setGaussianPosterior(arraySize/2, 12);
			   float tmpIndex;
			   tmpIndex = ( (beatTimes.tatum - ((tempoMinimum+tempoMaximum)/2) ) * arraySize)/(tempoMaximum - tempoMinimum);
			   tempoDistribution.translateDistribution(tmpIndex);
			   
			   sendMaxTempo();
		   }
}


void BayesDrumTracker::setUniformTempo(){
	for (int i = 0;i < arraySize;i++)
		tempoDistribution.posterior[i] = (float)1/arraySize;
}


void BayesDrumTracker::setUniformPhase(){
	for (int i = 0;i < arraySize;i++)
		beatDistribution.posterior[i] = (float)1/arraySize;
}

void BayesDrumTracker::setBeatNow(const double& beatTime){
	for (int i = 0;i < arraySize;i++)
		beatDistribution.prior[i] = (float)1/arraySize;
	
	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((arraySize/2)+(beatTimeToUse*arraySize), 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) /  arraySize;
	//would be introduced new in bayesian8
	maxTempo = tempoDistribution.getIntegratedEstimateIndex() * (tempoMaximum - tempoMinimum) /  arraySize;
	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 - (arraySize/2)) / arraySize;
	maxPhase = (beatDistribution.getIntegratedEstimateIndex() - (arraySize/2)) / arraySize;
//	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 * arraySize / (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 < arraySize;tmpBeatIndex++){
			
			tmpArray[tmpBeatIndex] = 0;
			float minusMsecToMakeUp =  beatIndexToMsec(tmpBeatIndex) / interval;
			float plusMsecToMakeUp =  beatIndexToMsec(arraySize - tmpBeatIndex) / interval;
			float convertMsecToTempoIndex = arraySize / (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) < arraySize 
					&& (tmpBeatIndex - (int)(interval*tmpTempoIndex)) >= 0 
					&& (tmpBeatIndex - (int)(interval*tmpTempoIndex))< arraySize){	
					tmpArray[tmpBeatIndex] += beatDistribution.posterior[tmpBeatIndex - (int)(interval*tmpTempoIndex)] * tempoDistribution.posterior[(int)tempoDistribution.maximumIndex + tmpTempoIndex];
				}
			}//end for tmpTmepo
			
			
			
		}
		
		float tmpFloat;
		for (tmpBeatIndex = 0;tmpBeatIndex < arraySize;tmpBeatIndex++){
			//debug - dont actually update::
			
			tmpFloat = 	beatDistribution.posterior[tmpBeatIndex];
			beatDistribution.posterior[tmpBeatIndex] = tmpArray[tmpBeatIndex];
			tmpArray[tmpBeatIndex] = tmpFloat;
		}
		beatDistribution.renormaliseArray(&beatDistribution.posterior[0], arraySize);
		
	}	//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(arraySize * (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) / arraySize;
	msec += tempoMinimum;
	return msec;
}

float BayesDrumTracker::beatIndexToMsec(const int& index){
	float msec;
	msec = index * maxTempo / arraySize;
	msec += tempoMinimum;
	return msec;
}