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

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

#define OBJECT_NAME "morphine~"

/*

Added additional inlet for morphine index

-EL 

*/

typedef struct _pickme {

  int		bin;
  float		value;

} pickme;


typedef struct _morphine
{
#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 *bitshuffle;
    float morphIndex;
    float exponScale;
    float *Wanal;	
    float *Wsyn;	
    float *inputOne;
    float *inputTwo;
    float *Hwin;
    float *bufferOne;
    float *bufferTwo;
    float *channelOne;
 	float *channelTwo;
    float *output;
    float mult; 
    float *trigland;
    pickme *picks;
    pickme *mirror;

  short connected[8];
  short mute;
  int overlap;//overlap factor
  int winfac;//window factor
  int vs;//vector size  

} t_morphine;


/* msp function prototypes */

void *morphine_new(t_symbol *s, int argc, t_atom *argv);
//t_int *offset_perform(t_int *w);
t_int *morphine_perform(t_int *w);
void morphine_dsp(t_morphine *x, t_signal **sp, short *count);
void morphine_assist(t_morphine *x, void *b, long m, long a, char *s);
void morphine_dest(t_morphine *x, double f);

int sortIncreasing( const void *a, const void *b );
//int qsortE (char *base_ptr, int total_elems, int size, int (*cmp)());
// avoid warning with legal function pointer prototype
int qsortE (char *base_ptr, int total_elems, int size, int (*cmp)(const void *a, const void *b));
void morphine_transition(t_morphine *x, t_floatarg f);
void morphine_free(t_morphine *x);
void morphine_mute(t_morphine *x, t_floatarg toggle);
void morphine_fftinfo(t_morphine *x);
void morphine_tilde_setup(void);
void morphine_overlap(t_morphine *x, t_floatarg o);
void morphine_winfac(t_morphine *x, t_floatarg o);
void morphine_init(t_morphine *x, short initialized);

int sortIncreasing( const void *a, const void *b )
{

  if ( ((pickme *) a)->value > ((pickme *) b)->value )
    return 1;

  if ( ((pickme *) a)->value < ((pickme *) b)->value )
    return -1;

  return 0;
}

#if MSP
void main(void)
{
    setup( (struct messlist **) &morphine_class, (void *) morphine_new, (method)morphine_free,
    		 (short) sizeof(t_morphine), 0, A_GIMME, 0);
  
    addmess((method)morphine_dsp, "dsp", A_CANT, 0);
    addmess((method)morphine_assist,"assist",A_CANT,0);    
    
    addmess((method)morphine_transition,"transition", A_FLOAT, 0);
  addmess((method)morphine_overlap,"overlap", A_FLOAT, 0);
  addmess((method)morphine_mute,"mute", A_FLOAT, 0);
  addmess((method)morphine_fftinfo,"fftinfo", 0);
  addmess((method)morphine_winfac,"winfac",A_FLOAT, 0);
  addfloat((method)morphine_dest);
  post("%s %s",OBJECT_NAME,FFTEASE_ANNOUNCEMENT);
    dsp_initclass();
}

void morphine_dest(t_morphine *x, double f)
{
int inlet = x->x_obj.z_in;

	if(inlet == 2)
		x->morphIndex = f;
//	post("index now %f", x->morphIndex);
}
#endif

#if PD
void morphine_tilde_setup(void)
{
  morphine_class = class_new(gensym("morphine~"), (t_newmethod)morphine_new, 
			     (t_method)morphine_free ,sizeof(t_morphine), 0,A_GIMME,0);
  CLASS_MAINSIGNALIN(morphine_class, t_morphine, x_f);
  class_addmethod(morphine_class, (t_method)morphine_dsp, gensym("dsp"), 0);
  class_addmethod(morphine_class, (t_method)morphine_assist, gensym("assist"), 0);
  class_addmethod(morphine_class, (t_method)morphine_overlap, gensym("overlap"), A_FLOAT,0);
  class_addmethod(morphine_class, (t_method)morphine_winfac, gensym("winfac"), A_FLOAT,0);
  class_addmethod(morphine_class, (t_method)morphine_mute, gensym("mute"), A_FLOAT,0);
  class_addmethod(morphine_class, (t_method)morphine_transition, gensym("transition"), A_FLOAT,0);
  class_addmethod(morphine_class, (t_method)morphine_fftinfo, gensym("fftinfo"), A_CANT,0);
  post("%s %s",OBJECT_NAME,FFTEASE_ANNOUNCEMENT);
}
#endif

