Mercurial > hg > midi-score-follower
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"); } }