/*--------------------------------------------------------
FILE     CMdxTool.c

ROLE:
     management of the performance tool, to modify tempo, volume...

LAST MODIFICATIONS:
      - J.Vibert - 21/01/98 - fading
      - J.Vibert - 21/01/98 - volumes modified by tool; Main volume, incremental volume for MDX
      - D.Dumont - created, tempo modified by tool
--------------------------------------------------------*/

#define INIT /*only initialize GUIDs once per project. */
#include "CMdxtool.h"

#define _UNICODE
#include <TChar.h>

#ifdef _DEBUG
#define dbg0(msg) \
	OutputDebugString(msg)
#else //release
#define dbg0(msg)
#endif //_DEBUG


const GUID MyIID_IDirectMusicTool2
={0xd2ac28ba, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd};


CMDXTool *pCM;

/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolQueryInterface(IMDXTool *pIm, REFIID iid, void **ppv)
---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolQueryInterface(IMDXTool *pIm, REFIID iid, void **ppv)
{

CMDXTool *pThis = (CMDXTool *)pIm;
IID iidIMDXTool = IID_IMDXTool;

if( IsEqualIID( iid, &IID_IUnknown) || IsEqualIID( iid, &MyIID_IDirectMusicTool2) )
	{
	*ppv = &(pThis->Im);
	((struct IMDXTool_Vtbl *)pIm->lpVtbl)->pfnAddRef( pIm );
	return S_OK;
	/*I think this means that we're technically cheating in the case of
	//IUnknown: we really give out an implementation of the interface
	//to IMDXTool(the first three of which are identical to IUnknown),and
	// simply rely on the user to assume that only IUnknown methods are
	// there*/
	}
else
	{
	ppv = NULL;
	return E_NOINTERFACE;
	}
}

