/*-------------------------------------------------------*/
/* ProjectOmega                 									*/
/* Written by T.Miles												*/
/* ID: 289175															*/
/* Module:	Midifile Class			 							   */
/* Derived from Input class										*/
/* Handles loading and playing of general midi files 		*/
/*-------------------------------------------------------*/

#include <stdio.h>
#include <misc/systemdefs.h>
#include <misc/defines.h>
#include <iostream.h>
#include <clib/camd_protos.h>
#include <clib/exec_protos.h>
#include <pragmas/camd_pragmas.h>
#include <midi/camd.h>

#include "misc/filereq.h"
#include "input/midifile.h"
#include "misc/progress.h"

#define TRACKSKIP 0

extern struct Library *CamdBase;
 
//--------------------------------------------------------------//
// MidiFileInput Constructor												 //
// Initialise standard stuff												 //
//--------------------------------------------------------------//

MidiFileInput::MidiFileInput(){
	char **array=AllocateValueTypeSpace(3);
	UINT *typeMap=GetTypeMap();
	op_Type=MAKE_TYPE('M','F');
	i_NeedMoreConfig=TRUE;
	mf_tempo=120;
	mf_curIndex=0;
	mf_buffer=NULL;
	mf_Interval=0;
	mf_filename=NULL;	
	oa_Author="T.Miles";
	oa_Version="1.0";
	oa_SetName("MidiFile Input");
	if (array){
		array[0]="Channel";
		typeMap[0]=1;
		array[1]="Value";
		typeMap[1]=2;
		array[2]="Velocity";
		typeMap[2]=2;
		gui_SetValueTypes(); 
	}
}

MidiFileInput::~MidiFileInput(){
	if (mf_buffer) 
		delete [] mf_buffer;
	if (CamdBase){
		if (mLink)
			RemoveMidiLink(mLink);
		if (mNode)
			DeleteMidi(mNode);
		CloseLibrary(CamdBase);
	}
	if (mf_filename)
		delete[] mf_filename;
}

//--------------------------------------------------------------//
// Print a number in hex form                                   //
// Can't use a straight printf as it seems to wait until the end//
// of the output stream.  Damn!!                                //
//--------------------------------------------------------------//

void MidiFileInput::PrintHex(ULONG input){
	UINT length;
	char *text=new char[9];
	sprintf(text, "%x", input);
	length=strlen(text);
	for (UINT n=0;n<length;n++){
		cout << text[n];
	}
	delete[] text;
}

//--------------------------------------------------------------//
// Read a variable length of bytes and sum to a value           //                                    */
// Read the initial byte and keep reading as long as highest bit//
// (0x80) is set                                                //
// Inputs: char buffer & pointer to an offset                   //
// Ouput: Returns value calculated                              //
//        offset will be updated to point to end of length data //
//--------------------------------------------------------------//

ULONG MidiFileInput::ReadVarLen(){
	ULONG value;
	UBYTE c;

	value=(ULONG)mf_buffer[mf_offset++];

	if(value&0x80){ 							// Is the highgest bit set
   	value&=0x7F; 							// Mask hi bit
   	do {
   		c=mf_buffer[mf_offset++];		// Get next char
   		value=(value<<7)+(c&0x7F);
   	} while(c&0x80);				// Keep going until hi bit not set
   }
   return value;
}

//--------------------------------------------------------------//
// Determine what type of chunk (if any) we are looking at in   //
// the buffer                                                   //
// Returns:  A midi chunk struct                                //
//--------------------------------------------------------------//
			  
MidiChunk MidiFileInput::ScanChunks(){
	MidiChunk *pChunk=(MidiChunk *)(mf_buffer+mf_offset);
	return *pChunk;
}

void MidiFileInput::ChangeTempo(ULONG tempo){
	SetTempo(60*1000000/tempo);
	mf_tempo=tempo;
	// Set the timer interval here so that we reduce calculations later
	mf_Interval=((float)mf_tempo/((float)mf_div*PPQ));
}

