view DrumTimingLoader_OF/src/RecordedMultipleAudio.cpp @ 1:106bc2d4f702

added timing analyser file
author Andrew N Robertson <andrew.robertson@eecs.qmul.ac.uk>
date Sat, 23 Nov 2013 15:44:47 +0000
parents 82352cfc0b23
children 50ba55abea8c
line wrap: on
line source
/*
 *  RecordedMultipleAudio.cpp
 *  MultipleAudioMathcher
 *
 *  Created by Andrew on 31/01/2012.
 *  Copyright 2012 QMUL. All rights reserved.
 *
 */

#include "RecordedMultipleAudio.h"

RecordedMultipleAudio::RecordedMultipleAudio(){
	
	infoFilepath = "../../../data/errorData.txt";
	//infoFilepath = "/Users/andrew/errorData.txt";
	
	timingOffset = 0;
}

void RecordedMultipleAudio::loadTestAudio(){


	numberOfAudioTracks = 2;
	
	printf("loaded max val  is %f\n", loadedAudioFiles[0].fileLoader.onsetDetect.onsetDetector.maximumDetectionValue);
	
	int multitrackToLoad = 5;
	setDifferentMultitracks(multitrackToLoad);//command to load this set of audio files - see below

	drumTimingAnalyser.phaseCost = 1000;//v high - i.e. dont do phase 
	
	drawWindow = 1;
	trackScreenHeight = 0.25;
	
	
	
//	printf("AFTER LOADING: \n");
//	printInfo();
	
}
#pragma mark -loadingPrerecordedTracks
void RecordedMultipleAudio::setDifferentMultitracks(const int& setToLoad){
	const char	*kickfilename ;//= "../../../data/sound/LiveDues/kick_liveDues.wav";	
	const char	*roomfilename ;//"../../../data/sound/LiveDues/bass_upsideLive.wav";	
	const char	*snarefilename ;
	std::string sonicVizBeatsFilename ;
	
	switch (setToLoad) {
		case 0:
				kickfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/kickFuture.wav";	
				roomfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/roomFuture.wav";	
				snarefilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/snareFuture.wav";	
				sonicVizBeatsFilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/FutureHidesBeats.txt";
			break;
			
			
		case 1:
			roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/Mixdown/PennyArcade_StudioMixdown.wav";
			kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/kick.wav";
			snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/snare.wav";
			sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/Mixdown/PennyArcade_StudioMixdown_beats.txt";
			break;	
	
		case 2:
			roomfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/colesL_bip.wav";
			kickfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/Kick_bip.wav";
			snarefilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/Snare_bip.wav";
			sonicVizBeatsFilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/IngramGreenSectionBeats.txt";
			break;		
			
		case 3:
			roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/coles_bip.wav";
			kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/kick d112_bip.wav";
			snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/snare bottom_bip.wav";
			sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/TakeOneBeats.txt";
			break;
			
		case 4:
			roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/neuamnn_bip.wav";
			kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/kick_bip.wav";
			snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/snare_bip.wav";
			sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/PennyArcade_take4_beats.txt";
			break;	
			
		case 5:
			roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/neuamnn_bip.wav";
			kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/kick_bip.wav";
			snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/snare_bip.wav";
			sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/PennyArcade_take5_beats.txt";
			break;	
			
			
			
	}
	if (kickfilename != NULL){
		
		loadAudioTrack(kickfilename, 0);
	}
	
	if (roomfilename != NULL){	
		printf("roomfilename: %s\n", roomfilename);
		loadAudioTrack(roomfilename, 1);
	}
	
	if (snarefilename != NULL)
		loadAudioTrack(snarefilename, 2);
	
	if (sonicVizBeatsFilename.c_str() != NULL){
		readInBeatsFile(sonicVizBeatsFilename);
		printBeatTimes();
		checkFileErrors(0);
		checkFileErrors(2);
		findBeatOnsets();
	}
}