/*-------------------------------------------------------------------------------------------
ULONG _stdcall CMDXToolAddRef( IMDXTool *pIm)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
ULONG _stdcall CMDXToolAddRef( IMDXTool *pIm)
{
	CMDXTool *pThis = (CMDXTool *)pIm;
	return ++pThis->cRef;
}


/*-------------------------------------------------------------------------------------------
ULONG _stdcall CMDXToolRelease( IMDXTool *pIm)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
ULONG _stdcall CMDXToolRelease( IMDXTool *pIm)
{
CMDXTool *pThis = (CMDXTool *)pIm;


if( --pThis->cRef <= 0 )
	{
	//cCMDXTool--;
	free( pThis );
	return 0;
	}
else
	{
	return pThis->cRef;
	}
}



/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolInit(IMDXTool *pIm,IDirectMusicGraph* pGraph)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolInit(IMDXTool *pIm,IDirectMusicGraph* pGraph)
{
// This tool has no need to do any type of initialization.
CMDXTool *pThis = (CMDXTool *)pIm;

return E_NOTIMPL;

}

/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolGetMsgDeliveryType(IMDXTool *pIm,DWORD* pdwDeliveryType )
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolGetMsgDeliveryType(IMDXTool *pIm,DWORD* pdwDeliveryType )
{
// This tool wants messages immediately.
// This is the default, so returning E_NOTIMPL
// would work. The other method is to specifically
// set *pdwDeliveryType to the delivery type, DMUS_PMSGF_TOOL_IMMEDIATE,
// DMUS_PMSGF_TOOL_QUEUE, or DMUS_PMSGF_TOOL_ATTIME.

*pdwDeliveryType = DMUS_PMSGF_TOOL_IMMEDIATE;
return S_OK;

}


/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolGetMediaTypeArraySize(IMDXTool *pIm,DWORD* pdwNumElements )
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolGetMediaTypeArraySize(IMDXTool *pIm,DWORD* pdwNumElements )
{
*pdwNumElements = 5;
return S_OK;
}


/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolGetMediaTypeArraySize(IMDXTool *pIm,DWORD* pdwNumElements )
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolGetMediaTypes(IMDXTool *pIm,DWORD** padwMediaTypes, DWORD dwNumElements) 
{
    
if( dwNumElements == 5)
    {
    // set the elements in the array to DMUS_PMSGT_NOTE,
    // DMUS_PMSGT_MIDI, and DMUS_PMSGT_PATCH
    (*padwMediaTypes)[0] = DMUS_PMSGT_NOTE;
    (*padwMediaTypes)[1] = DMUS_PMSGT_MIDI;
    (*padwMediaTypes)[2] = DMUS_PMSGT_PATCH;
	(*padwMediaTypes)[3] = DMUS_PMSGT_TEMPO;
	(*padwMediaTypes)[4] = DMUS_PMSGT_NOTIFICATION;
    return S_OK;
    }
else
    {
    // this should never happen
    return E_FAIL;
    }

}


/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolGetMediaTypeArraySize(IMDXTool *pIm,DWORD* pdwNumElements )
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolProcessPMsg(IMDXTool *pIm,IDirectMusicPerformance* pPerf, DMUS_PMSG* pPMsg) 
{
    DMUS_NOTE_PMSG* pNote;
	DMUS_NOTIFICATION_PMSG* pNotification;
	CMDXTool *		pThis = (CMDXTool *)pIm;
	char			cIncVolume,cDesiredVol;
	long			lVolume; //fading coefficient
	char			cMainVolume; //fading coefficient*gloabl main volume
	int				iChannel, iVolume;	
    DMUS_MIDI_PMSG* pMidi;

#ifdef   _DEBUG
	char			stmp[150];
	int				a,b,c;
#endif

// returning S_FREE frees the message. If StampPMsg()
// fails, there is no destination for this message so
// free it.

if(( NULL == pPMsg->pGraph ) || FAILED(pPMsg->pGraph->lpVtbl->StampPMsg(pPMsg->pGraph,pPMsg)))
	{
	return DMUS_S_FREE;
	}

if( pPMsg->dwType == DMUS_PMSGT_PATCH)
    {
		DMUS_PATCH_PMSG* pPatch;
		
	pPatch = (DMUS_PATCH_PMSG*)pPMsg;

	sprintf(stmp,"PROGRAM CHANGE WITH INSTRUMENT %d\n",pPatch->byInstrument);
	OutputDebugString(stmp);
	if (pPatch->byInstrument==0)
		{
		fn_vCallCallback();
		return DMUS_S_FREE;
		}
	
	}    
else
if( pPMsg->dwType == DMUS_PMSGT_NOTIFICATION)
    {
	pNotification = (DMUS_NOTIFICATION_PMSG*)pPMsg;

    if(IsEqualGUID(&(pNotification->guidNotificationType),&GUID_NOTIFICATION_SEGMENT))
		{
		switch(pNotification->dwNotificationOption)
			{
			case DMUS_NOTIFICATION_SEGABORT:
			OutputDebugString("===> Segment brutally Stopped");
			if (pThis->m_cFading!=0)
				{
				pThis->m_cFading=0; //natural end of fading
				lVolume=pThis->m_cMainVolume;
				lVolume*=pThis->m_cEndFadingVol;
				lVolume/=127;
				cMainVolume=(char)lVolume;
				if (pThis->pFadeCallback) //Fading callback
					pThis->pFadeCallback();
				}
	
				break;
			case DMUS_NOTIFICATION_SEGEND:
			OutputDebugString("===> Segment natural end detected by tool");
			if (pThis->m_cFading!=0)
				{
				pThis->m_cFading=0; //natural end of fading
				lVolume=pThis->m_cMainVolume;
				lVolume*=pThis->m_cEndFadingVol;
				lVolume/=127;
				cMainVolume=(char)lVolume;
				if (pThis->pFadeCallback) //Fading callback
					pThis->pFadeCallback();
				}
	

				break;

			}
		}

	OutputDebugString("===> Notification In Tool");
	}
else //DMUS_PMSGT_TEMPO

if( pPMsg->dwType == DMUS_PMSGT_TEMPO )
    {
		    DMUS_TEMPO_PMSG* pTempo;
	pTempo = (DMUS_TEMPO_PMSG*)pPMsg;
	pTempo->dblTempo = (pTempo->dblTempo /(double)100)*pCM->m_wUserTempo;
	}
else //DMUS_PMSGT_TEMPO
 if( pPMsg->dwType == DMUS_PMSGT_MIDI )
    {
	iChannel=pPMsg->dwPChannel&0xF;
	if (((pThis->m_wMuteFlag>>iChannel) & 1) == 1)
		{
			return DMUS_S_FREE;
		} else // not muted
		{
			if ( (((DMUS_MIDI_PMSG*)pPMsg)->bStatus&0xF0)==(BYTE)0xB0)
			{ //Ctrl Change msg
				//dbg0("Ctrl Change msg\n");
				 if (((DMUS_MIDI_PMSG*)pPMsg)->bByte1==(BYTE)7) //Main Volume
				 {  //keep track of the Main Volume	change
					 //dbg0("Main Volume intercepted\n");
					 pThis->m_RefVol[iChannel]=((DMUS_MIDI_PMSG*)pPMsg)->bByte2;
				 }
			} //if Ctrl Change msg 

			//if (((DMUS_MIDI_PMSG*)pPMsg)->bFlags&DMUS_NOTEF_NOTEON)

		} //not muted
	} //DMUS_PMSGT_MIDI
else 

if( pPMsg->dwType == DMUS_PMSGT_NOTE )
	{
    pNote = (DMUS_NOTE_PMSG*)pPMsg;

	iChannel=pPMsg->dwPChannel&0xF;

	if (((pThis->m_wMuteFlag>>iChannel) & 1) == 1)
		{
			return DMUS_S_FREE;
		} else
		{
			//COMPUTE MAIN VOLUME AFTER FADING

			if (pThis->m_cFading!=0)
			{
				if (pThis->m_rtEndFadingTime<=pPMsg->rtTime)
				{
					pThis->m_cFading=0; //natural end of fading

					lVolume=pThis->m_cMainVolume;
					lVolume*=pThis->m_cEndFadingVol;
					lVolume/=127;
					cMainVolume=(char)lVolume;

					if (pThis->pFadeCallback) //Fading callback
						pThis->pFadeCallback();
				} else
				{
					lVolume= //compute fading coefficient
						(long)pThis->m_cEndFadingVol-
						(long)				
							((pCM->m_lSlopeFading*
								((pThis->m_rtEndFadingTime-pPMsg->rtTime)/1000)
							)/1000000);

					//check fading coefficient and mix it with main volume
					if (lVolume<=0) pThis->m_cFadeCoeff=cMainVolume=0;
					else if (lVolume>=127)
					{
						cMainVolume=pThis->m_cMainVolume;
						pThis->m_cFadeCoeff=127;
					}
					else
					{
						pThis->m_cFadeCoeff=(char)lVolume;
						lVolume*=pThis->m_cMainVolume;
						lVolume/=127;
						cMainVolume=(char)lVolume;
					}
				}

			} else 
			{
				lVolume=pThis->m_cMainVolume;
				//we still have to take it into account, so that the volume isn't brutally reset at the end
				lVolume*=pThis->m_cFadeCoeff;
				lVolume/=127;
			    cMainVolume=(char)lVolume;
			    //pThis->m_cFadeCoeff=127;
			}

			//COMPUTE DESIRED VOLUME

			iVolume=(char)pThis->m_RefVol[iChannel];
			if (((pThis->m_wVolFlag>>iChannel) & 1) == 1) //with volume increment value
			{
				cIncVolume=(char)pThis->m_vol[iChannel];
				iVolume+=cIncVolume;
				if (iVolume<0) cDesiredVol=0;
				else if (iVolume>127) 
				{
					cDesiredVol=cMainVolume;
				} else
				{
					iVolume*=cMainVolume;
					cDesiredVol=(BYTE)(iVolume/127);
				}					
			} else //without volume increment value
			{
				iVolume*=cMainVolume;
				cDesiredVol=(BYTE)(iVolume/127);
			}


			//APPLY DESIRED VOLUME

			if (pThis->m_CurrVol[iChannel]!=cDesiredVol)
			{ // Volume must be updated

				if( SUCCEEDED( pPerf->lpVtbl->AllocPMsg(pPerf, sizeof(DMUS_MIDI_PMSG),
					(DMUS_PMSG**)&pMidi )))
				{ //Send Midi message to update the volume
					pMidi->dwType = DMUS_PMSGT_MIDI;
					pMidi->dwSize=sizeof(pMidi);		
					pPerf->lpVtbl->GetTime(pPerf,&pMidi->rtTime,&pMidi->mtTime);
					pMidi->dwFlags=DMUS_PMSGF_TOOL_IMMEDIATE|DMUS_PMSGF_REFTIME;

					pMidi->dwPChannel=pPMsg->dwPChannel;
					pMidi->dwVirtualTrackID=pPMsg->dwVirtualTrackID;

					pMidi->pTool=NULL;
					pMidi->pGraph=NULL;
					pMidi->punkUser=NULL;

					pMidi->bStatus=0xB0; //ctrl chg
					pMidi->bByte1=7;     //volume
					//if (pThis->m_cMainVolume>0x7F)
					//	 pMidi->bByte2=0x7F;
					//else
					pMidi->bByte2=(BYTE)cDesiredVol;//pThis->m_cMainVolume; //volume value				

					if (S_OK==pPerf->lpVtbl->SendPMsg(pPerf, (DMUS_PMSG*) pMidi))					
						pThis->m_CurrVol[iChannel]!=cDesiredVol;

				} //send MIDI message
			} //volume must be updated

		}
	}
// return DMUS_S_REQUEUE so the original message is requeued
return DMUS_S_REQUEUE;
}


/*-------------------------------------------------------------------------------------------
HRESULT _stdcall CMDXToolFlush(IMDXTool *pIm,IDirectMusicPerformance* pPerf, DMUS_PMSG* pDMUS_PMSG, REFERENCE_TIME rt)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
HRESULT _stdcall CMDXToolFlush(IMDXTool *pIm,IDirectMusicPerformance* pPerf, DMUS_PMSG* pDMUS_PMSG, REFERENCE_TIME rt)
{
	CMDXTool *pThis = (CMDXTool *)pIm;

    // this tool does not need to flush.
    return E_NOTIMPL;

}


/*-------------------------------------------------------------------------------------------
void	_stdcall SetUserTempo(IMDXTool *pIm,WORD wUserTempo)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void	_stdcall SetUserTempo(IMDXTool *pIm,WORD wUserTempo)
{
CMDXTool *pThis = (CMDXTool *)pIm;
pThis->m_wUserTempo = wUserTempo;
}

/*-------------------------------------------------------------------------------------------
void	_stdcall SetMuteFlag(IMDXTool *pIm,WORD wMuteFlag)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void	_stdcall SetMuteFlag(IMDXTool *pIm,WORD wMuteFlag)
{
CMDXTool *pThis = (CMDXTool *)pIm;
pThis->m_wMuteFlag = wMuteFlag;
}

/*-------------------------------------------------------------------------------------------
void	_stdcall SetVolFlag(IMDXTool *pIm,WORD wVolFlag)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void	_stdcall SetVolFlag(IMDXTool *pIm,WORD wVolFlag)
{
CMDXTool *pThis = (CMDXTool *)pIm;
pThis->m_wVolFlag = wVolFlag;
}

/*-------------------------------------------------------------------------------------------
void	_stdcall SetVolValue(IMDXTool *pIm,int iChannel,char newValue)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void	_stdcall SetVolValue(IMDXTool *pIm,int iChannel,char newValue)
{
	//int		iVolume;
	CMDXTool *pThis = (CMDXTool *)pIm;

	pThis->m_vol[iChannel] = newValue;
	pThis->m_RefVol[iChannel]=pThis->m_CurrVol[iChannel];
	pThis->m_CurrVol[iChannel]=(char)0xFF; //to force update

/*	iVolume=pThis->m_CurrVol[iChannel];
	iVolume+=newValue;
	if (iVolume<0) pThis->m_DesiredVol[iChannel]=0;
	else
	{
		iVolume>0
	}*/

	/*
				if (((pThis->m_wVolFlag>>iChannel) & 1) == 1) //with volume value
			{
				cIncVolume=(char)pThis->m_vol[iChannel];
				iVolume=(char)pThis->m_CurrVol[iChannel];
				iVolume+=cIncVolume;
				if (iVolume<0) pThis->m_DesiredVol[iChannel]=0;
				else if (iVolume>127) 
				{
					pThis->m_DesiredVol[iChannel]=(BYTE)pThis->m_cMainVolume;				
				} else
				{
					iVolume*=pThis->m_cMainVolume;
					pThis->m_DesiredVol[iChannel]=(BYTE)(iVolume/127);
				}
			} else pThis->m_DesiredVol[iChannel]=(BYTE)pThis->m_cMainVolume;

	*/
}

