#include "MSPd.h"
#include "fftease.h"

#if MSP
void *taint_class;
#endif
#if PD
static t_class *taint_class;
#endif

#define OBJECT_NAME "taint~"


/*
Adding -32dB pad for invert option. Also added latency mechanism in
 switching from normal to "invert" to avoid glitches from extreme
 amplitude disparities.
 
 Made all inlets of type signal (with float options).
 
 Threshold input is now linear, not dB (with Max doing the conversion
										if desired).
 
 -EL 10/1/2005
 
 */

typedef struct _taint
{
#if MSP
	t_pxobject x_obj;
#endif
#if PD
	t_object x_obj;
	float x_f;
#endif
	int R;
	int	N;
	int	N2;
	int	Nw;
	int	Nw2; 
	int	D; 
	int	i;
	int	inCount;
	int invert;
	int *bitshuffle;
    
	float threshold;
	float exponent;
	float *Wanal;	
	float *Wsyn;	
	float *inputOne;
	float *inputTwo;
	float *Hwin;
	float *bufferOne;
	float *bufferTwo;
	float *channelOne;
	float *channelTwo;
	float *output;
	float mult; 
	float *trigland;
	
	short connected[8];
	short mute;
	int overlap;//overlap factor
		int winfac;//window factor
			int vs;//vector size  
				int invert_countdown; // delay onset of invert effect to avoid loud glitches
				int invert_nextstate;// next state for invert   
					float invert_pad;   	
} t_taint;


/* msp function prototypes */

void *taint_new(t_symbol *s, int argc, t_atom *argv);
t_int *offset_perform(t_int *w);
t_int *taint_perform(t_int *w);
void taint_dsp(t_taint *x, t_signal **sp, short *count);
void taint_assist(t_taint *x, void *b, long m, long a, char *s);
void taint_dest(t_taint *x, double f);
void taint_invert(t_taint *x, t_floatarg toggle);
void taint_free(t_taint *x);
void taint_mute(t_taint *x, t_floatarg toggle);
void taint_fftinfo(t_taint *x);
void taint_tilde_setup(void);
void taint_overlap(t_taint *x, t_floatarg o);
void taint_winfac(t_taint *x, t_floatarg o);
void taint_init(t_taint *x, short initialized);
void taint_pad(t_taint *x, t_floatarg pad);


#if MSP
void main(void)
{
	setup( (struct messlist **) &taint_class, (void *) taint_new,
		   (method)dsp_free, (short) sizeof(t_taint),
		   0, A_GIMME, 0);
	
	addmess((method)taint_dsp, "dsp", A_CANT, 0);
	addmess((method)taint_assist,"assist",A_CANT,0);    
	addmess((method)taint_invert,"invert", A_FLOAT, 0);  
	post("%s %s",OBJECT_NAME,FFTEASE_ANNOUNCEMENT);
	
	addmess((method)taint_overlap,"overlap", A_FLOAT, 0);
	addmess((method)taint_winfac,"winfac", A_FLOAT, 0);
	addmess((method)taint_mute,"mute", A_FLOAT, 0);
	addmess((method)taint_pad,"pad", A_FLOAT, 0);
	addmess((method)taint_fftinfo,"fftinfo", 0);
	addfloat((method) taint_dest);
	dsp_initclass();
}

/* float input handling routine (MSP only)*/
void taint_dest(t_taint *x, double f)
{
	int inlet = x->x_obj.z_in;
	
	if ( inlet == 2 ) {
		x->exponent = f;	
	}
	
	if ( inlet == 3 ){
		/* x->threshold = (float) (pow( 10., (f * .05))); */
		x->threshold = f;
	}
}
#endif

#if PD
void taint_tilde_setup(void)
{
	taint_class = class_new(gensym("taint~"), (t_newmethod)taint_new, 
							(t_method)taint_free ,sizeof(t_taint), 0,A_GIMME,0);
	CLASS_MAINSIGNALIN(taint_class, t_taint, x_f);
	class_addmethod(taint_class, (t_method)taint_dsp, gensym("dsp"), 0);
	class_addmethod(taint_class, (t_method)taint_assist, gensym("assist"), 0);
	class_addmethod(taint_class, (t_method)taint_invert, gensym("invert"), A_FLOAT,0);
	class_addmethod(taint_class, (t_method)taint_overlap, gensym("overlap"), A_FLOAT,0);
	class_addmethod(taint_class, (t_method)taint_mute, gensym("mute"), A_FLOAT,0);
	class_addmethod(taint_class, (t_method)taint_pad, gensym("pad"), A_FLOAT,0);
	class_addmethod(taint_class, (t_method)taint_fftinfo, gensym("fftinfo"), A_CANT,0);
	post("%s %s",OBJECT_NAME,FFTEASE_ANNOUNCEMENT);
}
#endif