/* diagnostic messages for Max */

void morphine_assist (t_morphine *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) Morph Index"); break;
    }
  }

  else {

    if (msg == 2)
      sprintf(dst,"(signal) output");

  }
}

void morphine_transition(t_morphine *x, t_floatarg f)
{	
	x->exponScale = (float)f;
}


void *morphine_new(t_symbol *s, int argc, t_atom *argv)
{

#if MSP
  t_morphine 	*x = (t_morphine *) newobject(morphine_class);
  dsp_setup((t_pxobject *)x,3);
  outlet_new((t_pxobject *)x, "signal");
#endif

#if PD
  t_morphine *x = (t_morphine *)pd_new(morphine_class);
  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: exponent scale, overlap, window factor */

  x->exponScale = atom_getfloatarg(0,argc,argv);
  x->overlap = atom_getfloatarg(1,argc,argv);
  x->winfac = atom_getfloatarg(2,argc,argv);

  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();
 
  morphine_init(x,0);

  return (x);
}

void morphine_init(t_morphine *x, short initialized)
{
  int i;
int BIGGIE = 32768;
  
  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->morphIndex = 0.;
    x->mute = 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));
    x->picks = (pickme *) getbytes(((MAX_N2)+1) * sizeof(pickme));
    x->mirror = (pickme *) getbytes(((MAX_N2)+1) * sizeof(pickme));

  } 
		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, 0);
  
}

t_int *morphine_perform(t_int *w)
{

  int		
			i,j,
			inCount,
			R,
			N,
			N2,
			D,
			Nw,
			lookupIndex,
  			even, odd,
  	 		*bitshuffle;

  float		maxamp,	
  			threshMult = 1.,
			mult,
			morphIndex,
			exponScale,
			a1, b1,
  			a2, b2,
  			*inputOne,
			*inputTwo,
			*bufferOne,
			*bufferTwo,
			*output,
			*Wanal,
			*Wsyn,
			*channelOne,
			*channelTwo,
			*trigland;

  pickme	*picks,
  			*mirror;

/* get our inlets and outlets */
	
  t_morphine *x = (t_morphine *) (w[1]);
  t_float *inOne = (t_float *)(w[2]);
  t_float *inTwo = (t_float *)(w[3]);
  t_float *vec_morphIndex = (t_float *)(w[4]);
  t_float *out = (t_float *)(w[5]);
  t_int n = w[6];

  short *connected = x->connected;
  	
  if(x->mute){
	while(n--)
		*out++ = 0.0;
	return w+7;
  }

/* dereference structure  */	

  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;	
  picks = x->picks;
  mirror = x->mirror;
  morphIndex = x->morphIndex;
  exponScale = x->exponScale;
  
  morphIndex = connected[2] ? *vec_morphIndex : x->morphIndex;
//  post("connected %d index %f stored index %f",connected[2],morphIndex, x->morphIndex);
  if ( morphIndex < 0 )
  	morphIndex = 0.;
  else {
    if ( morphIndex > 1. )
    	morphIndex = 1.;
  }
  
  
/* 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 */

    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 );


/* find amplitude differences between home and visitors */

	  (picks+i)->value = fabs( *(channelOne+even) - 
	  						*(channelTwo+even) );
	  (picks+i)->bin = i;  
    }