/*-------------------------------------------------------------------------------------------
void	_stdcall SetMainVolValue(IMDXTool *pIm,char newValue)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void	_stdcall SetMainVolValue(IMDXTool *pIm,char newValue)
{
	CMDXTool *pThis = (CMDXTool *)pIm;
	int		iChannel;

	pThis->m_cMainVolume = (newValue<0)?127:newValue;
	for (iChannel=0; iChannel<16; iChannel++)
		pThis->m_CurrVol[iChannel]=(char)0xFF; //to force update
}

/*-------------------------------------------------------------------------------------------
char	_stdcall GetMainVolValue(IMDXTool *pIm)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
char	_stdcall GetMainVolValue(IMDXTool *pIm)
{
	CMDXTool *pThis = (CMDXTool *)pIm;

	return pThis->m_cMainVolume;
}


/*-------------------------------------------------------------------------------------------
char 	_stdcall GetFadeCoeff(IMDXTool *pIm)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
char 	_stdcall GetFadeCoeff(IMDXTool *pIm)
{
	CMDXTool *pThis = (CMDXTool *)pIm;

	return pThis->m_cFadeCoeff;
}


/*-------------------------------------------------------------------------------------------
void    _stdcall DoFade(IMDXTool *pIm, IDirectMusicPerformance* pPerf, char cBeginVolValue, char cEndVolValue, DWORD dwTimeFade, void *pCallback)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void    _stdcall DoFade(IMDXTool *pIm, IDirectMusicPerformance* pPerf, char cBeginVolValue, char cEndVolValue, DWORD dwTimeFade, void *pCallback)
{
	CMDXTool *pThis = (CMDXTool *)pIm;
	REFERENCE_TIME aTime;
	long int ldVol;
#ifdef _DEBUG
	char s[100];	
#endif
	DWORD dwDuration=(dwTimeFade==0)?1:((dwTimeFade>100000)?100000:dwTimeFade);

	(void*)pThis->pFadeCallback=pCallback;

	pPerf->lpVtbl->GetTime(pPerf,&aTime,NULL);
	ldVol=((long)cEndVolValue)-cBeginVolValue;
	pThis->m_rtEndFadingTime=aTime+dwTimeFade*10000; //10 000 000 = 1 second
	pThis->m_cEndFadingVol=cEndVolValue;
	pThis->m_lSlopeFading=ldVol*100000/(long)dwDuration;
	pThis->m_cFading=1;
	//if (pThis->m_cFading<127)
	//	pThis->m_cFading++;

}

/*-------------------------------------------------------------------------------------------
void    _stdcall EndFade(IMDXTool *pIm)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void    _stdcall EndFade(IMDXTool *pIm)
{
	CMDXTool *pThis = (CMDXTool *)pIm;
	pThis->m_cFading=0;
	pThis->m_cFadeCoeff=127;
	//if (pThis->m_cFading>0)
	//	pThis->m_cFading--;
}

/*-------------------------------------------------------------------------------------------
char	_stdcall IsFading(IMDXTool *pIm)
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
char	_stdcall IsFading(IMDXTool *pIm)
{
	return ((CMDXTool *)pIm)->m_cFading;
}


struct IMDXTool_Vtbl IMVT = 
	{
	CMDXToolQueryInterface, 
	CMDXToolAddRef, 
	CMDXToolRelease, 
	CMDXToolInit,
	CMDXToolGetMsgDeliveryType,
	CMDXToolGetMediaTypeArraySize,
	CMDXToolGetMediaTypes,
	CMDXToolProcessPMsg,
	CMDXToolFlush,
	SetUserTempo,
	SetMuteFlag,
	SetVolFlag,
	SetVolValue,
	SetMainVolValue,
	GetMainVolValue,
	GetFadeCoeff,
	DoFade,
	EndFade,
	IsFading
	};


/*-------------------------------------------------------------------------------------------
IMDXTool * fnpGetTool()
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
IMDXTool * fnpGetTool()
{
	int i;

	pCM = (CMDXTool *)malloc( sizeof( CMDXTool ) );
	if(pCM != NULL)
	{
		pCM->Im.lpVtbl = &IMVT;
		pCM->cRef = 1; // jamais release par un client !!!
		pCM->m_wMuteFlag = 0x0;
		pCM->m_wActifTempo = 100;
		pCM->m_wUserTempo = 100;

		pCM->m_wVolFlag=0x0;
		pCM->m_cMainVolume=127; //default volume

		pCM->m_lSlopeFading=0;
		pCM->m_cEndFadingVol=127;
		pCM->m_rtEndFadingTime=0;
		pCM->m_cFadeCoeff=127;
		pCM->m_cFading=0;
		pCM->pFadeCallback=NULL;

		for (i=0; i<16; i++) //loop on channels
		{
			pCM->m_vol[i]=0;
			pCM->m_CurrVol[i]=(char)0xFF; //to force update
			pCM->m_RefVol[i]=127;
		}
		return &(pCM->Im);

	} else return NULL;

}

/*-------------------------------------------------------------------------------------------
void fnvReleaseTool()
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------*/
void fnvReleaseTool()
{
if (pCM)
	{
	free(pCM);
	pCM = NULL;
	}
}

/*///////////////////////////////////// End of CMDXTool.c /////////////////*/
