
/*
  Kjetil S. Matheussen, eksamen mus2840 2003

  k_bank~ is an external for PD that does recording, playing, realtime resampling,
  panning and looping. It works in a close relationship with eksamen.py. Eksamen.py
  sends commands directly with the pd send command to this external.

  It uses the "secret rabbit code" libsamplerate library written by Erik de Castro Lopo 
  for resampling.

  TODO:
  -It does produce some horrible scratching noises sometimes. I think it happens
   when the the src value for the resample function change too much between
   too calls, but I haven't had the time to fix it.

   -Theres no working click-remover functionality when starting and stopping playing
    a sound.

   -Looping a pingpong playing sound produce some clicks. Know why, havent had time
    to fix.

    -Uses a lot more cpu-power than is necesarry because of the buffer that is filled
     with BUFFERSIZE number of frames at least one time for each call to the dsp function.
*/




#include "m_pd.h"
#include <math.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "samplerate.h"


#define BUFFERSIZE 4096

#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))

#define SCALE(x,x1,x2,y1,y2) ((y1)+(((x)-(x1))*((y2)-(y1)))/((x2)-(x1)))

#define NUM_BUFFERS 20
#define BUFFER_BUFFERSIZE (44100*10)

#define CLICKTHRESHOLD 0.1f
#define PANTHRESHOLD 0.01f
#define P_VOL_THRESHOLD 0.04f

static t_float k_buffers[NUM_BUFFERS][BUFFER_BUFFERSIZE]={{0}};
static int k_buffers_len[NUM_BUFFERS]={0};
static int k_buffers_writeplace[NUM_BUFFERS]={0};
static int k_buffers_buffernum=-1;

typedef struct _k_bank
{
  t_object x_obj;

  int banknum;
  
  t_float x_float; /* the signal dummy */

  float src;
  float pan;

  //int pause; /* when 1, dont make a sound. */
  float last_p_vol;
  float p_vol;

  t_float buffer[BUFFERSIZE];

  int buffernum;
  float loopstart;
  int lastread;
  float loopend;
  int num_lefttoplay;

  int backwards;
  int pingpong;

  int playlen;

  SRC_STATE *state;
  int src_error;

  float last_pan;

  // For the click-remover
  float last_r;
  float last_l;

} t_k_bank;


static t_class *k_bank_class;



static int k_bank_doit_testing(t_k_bank *x,
			   float *in,
			   int len_in,
			   float *out,
			   int len_out,
			   int *used_in
			   )
{
  int lokke=8;
  
  for(lokke=0;lokke<MIN(len_in,len_out);lokke++){
    out[lokke]=in[lokke];
  }
  *used_in=lokke;
  //printf("Playing\n");

  return lokke;
}

static int k_bank_doit(t_k_bank *x,
			    float *in,
			    int len_in,
			    float *out,
			    int len_out,
			    int *used_in
			   )
{

  SRC_DATA src_data={
    in,
    out,
    len_in,len_out,
    0,0,
    1,
    //MAX(0.4,1.0f/x->src)
    1.0f/x->src
  };

  src_process(x->state,&src_data);

  *used_in=src_data.input_frames_used;

  return src_data.output_frames_gen;
}
			    