//--------------------------------------------------------------//
// Determine what type of meta event we are looking at.  Print  //
// an appropriate message and update the buffer offset ot point //
// to end of event                                              //
//--------------------------------------------------------------//

void MidiFileInput::DealWithMeta(){
	UBYTE type;
	UINT length;
	ULONG tempo;
	char *nameBuffer;
	MidiEvent *pEvent;
	
	mf_offset++;
	
	type=mf_buffer[mf_offset++];
	length=ReadVarLen();
	
	if (type>=1 && type<=7){
		// Format the text and add it to our information panel
		nameBuffer=new char[length+11];
		sprintf(nameBuffer,"Track %d: ",curTrack);
		memcpy(nameBuffer+9,&mf_buffer[mf_offset],length);
		nameBuffer[length+9]=NULL;
		gui_AddInfo(nameBuffer);
		delete [] nameBuffer;
	}else{
		switch (type){
		case META_SEQ_NUMBER:
			break;
		case META_TRACK_END:
			break;
		case META_TEMPO:
			tempo=(ULONG)(mf_buffer[mf_offset]) << 16;  // Read 3 byte value!
			tempo+=(ULONG)(mf_buffer[mf_offset+1]) << 8;
			tempo+=(ULONG)(mf_buffer[mf_offset+2]);
			pEvent=new MidiEvent;
			if (pEvent){
				pEvent->me_Tempo=tempo;
				pEvent->me_absTime=mf_absTime;
				pEvent->me_Msg.l[0]=0;
				pEvent->me_Msg.l[1]=mf_curDelta;
				InsertEvent(pEvent);
			}
			else
				cout << "Unable to create event" << endl;
			break;
		case META_SMPTE:
			break;
		case META_TIME_SIG:
			break;
		case META_KEY_SIG:
			break;
		case META_SPECIFIC:
			break;
		default:
		}
	}
	mf_offset+=length;
}

//--------------------------------------------------------------//
//--------------------------------------------------------------//
void MidiFileInput::DealWithSysex(){
	mf_offset++;
	UINT length=ReadVarLen();
	mf_offset+=length;  // Skip over the sysex data (hopefully)
}

//--------------------------------------------------------------//
//--------------------------------------------------------------//

void MidiFileInput::ReadData(MidiEvent *event){
	UBYTE datalength;
	
	
	switch(mf_status & TYPE_MASK){
	case NOTE_OFF:
			datalength=NOTE_OFF_SIZE;
			break;
		case NOTE_ON:
			datalength=NOTE_ON_SIZE;
			break;
		case POLYPHONIC_AT:
			datalength=POLYPHONIC_AT_SIZE;
			break;
		case CONTROL_CHANGE:
			datalength=CONTROL_CHANGE_SIZE;
			break;
		case PROGRAM_CHANGE:
			datalength=PROGRAM_CHANGE_SIZE;
			break;
		case CHANNEL_PRESSURE:
			datalength=CHANNEL_PRESSURE_SIZE;
			break;
		case PITCHBEND:
			datalength=PITCHBEND_SIZE;
			break;
		default:
			datalength=0;
	}
	
	event->me_Msg.mm_Data[2]=0;
	for (int n=0;n<datalength;n++){
		event->me_Msg.mm_Data[n+1]=mf_buffer[mf_offset++]
	}
}	

//--------------------------------------------------------------//
// Determine type and value of midi msgs                        //
//--------------------------------------------------------------//

void MidiFileInput::DealWithMidi(){
	UBYTE newStatus;
	
	newStatus=mf_buffer[mf_offset]; 	// Get the current byte
	MidiEvent *newEvent=new MidiEvent;
		
	if (newStatus>127){			// It's a new midi event
			mf_offset++;				// Increase offset to point at data
			mf_status=newStatus;
	}
	
	mf_channels |= (1<<(mf_status & CHANNEL_MASK));
	if (newEvent){
		newEvent->me_Msg.mm_Time=mf_curDelta;
		newEvent->me_absTime=mf_absTime;
		newEvent->me_Msg.mm_Status=mf_status;
		newEvent->me_Msg.mm_Port=(mf_status & CHANNEL_MASK);
		newEvent->me_Tempo=0;
	
		ReadData(newEvent);
		InsertEvent(newEvent);
	}
	else
		cout << "Unable to create event (DealWithMidi)" << endl;
}

