/*
 * Routines for use by the vstutil.pyx code, providing a
 * minimal VST hosting interface.
 *
 * Many are just pass-throughs to the VST plugin.
 *
 * Many things are unimplemented.
 *  
 * This code is placed into the public domain, Oct 4, 2007, by Tim Thompson.
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string.h>
#include <stdio.h>
 
#define LIB_INSTANCE	HINSTANCE
#define mainEntryPoint	"main"

#include "aeffectx.h"
#include "vstsysex.h"

int vstdebug = 0;

int blocksize=512;

float samplerate=44100.0f;

long VSTCALLBACK host(AEffect *effect, long opcode, long index, long value, void *ptr, float opt);

extern "C" {

// These are just to get around the fact that Pyrex doesn't understand const
#include "portaudio.h"
char* My_Pa_GetErrorText(PaError errnum) {
	return (char *)Pa_GetErrorText(errnum);
}
PaDeviceInfo* My_Pa_GetDeviceInfo(PaDeviceID device) {
	return (PaDeviceInfo*) Pa_GetDeviceInfo(device);
}

float
vst_avg(float *blk, int sz)
{
	float tot = 1.0;
	float *fp = blk;
	int i;

	// printf("avg fp = %f %f %f %f\n",blk[0],blk[1],blk[2],blk[3]);
	for ( i=0; i<4; i++ ) {
		tot += *fp++;
	}
	// printf("tot=%f sz=%d\n",tot,sz);
	return tot / sz;
}

void
vst_gotinput(float *blk,int i)
{
	// printf("gotinput i=%d blk = %f %f %f %f\n",i,blk[0],blk[1],blk[2],blk[3]);
}

void
vst_set_samplerate(float s, int b)
{
	samplerate = s;
	blocksize = b;
	if ( vstdebug ) {
		printf("\nVST_SET_SAMPLERATE  setting s=%f blocksize=%d\n",s,b);
	}
}

void
vst_do_process(AEffect *plug, float *infd, float *outfd, float *outfd2,
		int len, int enabled)
{
	float *inputs[2];
	float *outputs[2];
	int n;

	inputs[0] = infd;
	inputs[1] = infd;
	outputs[0] = outfd;
	outputs[1] = outfd2;
	if ( enabled )
		plug->process(plug,inputs,outputs,len);
	else {
		for ( n=0; n<len; n++ ) {
			outputs[0][n] = inputs[0][n];
			outputs[1][n] = inputs[1][n];
		}
	}
}

int
vst_set_program(AEffect *plug, int value)
{
	//set to program zero and fetch back the name
	printf("vst_set_program called, setting to program %d\n",value);
	plug->dispatcher(plug,effSetProgram,0,value,NULL,0.0f);

	char s[24+2];
	plug->dispatcher(plug,effGetProgramName,0,value,s,0.0f);
	printf("PROGRAM NAME = %s\n",s);

	return 0;
}

int
vst_send_midi(AEffect *plug, int chan, int pitch, long dur, int vel)
{
	int nm = 0;	// number of normal midi events
	int ns = 0;	// number of sysex midi events
	BYTE *pEvData = NULL;
	int nHdrLen, nTotLen;
	VstMidiEvent *pEv;
	VstEvents *pEvents;
	int i;
	int nLength = blocksize;
	int b1, b2, b3;

	nm = 1;

	int v = plug->dispatcher(plug,effGetVstVersion,0,0,NULL,0.0f);
	// printf("VST pitch=%d vel=%d\n",pitch,vel);

	nHdrLen = sizeof(VstEvents) + 
		  ((nm - 1) * sizeof(VstMidiEvent *)) +
		  (ns * sizeof(VstMidiSysexEvent *));
	nTotLen = nHdrLen +
		  (nm * sizeof(VstMidiEvent)) +
		  (ns * sizeof(VstMidiSysexEvent));
	pEvData = new BYTE[nTotLen];
	pEvents = (VstEvents *) pEvData;
	memset(pEvents,0,nTotLen);

	pEvents->numEvents = nm + ns;

	i = 0;

	////// prepare 1 event

	pEv = ((VstMidiEvent *)(pEvData + nHdrLen)) + i;
	pEvents->events[i] = (VstEvent *)pEv;
	pEv->type = kVstMidiType;
	pEv->byteSize = sizeof(VstMidiEvent);
	if ( vel == 0 )
		b1 = (chan-1) | 0x80;
	else
		b1 = (chan-1) | 0x90;
	b2 = pitch;
	b3 = vel;	// velocity
	DWORD smsg =
		(DWORD)(b1 & 0xffL) +
		(DWORD)((b2 & 0xffL) << 8L) +
		(DWORD)((b3 & 0xffL) << 16L);
	// printf("smsg=0x%llx\n",(long long)smsg);
	memcpy(pEv->midiData, &smsg, sizeof(DWORD));
	DWORD dwDelta = 4000;
	if (dwDelta > (DWORD)nLength)
		dwDelta = nLength - 1;
	pEv->deltaFrames = dwDelta;

	///////

	// printf("Calling ProcessEvents\n");
	if (plug->dispatcher(plug,effProcessEvents,0,0,pEvData,0.0f)==1) {
		// printf("plugin processed events OK and wants more\n");
	}
	else {
		// printf("plugin does not want any more events\n");
	}
	// printf("SEND_MIDI end\n");

	return 0;
}

int
vst_can_receive_midi(AEffect * plug)
{
	return (plug->dispatcher(plug,
			effCanDo,0,0,(void*)"receiveVstMidiEvent",0.0f)>0);
}
int
vst_can_midi_program_names(AEffect * plug)
{
	return (plug->dispatcher(plug,
			effCanDo,0,0,(void*)"midiProgramNames",0.0f)>0);
}
int
vst_can_send_midi(AEffect * plug)
{
	return (plug->dispatcher(plug,
			effCanDo,0,0,(void*)"sendVstMidiEvent",0.0f)>0);
}

int
vst_is_synth(AEffect *plug)
{
	return (plug->flags & effFlagsIsSynth) != 0;
}

int
vst_is_vst2(AEffect * plug) {
	return (plug->dispatcher(plug,effGetVstVersion,0,0,NULL,0.0f)>=2);
}

int
vst_get_num_categories(AEffect *plug)
{
	return plug->dispatcher(plug,effGetNumProgramCategories,0,0,NULL,0.0f);
}

int
vst_get_program_name(AEffect *plug, int index, int category, char s[25])
{
	return plug->dispatcher(plug,effGetProgramNameIndexed,index,category,s,0.0f);
}

int
vst_get_param_name(AEffect *plug, int index, char s[25])
{
	return plug->dispatcher(plug,effGetParamName,index,0,s,0.0f);
}

int
vst_get_param_label(AEffect *plug, int index, char s[25])
{
	return plug->dispatcher(plug,effGetParamLabel,index,0,s,0.0f);
}

int
vst_get_param_display(AEffect *plug, int index, char s[25])
{
	return plug->dispatcher(plug,effGetParamDisplay,index,0,s,0.0f);
}

int
vst_set_param(AEffect *plug, int index, float f)
{
	plug->setParameter(plug,index,f);
	return 0;
}

float
vst_get_param(AEffect *plug, int index)
{
	return plug->getParameter(plug,index);
}

int
vst_get_product_string(AEffect *plug, char s[65])
{
	s[0] = 0;
	return plug->dispatcher(plug,effGetProductString,0,0,s,0.0f);
}

int
vst_get_param_properties_isswitch(AEffect *plug, int index)
{
	struct VstParameterProperties p;
	int b;

	p.maxInteger = -2;
	b = plug->dispatcher(plug,effGetParameterProperties,index,0,&p,0.0f);
	if ( b )
		// return p.flags & kVstParameterIsSwitch;
		return p.maxInteger;
	else
		return -1;
}

AEffect *
vst_get_plugin(char *fileNamePtr, char **err)
{
	AEffect* plug=NULL;

	LIB_INSTANCE libHandle = LoadLibrary (fileNamePtr);

	if (libHandle!=NULL) {

		//DLL was loaded OK
		AEffect* (__cdecl* getNewPlugInstance)(audioMasterCallback);
		getNewPlugInstance=(AEffect*(__cdecl*)(audioMasterCallback))GetProcAddress(libHandle, mainEntryPoint);

		if (getNewPlugInstance!=NULL) {

			plug=getNewPlugInstance(host);

			if (plug!=NULL) {
				if (plug->magic != kEffectMagic) {
					*err = "Not a VST plugin";
					FreeLibrary(libHandle);
					return NULL;
				}
			}
			else {
				*err = "Plugin could not be instantiated";
				FreeLibrary(libHandle);
				return NULL;
			}
		}
		else {
			*err = "Plugin main function could not be located";
			FreeLibrary(libHandle);
			return NULL;
		}
	}
	else {
		*err = "Plugin DLL could not be loaded";
		return NULL;
	};

	plug->dispatcher(plug,effOpen,0,0,NULL,0.0f);

	//switch the plugin off (calls Suspend)
	plug->dispatcher(plug,effMainsChanged,0,0,NULL,0.0f);

	//set sample rate and block size
	plug->dispatcher(plug,effSetSampleRate,0,0,NULL,samplerate);
	plug->dispatcher(plug,effSetBlockSize,0,blocksize,NULL,0.0f);

	if ((plug->dispatcher(plug,effGetVstVersion,0,0,NULL,0.0f)==2) &&
		(plug->flags & effFlagsIsSynth))
	{
		//get I/O configuration for synth plugins - they will declare their
		//own output and input channels
		for (int i=0; i<plug->numInputs+plug->numOutputs;i++)
		{
			if (i<plug->numInputs)
			{
				//input pin
				VstPinProperties temp;

				if (plug->dispatcher(plug,effGetInputProperties,i,0,&temp,0.0f)==1)
				{
#ifdef COUT
					cout << "Input pin " << (i+1) << " label " << temp.label << endl;
#endif
					//cout << "Input pin " << (i+1) << " shortLabel " << temp.shortLabel << endl;

					if (temp.flags & kVstPinIsActive)
					{
#ifdef COUT
						cout << "Input pin " << (i+1) << " is active" << endl;
#endif
					}

					if (temp.flags & kVstPinIsStereo)
					{
						// is index even or zero?
						if (i%2==0 || i==0)
						{
#ifdef COUT
							cout << "Input pin " << (i+1) << " is left channel of a stereo pair" << endl;
#endif
						}
						else
						{
#ifdef COUT
							cout << "Input pin " << (i+1) << " is right channel of a stereo pair" << endl;
#endif
						}
					}
				}
			}
			else
			{
				//output pin
				VstPinProperties temp;

				if (plug->dispatcher(plug,effGetOutputProperties,i,0,&temp,0.0f)==1)
				{
#ifdef COUT
					cout << "Output pin " << (i-plug->numInputs+1) << " label " << temp.label << endl;
#endif
					//cout << "Output pin " << (i-plug->numInputs+1) << " shortLabel " << temp.shortLabel << endl;

					if (temp.flags & kVstPinIsActive)
					{
#ifdef COUT
						cout << "Output pin " << (i-plug->numInputs+1) << " is active" << endl;
#endif
					}
					else
					{
#ifdef COUT
						cout << "Output pin " << (i-plug->numInputs+1) << " is inactive" << endl;
#endif
					}

					if (temp.flags & kVstPinIsStereo)
					{
						// is index even or zero?
						if ((i-plug->numInputs)%2==0 || (i-plug->numInputs)==0)
						{
#ifdef COUT
							cout << "Output pin " << (i+1) << " is left channel of a stereo pair" << endl;
#endif
						}
						else
						{
#ifdef COUT
							cout << "Output pin " << (i+1) << " is right channel of a stereo pair" << endl;
#endif
						}
					}
					else
					{
#ifdef COUT
						cout <<  "Output pin " << (i+1) << " is mono" << endl;
#endif
					}
				}
			}
		}
	}	//end VST2 specific

	//switch the plugin back on (calls Resume)
	plug->dispatcher(plug,effMainsChanged,0,1,NULL,0.0f);

	//set to program zero and fetch back the name
	plug->dispatcher(plug,effSetProgram,0,0,NULL,0.0f);

	char strProgramName[24+2];
	plug->dispatcher(plug,effGetProgramName,0,0,strProgramName,0.0f);

	if (vstdebug) {
		printf("INITIAL PROGRAM NAME (zero) = %s\n",strProgramName);
	}

	return plug;
}

}


#define PRINTCALLED(s) {if(vstdebug){printf("plugin called %s\n",s);}}

//host callback function
//this is called directly by the plug-in!!
//
long VSTCALLBACK host(AEffect *effect, long opcode, long index, long value, void *ptr, float opt)
{
	long retval=0;

	if (vstdebug) {
		printf("plugin called host(op=%d)!\n",opcode);
	}

	switch (opcode)
	{
		//VST 1.0 opcodes
		case audioMasterVersion:
			//Input values:
			//none

			//Return Value:
			//0 or 1 for old version
			//2 or higher for VST2.0 host?
			retval=2;
			PRINTCALLED("audioMasterVersion");
			break;

		case audioMasterAutomate:
			//Input values:
			//<index> parameter that has changed
			//<opt> new value

			//Return value:
			//not tested, always return 0

			//NB - this is called when the plug calls
			//setParameterAutomated

			PRINTCALLED("audioMasterAutomate");
			break;

		case audioMasterCurrentId:
			//Input values:
			//none

			//Return Value
			//the unique id of a plug that's currently loading
			//zero is a default value and can be safely returned if not known
			PRINTCALLED("audioMasterCurrentId");
			break;

		case audioMasterIdle:
			//Input values:
			//none

			//Return Value
			//not tested, always return 0

			//NB - idle routine should also call effEditIdle for all open editors
			Sleep(1);
			PRINTCALLED("audioMasterIdle");
			break;

		case audioMasterPinConnected:
			//Input values:
			//<index> pin to be checked
			//<value> 0=input pin, non-zero value=output pin

			//Return values:
			//0=true, non-zero=false
#ifdef COUT
			cout << "plug called audioMasterPinConnected" << endl;
#endif
			PRINTCALLED("audioMasterPinConnected");
			break;

		//VST 2.0 opcodes
		case audioMasterWantMidi:
			//Input Values:
			//<value> filter flags (which is currently ignored, no defined flags?)

			//Return Value:
			//not tested, always return 0
			// cout << "plug called audioMasterWantMidi" << endl;
			PRINTCALLED("audioMasterWantMidi");
			break;

		case audioMasterGetTime:
			//Input Values:
			//<value> should contain a mask indicating which fields are required
			//...from the following list?
			//kVstNanosValid
			//kVstPpqPosValid
			//kVstTempoValid
			//kVstBarsValid
			//kVstCyclePosValid
			//kVstTimeSigValid
			//kVstSmpteValid
			//kVstClockValid
#if 0
			if ( value & kVstNanosValid )
				cout << "kVstNanosValid" << endl;
			if ( value & kVstPpqPosValid )
				cout << "kVstPpqPosValid" << endl;
			if ( value & kVstTempoValid )
				cout << "kVstTempoValid" << endl;
			if ( value & kVstBarsValid )
				cout << "kVstBarsValid" << endl;
			if ( value & kVstCyclePosValid )
				cout << "kVstCyclePosValid" << endl;
			if ( value & kVstTimeSigValid )
				cout << "kVstTimeSigValid" << endl;
			if ( value & kVstSmpteValid )
				cout << "kVstSmpteValid" << endl;
			if ( value & kVstClockValid )
				cout << "kVstClockValid" << endl;
#endif

			//Return Value:
			//ptr to populated const VstTimeInfo structure (or 0 if not supported)

			//NB - this structure will have to be held in memory for long enough
			//for the plug to safely make use of it
			// cout << "plug called audioMasterGetTime, value = " << value << endl;
			PRINTCALLED("audioMasterGetTime");
			break;

		case audioMasterProcessEvents:
			//Input Values:
			//<ptr> Pointer to a populated VstEvents structure

			//Return value:
			//0 if error
			//1 if OK
			PRINTCALLED("audioMasterProcessEvents");
			break;

		case audioMasterSetTime:
			//IGNORE!
			PRINTCALLED("audioMasterSetTime");
			break;

		case audioMasterTempoAt:
			//Input Values:
			//<value> sample frame location to be checked

			//Return Value:
			//tempo (in bpm * 10000)
			// cout << "plug called audioMasterTempoAt" << endl;
			PRINTCALLED("audioMasterTempoAt");
			break;

		case audioMasterGetNumAutomatableParameters:
			//Input Values:
			//None

			//Return Value:
			//number of automatable parameters
			//zero is a default value and can be safely returned if not known

			//NB - what exactly does this mean? can the host set a limit to the
			//number of parameters that can be automated?
			PRINTCALLED("audioMasterGetNumAutomatableParameters");
			break;

		case audioMasterGetParameterQuantization:
			//Input Values:
			//None

			//Return Value:
			//integer value for +1.0 representation,
			//or 1 if full single float precision is maintained
			//in automation.

			//NB - ***possibly bugged***
			//Steinberg notes say "parameter index in <value> (-1: all, any)"
			//but in aeffectx.cpp no parameters are taken or passed
			PRINTCALLED("audioMasterGetParameterQuantization");
			break;

		case audioMasterIOChanged:
			//Input Values:
			//None

			//Return Value:
			//0 if error
			//non-zero value if OK
			// cout << "plug called audioMasterIOChanged" << endl;
			PRINTCALLED("audioMasterIOChanged");
			break;

		case audioMasterNeedIdle:
			//Input Values:
			//None

			//Return Value:
			//0 if error
			//non-zero value if OK

			//NB plug needs idle calls (outside its editor window)
			//this means that effIdle must be dispatched to the plug
			//during host idle process and not effEditIdle calls only when its
			//editor is open
			//Check despatcher notes for any return codes from effIdle
			// cout << "plug called audioMasterNeedIdle" << endl;
			PRINTCALLED("audioMasterNeedIdle");
			break;

		case audioMasterSizeWindow:
			//Input Values:
			//<index> width
			//<value> height

			//Return Value:
			//0 if error
			//non-zero value if OK
			PRINTCALLED("audioMasterSizeWindow");
			break;

		case audioMasterGetSampleRate:
			//Input Values:
			//None

			//Return Value:
			//not tested, always return 0

			//NB - Host must despatch effSetSampleRate to the plug in response
			//to this call
			//Check despatcher notes for any return codes from effSetSampleRate
			effect->dispatcher(effect,effSetSampleRate,0,0,NULL,samplerate);
			PRINTCALLED("audioMasterGetSampleRate");
			break;

		case audioMasterGetBlockSize:
			//Input Values:
			//None

			//Return Value:
			//not tested, always return 0

			//NB - Host must despatch effSetBlockSize to the plug in response
			//to this call
			//Check despatcher notes for any return codes from effSetBlockSize
			// cout << "plug called audioMasterGetBlockSize" << endl;
			effect->dispatcher(effect,effSetBlockSize,0,blocksize,NULL,0.0f);
			PRINTCALLED("audioMasterGetBlockSize");

			break;

		case audioMasterGetInputLatency:
			//Input Values:
			//None

			//Return Value:
			//input latency (in sampleframes?)
			PRINTCALLED("audioMasterGetInputLatency");
			break;

		case audioMasterGetOutputLatency:
			//Input Values:
			//None

			//Return Value:
			//output latency (in sampleframes?)
			PRINTCALLED("audioMasterGetOutputLatency");
			break;

		case audioMasterGetPreviousPlug:
			//Input Values:
			//None

			//Return Value:
			//pointer to AEffect structure or NULL if not known?

			//NB - ***possibly bugged***
			//Steinberg notes say "input pin in <value> (-1: first to come)"
			//but in aeffectx.cpp no parameters are taken or passed
			PRINTCALLED("audioMasterGetPreviousPlug");
			break;

		case audioMasterGetNextPlug:
			//Input Values:
			//None

			//Return Value:
			//pointer to AEffect structure or NULL if not known?

			//NB - ***possibly bugged***
			//Steinberg notes say "output pin in <value> (-1: first to come)"
			//but in aeffectx.cpp no parameters are taken or passed
			PRINTCALLED("audioMasterGetNextPlug");
			break;

		case audioMasterWillReplaceOrAccumulate:
			//Input Values:
			//None

			//Return Value:
			//0: not supported
			//1: replace
			//2: accumulate
			PRINTCALLED("audioMasterWillreplaceOrAccumulate");
			break;

		case audioMasterGetCurrentProcessLevel:
			//Input Values:
			//None

			//Return Value:
			//0: not supported,
			//1: currently in user thread (gui)
			//2: currently in audio thread (where process is called)
			//3: currently in 'sequencer' thread (midi, timer etc)
			//4: currently offline processing and thus in user thread
			//other: not defined, but probably pre-empting user thread.
			PRINTCALLED("audioMasterGetCurrentProcessLevel");
			break;

		case audioMasterGetAutomationState:
			//Input Values:
			//None

			//Return Value:
			//0: not supported
			//1: off
			//2:read
			//3:write
			//4:read/write
			PRINTCALLED("audioMasterGetAutomationState");
			break;

		case audioMasterGetVendorString:
			//Input Values:
			//<ptr> string (max 64 chars) to be populated

			//Return Value:
			//0 if error
			//non-zero value if OK
			PRINTCALLED("audioMasterGetVendorString");
			break;

		case audioMasterGetProductString:
			//Input Values:
			//<ptr> string (max 64 chars) to be populated

			//Return Value:
			//0 if error
			//non-zero value if OK
			PRINTCALLED("audioMasterGetProductString");
			break;

		case audioMasterGetVendorVersion:
			//Input Values:
			//None

			//Return Value:
			//Vendor specific host version as integer
			PRINTCALLED("audioMasterGetVendorVersion");
			break;

		case audioMasterVendorSpecific:
			//Input Values:
			//<index> lArg1
			//<value> lArg2
			//<ptr> ptrArg
			//<opt>	floatArg

			//Return Values:
			//Vendor specific response as integer
			PRINTCALLED("audioMasterGetVendorSpecific");
			break;

		case audioMasterSetIcon:
			//IGNORE
			break;

		case audioMasterCanDo:
			//Input Values:
			//<ptr> predefined "canDo" string

			//Return Value:
			//0 = Not Supported
			//non-zero value if host supports that feature

			//NB - Possible Can Do strings are:
			//"sendVstEvents",
			//"sendVstMidiEvent",
			//"sendVstTimeInfo",
			//"receiveVstEvents",
			//"receiveVstMidiEvent",
			//"receiveVstTimeInfo",
			//"reportConnectionChanges",
			//"acceptIOChanges",
			//"sizeWindow",
			//"asyncProcessing",
			//"offline",
			//"supplyIdle",
			//"supportShell"
			// cout << "plug called audioMasterCanDo" << endl;

			if (strcmp((char*)ptr,"sendVstEvents")==0 ||
				strcmp((char*)ptr,"sendVstMidiEvent")==0 ||
				strcmp((char*)ptr,"supplyIdle")==0)
			{
				retval=1;
			}
			else
			{
				retval=0;
			}
			PRINTCALLED("audioMasterCanDo");
			printf("AUDIOMASTERCANDO  ptr=%s\n",(char*)ptr);

			break;

		case audioMasterGetLanguage:
			//Input Values:
			//None

			//Return Value:
			//kVstLangEnglish
			//kVstLangGerman
			//kVstLangFrench
			//kVstLangItalian
			//kVstLangSpanish
			//kVstLangJapanese
			// cout << "plug called audioMasterGetLanguage" << endl;
			retval=1;
			PRINTCALLED("audioMasterGetLanguage");
			break;
/*
		MAC SPECIFIC?

		case audioMasterOpenWindow:
			//Input Values:
			//<ptr> pointer to a VstWindow structure

			//Return Value:
			//0 if error
			//else platform specific ptr
			// cout << "plug called audioMasterOpenWindow" << endl;
			break;

		case audioMasterCloseWindow:
			//Input Values:
			//<ptr> pointer to a VstWindow structure

			//Return Value:
			//0 if error
			//Non-zero value if OK
			// cout << "plug called audioMasterCloseWindow" << endl;
			break;
*/
		case audioMasterGetDirectory:
			//Input Values:
			//None

			//Return Value:
			//0 if error
			//FSSpec on MAC, else char* as integer

			//NB Refers to which directory, exactly?
			// cout << "plug called audioMasterGetDirectory" << endl;
			PRINTCALLED("audioMasterGetDirectory");
			break;

		case audioMasterUpdateDisplay:
			//Input Values:
			//None

			//Return Value:
			//Unknown
			// cout << "plug called audioMasterUpdateDisplay" << endl;
			PRINTCALLED("audioMasterUpdateDisplay");
			break;
	}
	if (vstdebug) {
		printf("host retval = %d\n",retval);
	}

	return retval;
}