static t_int *k_bank_perform(t_int *w)
{
  t_k_bank *x = (t_k_bank *)(w[1]);
  t_float *in = (t_float *)(w[2]);
  t_float *in2 = (t_float *)(w[3]);
  t_float *out1 = (t_float *)(w[4]);
  t_float *out2 = (t_float *)(w[5]);

  int n,n_org;
  int bufferlength=k_buffers_len[x->buffernum];
  int loopstart=MIN(bufferlength,MAX(0,bufferlength*x->loopstart));
  int loopend=MIN(bufferlength,MAX(loopstart+1,x->loopend*bufferlength));
  int lokke;
  int r_buffernum=k_buffers_buffernum;

  n=n_org= (int)(w[6]);

#if 0
  for(lokke=0;lokke<n;lokke++){
    out1[lokke]=in[lokke];
  }
  goto end;
#endif

  for(lokke=0;lokke<n;lokke++){
    out1[lokke]=0.0f;
  }

  /* Recording. */
  if(x->banknum==0 && r_buffernum>-1){
    for(lokke=0;lokke<n;lokke++){
      if(k_buffers_writeplace[r_buffernum]<BUFFER_BUFFERSIZE){
	k_buffers[r_buffernum][k_buffers_writeplace[r_buffernum]]=in[lokke];
	k_buffers_len[r_buffernum]++;
	k_buffers_writeplace[r_buffernum]++;
      }
    }
  }

  if(loopstart>=loopend) goto end;
  if(x->playlen==0) goto end;

  while (n>0){
    int readpoint=x->lastread;
    int used_in;
    int less;
    int backwards=x->backwards;

    if(x->num_lefttoplay<=0 && x->playlen==-1) break;

    for(lokke=0;lokke<BUFFERSIZE;lokke++){
      x->buffer[lokke]=k_buffers[x->buffernum][readpoint];
      //printf("playing %f %d %d\n",x->buffer[lokke],x->buffernum,readpoint);
      if(backwards==0){
	readpoint+=1;
	if(readpoint==loopend){
	  if(x->pingpong==0){
	    readpoint=loopstart;
	  }else{
	    readpoint-=1;
	    backwards=1;
	  }
	}
      }else{
	readpoint-=1;
	if(readpoint==loopstart){
	  if(x->pingpong==0){
	    readpoint=loopend;
	  }else{
	    backwards=0;
	  }
	}
      }
    }
    less = k_bank_doit(x,
		       x->buffer,
		       BUFFERSIZE,
		       &out1[n_org - n],
		       n,
		       &used_in
		       );

    if(x->backwards==0){
      x->lastread+=used_in;
      if(x->pingpong==0){
	while(x->lastread >= loopend){
	  x->lastread -= (loopend - loopstart);
	  x->num_lefttoplay--;
	}
      }else{
	if(x->lastread >= loopend){
	  x->lastread=loopend-1;
	  x->backwards=1;
	  x->num_lefttoplay--;
	}
      }
    }else{
      x->lastread-=used_in;
      if(x->pingpong==0){
	while(x->lastread < loopstart){
	  x->lastread += (loopend - loopstart);
	  x->num_lefttoplay--;
	}
      }else{
	if(x->lastread < loopstart){
	  x->lastread=loopstart;
	  x->num_lefttoplay--;
	  x->backwards=0;
	}
      }
    }

    if(less==0) goto end;
    n-=less;


  }

 end:
#if 1
  /* Panning and pausing*/
  {
    float lpan,rpan;
    float newpan=x->pan;

    // To large change in pan value cause clicks.
    if(fabs(x->last_pan - x->pan)>PANTHRESHOLD){
      if(x->pan < x->last_pan){
	newpan=x->last_pan-PANTHRESHOLD;
      }else{
	newpan=x->last_pan+PANTHRESHOLD;
      }
    }

    if(newpan<0.0f){
      lpan=1.0f;
      rpan= 1.0f + newpan;
    }else{
      lpan=SCALE(newpan,0.0f,1.0f,1.0f,0.0f);
      rpan=1.0f;
    }

    for(lokke=0;lokke<n_org;lokke++){
      float newp_vol=x->p_vol;
      if(fabs(x->last_p_vol - x->p_vol) > P_VOL_THRESHOLD){
	if(x->p_vol < x->last_p_vol){
	  newp_vol=x->last_p_vol-P_VOL_THRESHOLD;
	}else{
	  newp_vol=x->last_p_vol+P_VOL_THRESHOLD;
	}
      }
      out2[lokke]=out1[lokke]*rpan;
      out1[lokke]*=lpan;
      out2[lokke]*=newp_vol;
      out1[lokke]*=newp_vol;
      x->last_p_vol=newp_vol;
    }

    x->last_pan=newpan;
  }
#endif  

  if(x->playlen>0){
    x->playlen-=n_org;
    if(x->playlen<0) x->playlen=0;
  }


#if 0
  /* Remove clicks (didn't work) */
  for(lokke=0;lokke<n_org;lokke++){
    if(fabs(x->last_l -  out1[lokke] > CLICKTHRESHOLD)){
      if(out1[lokke]>x->last_l){
	out1[lokke]=x->last_l+CLICKTHRESHOLD;
      }else{
	out1[lokke]=x->last_l-CLICKTHRESHOLD;
      }
    }
    if(fabs(x->last_r -  out2[lokke] > CLICKTHRESHOLD)){
      if(out2[lokke] > x->last_r){
	out2[lokke]=x->last_r+CLICKTHRESHOLD;
      }else{
	out2[lokke]=x->last_r-CLICKTHRESHOLD;
      }
    }
    x->last_l=out1[lokke];
    x->last_r=out2[lokke];
  }
#endif

       
  return (w+7);

}