void taint_mute(t_taint *x, t_floatarg toggle)
{
	x->mute = (short)toggle;
	//  post("mute set to %f, %d",toggle,x->mute);
}

void taint_overlap(t_taint *x, t_floatarg o)
{
	if(!fftease_power_of_two(o)){
		error("%f is not a power of two",o);
		return;
	}
	x->overlap = (int)o;
	taint_init(x,1);
}

void taint_winfac(t_taint *x, t_floatarg f)
{
	if(!fftease_power_of_two(f)){
		error("%f is not a power of two",f);
		return;
	}
	x->winfac = (int)f;
	taint_init(x,1);
}

void taint_fftinfo( t_taint *x )
{
	if( ! x->overlap ){
		post("zero overlap!");
		return;
	}
	post("%s: FFT size %d, hopsize %d, windowsize %d", OBJECT_NAME, x->N, x->N/x->overlap, x->Nw);
	
}

void taint_free(t_taint *x)
{
#if MSP
	dsp_free((t_pxobject *) x);
#endif
	free(x->trigland);
	free(x->bitshuffle);
	free(x->Wanal);
	free(x->Wsyn);
	free(x->Hwin);
	free(x->inputOne);
	free(x->inputTwo);
	free(x->bufferOne);
	free(x->bufferTwo);
	free(x->channelOne);
	free(x->channelTwo);
	free(x->output);
}

void taint_pad(t_taint *x, t_floatarg pad)
{
	x->invert_pad = pad;
	taint_invert(x,x->invert);//resubmit to invert
}

void taint_invert(t_taint *x, t_floatarg toggle)
{
	
	x->invert_nextstate = toggle;
	x->invert_countdown = x->overlap; // delay effect for "overlap" vectors
	
	if(x->invert_nextstate){ // lower gain immediately; delay going to invert
		x->mult = (1. / (float) x->N) * x->invert_pad;
	} else {
		x->invert = 0; //immediately turn off invert; delay raising gain
	}
	
}

void taint_assist (t_taint *x, void *b, long msg, long arg, char *dst)
{
	
	if (msg == 1) {
		
		switch (arg) {
			case 0:		sprintf(dst,"(signal) Input One");break;
			case 1:		sprintf(dst,"(signal) Input Two"); break;
			case 2:		sprintf(dst,"(signal/float) Scaling Exponent"); break;
			case 3:		sprintf(dst,"(signal/float) Inverse Threshold"); break;
		}
	}
	
	else {
		
		if (msg == 2)
			sprintf(dst,"(signal) Output");
		
	}
}


void *taint_new(t_symbol *s, int argc, t_atom *argv)
{
	
#if MSP
	t_taint 	*x = (t_taint *) newobject(taint_class);
	dsp_setup((t_pxobject *)x,4);
	outlet_new((t_pxobject *)x, "signal");
	// x->x_obj.z_misc |= Z_NO_INPLACE; // probably not needed
#endif
#if PD
	t_taint *x = (t_taint *)pd_new(taint_class);
	/* add three additional signal inlets */
	inlet_new(&x->x_obj, &x->x_obj.ob_pd,gensym("signal"), gensym("signal"));
	inlet_new(&x->x_obj, &x->x_obj.ob_pd,gensym("signal"), gensym("signal"));
	inlet_new(&x->x_obj, &x->x_obj.ob_pd,gensym("signal"), gensym("signal"));
	outlet_new(&x->x_obj, gensym("signal"));
#endif
	
	
	/* optional arguments: scaling exponent, threshold (now linear), overlap, winfac */
	x->exponent = atom_getfloatarg(0,argc,argv);
	x->threshold = atom_getfloatarg(1,argc,argv);
	x->overlap = atom_getfloatarg(2,argc,argv);
	x->winfac = atom_getfloatarg(3,argc,argv);
	
	/*	
		x->threshold = (float) pow(10.0,(x->threshold * .05));
	*/
	/* sanity check */
	if(x->exponent < 0.25)
		x->exponent = 0.25;
	
	if(!fftease_power_of_two(x->overlap)){
		x->overlap = 4;
	}
	if(!fftease_power_of_two(x->winfac)){
		x->winfac = 1;
	}
  		
	x->vs = sys_getblksize();
	x->R = sys_getsr();
	
	taint_init(x,0);
	
	return (x);
}

