view jnmr/CannamMidiFileLoader.cpp @ 52:13194a9dca77 tip

Added exporting of image and text data
author Andrew N Robertson <andrew.robertson@eecs.qmul.ac.uk>
date Tue, 17 Jul 2012 22:13:10 +0100
parents 6cd3e0075adf
children
line wrap: on
line source
/*
 *  CannamMidiFileLoader.cpp
 *  midi-score-follower
 *
 *  Created by Andrew on 19/08/2011.
 *  Copyright 2011 QMUL. All rights reserved.
 *
 */

#include "MIDIFileReader.h"
#include "CannamMidiFileLoader.h"

CannamMidiFileLoader::CannamMidiFileLoader(){
	chopBeginning = false;
	firstTickTime = 0;
	printMidiInfo = false;
}

int CannamMidiFileLoader::loadFile(std::string& filename, midiEventHolder& myMidiEvents){
		
		noteOnIndex = 0;
		firstTickTime = 0;
		myMidiEvents.clearAllEvents();
		beatsPerMeasure = 4;
		numberOfBeatsAtLastPosition = 0;
		lastBeatPosition = 0;
	
		setTempoFromMidiValue(500000, myMidiEvents);//default is 120bpm
	
		myMidiEvents.pulsesPerQuarternote = 240;//default
		//myMidiEvents.measureVector.push_back(0);
		//int main(int argc, char **argv)
		//{
		//	if (argc != 2) {
		//		cerr << "Usage: midifile <file.mid>" << endl;
		//		return 1;
		//	}
		
	//	std::string filename = midiFileName;//argv[1];
		
		MIDIFileReader fr(filename);
	
		if (!fr.isOK()) {
			std::cerr << "Error: " << fr.getError().c_str() << std::endl;
			return 1;
		}
		
		MIDIComposition c = fr.load();
		
		switch (fr.getFormat()) {
			case MIDI_SINGLE_TRACK_FILE: cout << "Format: MIDI Single Track File" << endl; break;
			case MIDI_SIMULTANEOUS_TRACK_FILE: cout << "Format: MIDI Simultaneous Track File" << endl; break;
			case MIDI_SEQUENTIAL_TRACK_FILE: cout << "Format: MIDI Sequential Track File" << endl; break;
			default: cout << "Format: Unknown MIDI file format?" << endl; break;
		}
		
		cout << "Tracks: " << c.size() << endl;
		
		int td = fr.getTimingDivision();
		if (td < 32768) {
				if (printMidiInfo)
					cout << "Timing division: " << fr.getTimingDivision() << " ppq" << endl;
			myMidiEvents.pulsesPerQuarternote = fr.getTimingDivision();
			ticksPerMeasure = myMidiEvents.pulsesPerQuarternote * 4;//default setting
			
		} else {
			int frames = 256 - (td >> 8);
			int subframes = td & 0xff;
				if (printMidiInfo)
					cout << "SMPTE timing: " << frames << " fps, " << subframes << " subframes" << endl;
		}
		
		for (MIDIComposition::const_iterator i = c.begin(); i != c.end(); ++i) {
				if (printMidiInfo)
					cout << "Start of track: " << i->first+1 << endl;
			
			for (MIDITrack::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
				
				unsigned int t = j->getTime();
				int ch = j->getChannelNumber();
				
				if (j->isMeta()) {
					int code = j->getMetaEventCode();
					string name;
					bool printable = true;
					switch (code) {
							
						case MIDI_END_OF_TRACK:
							cout << t << ": End of track" << endl;
							break;
							
						case MIDI_TEXT_EVENT: name = "Text"; break;
						case MIDI_COPYRIGHT_NOTICE: name = "Copyright"; break;
						case MIDI_TRACK_NAME: name = "Track name"; break;
						case MIDI_INSTRUMENT_NAME: name = "Instrument name"; break;
						case MIDI_LYRIC: name = "Lyric"; break;
						case MIDI_TEXT_MARKER: name = "Text marker"; break;
						case MIDI_SEQUENCE_NUMBER: name = "Sequence number"; printable = false; break;
						case MIDI_CHANNEL_PREFIX_OR_PORT: name = "Channel prefix or port"; printable = false; break;
						case MIDI_CUE_POINT: name = "Cue point"; break;
						case MIDI_CHANNEL_PREFIX: name = "Channel prefix"; printable = false; break;
						case MIDI_SEQUENCER_SPECIFIC: name = "Sequencer specific"; printable = false; break;
						case MIDI_SMPTE_OFFSET: name = "SMPTE offset"; printable = false; break;
							
						case MIDI_SET_TEMPO:
						{
							int m0 = j->getMetaMessage()[0];
							int m1 = j->getMetaMessage()[1];
							int m2 = j->getMetaMessage()[2];
							long tempo = (((m0 << 8) + m1) << 8) + m2;
								//if (printMidiInfo)
								cout << t << ": Tempo: " << 60000000.0 / double(tempo) << endl;
							//setTempoFromMidiValue(tempo, myMidiEvents);
							DoubleVector tmp;

					/*		
							double lastTickInMillis = 0;
							double millisTimeNow = lastTickInMillis;
							int tickInterval = 0;
							if (myMidiEvents.periodValues.size() > 0){
								lastTickInMillis = myMidiEvents.periodValues[myMidiEvents.periodValues.size()-1][2];
								tickInterval = (t - firstTickTime) - myMidiEvents.periodValues[myMidiEvents.periodValues.size()-1][0];
								millisTimeNow = lastTickInMillis + (myMidiEvents.periodValues[myMidiEvents.periodValues.size()-1][1]*tickInterval);
								
							}
							
							if (!chopBeginning)
								tmp.push_back(t);
							else
								tmp.push_back(t - firstTickTime);
							
							tmp.push_back(60000000.0 / double(tempo));	
							tmp.push_back(millisTimeNow);
							myMidiEvents.periodValues.push_back(tmp);
					 */
							
							double lastTickInMillis = 0;
							double millisTimeNow = lastTickInMillis;
							int tickInterval = 0;
							if (myMidiEvents.periodValues.size() > 0){
								lastTickInMillis = myMidiEvents.periodValues[myMidiEvents.periodValues.size()-1][2];
								tickInterval = t  - myMidiEvents.periodValues[myMidiEvents.periodValues.size()-1][0];
								millisTimeNow = lastTickInMillis + (myMidiEvents.periodValues[myMidiEvents.periodValues.size()-1][1]*tickInterval);
								
							}
							
							tmp.push_back(t);
							
							
							tmp.push_back(60000000.0 / double(tempo));	
							double tmpTempoVal = 60000000.0 / double(tempo);
							tmp.push_back(millisTimeNow);
							//if (tmpTempoVal > 180.0)
							myMidiEvents.periodValues.push_back(tmp);
							
							printf("tick[%i]: TEMPO %d tempoVal %f : time now %f\n", t, tempo, tmpTempoVal, millisTimeNow);
							//printf("period double is %f\n", myMidiEvents.period);
						
						}
							break;
							
						case MIDI_TIME_SIGNATURE:
						{
							int numerator = j->getMetaMessage()[0];
							int denominator = 1 << (int)j->getMetaMessage()[1];
							
							newTimeSignature(t, numerator, denominator, myMidiEvents);
							
							//if (printMidiInfo)
							cout << t << ": Time signature: " << numerator << "/" << denominator << endl;
							printf(" ticks %i Time signature: %i by %i \n", t,  numerator , denominator );
						}
							
						case MIDI_KEY_SIGNATURE:
						{
							int accidentals = j->getMetaMessage()[0];
							int isMinor = j->getMetaMessage()[1];
							bool isSharp = accidentals < 0 ? false : true;
							accidentals = accidentals < 0 ? -accidentals : accidentals;
							if (printMidiInfo)
								cout << t << ": Key signature: " << accidentals << " "
								<< (isSharp ?
								(accidentals > 1 ? "sharps" : "sharp") :
								(accidentals > 1 ? "flats" : "flat"))
								<< (isMinor ? ", minor" : ", major") << endl;
						}
							
					}
					
					
					if (name != "") {
						if (printable) {
							cout << t << ": File meta event: code " << code
							<< ": " << name << ": \"" << j->getMetaMessage()
							<< "\"" << endl;
						} else {
							cout << t << ": File meta event: code " << code
							<< ": " << name << ": ";
							for (int k = 0; k < j->getMetaMessage().length(); ++k) {
								cout << (int)j->getMetaMessage()[k] << " ";
							}
						}
					}
					continue;
				}
				double newBeatLocation = 0;
				switch (j->getMessageType()) {
						
					case MIDI_NOTE_ON:
						if (printMidiInfo)
						cout << t << ": Note: channel " << ch
						<< " duration " << j->getDuration()
						<< " pitch " << j->getPitch()
						<< " velocity " << j->getVelocity() 
						<< "event time " << myMidiEvents.getEventTimeMillis(t) << endl;
						
						newBeatLocation = getBeatPositionForTickCount(t, myMidiEvents);
						
					//	printf("%i channel %i durn %i pitch %i vel %i event time %f beat pos %f\n", t, ch, (int)j->getDuration(), (int)j->getPitch(), (int)j->getVelocity(), myMidiEvents.getEventTimeMillis(t)
					//		   , newBeatLocation);
						
						
						
					//	printf("Beat location %3.2f\n", newBeatLocation);
						
						/*
						if (noteOnIndex == 0  || t < firstTickTime){
							//easier just to pick the minimum
							firstTickTime = t;
							printf("FIRST TICK TIME %i\n", firstTickTime);
						}
						
						noteOnIndex++;
						*/
						v.clear();
						
					//	printf("note on at %i\n", t);
						
						//if (!chopBeginning)
							v.push_back(t);
						//else
						//	v.push_back(t - firstTickTime);
						
						v.push_back(j->getPitch());
						v.push_back(j->getVelocity());
						v.push_back(j->getDuration());
						myMidiEvents.recordedNoteOnMatrix.push_back(v);
						myMidiEvents.noteOnMatches.push_back(false);
						myMidiEvents.beatPositions.push_back(newBeatLocation);
						
						
						break;
						
					case MIDI_POLY_AFTERTOUCH:
						if (printMidiInfo)
						cout << t << ": Polyphonic aftertouch: channel " << ch
						<< " pitch " << j->getPitch()
						<< " pressure " << j->getData2() << endl;
						break;
						
					case MIDI_CTRL_CHANGE:
					{
						int controller = j->getData1();
						string name;
						switch (controller) {
							case MIDI_CONTROLLER_BANK_MSB: name = "Bank select MSB"; break;
							case MIDI_CONTROLLER_VOLUME: name = "Volume"; break;
							case MIDI_CONTROLLER_BANK_LSB: name = "Bank select LSB"; break;
							case MIDI_CONTROLLER_MODULATION: name = "Modulation wheel"; break;
							case MIDI_CONTROLLER_PAN: name = "Pan"; break;
							case MIDI_CONTROLLER_SUSTAIN: name = "Sustain"; break;
							case MIDI_CONTROLLER_RESONANCE: name = "Resonance"; break;
							case MIDI_CONTROLLER_RELEASE: name = "Release"; break;
							case MIDI_CONTROLLER_ATTACK: name = "Attack"; break;
							case MIDI_CONTROLLER_FILTER: name = "Filter"; break;
							case MIDI_CONTROLLER_REVERB: name = "Reverb"; break;
							case MIDI_CONTROLLER_CHORUS: name = "Chorus"; break;
							case MIDI_CONTROLLER_NRPN_1: name = "NRPN 1"; break;
							case MIDI_CONTROLLER_NRPN_2: name = "NRPN 2"; break;
							case MIDI_CONTROLLER_RPN_1: name = "RPN 1"; break;
							case MIDI_CONTROLLER_RPN_2: name = "RPN 2"; break;
							case MIDI_CONTROLLER_SOUNDS_OFF: name = "All sounds off"; break;
							case MIDI_CONTROLLER_RESET: name = "Reset"; break;
							case MIDI_CONTROLLER_LOCAL: name = "Local"; break;
							case MIDI_CONTROLLER_ALL_NOTES_OFF: name = "All notes off"; break;
						}
						if (printMidiInfo)
						cout << t << ": Controller change: channel " << ch
						<< " controller " << j->getData1();
						if (name != "") cout << " (" << name << ")";
						cout << " value " << j->getData2() << endl;
					}
						break;
						
					case MIDI_PROG_CHANGE:
						if (printMidiInfo)
						cout << t << ": Program change: channel " << ch
						<< " program " << j->getData1() << endl;
						break;
						
					case MIDI_CHNL_AFTERTOUCH:
						if (printMidiInfo)
						cout << t << ": Channel aftertouch: channel " << ch
						<< " pressure " << j->getData1() << endl;
						break;
						
					case MIDI_PITCH_BEND:
						if (printMidiInfo)
						cout << t << ": Pitch bend: channel " << ch
						<< " value " << (int)j->getData2() * 128 + (int)j->getData1() << endl;
						break;
						
					case MIDI_SYSTEM_EXCLUSIVE:
						if (printMidiInfo)
						cout << t << ": System exclusive: code "
						<< (int)j->getMessageType() << " message length " <<
						j->getMetaMessage().length() << endl;
						break;
						
						
				}
				
				
			}
			
			
		}
//	if (printMidiInfo)
//	myMidiEvents.printRecordedEvents();
	
	//printMeasuresSoFar(myMidiEvents);
	
	if (myMidiEvents.recordedNoteOnMatrix.size() > 0){
		
		printf("END FILE MEASURE UPDATE\n");
		updateMeasureToTickPosition(myMidiEvents.recordedNoteOnMatrix[myMidiEvents.recordedNoteOnMatrix.size()-1][0], myMidiEvents);
	//	printMeasuresSoFar(myMidiEvents);
	}
	
//	printf("|||||||||||||||||||||| \n\n\n\n\n\n\n");
	myMidiEvents.reorderMatrixFromNoteTimes(myMidiEvents.recordedNoteOnMatrix);
	myMidiEvents.correctTiming(myMidiEvents.recordedNoteOnMatrix);
	myMidiEvents.doublecheckOrder(myMidiEvents.recordedNoteOnMatrix);

	if (chopBeginning)
		chopBeginningfromEvents(myMidiEvents);
	
	createEventTiming(myMidiEvents);
	
	printf("BEFORE DOING MEASURE UPDATE\n first tick pos is %i\n", firstTickTime);
//	printMeasuresSoFar(myMidiEvents);
	if (chopBeginning)
		correctMeasuresTiming(myMidiEvents);
	
//	if (printMidiInfo)
//	myMidiEvents.printRecordedEvents();	
	
	printf("Duration of MIDI file is %f \n", myMidiEvents.recordedEventTimes[myMidiEvents.recordedEventTimes.size()-1]);
	printf("And first note offset is %i ticks == %f msec \n", firstTickTime, firstNoteTime);
	printUpToIndex(50, myMidiEvents);
	//printMeasuresSoFar(myMidiEvents);
	
}//end cannam midi main
	