void RecordedMultipleAudio::loadAudioTrack(std::string name, const int& channel){
	//kick - track type 0
	//bass - type 1
	//snare type 2
	//guitar type 3
	if (channel >= 0 && channel <= numberOfAudioTracks){
	loadedAudioPtr = new LoadedAudioHolder;
	//set tracktype before we do analysis
	//so we dont do unnecessary chroma and pitch calculations
		if (channel == 0 || channel == 2){
			loadedAudioPtr->setTrackType(channel);
		}
		else{
			loadedAudioPtr->setTrackType(0);
		}
	loadedAudioPtr->loadAudioFile(name);
		
	loadedAudioFiles[channel] = *loadedAudioPtr;
	loadedAudioFiles[channel].fileLoader.onsetDetect.window.setToRelativeSize(0, trackScreenHeight*channel, 1, trackScreenHeight);
	//loadedAudioFiles[channel].setTrackType(channel);
	}
}



void RecordedMultipleAudio::readInBeatsFile(std::string& pathName){
	
	// "/Users/andrew/Documents/work/MuseScore/RWC/ANNOTATION/RM-C002_annotation+WavPos.csv"
	beatTimes.clear();
	
	printf("- - - - \n\nREAD FILE %s\n", pathName.c_str());
	ifstream file ( pathName.c_str());
	string value, tmpLine;
	stringstream iss;
	int count = 0;
	
	while ( file.good() )
	{
		getline(file, tmpLine);
		iss << tmpLine;
		int lineCount = 0;
		//			printf("tmp line %s\n", tmpLine.c_str());
		while(getline ( iss, value, '\t' )){ // read a string until next comma: http://www.cplusplus.com/reference/string/getline/
			//	cout << string( value, 1, value.length()-2 ); // display value removing the first and the last character from it
			//		printf("line:%s\n", value.c_str());
			string::size_type start = value.find_first_not_of(" ,\t\v\n");
			
			string part = value.substr(start, string::npos);
			
			//printf("%s\n", firstpart.c_str());
			if (lineCount == 0){
				//printf("First part of line found '%s'\n", part.c_str());
				double newBeatTime = atof(part.c_str());
				beatTimes.push_back(newBeatTime);
			}
			lineCount++;
			
		}//end while reading line
		iss.clear();
		
		
	}//end while
	
	//	printBeatTimes();
	printf("There are %i BEAT annotations\n", (int)beatTimes.size());
	
}

void RecordedMultipleAudio::printBeatTimes(){
	for (int i = 0;i < beatTimes.size();i++){
		printf("Beat[%i] = %f\n", i, beatTimes[i]);
	}
}


