view src/midiEventHolder.cpp @ 5:195907bb8bb7

added purple where notes have been seen - lets you see what updates have been used. Also the chopping of midi files to the beginning was introduced recently, so when they load, you chop any white space at the beginning, then use first note to launch.
author Andrew N Robertson <andrew.robertson@eecs.qmul.ac.uk>
date Fri, 19 Aug 2011 16:38:30 +0100
parents 4a8e6a6cd224
children 6f5836d432ca
line wrap: on
line source
/*
 *  midiEventHolder.cpp
 *  midiCannamReader3
 *
 *  Created by Andrew on 19/07/2011.
 *  Copyright 2011 QMUL. All rights reserved.
 *
 */
//hello

#include "midiEventHolder.h"

midiEventHolder::midiEventHolder(){
//	recordedNoteOnIndex = 0;
	
	width = ofGetWidth();
	height = ofGetHeight();
	screenWidth= &width;
	screenHeight = &height;
	
	ticksPerScreen = 4000;
	tickLocation = 0;
	pulsesPerQuarternote = 240;
	noteArrayIndex = 0;
	noteMinimum = 30;
	noteMaximum = 96;
	
	minimumMatchSpeed = 0.0;
	maximumMatchSpeed = 2.0;

	likelihoodWidth = 100;
	likelihoodToNoiseRatio = 0.02;
	
	bayesStruct.speedLikelihoodNoise = 0.02;//was 0.05
	bayesStruct.speedDecayWidth = 20;
	bayesStruct.speedDecayAmount = 10;
	
	speedPriorValue = 1.0;
	
	matchWindowWidth = 8000;//window size for matching in ms 
	
	bayesStruct.resetSize(matchWindowWidth);
	bayesStruct.setPositionDistributionScalar(1);
	
	bayesStruct.resetSpeedSize(200);
	bayesStruct.setRelativeSpeedScalar(0.01);
	bayesStruct.relativeSpeedPrior.getMaximum();
	//bayesStruct.simpleExample();
	
	speedPriorValue = 1.0;
	noteHeight = (*screenHeight) / (float)(noteMaximum - noteMinimum);
	
	printf("lookup index %f value %f\n", bayesStruct.prior.getLookupIndex(100, 30., 10.0), bayesStruct.prior.gaussianLookupTable[(int)bayesStruct.prior.getLookupIndex(100, 30., 10.0)]);
}



void midiEventHolder::reset(){
	//called when we start playing
	
	noteArrayIndex = 0;
	tickLocation = 0;
	lastPeriodUpdateTime = ofGetElapsedTimeMillis();
	bayesStruct.lastEventTime = ofGetElapsedTimeMillis();
	numberOfScreensIn = 0;
//	recordedNoteOnIndex = 0;
	bayesStruct.setNewDistributionOffsets(0);
	bayesStruct.posterior.offset = 0;
	
	playedEventTimes.clear();
	playedNoteOnMatrix.clear();
	matchMatrix.clear();
	bestMatchIndex = 0;
	
	bayesStruct.resetSpeedToOne();
	bayesStruct.setSpeedPrior(speedPriorValue);
	setMatchedNotesBackToFalse();
}

void midiEventHolder::setMatchedNotesBackToFalse(){
	for (int i = 0;i < noteOnMatches.size();i++)
		noteOnMatches[i] = false;
}

void midiEventHolder::clearAllEvents(){
	recordedNoteOnMatrix.clear();
	matchesFound.clear();
	noteOnMatches.clear();
	recordedEventTimes.clear();
	
	//played events:
	playedEventTimes.clear();
	playedNoteOnMatrix.clear();
	matchMatrix.clear();
}

void midiEventHolder::printNotes(){
	printf("RECORDED MATRIX");
	for (int i = 0;i < recordedNoteOnMatrix.size();i++){
		printf("%i :: %i @ %f\n", recordedNoteOnMatrix[i][0], recordedNoteOnMatrix[i][1], recordedEventTimes[i]);
	}
}


double midiEventHolder::getEventTimeTicks(double millis){
	return (millis * pulsesPerQuarternote / period);
}

double midiEventHolder::getEventTimeMillis(double ticks){
	return (period * ticks / (double) pulsesPerQuarternote);
}