void CannamMidiFileLoader::chopBeginningfromEvents(midiEventHolder&  myMidiEvents){

	firstTickTime = myMidiEvents.recordedNoteOnMatrix[0][0];
	firstNoteTime = myMidiEvents.getEventTimeMillis(myMidiEvents.recordedNoteOnMatrix[0][0]);
	myMidiEvents.firstEventOffsetTimeMillis = firstNoteTime;
	
	printf("BEFORE chop - FIRST NOTE IS %f and tick %i\n", firstNoteTime, firstTickTime);
//	myMidiEvents.printRecordedEvents();
	
	for (int i = 0;i < myMidiEvents.recordedNoteOnMatrix.size();i++){
		myMidiEvents.recordedNoteOnMatrix[i][0] -= firstTickTime;
	}
	
	//here are period values, but we dont use them
	for (int i = 0;i < myMidiEvents.periodValues.size();i++){
		myMidiEvents.periodValues[i][0] -= firstTickTime;
		myMidiEvents.periodValues[i][2] -= firstNoteTime;
		printf("[%i], period values(%i):: @ %f is %f\n", (int)myMidiEvents.periodValues[i][0], i, myMidiEvents.periodValues[i][2], myMidiEvents.periodValues[i][1]);
	}
	
	printf("\n\n\nAFTER chop - \n");

//myMidiEvents.printRecordedEvents();
	
}