static void k_bank_dsp(t_k_bank *x, t_signal **sp)
{
  dsp_add(
	  k_bank_perform,
	  6,
	  x,
	  sp[0]->s_vec,
	  sp[1]->s_vec,
	  sp[2]->s_vec,
	  sp[3]->s_vec,
	  sp[0]->s_n
	  );
}


static void k_bank_set(t_k_bank *x,t_symbol *s,float f_buffernum){
  t_garray *a;
  int lokke;
  int len;
  float *data;
  int buffernum=f_buffernum;

  if (!(a = (t_garray *)pd_findbyclass(s, garray_class))){
    error("%s: no such array", s->s_name);
    return;
  }

  if (!garray_getfloatarray(a, &len, &data)){
    error("bad template for tabread");
    return;
  }

  for(lokke=0;lokke<MIN(len,BUFFER_BUFFERSIZE);lokke++){
    k_buffers[buffernum][lokke]=data[lokke];
  }

  k_buffers_len[buffernum]=MIN(len,BUFFER_BUFFERSIZE);

  printf("k_bank_set num:%d len:%d\n",buffernum,MIN(BUFFER_BUFFERSIZE,len));
}


static void k_bank_reset(t_k_bank *x){
  x->loopstart=0;
  x->loopend=0;
  x->lastread=0;
}


static void k_bank_set_src(t_k_bank *x, t_float f){
  x->src=MAX(0.01f,f);
  //  printf("k_bank_set_src %f\n",x->src);
}

static void k_bank_set_pan(t_k_bank *x, t_float f){
  x->pan=MAX(-1.0f,MIN(1.0,f));
  //printf("k_bank_set_pan %f\n",x->pan);
}

static void k_bank_record(t_k_bank *x,t_float f){
  int buffernum=f;
  if(x->banknum>0) return;
  printf("k_bank_record %d\n",buffernum);
  k_buffers_len[buffernum]=0;
  k_buffers_writeplace[buffernum]=0;
  k_buffers_buffernum=buffernum;
}

static void k_bank_stop(t_k_bank *x){
  printf("k_bank_stop\n");
  k_buffers_buffernum=-1;
}

static void k_bank_play(t_k_bank *x,int soundnum,float src,float start,float loopstart,float loopend,int repeat,float playlen,int backwards,int pingpong){
  printf("k_bank_play num=%d src=%f start=%f loopstart=%f loopend=%f repeat=%d playlen=%f backwards=%d pingpong=%d\n",
	 soundnum,src,start,loopstart,loopend,repeat,playlen,backwards,pingpong);
  x->buffernum=soundnum;
  if(src>=0.0f)
    x->src=src;
  x->lastread=k_buffers_len[soundnum]*start;
  x->loopstart=loopstart;
  x->loopend=loopend;
  x->backwards=backwards;
  x->pingpong=pingpong;
  x->num_lefttoplay=repeat;
  if(playlen==0.0f)
    x->playlen=-1;
  else
    x->playlen=playlen*sys_getsr();
}

static void k_bank_play2(t_k_bank *x, t_symbol *s, int argc, t_atom *argv){
  k_bank_play(x,
	      atom_getfloatarg(0,argc,argv),
	      atom_getfloatarg(1,argc,argv),
	      atom_getfloatarg(2,argc,argv),
	      atom_getfloatarg(3,argc,argv),
	      atom_getfloatarg(4,argc,argv),
	      atom_getfloatarg(5,argc,argv),
	      atom_getfloatarg(6,argc,argv),
	      atom_getfloatarg(7,argc,argv),
	      atom_getfloatarg(8,argc,argv)
	      );
}


/* Messages from eksamen.py arrives here. */