void RecordedMultipleAudio::checkFileErrors(int channel){
	int beatIndex = 0;
	int cutoff = 50;//ms width to check
	for (int i = 0;i < loadedAudioFiles[channel].onsetTimesMillis.size();i++){
		while (beatIndex < beatTimes.size() && 1000.0*beatTimes[beatIndex] < loadedAudioFiles[channel].onsetTimesMillis[i] - cutoff) {
			beatIndex++;
		}
		double error = (1000.0*beatTimes[beatIndex] - loadedAudioFiles[channel].onsetTimesMillis[i]);
		if (fabs(error) < cutoff){
			if (channel == 0) 
				printf("Pos: %i Beat Time %f Kick Time %f Error %f\n", beatIndex%4, 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
			else 
				printf("Pos: %i Beat Time %f Snare Time %f Error %f\n", beatIndex%4, 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
			
		}else{
			if (channel == 0) 
				printf("Out of Beat: Kick Time %f beat error %f\n", 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
			else 
				printf("Out of Beat: %f Snare Time %f best error %f\n", 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
			
		}
	}
}


#pragma mark -labelExactOnsets

void RecordedMultipleAudio::findBeatOnsets(){
//tries to find kicks on 1, 3; snares on 2,4
	int beatIndex = 0;
	int kickIndex = 0;
	int snareIndex = 0;
	double kickTime, snareTime;
	int cutoff = 50;//ms width to check
	
	onsetInfo.clear();
	
//	kickErrors.clear();
//	snareErrors.clear();
	
	bool beatFound;
	for (int k = 0;k < beatTimes.size();k++){
		beatFound = false;
		double newBeatTime = beatTimes[k]*1000.0;
		int beatPosition = k % 4;
		OnsetInformation information;
		switch (beatPosition) {
			case 0: case 2://check for kick when it is on the `one' or 'three' (0 or 2 in our metrical position)
			//	printf("check %i kindex %i\n", beatPosition, kickIndex);
				while (kickIndex < loadedAudioFiles[0].onsetTimesMillis.size() && loadedAudioFiles[0].onsetTimesMillis[kickIndex] < newBeatTime - cutoff){
					kickIndex++;
					kickTime = loadedAudioFiles[0].onsetTimesMillis[kickIndex];
			//		printf("checking beat[%i] %f kick %f error %f\n", k, beatTimes[k]*1000.0, kickTime, (kickTime - beatTimes[k]*1000.0));
					if (fabs(kickTime - beatTimes[k]*1000.0) < cutoff){
						beatFound = true;
						printf("beat[%i] %f kick %f error %f\n", k, beatTimes[k]*1000.0, kickTime, (kickTime - beatTimes[k]*1000.0));

						information.error = (kickTime - beatTimes[k]*1000.0);//FOR NOW ONLY
						information.metricalPosition = beatPosition;
						information.type = 0;	
						information.exactOnsetTime = kickTime;
			//			exactBeatPositions.push_back(kickTime);
					}
				}
				
				break;
			case 1: case 3://snare
				while (snareIndex < loadedAudioFiles[1].onsetTimesMillis.size() && loadedAudioFiles[1].onsetTimesMillis[snareIndex] < newBeatTime - cutoff ){
					snareIndex++;
					snareTime = loadedAudioFiles[1].onsetTimesMillis[snareIndex];
					if (fabs(snareTime - beatTimes[k]*1000.0) < cutoff){
						beatFound = true;
					//	snareErrors.push_back((beatTimes[k]*1000.0 - snareTime));
						information.error = (snareTime - beatTimes[k]*1000.0);
						information.metricalPosition = beatPosition;//.push_back(beatPosition);
						information.type = 1;
						information.exactOnsetTime = snareTime;
						printf("beat[%i] %f snare %f error %f\n", k, beatTimes[k]*1000.0, snareTime, (snareTime - beatTimes[k]*1000.0));
			//			exactBeatPositions.push_back(snareTime);
					}
				}
				
				break;
		}
		if (!beatFound){
			information.type = -1;//not a kick or snare
			information.exactOnsetTime = beatTimes[k]*1000.0;
			information.metricalPosition = beatPosition;//.push_
		//	exactBeatPositions.push_back(beatTimes[k]*1000.0);//have to go with the annotated beat instead (no matching kick or snare)
			
			printf("beat[%i] %f NOT FOUND, kicktime %f snaretime %f\n", k, beatTimes[k]*1000.0, kickTime, snareTime );
		}
		
		onsetInfo.push_back(information);
		
	}//end for all beat annotations

	correctExactBeatTiming();//get rid of the first onset time
	
	calculateTimingAnalysis();
	
}

#pragma mark -doTimingAnalysis
void RecordedMultipleAudio::correctExactBeatTiming(){
	//get rid of firtst onset time
	if (onsetInfo.size() > 0){
		timingOffset = onsetInfo[0].exactOnsetTime;//s[0];
		double tmpPosn;
		for (int i = 0;i < onsetInfo.size();i++){
			onsetInfo[i].beatTimeToProcess = onsetInfo[i].exactOnsetTime - timingOffset;
			printf("exact [%i] type %i %f, corrected %f\n", i, onsetInfo[i].type, onsetInfo[i].exactOnsetTime, onsetInfo[i].beatTimeToProcess );
		}
	}
}

void RecordedMultipleAudio::calculateTimingAnalysis(){
	
	for (int i = 0;i < onsetInfo.size();i++){
		drumTimingAnalyser.updateCostToPoint(onsetInfo[i].beatTimeToProcess, i);
		//updatecounter is the beat position for this note event - can be used to do other kinds of durations than just simple beats
		
		drumTimingAnalyser.beatPosition.push_back(onsetInfo[i].exactOnsetTime);
	}
	drumTimingAnalyser.processPathHistory();
	drumTimingAnalyser.calculateTempoLimits();

	getErrorTimesFromAnalysis();
	
	alternativeKickRelativeAnalysis();
	
	exportErrorInformation();
	
	displayKickRelativeMedianErrors();//NB messes ther order of these
	
	//this was how we did it in multimatch program
//	timer.processPathHistory();
//	timer.calculateTempoLimits();
//	timer.exportTimingData();
//	timer.exportProcessedBeatTimes(firstNoteTime);
}

void RecordedMultipleAudio::getErrorTimesFromAnalysis(){
	printf("\nDrumTimingLoader: get error times from analysis!!!\n");
	setUpErrorsByMetricalPosition();
	//gets the errors from the drum timing analyser (i.e. ISMIR paper code) and attributes these to the onsets
	for (int i = 0;i < drumTimingAnalyser.timingData.size();i++){
		onsetInfo[i].error = drumTimingAnalyser.timingData[i][5];
		onsetInfo[i].clickTime = drumTimingAnalyser.timingData[i][1] + timingOffset;//this is where teh timing analyser placed the click times
		errorsByMetricalPosition[onsetInfo[i].metricalPosition].push_back(onsetInfo[i].error);
		printf("beat %i metrical posn %i exact beat time %f error %i\n", i, onsetInfo[i].metricalPosition, onsetInfo[i].exactOnsetTime, (int)onsetInfo[i].error);
	}
	displayMedianErrors();
}


void RecordedMultipleAudio::alternativeKickRelativeAnalysis(){
	printf("\n\nAnalysis Relative to the Kick Drum on the ONE\n");
	//this sees kicks as ON the beat, looks at relative error of the three other beats if we chop the bar evenly
	
	double recentBeatTime, currentTempo;	
	kickRelativeErrors.clear();
	kickRelativeClickTimes.clear();
	
	for (int i = 0;i < drumTimingAnalyser.timingData.size();i++){
		int beatPosition = i%4;
		if (beatPosition == 0){
			recentBeatTime = onsetInfo[i].exactOnsetTime;
			if (i+4 < onsetInfo.size())
				currentTempo =  (onsetInfo[i+4].exactOnsetTime - onsetInfo[i].exactOnsetTime)/4.0;
			printf("new beat time %f tempo %f\n", recentBeatTime, currentTempo);
		}
		double error = onsetInfo[i].exactOnsetTime - (recentBeatTime + beatPosition*currentTempo);
		printf("Beat %i KR Predicted Beat %f Actual exact %f KRerror %f DTerror %i\n", beatPosition, (recentBeatTime + beatPosition*currentTempo), onsetInfo[i].exactOnsetTime, error, (int)onsetInfo[i].error); 
		kickRelativeErrors.push_back(error);
		kickRelativeClickTimes.push_back(recentBeatTime + beatPosition*currentTempo);
	}
}

#pragma label -exportInfo
void RecordedMultipleAudio::exportErrorInformation(){
	printf("Export final timing information\n");
	
	ofstream ofs(infoFilepath.c_str());
	for (int i = 0;i < onsetInfo.size() && kickRelativeErrors.size();i++){// drumTimingAnalyser.timingData.size()
		ofs << i << "," << (i%4) << "," << onsetInfo[i].exactOnsetTime << ",";
		ofs << onsetInfo[i].clickTime << "," << onsetInfo[i].error << ",";//the error for the ISMIR timing analyser
		ofs << kickRelativeClickTimes[i] << "," << kickRelativeErrors[i];//the click and error for beats evenly between kicks
		ofs << endl;
	}
	
}

void RecordedMultipleAudio::printKickRelativeErrors(){
	for (int i = 0;i < kickRelativeErrors.size();i++){
	printf("KR error [%i] : %.1f\n", i, kickRelativeErrors[i]);
	}
}

void RecordedMultipleAudio::setUpErrorsByMetricalPosition(){
	//clear this matrix
	errorsByMetricalPosition.clear();
	for (int i = 0;i < 4;i++){
		DoubleVector v;
		errorsByMetricalPosition.push_back(v);
	}
	
}

void RecordedMultipleAudio::displayMedianErrors(){
	printf("Medians of the Decoded Tempo variations\n");
	for (int i = 0;i < 4;i++){
		//printErrorsForMetricalPosition(i);
		std::sort(errorsByMetricalPosition[i].begin(), errorsByMetricalPosition[i].end());//sort vector
		double median = errorsByMetricalPosition[i][(int)(errorsByMetricalPosition[i].size()/2)];
		printf("median for metrical position %i is %f\n", i, median);
	}
}


void RecordedMultipleAudio::displayKickRelativeMedianErrors(){
	printf("Medians of the KR variations\n");
	
	DoubleVector tmpKRErrors;

	
	for (int i = 0;i < 4;i++){
		
		tmpKRErrors.clear();
		int index = 0;
		
		while (index+i < kickRelativeErrors.size()) {
			tmpKRErrors.push_back(kickRelativeErrors[index + i]);
			index += 4;
		}
		
//		for (int k = 0;k < tmpKRErrors.size();k++)
//		printf("kr %i [%i] = %f\n", i, k, tmpKRErrors[k]);						  
								  
		//printErrorsForMetricalPosition(i);
		std::sort(tmpKRErrors.begin(), tmpKRErrors.end());//sort vector
		
//		for (int k = 0;k < tmpKRErrors.size();k++)
//			printf("sorted kr %i [%i] = %f\n", i, k, tmpKRErrors[k]);
		
		
		double median = tmpKRErrors[(int)(tmpKRErrors.size()/2)];
		printf("median for metrical position %i is %f\n", i, median);
	}
}

void RecordedMultipleAudio::printErrorsForMetricalPosition(const int& i){
	for (int k = 0;k < errorsByMetricalPosition[i].size();k++){
		printf("metrical posn %i, [%i] = %f\n", i, k,  errorsByMetricalPosition[i][k]);
	}
}

#pragma mark -drawTracks

void RecordedMultipleAudio::drawTracks(){
	if (drawWindow == 0){
		for (int i = 0;i < numberOfAudioTracks;i++){		
			loadedAudioFiles[i].draw();
		}
	} else {
		drumTimingAnalyser.drawTempoCurve();
	}
}

#pragma mark -update 
void RecordedMultipleAudio::updatePosition(){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].updateToPlayPosition();
}

void RecordedMultipleAudio::updatePositionToMillis(const double& millis){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].updateToMillisPosition(millis);
}

void RecordedMultipleAudio::updatePlaybackPositionToMillis(const double& millis){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].updatePlaybackPositionToMillis(millis);
}

void RecordedMultipleAudio::switchScreens(){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].switchScreens();
}


void RecordedMultipleAudio::togglePlay(){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].togglePlay();
}

void RecordedMultipleAudio::stop(){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].stop();
}


void RecordedMultipleAudio::printInfo(){
	loadedAudioFiles[0].fileLoader.onsetDetect.printChromaInfo();
	loadedAudioFiles[0].printEvents();
}

void RecordedMultipleAudio::windowResized(const int& w, const int& h){
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].windowResized(w, h);
}

void RecordedMultipleAudio::zoomIn(){
	if (drawWindow == 0){
		printf("zoom in\n");
		for (int i = 0;i < numberOfAudioTracks;i++)
			loadedAudioFiles[i].fileLoader.zoomIn();	
	}
	
	if (drawWindow == 1)
		drumTimingAnalyser.zoomIn();//numberOfPointsPerPage /= 2;
}

void RecordedMultipleAudio::zoomOut(){
		printf("zoom out\n");
	for (int i = 0;i < numberOfAudioTracks;i++)
		loadedAudioFiles[i].fileLoader.zoomOut();
	
	if (drawWindow == 1)
		drumTimingAnalyser.zoomOut();//numberOfPointsPerPage *= 2;
	
}