void midiEventHolder::newNoteOnEvent(int pitch, int velocity, double timePlayed){
	
	//MOVE INTO BAYESSTRUCT?? XXX
	//bayesStruct.copyPriorToPosterior();
	//why was this here??
	bayesStruct.prior.copyFromDynamicVector(bayesStruct.posterior);//try the otehr way
	//bayesStruct.copyPriorToPosterior();
	//need to get new MAP position and set the offset of the arrays
	//currently bestEstimate is the approx for the new MAP position
	
	
	//add the new event to our played information matrix
	IntVector v;
	v.push_back(pitch);
	v.push_back(velocity);
	playedNoteOnMatrix.push_back(v);

	
	//would update the arrays at this point to show where out current location (phase) and tempo is.
//	double timeNow = ofGetElapsedTimeMillis() - startTime;
	double timeNow = timePlayed;// - startTime;
	recentNoteOnTime = timePlayed;
	
//	printf("Max time %f OF time %f \n", timePlayed, timeNow);
	
	playedEventTimes.push_back(timePlayed);
	
//	double timeDifference = ofGetElapsedTimeMillis() - bayesStruct.lastEventTime; 
	double timeDifference = timePlayed - bayesStruct.lastEventTime; 
	
	printf("note %i played at %f and last event %f\n", pitch, timePlayed, bayesStruct.lastEventTime);
	//addnoise to the tempo distribution
	//bayesStruct.decaySpeedDistribution(timeDifference);
	if (timeDifference > 50){
	bayesStruct.addGaussianNoiseToSpeedPosterior(timeDifference * 10 / 100.);
//	bayesStruct.addTriangularNoiseToSpeedPosterior(timeDifference * 10 / 100.);
	}
	
	bayesStruct.updateTmpBestEstimate(timeDifference);// debug - didnt work	bayesStruct.bestEstimate = bayesStruct.tmpBestEstimate;
	bayesStruct.updateBestEstimate();
	bayesStruct.lastBestEstimateUpdateTime = ofGetElapsedTimeMillis();
	
//	double newMAPestimateTime = bayesStruct.posterior.getIndexInRealTerms(bayesStruct.posterior.MAPestimate);
	//was offset + bayesStruct.posterior.MAPestimate; but this doesnt include scalar to convert to millis

	timeString = "Pitch:"+ofToString(pitch);
	timeString += ", time now:"+ofToString(timeNow, 1);
	timeString += "  TD "+ofToString(timeDifference, 1);
	timeString += "  offset "+ofToString(bayesStruct.posterior.offset , 0);
	timeString += " map Est: "+ofToString(bayesStruct.posterior.MAPestimate, 0); 
//	timeString += " Previous time" + ofToString(newMAPestimateTime,0);
	timeString += " speedMap "+ofToString(bayesStruct.relativeSpeedPosterior.MAPestimate, 2);
	timeString += " :: "+ofToString(bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate), 2);
	
//	newMAPestimateTime += (timeDifference * bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate));
//	timeString += " :  Predicted MAP time" + ofToString(newMAPestimateTime,0);

	//then we recalculate the window start based on MAP being central
	//then we do the matches on these and the likelihood on these.
	
	bayesStruct.setNewDistributionOffsets(max(0., bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2)));
//	bayesStruct.prior.offset = max(0.,newMAPestimateTime - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2));
	
	timeString += " \n :  new offset " + ofToString(bayesStruct.prior.offset , 0);
	timeString += " \n best estimate "+ofToString(bayesStruct.bestEstimate, 1);
	timeString += " map "+ofToString(bayesStruct.relativeSpeedPosterior.MAPestimate, 1);
	timeString += " rel speed "+ofToString(bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate), 1);
	
	
	//be able to draw the prior in correct location relative to the midi notes
	//this calculates the cross update of all possible speeds and all possible positions
	bayesStruct.crossUpdateArrays(bayesStruct.posterior, bayesStruct.relativeSpeedPosterior, timeDifference);

	
	timeString += " new OFF "+ofToString(bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2), 1);
	timeString += " notearrayindex "+ofToString(noteArrayIndex, 0);
	//when this is off teh screen there is a problem somehow XXX
	bayesStruct.posterior.offset = max(0., bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2));
//	bayesStruct.prior.offset = max(0., bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2));
	//trying to switch to prior
	
	//bayesStruct.lastEventTime = ofGetElapsedTimeMillis();
	bayesStruct.lastEventTime = timePlayed;
	
	//do the cross update to find current posterior for location
//	totalConfidence= 0;
	int numberOfMatchesFound = findLocalMatches(pitch);
	setMatchLikelihoods(numberOfMatchesFound);
	bayesStruct.calculatePosterior();
	
	//having found matches we have matches for new note and matches for previous notes
	findLocalTempoPairs();
	
	//bayesStruct.addGaussianNoiseToSpeedPosterior(10);
	 
}