void MidiFileInput::InsertEvent(MidiEvent *newEvent){
	UINT size;
	MidiEvent *pEvent;
	BOOL loop=TRUE;
		
	// Insert the new event into the list in absTime order
	size=midiEventList.GetSize();		
	
	// If we're looking at the first item in the list then we
	// need to reset the list iterator
	// Otherwise we just check the current iterator postion
	if (mf_curIndex==0)
		pEvent=midiEventList.GetItem(mf_curIndex);
	else
		pEvent=midiEventList.GetCurrentItem();

//	if (!pEvent)
//		cout << "Invalid event from list" << endl;

	while(loop && mf_curIndex<size){
		if (pEvent && newEvent->me_absTime<pEvent->me_absTime){
			midiEventList.InsertItem(newEvent,mf_curIndex);
			loop=FALSE;
		}
		else{
			mf_curIndex++;
			pEvent=midiEventList.GetNextItem();
		}
	}
	if (mf_curIndex==size)
		midiEventList.AddItem(newEvent);
}

//--------------------------------------------------------------//
//--------------------------------------------------------------//

void MidiFileInput::ScanEvent(){
	UBYTE eventByte=(UBYTE)mf_buffer[mf_offset];  // Don't increase the offset as DealWithMidi reads it again

	if (eventByte==META_EVENT){
		DealWithMeta();
	}else
	if (eventByte==SYSEX_TYPE1_EVENT || eventByte==SYSEX_TYPE2_EVENT){
		DealWithSysex();
	}else{
		DealWithMidi();
	}
}

//--------------------------------------------------------------//
//--------------------------------------------------------------//
ULONG MidiFileInput::DealWithDiv(LONG division){
	ULONG tfactor=0,w;
	
	if (division < 0)
   {
      /* Real time: find frame rate */
      w=(division >> 8) & 0x007f;
      if(w!=24 && w!=25 && w!=29 && w!=30)
         Error("Non-metrical time specified; not MIDI/SMPTE frame rate",NULL);
      else 
         Error("Non-metrical time; MIDI/SMPTE frame rate\n", NULL);

      /* Real-time: find seconds resolution */
      w=division & 0x007f;
      if(w==4)
         Error("Non-metrical time in quarter seconds (MTC resolution)",NULL);
      else if(w==8 || w==10 || w==80)
         Error("Non-metrical time in 1/nths of a second (bit resolution)",NULL);
      else
         Error("Non-metrical time in 1/nths of a second", NULL);
   }
   else
   {
      tfactor=(ULONG)division;
 
      /* According to "Standrd MIDI Files 1.0", July 1988, page 5 */
      /* para. 4: "...time signature is assumed to be 4/4 and the */
      /*           tempo 120 beats per minute."                   */
 //     tfactor=changetempo(500000L,tfactor,0L); /* One quarter note every half second */
   }
   return tfactor;
}

