#include "midiPlayer.h"

static int idCounter = 1;


//-----------------------------------------------------------
midiPlayer::midiPlayer(){
	bMidiInited = false;
	currentChannel = 0;
	currentInstrument = 0;
}

//-----------------------------------------------------------
void midiPlayer::setInstrument(int instrument){
	instrument = CLAMP(instrument, 0, 127);
	if (bMidiInited == true){
		currentInstrument = instrument;
  	}
}

//-----------------------------------------------------------
int midiPlayer::getInstrument(){
	return currentInstrument;
}

//-----------------------------------------------------------
void midiPlayer::setChannel(int channel){
	channel = CLAMP(channel, 0, 15);
	if (bMidiInited == true){
		currentChannel = channel;
	}
}

//-----------------------------------------------------------
int midiPlayer::getChannel(){
	return currentChannel;
}

//-----------------------------------------------------------
void midiPlayer::setOverallVolume(int volume){

	volume = CLAMP(volume, 0, 127);
	
	if (bMidiInited == true){
		message.clear();
		message.push_back( 176 );
		message.push_back( 7 );
		message.push_back( volume );
		midiout->sendMessage( &message );
	}
}

//-----------------------------------------------------------
int midiPlayer::percusiveNoteOn(int instrument, int volume){


	
	volume 		= CLAMP(volume, 0, 127);
	instrument 	= CLAMP(instrument, 27, 87); 
	
											// trial and error
											// maybe this is different
											// per sound card
											// I found slightly more sounds (27-87, as opposed to 34-80)
											// then were listed here:
											// http://www.sfu.ca/sca/Manuals/247/midi/Wavetable.html
											// we need feedback on this....
											
	int prevChannel = getChannel();
	
	setChannel(9);							// this is the "percussion channel"
											// on all sound cards?
											// on all platforms?
											// ?

	message.clear();
	message.push_back( 144 + currentChannel );
	message.push_back( instrument );							
	message.push_back( volume );
	midiout->sendMessage( &message );
	
	setChannel(prevChannel);				// back to prev channel 

}


//-----------------------------------------------------------
int midiPlayer::noteOn(int note, int volume){
	
	if (bMidiInited == true){
		
		//note 		= CLAMP(volume, 0, 127);
		//volume 		= CLAMP(volume, 0, 127);
	
		
		deleteNote(currentChannel,currentInstrument, note);
		
		message.clear();
		message.push_back( 192  + currentChannel );
		message.push_back( currentInstrument );
		midiout->sendMessage( &message );
		
		message.clear();
		message.push_back( 144 + currentChannel );
		message.push_back( note );
		message.push_back( volume );
		midiout->sendMessage( &message );
		
		midiNote newNote;
		newNote.channel 		= currentChannel;
		newNote.instrument 		= currentInstrument;
		newNote.note 			= note;
		newNote.volume 			= volume;
		newNote.idNumber 		= idCounter;
		newNote.startTime 		= ofGetElapsedTimef();		// time in float
		newNote.bHasDuration 	= false;					// time in float
		
		idCounter ++;
		idCounter %= 1000000000; 						// 1 billion notes served.
		
		notes.push_back(newNote);
		
		return newNote.idNumber;
  	} else {
  		return -1;
  	}
}


//-----------------------------------------------------------
int midiPlayer::noteOnForDuration(int note, int volume, float duration){
	if (bMidiInited == true){
		
		deleteNote(currentChannel,currentInstrument, note);
		
		message.clear();
		message.push_back( 192  + currentChannel );
		message.push_back( currentInstrument );
		midiout->sendMessage( &message );
		
		message.clear();
		message.push_back( 144 + currentChannel );
		message.push_back( note );
		message.push_back( volume );
		midiout->sendMessage( &message );
		
		midiNote newNote;
		newNote.channel 		= currentChannel;
		newNote.instrument 		= currentInstrument;
		newNote.note 			= note;
		newNote.volume 			= volume;
		newNote.idNumber 		= idCounter;
		newNote.startTime 		= ofGetElapsedTimef();		// time in float
		newNote.bHasDuration 	= true;					// time in float
		newNote.duration 		= duration;					// time in float
		
		idCounter ++;
		idCounter %= 1000000000; 						// 1 billion notes served.
		
		notes.push_back(newNote);
		
		return newNote.idNumber;
  	} else {
  		return -1;
  	}
}