void CannamMidiFileLoader::createEventTiming( midiEventHolder& myMidiEvents){
	
	long t;
//	t = myMidiEvents.recordedNoteOnMatrix[0][0];
//	firstTickTime = t;
//	firstNoteTime = myMidiEvents.getEventTimeMillis(firstTickTime);
	
	for (int i = 0; i < myMidiEvents.recordedNoteOnMatrix.size();i++){
		t = myMidiEvents.recordedNoteOnMatrix[i][0];
		
		if (!chopBeginning)
			myMidiEvents.recordedEventTimes.push_back(myMidiEvents.getEventTimeMillis(t));
		else {
			myMidiEvents.recordedEventTimes.push_back(myMidiEvents.getEventTimeMillis(t));// - firstticktime			
		}
	}
	
	
	
}

void CannamMidiFileLoader::setTempoFromMidiValue(long tempo,  midiEventHolder& myMidiEvents){
	myMidiEvents.tempo = 60000000.0 / double(tempo);
	myMidiEvents.period = double(tempo)/1000.0;
	myMidiEvents.ticksFactor = myMidiEvents.pulsesPerQuarternote / myMidiEvents.period;
}
	

void CannamMidiFileLoader::newTimeSignature(int ticks, int numerator, int denominator, midiEventHolder& myMidiEvents){

	updateMeasureToTickPosition(ticks, myMidiEvents);
	
	beatsPerMeasure = 4.0 * (float)numerator / denominator;
	ticksPerMeasure = myMidiEvents.pulsesPerQuarternote * 4 * numerator / denominator;
	
}