//--------------------------------------------------------------//
// Load in the file, allocate a buffer then copy the file into  //
// it.																			 //
// Returns: 0 if failed any part of this, filesize if not		 //
//--------------------------------------------------------------//
UINT MidiFileInput::LoadFile(BOOL ask){
	FileReq myFileReq;
	FILE *inputfile;
	UINT filesize=0, listsize;
	char *fname;
	UBYTE len;
	
	if (ask){
		myFileReq.CreateRequester("Select Midi File",NULL,NULL,"#?.mid",FALSE,FALSE);
		if (myFileReq.Cancelled())
			return 0;
			
		fname=myFileReq.GetName();
	}
	else
		fname=mf_filename;
	
	inputfile=fopen(fname,"rb");
		
	if (inputfile){
		delete[] mf_buffer;	
		// Find out the size of the input file, allocate a buffer
		fseek(inputfile,0,SEEK_END);
		filesize=ftell(inputfile);
		fseek(inputfile,0,SEEK_SET);
		mf_buffer=new BYTE[filesize+10]; //  Add some one the end just in case!
		if (mf_buffer)
			fread(mf_buffer,filesize,1,inputfile);
		else{
			Error("Unable to Allocate Midi Buffer","Not Enough Memory?");
			filesize=0;
		}
		fclose(inputfile);

		// If we've used the requester then chances are the name has changed
		// so Copy the filename into the buffer (used for saving config)
		if (ask){
			len=strlen(fname);
			if (mf_filename)
				delete[]mf_filename;
			mf_filename=new char[len+1];
			strcpy(mf_filename,fname);
			mf_filename[len]='\0';		
		}
		
		gui_ClearInfo();
		// Clear the linked list!
		// Actual pointer to midievent gets deleted in listnode deconstructor
		listsize=midiEventList.GetSize();
		for (UINT n=0;n<listsize;n++)
			midiEventList.DeleteItem(0);
		
		// Display the filename in the info panel
		gui_AddInfo(mf_filename);
		gui_AddInfo("");
		op_Configured=TRUE;
		ScanBuffer();
	}
	else{
		Error("File Loading Error","Invalid File or Directory");
		return 0;
	}
	return filesize;
}
	


//--------------------------------------------------------------//
// Configuration unique to this module.								 //
// Deals with loading a new midifile									 //
//--------------------------------------------------------------//
void MidiFileInput::i_Configure(){
	LoadFile(TRUE);
}

//
// The midifile we have is held in memory as a byte array
// We need to scan through this and parse it into a list
// of midievents that can then be sent to the midi port
// 
void MidiFileInput::ScanBuffer(){
	MidiHeader *pHeader;
	MidiChunk currentChunk;
	UINT nextChunk;
	UINT n;
	mf_offset=0;
	curTrack=0;
	ProgressIndicator myProgress;

	currentChunk=ScanChunks();
	// Is the first chunk a header chunk?  i.e. is it a midifile
	if (currentChunk.ch_type==CHUNK_HDR){
		pHeader=(MidiHeader *)mf_buffer;
		// Check that bit 15 isn't set and find delay divisor
		mf_div=DealWithDiv(pHeader->division);
		mf_Interval=(120.0/((float)mf_div*PPQ));		// Set the interval at the default rate of 120bpm

		myProgress.Initialise(pHeader->track_chunks,"Scanning File");
		mf_offset=sizeof(MidiHeader);
			
		// We've found out how many tracks there are so we need to analyse
		// each track
		for (n=TRACKSKIP;n<pHeader->track_chunks;n++){
			currentChunk=ScanChunks();
			mf_offset+=sizeof(MidiChunk);
			// We've got a track so reset the absTime (tracks start at time 0)
			if(currentChunk.ch_type==CHUNK_TRK){
				mf_absTime=0;
				mf_curIndex=0;
				// Find where the next track is
				nextChunk=mf_offset+currentChunk.ch_size;	
				while(mf_offset<nextChunk){
					mf_curDelta=ReadVarLen();		// Find offset
					mf_absTime+=mf_curDelta;
					ScanEvent();						// Parse the event
				}
				mf_offset=nextChunk;
			}
			myProgress.Update();
			curTrack++;
		}
		myProgress.Clearup();
	}
	else{
		op_Configured=FALSE;
		Error("Not a valid MidiFile",NULL);
	}
}