/* sort our differences in ascending order */


	qsortE( (char *) picks, (int) N2+1, (int) sizeof(pickme),
		 sortIncreasing );

      /* now we create an effective mirror of the sorted distribution.
         we will assure that the initial transition will be made from
         small spectral differences (when the sort behavior is increasing)
         and the ending transition will also be made from small spectral
         differences */

      for ( i=0; i <= N2; i += 2 ) {
        (mirror+(i/2))->bin = (picks+i)->bin;
        (mirror+(i/2))->value = (picks+i)->value;
      }

      for ( i=1; i <= N2; i += 2 ) {
        (mirror+(N2-(i/2)))->bin = (picks+i)->bin;
        (mirror+(N2-(i/2)))->value = (picks+i)->value;
      }


/* calculate our morphIndex from an exponential function based on exponScale */
      
     if (exponScale == 0.) 
       lookupIndex = (int) (( (float) N2 ) * morphIndex);
       
     else {
     
     	if ( morphIndex < .5 ) {
     
          lookupIndex = (int) ( ((float) N2) * ((
          			 (1. - exp( exponScale * morphIndex * 2. )) /
    				 (1. - exp( exponScale )) ) * .5) );
    	}
    				 
    	else {
    	
    	  lookupIndex = (int) ( ((float) N2) * ( .5 +  
          			 (( (1. - exp( -exponScale * (morphIndex - .5) * 2. )) /
    				 (1. - exp( -exponScale )) ) * .5) ) );
    	}			 
    				 
      }
      
      
//      post("%d", lookupIndex);
      
/* choose the bins that are least different first */

    for ( i=0; i <= lookupIndex; i++ ) {

	  even = ((mirror+i)->bin)<<1,
	  odd = (((mirror+i)->bin)<<1) + 1;	

	  *(channelOne+even) = *(channelTwo+even);
	  *(channelOne+odd) = *(channelTwo+odd);
    }

/* 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 );


/* 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+7);
}		
void morphine_free(t_morphine *x)
{
#if MSP
  dsp_free((t_pxobject *) x);
#endif
  freebytes(x->trigland,0);
  freebytes(x->bitshuffle,0);
  freebytes(x->Wanal,0);
  freebytes(x->Wsyn,0);
  freebytes(x->Hwin,0);
  freebytes(x->inputOne,0);
  freebytes(x->inputTwo,0);
  freebytes(x->bufferOne,0);
  freebytes(x->bufferTwo,0);
  freebytes(x->channelOne,0);
  freebytes(x->channelTwo,0);
  freebytes(x->picks,0);
  freebytes(x->mirror,0);
  freebytes(x->output,0);
}

void morphine_fftinfo( t_morphine *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 morphine_overlap(t_morphine *x, t_floatarg df)
{
int o = (int)df;

  if(!fftease_power_of_two(o)){
    error("%d is not a power of two",o);
    return;
  }
  x->overlap = (int)o;
  morphine_init(x,1);
}

void morphine_winfac(t_morphine *x, t_floatarg df)
{
int wf = (int) df;
  if(!fftease_power_of_two(wf)){
    error("%d is not a power of two",wf);
    return;
  }
  x->winfac = wf;
  morphine_init(x,1);
}

void morphine_mute(t_morphine *x, t_floatarg toggle)
{
  x->mute = (short)toggle;
}

void morphine_dsp(t_morphine *x, t_signal **sp, short *count)
{
long i;

#if MSP
  for( i = 0; i < 3; i++ ){
    x->connected[i] = count[i];
  }
#endif
  /* signal is always connected in Pd */
#if PD 
  for( i = 0; i < 3; 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;
    morphine_init(x,1);
  }	

	dsp_add(morphine_perform, 6, x,
		sp[0]->s_vec,
		sp[1]->s_vec,
		sp[2]->s_vec,
		sp[3]->s_vec,
		sp[0]->s_n);
}