void CannamMidiFileLoader::updateMeasureToTickPosition(int ticks,  midiEventHolder& myMidiEvents){
	printf("update measure at tick pos %i at tpm %i\n", ticks, ticksPerMeasure);
	
	int measureVectorSize = myMidiEvents.measureVector.size();
	int lastMeasurePosition = 0;
//	if (chopBeginning)
//		lastMeasurePosition = -1*firstTickTime;
	
	if (measureVectorSize > 0)
		lastMeasurePosition = myMidiEvents.measureVector[measureVectorSize-1];

	
while (lastMeasurePosition < ticks){
	//update
	lastMeasurePosition += ticksPerMeasure;
	numberOfBeatsAtLastPosition += beatsPerMeasure;
	lastBeatPosition = lastMeasurePosition;
	myMidiEvents.measureVector.push_back(lastMeasurePosition);
	
//	cout << "MEASURE " << myMidiEvents.measureVector.size()-1 << " is " << lastMeasurePosition << endl;
//	printf("MEASURE %i is %i \n", (int)myMidiEvents.measureVector.size()-1 , lastMeasurePosition);
	}
}


void CannamMidiFileLoader::correctMeasuresTiming(midiEventHolder& myMidiEvents){
	//correct measures
	for (int i = 0;i <myMidiEvents.measureVector.size();i++){
		myMidiEvents.measureVector[i] -= firstTickTime;
	}
	printf("AFTER DOING MEASURE UPDATE\n");
	//printMeasuresSoFar(myMidiEvents);
}