void taint_init(t_taint *x, short initialized)
{
	int i;
	x->D = x->vs;
	x->N = x->D * x->overlap;
	x->Nw = x->N * x->winfac;
	limit_fftsize(&x->N,&x->Nw,OBJECT_NAME);
	
	x->N2 = (x->N)>>1;
	x->Nw2 = (x->Nw)>>1;
	x->inCount = -(x->Nw);
	x->mult = 1. / (float) x->N;
	if(!initialized){
		x->invert_pad = 0.025; // -32 dB
		x->invert_countdown = 0;
		x->mute = 0;
		x->invert = 0;
		x->Wanal = (float *) getbytes( MAX_Nw * sizeof(float) );	
		x->Wsyn = (float *) getbytes( MAX_Nw * sizeof(float) );	
		x->Hwin = (float *) getbytes( MAX_Nw * sizeof(float) ); 
		x->inputOne = (float *) getbytes( MAX_Nw * sizeof(float) );
		x->inputTwo = (float *) getbytes( MAX_Nw * sizeof(float) );	
		x->bufferOne = (float *) getbytes( MAX_N * sizeof(float) );
		x->bufferTwo = (float *) getbytes( MAX_N * sizeof(float) ); 
		x->channelOne = (float *) getbytes( (MAX_N+2) * sizeof(float) );
		x->channelTwo = (float *) getbytes( (MAX_N+2) * sizeof(float) );
		x->output = (float *) getbytes( MAX_Nw * sizeof(float) );
		x->bitshuffle = (int *) getbytes( MAX_N * 2 * sizeof( int ) );
		x->trigland = (float *) getbytes( MAX_N * 2 * sizeof( float ) );
	} 
	if(x->invert){
		x->mult *= x->invert_pad;
    }
	memset((char *)x->inputOne,0,x->Nw * sizeof(float));
	memset((char *)x->inputTwo,0,x->Nw * sizeof(float));
	memset((char *)x->output,0,x->Nw * sizeof(float));
		
	init_rdft( x->N, x->bitshuffle, x->trigland);
	makehanning( x->Hwin, x->Wanal, x->Wsyn, x->Nw, x->N, x->D, 1);
	
}