int midiEventHolder::findLocalMatches(int notePitch){

	//here we find the matches to the new note within appropriate range
		
	matchString = "";
	
	windowStartTime = max(0.0,(bayesStruct.bestEstimate - matchWindowWidth/2));//was playPositionInMillis
	int numberOfMatches = findMatch(notePitch, windowStartTime, windowStartTime + matchWindowWidth);

	
	matchString += " pitch: "+ofToString(notePitch)+" matches "+ofToString(numberOfMatches)+" win start "+ofToString(windowStartTime);	
	
	return numberOfMatches;
	
	
}


void midiEventHolder::setMatchLikelihoods(int numberOfMatches){
//reset the offset to match the prior
	bayesStruct.likelihood.offset = bayesStruct.prior.offset;
	bayesStruct.likelihood.zero();//set to zero
	
	double quantity = likelihoodToNoiseRatio / numberOfMatches;
	
	for (int i = 0;i < numberOfMatches && matchesFound[i] >= 0 && matchesFound[i] < recordedEventTimes.size();i++){
	//	printf("match times %i of %i::%f adding likelihood to %f\n", i, numberOfMatches, recordedEventTimes[matchesFound[i]], recordedEventTimes[matchesFound[i]] - bayesStruct.likelihood.offset);
		//this is the vent time since start of file
		if (recordedEventTimes[matchesFound[i]] - bayesStruct.likelihood.offset < bayesStruct.likelihood.arraySize){
	//		double confidenceMeasure = 0;
	//		if (totalConfidence > 0)
		//		confidenceMeasure =	bayesStruct.posterior.getValueAtMillis(recordedEventTimes[matchesFound[i]])/totalConfidence;
			
			bayesStruct.likelihood.addGaussianShape(recordedEventTimes[matchesFound[i]] - bayesStruct.likelihood.offset, likelihoodWidth,  quantity);//* confidenceMeasure
		}//end if
	}
	bayesStruct.likelihood.addConstant((1-likelihoodToNoiseRatio)/bayesStruct.likelihood.length);
}

int midiEventHolder::findMatch(const int& notePitch, const int& startTime, const int& endTime){

	matchesFound.clear();
	int startIndex = 0;
	
	if (recordedEventTimes.size() > 0){
	
		//get to the right range of events to check in
		while (startIndex < recordedEventTimes.size() && recordedEventTimes[startIndex] < startTime)
			startIndex++;
		   
		}

	double minimumConfidence = 0;
	while (startIndex < recordedEventTimes.size() && recordedEventTimes[startIndex] < endTime){
		if (recordedNoteOnMatrix[startIndex][1] == notePitch){
			matchesFound.push_back(startIndex);
			double eventConfidence = bayesStruct.posterior.getValueAtMillis(recordedEventTimes[startIndex]);
				if (eventConfidence > minimumConfidence){
					minimumConfidence = eventConfidence;
					bestMatchIndex = startIndex;
				}
			
			double confidence = bayesStruct.posterior.getValueAtMillis(mouseX);
	//																   recordedEventTimes[startIndex]);
			matchString += "["+ofToString(startIndex)+"] = "+ofToString(confidence, 3)+" .";
		}
		startIndex++;
	}
	

//	printf("%i MATCHES TO Note %i found\n", (int)matchesFound.size(), notePitch);
	int size = matchesFound.size();
		if (size > 0)
		noteOnMatches[bestMatchIndex] = true;
	
	IntVector v;
	v.push_back(size);
	for (int i = 0;i < matchesFound.size();i++)
		v.push_back(matchesFound[i]);
	
	matchMatrix.push_back(v);
	
	return size;
}

bool midiEventHolder::checkIfMatchedNote(const int& tmpIndex){
	for (int i = 0;i < matchesFound.size();i++){
	if (matchesFound[i] == tmpIndex)
		return true;
	}
	return false;
}