//-----------------------------------------------------------
void midiPlayer::noteOff(int channel, int instrument, int note){
	if (bMidiInited == true){
		
		message.clear();
		message.push_back( 192  + channel );
		message.push_back( instrument );
		midiout->sendMessage( &message );
		
		message.clear();
		message.push_back( 128  + channel );
		message.push_back( note );
		message.push_back( 40 );
		midiout->sendMessage( &message );
		
		// do any of the notes in the vector match this one?
		// if so, get rid of them!
		deleteNote(channel, instrument, note);
  	}
}

//-----------------------------------------------------------
void midiPlayer::noteOff(int idNumber){

	std::vector <midiNote>::iterator it;
	for(it = notes.begin(); it != notes.end();){
  		if((it->idNumber == idNumber)){
    		
			message.clear();
			message.push_back( 192  + it->channel );
			message.push_back( it->instrument );
			midiout->sendMessage( &message );

			message.clear();
			message.push_back( 128  + it->channel );
			message.push_back( it->note );
			message.push_back( 40 );
			midiout->sendMessage( &message );
			
    		it = notes.erase(it);
    	
    	} else { 
    		++it;
    	}
	}

}

//-----------------------------------------------------------
void midiPlayer::update(){

	int nNotes = notes.size();
	
	for (int i = 0; i < nNotes; i++){
		int id = notes[i].idNumber;	
		if (notes[i].bHasDuration){
			float endTime = notes[i].startTime + notes[i].duration;
			
			if (ofGetElapsedTimef() > endTime){
				noteOff(id);
			}	
		}
	}
}

//-----------------------------------------------------------
bool midiPlayer::deleteNote(int channel, int instrument, int note){

	bool bDeleted = false;
	
	std::vector <midiNote>::iterator it;
	for(it = notes.begin(); it != notes.end();){
  		if(	(it->channel == channel) &&
  			(it->instrument == instrument) &&
  			(it->note == note)){
    		it = notes.erase(it);
    		bDeleted = true;
    	} else { 
    		++it;
    	}
	}
	
	return bDeleted;	// no match
}		



//-----------------------------------------------------------
void midiPlayer::turnAllNotesOff(){
	if (bMidiInited == true){
		message.clear();
		message.push_back( 176 );  // control change
		message.push_back( 123 );  // "all notes off"
		message.push_back( 0 );
  		midiout->sendMessage( &message );
  		notes.clear();
	}
}

//-----------------------------------------------------------
void midiPlayer::pitchBend(int channel, float range){
	
	int output			= (int)((range+1.0)/2.0*16383 + 0.5);
	int MSB 			= (int)((output >> 7) & 0x7f);
	int LSB 			= (int)(output & 0x7f);
	
	message.clear();
	message.push_back( 0xe0 | (channel & 0x0f) );  // channel + pitch bend
	message.push_back( LSB );  // "all notes off"
	message.push_back( MSB );
	midiout->sendMessage( &message );
	
}


//-----------------------------------------------------------
void midiPlayer::setupMidi(int port){

	midiout 		= 0;
	bMidiInited 	= false;	
	//-----------------------------
	try {
		midiout = new RtMidiOut();
	}	catch (RtError &error) {
		error.printMessage();
		return;
	}
	//-----------------------------
	// Call function to select port.
	try {
		
		int nPorts = midiout->getPortCount();
		if (nPorts == 0){
			printf ("ofMidi - sorry, you do not have any ports available\n");
		}
		
		if (port == 0){	// choose first one (default?)
			 midiout->openPort( 0 );	
		} else {
			if ((port - 1) < nPorts){
			 	midiout->openPort( port-1 );
			} else {
				printf ("ofMidi - sorry, your requested port %i is larger then the total number of available ports % \n", port, nPorts);
				return;
			}
		}
	} catch (RtError &error) {
 		error.printMessage();
		return;
	}
	
	bMidiInited = true;

}

//-----------------------------------------------------------
void midiPlayer::listPorts(){

	RtMidiOut * midioutForList;
	try {
		midioutForList = new RtMidiOut();
	}
	catch (RtError &error) {
		error.printMessage();
		return;
	}

	// Check outputs.
	int nPorts = midioutForList->getPortCount();
	std::cout << "\nthere are " << nPorts << " MIDI output ports available.\n";
	 std::string portName;
	for ( unsigned int i=0; i<nPorts; i++ ) {
		try {
		  portName = midioutForList->getPortName(i);
		}
		catch (RtError &error) {
		  error.printMessage();
		  goto cleanup;
		}
		std::cout << "output Port #" << i+1 << ": " << portName << '\n';
	}
	std::cout << '\n';
	cleanup:
		delete midioutForList;
}