// StartUp
// Add all the tracks for the coordinator to display
BOOL MidiFileInput::StartUp(){
	BOOL result=FALSE;
	
	oi_AddType("Channel",1,1,16);
	oi_AddType("Velocity",2,0,127);
	oi_AddType("NoteOff",3,0,127);
	oi_AddType("NoteOn",4,0,127);
	oi_AddType("Aftertouch",5,0,127);
	oi_AddType("Control",6,0,127);
	oi_AddType("Program",7,0,127);
	oi_AddType("Pressure",8,0,127);
	oa_SetStatus(APP_STATUS_REALTIME,TRUE);	// Say that we want to use the timer

	// This stuff initialises all the midi
	struct TagItem midi_tags[]={
		{MIDI_MsgQueue, 2048},
		{MIDI_SysExSize,1000L},
		{TAG_DONE,	NULL},
	};
	
	struct TagItem link_tags[]={
		{MLINK_Name, (ULONG)"Omega.out"},
		{MLINK_Location, (ULONG)"out.0"},
		{TAG_DONE,		NULL},
	};
	// Create a midi not and link, then add link to node	
	if (CamdBase=OpenLibrary("camd.library",2)){
		if (mNode=CreateMidiA(midi_tags)){
			mLink=AddMidiLinkA(mNode,MLTYPE_Sender,link_tags);
			if (mLink){
				result=TRUE;
			}
		}
	} 	

	if (result)
		return OmegaInput::StartUp();
	else{
		Error("Unable to set up Midi Links",NULL);
		return FALSE;
	}
}

//
// We've woken up from our last sleep so we need to grab the next
// midimsg, play it and send the information of & set the sleeper
// for our next delay
// 
void MidiFileInput::DealWithTimer(){
	MidiMsg *prevMsg=curMsg;
	UBYTE values[6];
	// Send the midi Data
	PutMidiMsg(mLink,curMsg);
	// Find next delay
	if (IsRunning())
		SetNextTimer();
	// Send the value off
	// We use prevMsg here so that if sending off the information
	// takes longer than the timer we will deal with it straight away
	// thus (hopefully) reducing some latency
	values[0]=1;									// Channel type
	values[1]=prevMsg->mm_Port+1;				// Channel Number
	values[2]=(((prevMsg->mm_Status)>>4)-5);// Shift by 4 to lose the channel data & -5 to bring in line with type id
	values[3]=prevMsg->mm_Data[1];				// Event data
	values[4]=2;									// Velocity type
	values[5]=prevMsg->mm_Data[2];				// Velocity
	PutInTrackBuffer(6,&values[0]);

}

// Find out how long we need to wait before sending off the next midi event	
void MidiFileInput::SetNextTimer(){
	ULONG timer;
	float fTimer;
	MidiEvent *event=midiEventList.GetNextItem();
	if (event==NULL)
		event=midiEventList.GetItem(0);	// If we've hit the end then start again

	// We've hit a tempo event so deal with it
	if (event->me_Tempo!=0){
		ChangeTempo(event->me_Tempo);
	}
	curMsg=(MidiMsg *)event;
	timer=event->me_absTime-mf_absTime;
	mf_absTime=event->me_absTime;

	fTimer=mf_Interval*(float)timer;
	oa_SetTimerAlarm((ULONG)fTimer);
}

void MidiFileInput::OnStop(){
	MidiMsg *msg=new MidiMsg;
	
	// Send an all notes off message to every channel
	if (msg){
		for (UINT n=0;n<15;n++){
			msg->mm_Status=CONTROL_CHANGE | n;
			msg->mm_Port=n;
			msg->mm_Data[1]=0x7B;		// 0x7b = notes off
			msg->mm_Data[2]=0;
		
			PutMidiMsg(mLink,msg);
		}
		delete msg;
	}
}

void MidiFileInput::OnStart(){
	mf_absTime=0;
	MidiEvent *pEvent=midiEventList.GetItem(0);	// Get the first item and reset the iterator
	if (pEvent->me_Tempo!=0)
		ChangeTempo(pEvent->me_Tempo);

	curMsg=(MidiMsg *)pEvent;
	DealWithTimer();										// Send off the first event
}


void MidiFileInput::OnSaveConfig(FILE *f){
	fprintf(f,"%s\n",mf_filename);
}

void MidiFileInput::OnLoadConfig(FILE *f){
	delete[]mf_filename;
	mf_filename=new char[255]; // Should be long enough :/
	
	if (fscanf(f,"%s",mf_filename)==1){
		printf("%s\n",mf_filename);
		if(LoadFile(FALSE)==0){
			op_Configured=FALSE;
			Error("Invalid Midi File",NULL);
		}
	}
}