void midiEventHolder::findLocalTempoPairs(){

	int currentPlayedIndex = playedNoteOnMatrix.size()-1;
//	printf("played %i : %i, vel %i\n", currentPlayedIndex, playedNoteOnMatrix[currentPlayedIndex][0], playedNoteOnMatrix[currentPlayedIndex][1]);
//	printMatchesFound();
//	printMatchMatrix();
//	printf("possible notes \n");
	
	
	for (int i = 0;i < matchMatrix[currentPlayedIndex][0];i++){

		int recordedCurrentIndex = matchMatrix[currentPlayedIndex][i+1];
		
		int previousIndex = currentPlayedIndex-1;
		
		while (previousIndex >= 0 && playedEventTimes[previousIndex] + 2000 > playedEventTimes[currentPlayedIndex]) {
			double playedTimeDifference = playedEventTimes[currentPlayedIndex] - playedEventTimes[previousIndex];

			for (int k = 0;k < matchMatrix[previousIndex][0];k++){
				int recordedPreviousIndex = matchMatrix[previousIndex][k+1];
				


				double recordedTimeDifference = recordedEventTimes[recordedCurrentIndex] - recordedEventTimes[recordedPreviousIndex];

				
				//we want the speed of the recording relative to that of the playing live
				
				double speedRatio = recordedTimeDifference / playedTimeDifference;
				if (speedRatio <= maximumMatchSpeed && speedRatio >= minimumMatchSpeed){
						/*
						 printf("(%i)", matchMatrix[currentPlayedIndex][i+1]);
								printf("[%i] :: ", recordedPreviousIndex);
								printf(" rec{%f} vs play(%f) ", recordedTimeDifference, playedTimeDifference);
								printf("update on speed ratio %f\n", speedRatio);
						 */
					//	matchString += " speed: "+ofToString(speedRatio, 3);
				//	commented for debug
					
						 bayesStruct.updateTempoDistribution(speedRatio, 0.1);//second paramter is confidence in the match
			
				}
	//		printf("\n");	
			}
			
			previousIndex--;
		}//end while previousindex countdown
	}//end for loop through possible current matches
	
	//printf("current speed is %f\n", bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate));
}


void midiEventHolder::updatePlayPosition(){
	
	//in actual fact if we are changing the speed of the play position 
	//we will need to update this via the file
	
	double timeDifference = ofGetElapsedTimeMillis() - lastPeriodUpdateTime;
	//this is time diff in milliseconds
	//then we have 
	double quarterNoteIntervals = (timeDifference / period);
	tickLocation = quarterNoteIntervals * pulsesPerQuarternote; 
	
	playPositionInMillis = timeDifference;//based on updating from when we change period
	//this to be added
	
	bayesStruct.updateBestEstimate();
	
}