static void k_bank_list(t_k_bank *x, t_symbol *s, int argc, t_atom *argv){
  char *arg0=atom_getsymbolarg(0,argc,argv)->s_name;
  if(!strcmp(arg0,"record")){
    k_bank_record(x,atom_getfloatarg(1,argc,argv));
    return;
  }
  if(!strcmp(arg0,"stop")){
    k_bank_stop(x);
    return;
  }
  if(!strcmp(arg0,"set_pause")){
    x->p_vol=0.0f;
    return;
  }
  if(!strcmp(arg0,"unset_pause")){
    x->p_vol=1.0f;
    return;
  }
  if(!strcmp(arg0,"set_src")){
    k_bank_set_src(x,atom_getfloatarg(1,argc,argv));
    return;
  }
  if(!strcmp(arg0,"set_pan")){
    k_bank_set_pan(x,atom_getfloatarg(1,argc,argv));
    return;
  }
  if(!strcmp(arg0,"play")){
    k_bank_play(x,
		atom_getfloatarg(1,argc,argv),
		atom_getfloatarg(2,argc,argv),
		atom_getfloatarg(3,argc,argv),
		atom_getfloatarg(4,argc,argv),
		atom_getfloatarg(5,argc,argv),
		atom_getfloatarg(6,argc,argv),
		atom_getfloatarg(7,argc,argv),
		atom_getfloatarg(8,argc,argv),
		atom_getfloatarg(9,argc,argv)
		);
    return;
  }
  if(!strcmp(arg0,"set")){
    k_bank_set(x,
	       atom_getsymbolarg(1,argc,argv),
	       atom_getfloatarg(2,argc,argv)
	       );
    return;
  }

  printf("got unknown list -%s-\n",arg0);
}


static void k_bank_free(t_k_bank *x){
  src_delete(x->state);
}

static void *k_bank_new(t_floatarg banknum){
  char temp[500];
  long i;
  
  t_k_bank *x = (t_k_bank *)pd_new(k_bank_class);

  x->banknum=banknum;
  //x->state=src_new(SRC_SINC_BEST_QUALITY,1,&x->src_error);
  x->state=src_new(SRC_SINC_FASTEST,1,&x->src_error);
  x->last_p_vol=1.0f;
  x->p_vol=1.0f;

  inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
  outlet_new(&x->x_obj, gensym("signal"));	/* signal out for channel 1 */
  outlet_new(&x->x_obj, gensym("signal"));	/* signal out for channel 2 */

  sprintf(temp,"b%d",x->banknum);
  pd_bind(&x->x_obj.ob_pd,gensym(temp));

  if(x->banknum==0){
    pd_bind(&x->x_obj.ob_pd,gensym("record"));
    pd_bind(&x->x_obj.ob_pd,gensym("stop"));
  }
  
  return (x);
}


void k_bank_tilde_setup(void)
{

  k_bank_class = class_new(gensym("k_bank~"), (t_newmethod)k_bank_new, (t_method)k_bank_free,
			   sizeof(t_k_bank), 0, A_DEFFLOAT, 0);

  CLASS_MAINSIGNALIN(k_bank_class, t_k_bank, x_float);
  class_addmethod(k_bank_class, (t_method)k_bank_dsp, gensym("dsp"), 0);
  class_addmethod(k_bank_class, (t_method)k_bank_reset, gensym("reset"),0);
  class_addmethod(k_bank_class, (t_method)k_bank_set, gensym("set"), A_SYMBOL, A_FLOAT, 0);
  
  class_addmethod(k_bank_class, (t_method)k_bank_set_src, gensym("set_src"), A_FLOAT, 0);
  class_addmethod(k_bank_class, (t_method)k_bank_set_pan, gensym("set_pan"), A_FLOAT, 0);
  class_addmethod(k_bank_class, (t_method)k_bank_record, gensym("record"), A_FLOAT, 0);
  class_addmethod(k_bank_class, (t_method)k_bank_stop, gensym("stop"), 0);
  class_addmethod(k_bank_class, (t_method)k_bank_play2, gensym("play"), A_GIMME,0);
  
  class_addlist(k_bank_class, k_bank_list);
  
  class_sethelpsymbol(k_bank_class, gensym("help-k_bank~.pd"));
}

