andrew@0: /* andrew@0: * midiEventHolder.cpp andrew@0: * midiCannamReader3 andrew@0: * andrew@0: * Created by Andrew on 19/07/2011. andrew@0: * Copyright 2011 QMUL. All rights reserved. andrew@0: * andrew@0: */ andrew@0: //hello andrew@0: andrew@0: #include "midiEventHolder.h" andrew@0: andrew@0: midiEventHolder::midiEventHolder(){ andrew@0: // recordedNoteOnIndex = 0; andrew@0: andrew@0: width = ofGetWidth(); andrew@0: height = ofGetHeight(); andrew@0: screenWidth= &width; andrew@0: screenHeight = &height; andrew@0: andrew@0: ticksPerScreen = 4000; andrew@0: tickLocation = 0; andrew@0: pulsesPerQuarternote = 240; andrew@0: noteArrayIndex = 0; andrew@0: noteMinimum = 30; andrew@0: noteMaximum = 96; andrew@0: andrew@1: minimumMatchSpeed = 0.7; andrew@1: maximumMatchSpeed = 1.3; andrew@0: likelihoodWidth = 100; andrew@0: likelihoodToNoiseRatio = 50; andrew@0: andrew@1: matchWindowWidth = 4000;//window size for matching in ms andrew@0: andrew@1: bayesStruct.resetSize(matchWindowWidth); andrew@0: bayesStruct.resetSpeedSize(200); andrew@0: bayesStruct.setRelativeSpeedScalar(0.01); andrew@0: bayesStruct.relativeSpeedPrior.getMaximum(); andrew@0: bayesStruct.simpleExample(); andrew@0: andrew@0: noteHeight = (*screenHeight) / (float)(noteMaximum - noteMinimum); andrew@0: } andrew@0: andrew@0: andrew@0: andrew@0: void midiEventHolder::reset(){ andrew@0: noteArrayIndex = 0; andrew@0: tickLocation = 0; andrew@0: lastPeriodUpdateTime = ofGetElapsedTimeMillis(); andrew@0: bayesStruct.lastEventTime = ofGetElapsedTimeMillis(); andrew@0: numberOfScreensIn = 0; andrew@0: // recordedNoteOnIndex = 0; andrew@0: bayesStruct.setNewDistributionOffsets(0); andrew@0: bayesStruct.posterior.offset = 0; andrew@0: andrew@0: playedEventTimes.clear(); andrew@0: playedNoteOnMatrix.clear(); andrew@0: matchMatrix.clear(); andrew@0: andrew@0: bayesStruct.resetSpeedToOne(); andrew@0: andrew@0: } andrew@0: andrew@1: void midiEventHolder::clearAllEvents(){ andrew@1: recordedNoteOnMatrix.clear(); andrew@1: matchesFound.clear(); andrew@1: noteOnMatches.clear(); andrew@1: recordedEventTimes.clear(); andrew@1: andrew@1: //played events: andrew@1: playedEventTimes.clear(); andrew@1: playedNoteOnMatrix.clear(); andrew@1: matchMatrix.clear(); andrew@1: } andrew@1: andrew@0: void midiEventHolder::printNotes(){ andrew@0: printf("RECORDED MATRIX"); andrew@0: for (int i = 0;i < recordedNoteOnMatrix.size();i++){ andrew@0: printf("%i :: %i @ %f\n", recordedNoteOnMatrix[i][0], recordedNoteOnMatrix[i][1], recordedEventTimes[i]); andrew@0: } andrew@0: } andrew@0: andrew@0: andrew@0: double midiEventHolder::getEventTimeTicks(double millis){ andrew@0: return (millis * pulsesPerQuarternote / period); andrew@0: } andrew@0: andrew@0: double midiEventHolder::getEventTimeMillis(double ticks){ andrew@0: return (period * ticks / (double) pulsesPerQuarternote); andrew@0: } andrew@0: andrew@0: void midiEventHolder::newNoteOnEvent(int pitch, int velocity, double timePlayed){ andrew@0: andrew@0: //MOVE INTO BAYESSTRUCT?? XXX andrew@0: //bayesStruct.copyPriorToPosterior(); andrew@0: //why was this here?? andrew@0: bayesStruct.prior.copyFromDynamicVector(bayesStruct.posterior);//try the otehr way andrew@0: //bayesStruct.copyPriorToPosterior(); andrew@0: //need to get new MAP position and set the offset of the arrays andrew@0: //currently bestEstimate is the approx for the new MAP position andrew@0: andrew@0: andrew@0: //add the new event to our played information matrix andrew@0: IntVector v; andrew@0: v.push_back(pitch); andrew@0: v.push_back(velocity); andrew@0: playedNoteOnMatrix.push_back(v); andrew@0: andrew@0: andrew@0: //would update the arrays at this point to show where out current location (phase) and tempo is. andrew@0: double timeNow = ofGetElapsedTimeMillis() - startTime; andrew@0: recentNoteOnTime = timeNow; andrew@0: andrew@0: printf("Max time %f OF time %f \n", timePlayed, timeNow); andrew@0: andrew@0: playedEventTimes.push_back(timePlayed); andrew@0: andrew@0: double timeDifference = ofGetElapsedTimeMillis() - bayesStruct.lastEventTime; andrew@0: andrew@0: //addnoise to the tempo distribution andrew@0: bayesStruct.decaySpeedDistribution(timeDifference); andrew@0: andrew@0: andrew@0: // double newMAPestimateTime = bayesStruct.posterior.getIndexInRealTerms(bayesStruct.posterior.MAPestimate); andrew@0: //was offset + bayesStruct.posterior.MAPestimate; but this doesnt include scalar to convert to millis andrew@0: andrew@0: timeString = "Pitch:"+ofToString(pitch); andrew@0: timeString += ", time now:"+ofToString(timeNow, 1); andrew@0: timeString += " TD "+ofToString(timeDifference, 1); andrew@0: timeString += " offset "+ofToString(bayesStruct.posterior.offset , 0); andrew@0: timeString += " map Est: "+ofToString(bayesStruct.posterior.MAPestimate, 0); andrew@0: // timeString += " Previous time" + ofToString(newMAPestimateTime,0); andrew@0: timeString += " speedMap "+ofToString(bayesStruct.relativeSpeedPosterior.MAPestimate, 2); andrew@0: timeString += " :: "+ofToString(bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate), 2); andrew@0: andrew@0: // newMAPestimateTime += (timeDifference * bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate)); andrew@0: // timeString += " : Predicted MAP time" + ofToString(newMAPestimateTime,0); andrew@0: andrew@0: //then we recalculate the window start based on MAP being central andrew@0: //then we do the matches on these and the likelihood on these. andrew@0: andrew@0: bayesStruct.setNewDistributionOffsets(max(0., bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2))); andrew@0: // bayesStruct.prior.offset = max(0.,newMAPestimateTime - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2)); andrew@0: andrew@0: timeString += " \n : new offset " + ofToString(bayesStruct.prior.offset , 0); andrew@0: timeString += " \n best estimate "+ofToString(bayesStruct.bestEstimate, 1); andrew@0: timeString += " map "+ofToString(bayesStruct.relativeSpeedPosterior.MAPestimate, 1); andrew@0: timeString += " rel speed "+ofToString(bayesStruct.relativeSpeedPosterior.getIndexInRealTerms(bayesStruct.relativeSpeedPosterior.MAPestimate), 1); andrew@0: andrew@0: andrew@0: //be able to draw the prior in correct location relative to the midi notes andrew@0: bayesStruct.crossUpdateArrays(bayesStruct.posterior, bayesStruct.relativeSpeedPosterior, timeDifference); andrew@0: // bayesStruct.crossUpdateArrays(bayesStruct.prior, bayesStruct.relativeSpeedPosterior, timeDifference); andrew@0: andrew@0: andrew@0: timeString += " new OFF "+ofToString(bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2), 1); andrew@0: timeString += " notearrayindex "+ofToString(noteArrayIndex, 0); andrew@0: //when this is off teh screen there is a problem somehow XXX andrew@0: bayesStruct.posterior.offset = max(0., bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2)); andrew@0: // bayesStruct.prior.offset = max(0., bayesStruct.bestEstimate - (bayesStruct.prior.scalar*bayesStruct.prior.arraySize/2)); andrew@0: //trying to switch to prior andrew@0: andrew@0: bayesStruct.lastEventTime = ofGetElapsedTimeMillis(); andrew@0: andrew@0: //do the cross update to find current posterior for location andrew@1: // totalConfidence= 0; andrew@0: int numberOfMatchesFound = findLocalMatches(pitch); andrew@0: setMatchLikelihoods(numberOfMatchesFound); andrew@0: bayesStruct.calculatePosterior(); andrew@0: andrew@0: //having found matches we have matches for new note and matches for previous notes andrew@0: findLocalTempoPairs(); andrew@0: andrew@0: andrew@0: andrew@0: } andrew@0: andrew@0: int midiEventHolder::findLocalMatches(int notePitch){ andrew@0: andrew@0: //here we find the matches to the new note within appropriate range andrew@0: andrew@1: matchString = ""; andrew@0: andrew@0: windowStartTime = max(0.0,(bayesStruct.bestEstimate - matchWindowWidth/2));//was playPositionInMillis andrew@0: int numberOfMatches = findMatch(notePitch, windowStartTime, windowStartTime + matchWindowWidth); andrew@0: andrew@1: matchString += " pitch: "+ofToString(notePitch)+" matches "+ofToString(numberOfMatches)+" win start "+ofToString(windowStartTime); andrew@1: andrew@0: return numberOfMatches; andrew@0: andrew@0: andrew@0: } andrew@0: andrew@0: andrew@0: void midiEventHolder::setMatchLikelihoods(int numberOfMatches){ andrew@0: //reset the offset to match the prior andrew@0: bayesStruct.likelihood.offset = bayesStruct.prior.offset; andrew@0: bayesStruct.likelihood.zero();//set to zero andrew@0: andrew@0: andrew@0: andrew@0: for (int i = 0;i < numberOfMatches && matchesFound[i] >= 0 && matchesFound[i] < recordedEventTimes.size();i++){ andrew@0: // printf("match times %i of %i::%f adding likelihood to %f\n", i, numberOfMatches, recordedEventTimes[matchesFound[i]], recordedEventTimes[matchesFound[i]] - bayesStruct.likelihood.offset); andrew@0: //this is the vent time since start of file andrew@0: if (recordedEventTimes[matchesFound[i]] - bayesStruct.likelihood.offset < bayesStruct.likelihood.arraySize){ andrew@1: // double confidenceMeasure = 0; andrew@1: // if (totalConfidence > 0) andrew@1: // confidenceMeasure = bayesStruct.posterior.getValueAtMillis(recordedEventTimes[matchesFound[i]])/totalConfidence; andrew@1: andrew@1: bayesStruct.likelihood.addGaussianShape(recordedEventTimes[matchesFound[i]] - bayesStruct.likelihood.offset, likelihoodWidth, 0.5 * likelihoodToNoiseRatio );//* confidenceMeasure andrew@0: }//end if andrew@0: } andrew@0: bayesStruct.likelihood.addConstant(0.01); andrew@0: } andrew@0: andrew@0: int midiEventHolder::findMatch(const int& notePitch, const int& startTime, const int& endTime){ andrew@0: andrew@0: matchesFound.clear(); andrew@0: int startIndex = 0; andrew@0: andrew@0: if (recordedEventTimes.size() > 0){ andrew@0: andrew@0: //get to the right range of events to check in andrew@0: while (startIndex < recordedEventTimes.size() && recordedEventTimes[startIndex] < startTime) andrew@0: startIndex++; andrew@0: andrew@0: } andrew@0: andrew@0: andrew@0: while (startIndex < recordedEventTimes.size() && recordedEventTimes[startIndex] < endTime){ andrew@0: if (recordedNoteOnMatrix[startIndex][1] == notePitch){ andrew@0: matchesFound.push_back(startIndex); andrew@1: double confidence = bayesStruct.posterior.getValueAtMillis(mouseX); andrew@1: // recordedEventTimes[startIndex]); andrew@1: matchString += "["+ofToString(startIndex)+"] = "+ofToString(confidence, 3)+" ."; andrew@0: } andrew@0: startIndex++; andrew@0: } andrew@0: andrew@0: // printf("%i MATCHES TO Note %i found\n", (int)matchesFound.size(), notePitch); andrew@0: int size = matchesFound.size(); andrew@0: andrew@0: IntVector v; andrew@0: v.push_back(size); andrew@0: for (int i = 0;i < matchesFound.size();i++) andrew@0: v.push_back(matchesFound[i]); andrew@0: andrew@0: matchMatrix.push_back(v); andrew@0: andrew@0: return size; andrew@0: } andrew@0: andrew@0: bool midiEventHolder::checkIfMatchedNote(const int& tmpIndex){ andrew@0: for (int i = 0;i < matchesFound.size();i++){ andrew@0: if (matchesFound[i] == tmpIndex) andrew@0: return true; andrew@0: } andrew@0: return false; andrew@0: } andrew@0: andrew@0: andrew@0: andrew@0: void midiEventHolder::findLocalTempoPairs(){ andrew@0: andrew@0: int currentPlayedIndex = playedNoteOnMatrix.size()-1; andrew@0: // printf("played %i : %i, vel %i\n", currentPlayedIndex, playedNoteOnMatrix[currentPlayedIndex][0], playedNoteOnMatrix[currentPlayedIndex][1]); andrew@0: // printMatchesFound(); andrew@0: // printMatchMatrix(); andrew@0: // printf("possible notes \n"); andrew@0: andrew@0: andrew@0: for (int i = 0;i < matchMatrix[currentPlayedIndex][0];i++){ andrew@0: andrew@0: int recordedCurrentIndex = matchMatrix[currentPlayedIndex][i+1]; andrew@0: andrew@0: int previousIndex = currentPlayedIndex-1; andrew@0: andrew@0: while (previousIndex >= 0 && playedEventTimes[previousIndex] + 2000 > playedEventTimes[currentPlayedIndex]) { andrew@0: double playedTimeDifference = playedEventTimes[currentPlayedIndex] - playedEventTimes[previousIndex]; andrew@0: andrew@0: for (int k = 0;k < matchMatrix[previousIndex][0];k++){ andrew@0: int recordedPreviousIndex = matchMatrix[previousIndex][k+1]; andrew@0: andrew@1: andrew@0: andrew@0: double recordedTimeDifference = recordedEventTimes[recordedCurrentIndex] - recordedEventTimes[recordedPreviousIndex]; andrew@1: andrew@0: andrew@0: //we want the speed of the recording relative to that of the playing live andrew@0: andrew@0: double speedRatio = recordedTimeDifference / playedTimeDifference; andrew@0: if (speedRatio <= maximumMatchSpeed && speedRatio >= minimumMatchSpeed){ andrew@1: printf("(%i)", matchMatrix[currentPlayedIndex][i+1]); andrew@1: printf("[%i] :: ", recordedPreviousIndex); andrew@1: printf(" rec{%f} vs play(%f) ", recordedTimeDifference, playedTimeDifference); andrew@1: printf("update on speed ratio %f\n", speedRatio); andrew@1: // matchString += " speed: "+ofToString(speedRatio, 3); andrew@0: // commented for debug andrew@0: bayesStruct.updateTempoDistribution(speedRatio, 0.1);//second paramter is confidence in the match andrew@0: andrew@0: } andrew@0: // printf("\n"); andrew@0: } andrew@0: andrew@0: previousIndex--; andrew@0: }//end while previousindex countdown andrew@0: }//end for loop through possible current matches andrew@0: andrew@0: andrew@0: } andrew@0: andrew@0: andrew@0: void midiEventHolder::updatePlayPosition(){ andrew@0: andrew@0: //in actual fact if we are changing the speed of the play position andrew@0: //we will need to update this via the file andrew@0: andrew@0: double timeDifference = ofGetElapsedTimeMillis() - lastPeriodUpdateTime; andrew@0: //this is time diff in milliseconds andrew@0: //then we have andrew@0: double quarterNoteIntervals = (timeDifference / period); andrew@0: tickLocation = quarterNoteIntervals * pulsesPerQuarternote; andrew@0: andrew@0: playPositionInMillis = timeDifference;//based on updating from when we change period andrew@0: //this to be added andrew@0: andrew@0: bayesStruct.updateBestEstimate(); andrew@0: andrew@0: } andrew@0: andrew@0: andrew@0: void midiEventHolder::drawFile(){ andrew@0: //draws midi file on scrolling screen andrew@0: int size = recordedNoteOnMatrix.size(); andrew@0: if (size > 0){ andrew@0: andrew@0: numberOfScreensIn = floor(bayesStruct.bestEstimate / getEventTimeMillis(ticksPerScreen));//rpounds down on no screens in andrew@0: andrew@0: // numberOfScreensIn = tickLocation / ticksPerScreen;//rounds down andrew@0: timeOffsetForScreen = getEventTimeMillis(numberOfScreensIn * ticksPerScreen); andrew@0: andrew@0: while (noteArrayIndex < recordedNoteOnMatrix.size() && tickLocation > recordedNoteOnMatrix[noteArrayIndex][0] ) andrew@0: noteArrayIndex++; andrew@0: andrew@0: andrew@0: while (noteArrayIndex > 0 && noteArrayIndex < size && tickLocation < recordedNoteOnMatrix[noteArrayIndex][0]) andrew@0: noteArrayIndex--; andrew@0: andrew@0: //need to start where we currently are in file andrew@0: int maxNoteIndexToPrint = noteArrayIndex; andrew@0: int minNoteIndexToPrint = noteArrayIndex; andrew@0: andrew@0: while (maxNoteIndexToPrint < recordedNoteOnMatrix.size() && recordedNoteOnMatrix[maxNoteIndexToPrint][0] < (numberOfScreensIn+1)*ticksPerScreen ) andrew@0: maxNoteIndexToPrint++; andrew@0: andrew@0: while (minNoteIndexToPrint > 0 && minNoteIndexToPrint < size && recordedNoteOnMatrix[minNoteIndexToPrint][0] > numberOfScreensIn*ticksPerScreen) andrew@0: minNoteIndexToPrint--; andrew@0: andrew@0: for (int tmpIndex = max(0,minNoteIndexToPrint);tmpIndex < min(maxNoteIndexToPrint, (int)recordedNoteOnMatrix.size());tmpIndex++){ andrew@0: andrew@0: if (checkIfMatchedNote(tmpIndex)) andrew@0: ofSetColor(0,0,255); andrew@0: else andrew@0: ofSetColor(255,255,255); andrew@0: andrew@0: // XXX replace ofgetwidth below andrew@0: //if (tmpIndex >= 0 && tmpIndex < size) andrew@0: int xLocation = (float)(recordedNoteOnMatrix[tmpIndex][0] - numberOfScreensIn*ticksPerScreen)*(*screenWidth)/(float)ticksPerScreen; andrew@0: int duration = (float)(recordedNoteOnMatrix[tmpIndex][3]*(*screenWidth))/(float)ticksPerScreen; andrew@0: andrew@0: andrew@0: int yLocation = (*screenHeight) - ((recordedNoteOnMatrix[tmpIndex][1] - noteMinimum )*(*screenHeight)/ (float)(noteMaximum - noteMinimum)); andrew@0: ofRect(xLocation,yLocation, duration, noteHeight); andrew@0: andrew@0: } andrew@0: andrew@0: andrew@0: int xLocation;// = getLocationFromTicks(tickLocation); andrew@0: // ofLine(xLocation, 0, xLocation, (*screenHeight)); andrew@0: andrew@0: //orange line at best estimate andrew@0: xLocation = getLocationFromMillis(bayesStruct.bestEstimate); andrew@0: ofSetColor(250,100,0); andrew@0: ofLine(xLocation, 0, xLocation, (*screenHeight)); andrew@0: andrew@0: andrew@0: //lines where matching window start and end are andrew@0: ofSetColor(0,100,255); andrew@0: xLocation = getLocationFromMillis(windowStartTime); andrew@0: ofLine(xLocation, 0, xLocation, (*screenHeight)); andrew@0: xLocation = getLocationFromMillis(windowStartTime+matchWindowWidth); andrew@0: ofLine(xLocation, 0, xLocation, (*screenHeight)); andrew@0: andrew@0: andrew@0: } andrew@0: andrew@1: andrew@1: andrew@0: ofDrawBitmapString(ofToString(timeOffsetForScreen, 1), 20,20); andrew@0: andrew@0: ofDrawBitmapString(timeString, 20, 60); andrew@0: andrew@0: // bayesStruct.drawArrays(); andrew@0: andrew@0: // ofSetColor(200,200,0); andrew@0: // bayesStruct.prior.drawConstrainedVector(0, bayesStruct.prior.arraySize, 400, 800); andrew@0: andrew@0: //need to draw arrays within correct timescope andrew@0: bayesStruct.drawArraysRelativeToTimeframe(timeOffsetForScreen, timeOffsetForScreen + getEventTimeMillis(ticksPerScreen)); andrew@0: andrew@1: if (drawTempoMode) andrew@1: bayesStruct.drawTempoArrays(); andrew@0: andrew@1: andrew@1: ofSetColor(0, 0, 0); andrew@0: ofDrawBitmapString(matchString, 20, ofGetHeight() - 20); andrew@0: andrew@1: double confidence = bayesStruct.posterior.getValueAtMillis(mouseX); andrew@1: string mouseString = "mouseX "+ofToString(confidence, 3)+" ."; andrew@1: ofDrawBitmapString(mouseString, 20 , ofGetHeight() - 40); andrew@0: } andrew@0: andrew@0: int midiEventHolder::getLocationFromTicks(double tickPosition){ andrew@0: return (int)((float)(tickPosition - numberOfScreensIn*ticksPerScreen)*(*screenWidth)/(float)ticksPerScreen); andrew@0: } andrew@0: andrew@0: int midiEventHolder::getLocationFromMillis(double millisPosition){ andrew@0: //(getEventTimeTicks(windowStartTime+matchWindowWidth) - numberOfScreensIn*ticksPerScreen)*(*screenWidth) / (double)ticksPerScreen andrew@0: return (millisPosition - timeOffsetForScreen)*(*screenWidth)/getEventTimeMillis(ticksPerScreen); andrew@0: } andrew@0: andrew@0: andrew@0: void midiEventHolder::exampleCrossUpdate(){ andrew@0: andrew@0: bayesStruct.crossUpdateArrays(bayesStruct.posterior, bayesStruct.relativeSpeedPosterior, 200); andrew@0: andrew@0: } andrew@0: andrew@0: andrew@0: void midiEventHolder::setStartPlayingTimes(){ andrew@0: lastPeriodUpdateTime = ofGetElapsedTimeMillis(); andrew@0: bayesStruct.lastEventTime = ofGetElapsedTimeMillis(); andrew@0: startTime = lastPeriodUpdateTime; andrew@0: andrew@0: bayesStruct.resetArrays(); andrew@0: // bayesStruct.lastBestEstimateUpdateTime = ofGetElapsedTimeMillis(); andrew@0: matchString = ""; andrew@0: } andrew@0: andrew@0: andrew@0: void midiEventHolder::printMatchMatrix(){ andrew@0: printf("match matrix:\n"); andrew@0: for (int i = 0;i < matchMatrix.size();i++){ andrew@0: for (int k = 0;k < matchMatrix[i].size();k++){ andrew@0: printf("%i , ", matchMatrix[i][k]); andrew@0: } andrew@0: printf("\n"); andrew@0: } andrew@0: andrew@0: andrew@0: }