void midiEventHolder::drawFile(){
	//draws midi file on scrolling screen
	int size = recordedNoteOnMatrix.size();
	if (size > 0){
		
		numberOfScreensIn = floor(bayesStruct.bestEstimate / getEventTimeMillis(ticksPerScreen));//rpounds down on no screens in
		
	//	numberOfScreensIn = tickLocation / ticksPerScreen;//rounds down
		timeOffsetForScreen = getEventTimeMillis(numberOfScreensIn * ticksPerScreen);
		
		while (noteArrayIndex < recordedNoteOnMatrix.size() && tickLocation > recordedNoteOnMatrix[noteArrayIndex][0] )
			noteArrayIndex++;
		
		
		while (noteArrayIndex > 0 && noteArrayIndex < size && tickLocation < recordedNoteOnMatrix[noteArrayIndex][0])
			noteArrayIndex--;
		
		//need to start where we currently are in file
		int maxNoteIndexToPrint	= noteArrayIndex;
		int minNoteIndexToPrint = noteArrayIndex;
		
		while (maxNoteIndexToPrint < recordedNoteOnMatrix.size() && recordedNoteOnMatrix[maxNoteIndexToPrint][0] < (numberOfScreensIn+1)*ticksPerScreen )
			maxNoteIndexToPrint++;
		
		while (minNoteIndexToPrint > 0 && minNoteIndexToPrint < size && recordedNoteOnMatrix[minNoteIndexToPrint][0] > numberOfScreensIn*ticksPerScreen)
			minNoteIndexToPrint--;
		
		for (int tmpIndex = max(0,minNoteIndexToPrint);tmpIndex < min(maxNoteIndexToPrint, (int)recordedNoteOnMatrix.size());tmpIndex++){
			
			if (checkIfMatchedNote(tmpIndex))
				ofSetColor(0,0,255);
			else if(noteOnMatches[tmpIndex]){
			ofSetColor(255,0,255);
			}else{
				ofSetColor(255,255,255);
			}
		
			

	//		 XXX replace ofgetwidth below
			//if (tmpIndex >= 0 && tmpIndex < size)
			int xLocation = (float)(recordedNoteOnMatrix[tmpIndex][0] - numberOfScreensIn*ticksPerScreen)*(*screenWidth)/(float)ticksPerScreen;
			int duration = (float)(recordedNoteOnMatrix[tmpIndex][3]*(*screenWidth))/(float)ticksPerScreen;
			
			
			int yLocation = (*screenHeight) - ((recordedNoteOnMatrix[tmpIndex][1] - noteMinimum )*(*screenHeight)/ (float)(noteMaximum - noteMinimum));						 
			ofRect(xLocation,yLocation, duration,  noteHeight);
			
		}
		
		
		int xLocation;// = getLocationFromTicks(tickLocation);
	//	ofLine(xLocation, 0, xLocation, (*screenHeight));
		
		//orange line at best estimate
		xLocation = getLocationFromMillis(bayesStruct.bestEstimate);
		ofSetColor(250,100,0);
		ofLine(xLocation, 0, xLocation, (*screenHeight));
		
		xLocation = getLocationFromMillis(bayesStruct.tmpBestEstimate);
		ofSetColor(250,100,0);
		ofLine(xLocation, 0, xLocation, (*screenHeight));
		
		
		//lines where matching window start and end are 
		ofSetColor(0,100,255);
		xLocation = getLocationFromMillis(windowStartTime);
		ofLine(xLocation, 0, xLocation, (*screenHeight));
		xLocation = getLocationFromMillis(windowStartTime+matchWindowWidth);
		ofLine(xLocation, 0, xLocation, (*screenHeight));
		 
		
	}	
	
	
	
	ofDrawBitmapString(ofToString(timeOffsetForScreen, 1), 20,20);
	
	ofDrawBitmapString(timeString, 20, 60);
	
//	bayesStruct.drawArrays();
	
//	ofSetColor(200,200,0);
//	bayesStruct.prior.drawConstrainedVector(0, bayesStruct.prior.arraySize, 400, 800);
	
	//need to draw arrays within correct timescope
	bayesStruct.drawArraysRelativeToTimeframe(timeOffsetForScreen, timeOffsetForScreen + getEventTimeMillis(ticksPerScreen));
	
	if (drawTempoMode)
	bayesStruct.drawTempoArrays();
	
	
	ofSetColor(0, 0, 0);
	ofDrawBitmapString(matchString, 20, ofGetHeight() - 20);
	
	double confidence = bayesStruct.posterior.getValueAtMillis(mouseX);
/*
 string mouseString = "mouseX "+ofToString(confidence, 3)+" .";
	ofDrawBitmapString(mouseString, 20 ,  ofGetHeight() - 40);
*/	
	string mouseString = "updateCounter "+ofToString(bayesStruct.updateCounter);
	ofDrawBitmapString(mouseString, 20 ,  ofGetHeight() - 40);
	
	string infostring = "speed "+ofToString(bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate), 3);
	ofDrawBitmapString(infostring, 20 ,  ofGetHeight() - 60);							   
}

int midiEventHolder::getLocationFromTicks(double tickPosition){
	return (int)((float)(tickPosition - numberOfScreensIn*ticksPerScreen)*(*screenWidth)/(float)ticksPerScreen);
}

int midiEventHolder::getLocationFromMillis(double millisPosition){
	//(getEventTimeTicks(windowStartTime+matchWindowWidth) - numberOfScreensIn*ticksPerScreen)*(*screenWidth) / (double)ticksPerScreen
	return (millisPosition - timeOffsetForScreen)*(*screenWidth)/getEventTimeMillis(ticksPerScreen);
}


void midiEventHolder::exampleCrossUpdate(){
	
	bayesStruct.crossUpdateArrays(bayesStruct.posterior, bayesStruct.relativeSpeedPosterior, 200);
	
}


void midiEventHolder::setStartPlayingTimes(){
	lastPeriodUpdateTime = ofGetElapsedTimeMillis();
	startTime = lastPeriodUpdateTime;
	
/*	
 	bayesStruct.lastEventTime = 0;//ofGetElapsedTimeMillis();
	bayesStruct.bestEstimate = 0;
	bayesStruct.resetArrays();
	bayesStruct.lastBestEstimateUpdateTime = ofGetElapsedTimeMillis();
*/
	bayesStruct.setStartPlaying();
	matchString = "";
}


void midiEventHolder::printMatchMatrix(){
	printf("match matrix:\n");
	for (int i = 0;i < matchMatrix.size();i++){
		for (int k = 0;k < matchMatrix[i].size();k++){
			printf("%i , ", matchMatrix[i][k]);
		}
		printf("\n");
	}
	
	
}