t_int *taint_perform(t_int *w)
{
	
	int		
    i,j,
    inCount,
    R,
    N,
    N2,
    D,
    Nw,
    invert = 0,
    even, odd,
    *bitshuffle;
	
	float		maxamp,	
		threshold = 1.,
		mult,
		exponent,
		a1, b1,
		a2, b2,
		*inputOne,
		*inputTwo,
		*bufferOne,
		*bufferTwo,
		*output,
		*Wanal,
		*Wsyn,
		*channelOne,
		*channelTwo,
		*trigland;
	
	
	
	/* get our inlets and outlets */
	
	t_taint *x = (t_taint *) (w[1]);
	t_float *inOne = (t_float *)(w[2]);
	t_float *inTwo = (t_float *)(w[3]);
	t_float *vec_exponent = (t_float *)(w[4]);
	t_float *vec_threshold = (t_float *)(w[5]);
	t_float *out = (t_float *)(w[6]);
	t_int n = w[7];
	
	short *connected = x->connected;
	/* dereference structure  */	
	if(connected[2])
		x->exponent = *vec_exponent;
	if(connected[3]){
		x->threshold = *vec_threshold;
		/*
		x->threshold = (float) (pow( 10., (x->threshold * .05)));
		 */
	}
	
	if(x->mute){
		while(n--)
			*out++ = 0.0;
		return w+8;
	}
	// do countdown
	if(x->invert_countdown > 0){
		
		if(x->invert) { // we 
		} else {
		}  
		--(x->invert_countdown);
		if(! x->invert_countdown){ // countdown just ended
			if(x->invert_nextstate){ // moving to invert (gain is already down)
				x->invert = x->invert_nextstate;
			} else { // invert is already off - now reset gain
				x->mult = 1. / (float) x->N;
			}
		}
	}
	
	inputOne = x->inputOne;
	inputTwo = x->inputTwo;
	bufferOne = x->bufferOne;
	bufferTwo = x->bufferTwo;
	inCount = x->inCount;
	R = x->R;
	N = x->N;
	N2 = x->N2;
	D = x->D;
	Nw = x->Nw;
	Wanal = x->Wanal;
	Wsyn = x->Wsyn;
	output = x->output;
	channelOne = x->channelOne;
	channelTwo = x->channelTwo;
	bitshuffle = x->bitshuffle;
	trigland = x->trigland;
	mult = x->mult;	
	invert = x->invert;
	exponent = x->exponent;
	
	if ( x->threshold != 0. )
		threshold = x->threshold;
	
	/* fill our retaining buffers */
	
	inCount += D;
	
	for ( j = 0 ; j < Nw - D ; j++ ) {
		inputOne[j] = inputOne[j+D];
		inputTwo[j] = inputTwo[j+D];
	}
	
	for ( j = Nw - D; j < Nw; j++ ) {
		inputOne[j] = *inOne++;
		inputTwo[j] = *inTwo++;
	}
	
	/* apply hamming window and fold our window buffer into the fft buffer */ 
	
	fold( inputOne, Wanal, Nw, bufferOne, N, inCount );
	fold( inputTwo, Wanal, Nw, bufferTwo, N, inCount );
	
	/* do an fft */ 
	
	rdft( N, 1, bufferOne, bitshuffle, trigland );
	rdft( N, 1, bufferTwo, bitshuffle, trigland );
	
	/* convert to polar coordinates from complex values */
	
	if (invert) {
		
		for ( i = 0; i <= N2; i++ ) {
			
			float magnitude;	
			
			odd = ( even = i<<1 ) + 1;
			
			a1 = ( i == N2 ? *(bufferOne+1) : *(bufferOne+even) );
			b1 = ( i == 0 || i == N2 ? 0. : *(bufferOne+odd) );
			
			a2 = ( i == N2 ? *(bufferTwo+1) : *(bufferTwo+even) );
			b2 = ( i == 0 || i == N2 ? 0. : *(bufferTwo+odd) );
			
			*(channelOne+even) = hypot( a1, b1 );
			*(channelOne+odd) = -atan2( b1, a1 );
			
			magnitude = *(channelTwo+even) = hypot( a2, b2 );
			*(channelTwo+odd) = -atan2( b2, a2 );
			
			/* use threshold for inverse filtering to avoid division by zero */
			
			if ( magnitude < threshold )
				magnitude = 0.;
			
			else  
				magnitude = 1. / magnitude;
			
			*(channelOne+even) *= magnitude;
			*(channelOne+even) = pow( *(channelOne+even), exponent );
		}
	}
	
	
	else {
		
		for ( i = 0; i <= N2; i++ ) {
			
			odd = ( even = i<<1 ) + 1;
			
			a1 = ( i == N2 ? *(bufferOne+1) : *(bufferOne+even) );
			b1 = ( i == 0 || i == N2 ? 0. : *(bufferOne+odd) );
			
			a2 = ( i == N2 ? *(bufferTwo+1) : *(bufferTwo+even) );
			b2 = ( i == 0 || i == N2 ? 0. : *(bufferTwo+odd) );
			
			*(channelOne+even) = hypot( a1, b1 );
			*(channelOne+odd) = -atan2( b1, a1 );
			
			*(channelTwo+even) = hypot( a2, b2 );
			*(channelTwo+odd) = -atan2( b2, a2 );
			
			/* simple multiplication of magnitudes */
			
			*(channelOne+even) *= *(channelTwo+even);
			
			*(channelOne+even) = pow( *(channelOne+even), exponent );
		}
	}
	
	/* convert back to complex form, read for the inverse fft */
	
	for ( i = 0; i <= N2; i++ ) {
		
		odd = ( even = i<<1 ) + 1;
		
		*(bufferOne+even) = *(channelOne+even) * cos( *(channelOne+odd) );
		
		if ( i != N2 )
			*(bufferOne+odd) = -(*(channelOne+even)) * sin( *(channelOne+odd) );
	}
	
	
	/* do an inverse fft */
	
	rdft( N, -1, bufferOne, bitshuffle, trigland );
	
	/* use slow inverse fft */
	
	//  rfft( bufferOne, N2, INVERSE );
	
	/* dewindow our result */
	
	overlapadd( bufferOne, N, Wsyn, output, Nw, inCount);
	
	/* set our output and adjust our retaining output buffer */
	
	for ( j = 0; j < D; j++ )
		*out++ = output[j] * mult;
	
	for ( j = 0; j < Nw - D; j++ )
		output[j] = output[j+D];
	
	for ( j = Nw - D; j < Nw; j++ )
		output[j] = 0.;
	
	
	/* restore state variables */
	
	x->inCount = inCount % Nw;
	return (w+8);
}		


void taint_dsp(t_taint *x, t_signal **sp, short *count)
{
	long i;
	
#if MSP
	for( i = 0; i < 4; i++ ){
		x->connected[i] = count[i];
	}
#endif
	/* signal is always connected in Pd */
#if PD 
	for( i = 0; i < 4; i++ ){
		x->connected[i] = 1;
	}
#endif
	
	/* reinitialize if vector size or sampling rate has been changed */
	if(x->vs != sp[0]->s_n || x->R != sp[0]->s_sr){
		x->vs = sp[0]->s_n;
		x->R = sp[0]->s_sr;
		taint_init(x,1);
	}	
	
	dsp_add(taint_perform, 7, x,
			sp[0]->s_vec,
			sp[1]->s_vec,
			sp[2]->s_vec,
			sp[3]->s_vec,
			sp[4]->s_vec,
			sp[0]->s_n);
}