void CannamMidiFileLoader::printMeasuresSoFar(midiEventHolder& myMidiEvents){
	for (int i = 0;i < myMidiEvents.measureVector.size();i++){
		printf("measure [%i] at %i\n", i, myMidiEvents.measureVector[i]);
	}
}

double CannamMidiFileLoader::getBeatPositionForTickCount(long t, midiEventHolder& myMidiEvents){
	int lastMeasurePosition = 0;
	if (myMidiEvents.measureVector.size() > 0)
		lastMeasurePosition = myMidiEvents.measureVector[myMidiEvents.measureVector.size()-1];
	
	int ticksSinceBeatCounted = t - lastMeasurePosition;
	double beats = numberOfBeatsAtLastPosition;
	beats += ticksSinceBeatCounted * beatsPerMeasure / ticksPerMeasure;
//	printf("ticks since %i, beat per measure %f at %i tick per measure :: %f beats\n", ticksSinceBeatCounted, beatsPerMeasure, ticksPerMeasure, beats);
	return beats;
}


void  CannamMidiFileLoader::printUpToIndex(const int& index, midiEventHolder& myMidiEvents){
	for (int i = 0;i < index;i++){
		printf("Beat pos %f MIIDI %i event time %f\n", myMidiEvents.beatPositions[i], myMidiEvents.recordedNoteOnMatrix[i][1], myMidiEvents.recordedEventTimes[i]);
	}
}