/* Copyright (c) 1997-1999 Miller Puckette.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

#include "m_pd.h"
#include "iemlib.h"
#include "iem_dp.h"

#define DEFDELVS_TT 64             /* LATER get this from canvas at DSP time */
#define XTRASAMPS_TT 4
#define SAMPBLK_TT 4

/* ----------------------------- delwrite~~ ----------------------------- */

typedef struct delwrite_tilde_tilde_ctl
  {
    int c_n;
    t_sample *c_vec;
    int c_phase;
  } t_delwrite_tilde_tilde_ctl;

typedef struct _delwrite_tilde_tilde
  {
    t_object x_obj;
    t_symbol *x_sym;
    double x_deltime;
    t_delwrite_tilde_tilde_ctl x_cspace;
    int x_sortno;   /* DSP sort number at which this was last put on chain */
    int x_rsortno;  /* DSP sort # for first delread or write in chain */
    int x_vecsize;  /* vector size for delread~ to use */
    t_float x_f;
  } t_delwrite_tilde_tilde;

//extern static int delread_zero;

static t_class *delwrite_tilde_tilde_class;

static void delwrite_tilde_tilde_updatesr (t_delwrite_tilde_tilde *x, t_float sr) /* added by Mathieu Bouchard */
{
  int nsamps = (int)(x->x_deltime * (double)sr * (double)(0.001));
  if (nsamps < 1) nsamps = 1;
  nsamps += ((- nsamps) & (SAMPBLK_TT - 1));
  nsamps += DEFDELVS_TT;
  if(x->x_cspace.c_n != nsamps)
  {
    x->x_cspace.c_vec = (t_sample *)resizebytes(x->x_cspace.c_vec,
        (x->x_cspace.c_n + XTRASAMPS_TT) * sizeof(t_sample),
        (nsamps + XTRASAMPS_TT) * sizeof(t_sample));
    x->x_cspace.c_n = nsamps;
    x->x_cspace.c_phase = XTRASAMPS_TT;
  }
}

    /* routine to check that all delwrites/delreads/vds have same vecsize */
static void delwrite_tilde_tilde_checkvecsize(t_delwrite_tilde_tilde *x, int vecsize)
{
    if (x->x_rsortno != ugen_getsortno())
    {
        x->x_vecsize = vecsize;
        x->x_rsortno = ugen_getsortno();
    }
    /*
        LATER this should really check sample rate and blocking, once that is
        supported.  Probably we don't actually care about vecsize.
        For now just suppress this check. */
#if 0
    else if (vecsize != x->x_vecsize)
        pd_error(x, "delread/delwrite/vd vector size mismatch");
#endif
}

static void *delwrite_tilde_tilde_new(t_symbol *s, t_floatarg coarse_msec, t_floatarg fine_msec)
{
  t_delwrite_tilde_tilde *x = (t_delwrite_tilde_tilde *)pd_new(delwrite_tilde_tilde_class);

  if(!*s->s_name)
    s = gensym("delwrite~~");

  pd_bind(&x->x_obj.ob_pd, s);
  x->x_sym = s;
  x->x_deltime = iem_dp_calc_sum(coarse_msec, fine_msec);
  x->x_cspace.c_n = 0;
  x->x_cspace.c_vec = (t_sample *)getbytes(XTRASAMPS_TT * sizeof(t_sample));
  x->x_sortno = 0;
  x->x_vecsize = 0;
  x->x_f = 0;
  return (x);
}

static t_int *delwrite_tilde_tilde_perform(t_int *w)
{
    t_float *in = (t_float *)(w[1]);
    t_delwrite_tilde_tilde_ctl *c = (t_delwrite_tilde_tilde_ctl *)(w[2]);
    int n = (int)(w[3]);
    int phase = c->c_phase, nsamps = c->c_n;
    t_sample *vp = c->c_vec, *bp = vp + phase, *ep = vp + (c->c_n + XTRASAMPS_TT);

    phase += n;
    while (n--)
    {
        t_sample f = *in++;
        if(IEM_DENORMAL(f))
            f = 0;
        *bp++ = f;
        if (bp == ep)
        {
            vp[0] = ep[-4];
            vp[1] = ep[-3];
            vp[2] = ep[-2];
            vp[3] = ep[-1];
            bp = vp + XTRASAMPS_TT;
            phase -= nsamps;
        }
    }
    c->c_phase = phase;
    return (w+4);
}

static void delwrite_tilde_tilde_dsp(t_delwrite_tilde_tilde *x, t_signal **sp)
{
  dsp_add(delwrite_tilde_tilde_perform, 3, sp[0]->s_vec, &x->x_cspace, sp[0]->s_n);
  x->x_sortno = ugen_getsortno();
  delwrite_tilde_tilde_checkvecsize(x, sp[0]->s_n);
  delwrite_tilde_tilde_updatesr(x, sp[0]->s_sr);
}

static void delwrite_tilde_tilde_free(t_delwrite_tilde_tilde *x)
{
    pd_unbind(&x->x_obj.ob_pd, x->x_sym);
    freebytes(x->x_cspace.c_vec,
        (x->x_cspace.c_n + XTRASAMPS_TT) * sizeof(t_sample));
}

static void delwrite_tilde_tilde_setup(void)
{
    delwrite_tilde_tilde_class = class_new(gensym("delwrite~~"),
        (t_newmethod)delwrite_tilde_tilde_new, (t_method)delwrite_tilde_tilde_free,
        sizeof(t_delwrite_tilde_tilde), 0, A_DEFSYM, A_DEFFLOAT, A_DEFFLOAT, 0);
    CLASS_MAINSIGNALIN(delwrite_tilde_tilde_class, t_delwrite_tilde_tilde, x_f);
    class_addmethod(delwrite_tilde_tilde_class, (t_method)delwrite_tilde_tilde_dsp,
        gensym("dsp"), A_CANT, 0);
}

/* ----------------------------- delread~~ ----------------------------- */
static t_class *delread_tilde_tilde_class;

typedef struct _delread_tilde_tilde
  {
    t_object x_obj;
    t_symbol *x_sym;
    t_float x_fine;
    double x_deltime;  /* delay in msec */
    int x_delsamps;     /* delay in samples */
    t_float x_sr;       /* samples per msec */
    t_float x_n;        /* vector size */
    int x_zerodel;      /* 0 or vecsize depending on read/write order */
  } t_delread_tilde_tilde;

static void delread_tilde_tilde_list(t_delread_tilde_tilde *x, t_symbol *s, int ac, t_atom *av);

static void *delread_tilde_tilde_new(t_symbol *s, int ac, t_atom *av)
{
  t_symbol *delname;
  t_delread_tilde_tilde *x = (t_delread_tilde_tilde *)pd_new(delread_tilde_tilde_class);

  if((ac > 0) && IS_A_SYMBOL(av, 0))
    delname = atom_getsymbolarg(0, ac, av);
  else
    delname = &s_;
  x->x_sym = delname;
  x->x_sr = 1;
  x->x_n = 1;
  x->x_zerodel = 0;
  delread_tilde_tilde_list(x, &s_list, ac-1, av+1);
  floatinlet_new(&x->x_obj, &x->x_fine);
  outlet_new(&x->x_obj, &s_signal);
  return (x);
}

static void delread_tilde_tilde_list(t_delread_tilde_tilde *x, t_symbol *s, int ac, t_atom *av)
{
  t_float coarse, fine;
  t_delwrite_tilde_tilde *delwriter = (t_delwrite_tilde_tilde *)pd_findbyclass(x->x_sym, delwrite_tilde_tilde_class);

  if((ac > 0) && (IS_A_FLOAT(av, 0)))
    coarse = atom_getfloatarg(0, ac, av);
  else
    coarse = 0.0;
  if((ac > 1) && (IS_A_FLOAT(av, 1)))
    fine = atom_getfloatarg(1, ac, av);
  else
    fine = 0.0;
  x->x_deltime = iem_dp_calc_sum(coarse, fine);
  if(delwriter)
  {
    int delsize = delwriter->x_cspace.c_n;

    x->x_delsamps = (int)(0.5 + (double)x->x_sr * x->x_deltime) + x->x_n - x->x_zerodel;
    if(x->x_delsamps < x->x_n)
      x->x_delsamps = x->x_n;
    else if(x->x_delsamps > delwriter->x_cspace.c_n - DEFDELVS_TT)
      x->x_delsamps = delwriter->x_cspace.c_n - DEFDELVS_TT;
  }
}

static void delread_tilde_tilde_double(t_delread_tilde_tilde *x, double d)
{
  t_delwrite_tilde_tilde *delwriter = (t_delwrite_tilde_tilde *)pd_findbyclass(x->x_sym, delwrite_tilde_tilde_class);

  x->x_deltime = d;
  if(delwriter)
  {
    int delsize = delwriter->x_cspace.c_n;

    x->x_delsamps = (int)(0.5 + (double)x->x_sr * x->x_deltime) + x->x_n - x->x_zerodel;
    if(x->x_delsamps < x->x_n)
      x->x_delsamps = x->x_n;
    else if(x->x_delsamps > delwriter->x_cspace.c_n - DEFDELVS_TT)
      x->x_delsamps = delwriter->x_cspace.c_n - DEFDELVS_TT;
  }
}

static void delread_tilde_tilde_float(t_delread_tilde_tilde *x, t_float coarse)
{
  t_delwrite_tilde_tilde *delwriter = (t_delwrite_tilde_tilde *)pd_findbyclass(x->x_sym, delwrite_tilde_tilde_class);

  x->x_deltime = iem_dp_calc_sum(coarse, x->x_fine);
  if(delwriter)
  {
    int delsize = delwriter->x_cspace.c_n;

    x->x_delsamps = (int)(0.5 + (double)x->x_sr * x->x_deltime) + x->x_n - x->x_zerodel;
    if(x->x_delsamps < x->x_n)
      x->x_delsamps = x->x_n;
    else if(x->x_delsamps > delwriter->x_cspace.c_n - DEFDELVS_TT)
      x->x_delsamps = delwriter->x_cspace.c_n - DEFDELVS_TT;
  }
}

static t_int *delread_tilde_tilde_perform(t_int *w)
{
  t_sample *out = (t_float *)(w[1]);
  t_delwrite_tilde_tilde_ctl *c = (t_delwrite_tilde_tilde_ctl *)(w[2]);
  int delsamps = *(int *)(w[3]);
  int n = (int)(w[4]);
  int phase = c->c_phase - delsamps, nsamps = c->c_n;
  t_sample *vp = c->c_vec, *bp, *ep = vp + (c->c_n + XTRASAMPS_TT);

  if(phase < 0)
    phase += nsamps;
  bp = vp + phase;
  while(n--)
  {
    *out++ = *bp++;
    if(bp == ep)
      bp -= nsamps;
  }
  return (w+5);
}

static void delread_tilde_tilde_dsp(t_delread_tilde_tilde *x, t_signal **sp)
{
  t_delwrite_tilde_tilde *delwriter = (t_delwrite_tilde_tilde *)pd_findbyclass(x->x_sym, delwrite_tilde_tilde_class);
  x->x_sr = sp[0]->s_sr * 0.001;
  x->x_n = sp[0]->s_n;
  if(delwriter)
  {
    delwrite_tilde_tilde_checkvecsize(delwriter, sp[0]->s_n);
    x->x_zerodel = (delwriter->x_sortno == ugen_getsortno() ?
                    0 : delwriter->x_vecsize);
    delread_tilde_tilde_double(x, x->x_deltime);
    dsp_add(delread_tilde_tilde_perform, 4,
            sp[0]->s_vec, &delwriter->x_cspace, &x->x_delsamps, sp[0]->s_n);
  }
  else if (*x->x_sym->s_name)
    error("delread~~: %s: no such delwrite~~",x->x_sym->s_name);
}

static void delread_tilde_tilde_setup(void)
{
  delread_tilde_tilde_class = class_new(gensym("delread~~"),
    (t_newmethod)delread_tilde_tilde_new, 0,
    sizeof(t_delread_tilde_tilde), 0, A_GIMME, 0);
  class_addmethod(delread_tilde_tilde_class, (t_method)delread_tilde_tilde_dsp,
    gensym("dsp"), A_CANT, 0);
  class_addfloat(delread_tilde_tilde_class, (t_method)delread_tilde_tilde_float);
}

/* ----------------------------- vd~~ ----------------------------- */
static t_class *vd_tilde_tilde_class;

typedef struct _vd_tilde_tilde
  {
    t_object x_obj;
    t_symbol *x_sym;
    t_float x_sr;       /* samples per msec */
    int x_zerodel;      /* 0 or vecsize depending on read/write order */
    t_float x_f;
  } t_vd_tilde_tilde;

static void *vd_tilde_tilde_new(t_symbol *s)
{
  t_vd_tilde_tilde *x = (t_vd_tilde_tilde *)pd_new(vd_tilde_tilde_class);

  if(!*s->s_name)
    s = gensym("vd~~");
  x->x_sym = s;
  x->x_sr = 1;
  x->x_zerodel = 0;
  inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
  outlet_new(&x->x_obj, &s_signal);
  x->x_f = 0;
  return (x);
}

static t_int *vd_tilde_tilde_perform(t_int *w)
{
  t_sample *cin = (t_sample *)(w[1]);
  t_sample *fin = (t_sample *)(w[2]);
  t_sample *out = (t_sample *)(w[3]);
  t_delwrite_tilde_tilde_ctl *ctl = (t_delwrite_tilde_tilde_ctl *)(w[4]);
  t_vd_tilde_tilde *x = (t_vd_tilde_tilde *)(w[5]);
  int n = (int)(w[6]);

  int nsamps = ctl->c_n;
  double limit = (double)nsamps - (double)n - 1.0;
  t_sample fn = n-1;
  t_sample *vp = ctl->c_vec, *bp, *wp = vp + ctl->c_phase;
  t_sample zerodel = x->x_zerodel;
  while (n--)
  {
    double delsamps = (double)x->x_sr * iem_dp_calc_sum(*cin++, *fin++) - (double)zerodel;
    t_sample frac;
    int idelsamps;
    t_sample a, b, c, d, cminusb;

    if(delsamps < 1.00001)
      delsamps = 1.00001;
    if(delsamps > limit)
      delsamps = limit;
    delsamps += (double)fn;
    fn = fn - 1.0f;
    idelsamps = (int)delsamps;
    frac = (t_sample)(delsamps - (double)idelsamps);
    bp = wp - idelsamps;
    if(bp < vp + 4)
      bp += nsamps;
    d = bp[-3];
    c = bp[-2];
    b = bp[-1];
    a = bp[0];
    cminusb = c - b;
    *out++ = b + frac * ( cminusb - 0.1666667f * (1.-frac) *
        ( (d - a - 3.0f * cminusb) * frac + (d + 2.0f*a - 3.0f*b) ) );
  }
  return (w+7);
}

static void vd_tilde_tilde_dsp(t_vd_tilde_tilde *x, t_signal **sp)
{
  t_delwrite_tilde_tilde *delwriter = (t_delwrite_tilde_tilde *)pd_findbyclass(x->x_sym, delwrite_tilde_tilde_class);
  x->x_sr = sp[0]->s_sr * 0.001;
  if(delwriter)
  {
    delwrite_tilde_tilde_checkvecsize(delwriter, sp[0]->s_n);
    x->x_zerodel = (delwriter->x_sortno == ugen_getsortno() ?
      0 : delwriter->x_vecsize);
    dsp_add(vd_tilde_tilde_perform, 6, sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec,
      &delwriter->x_cspace, x, sp[0]->s_n);
  }
  else
    error("vd~~: %s: no such delwrite~~", x->x_sym->s_name);
}

static void vd_tilde_tilde_setup(void)
{
  vd_tilde_tilde_class = class_new(gensym("vd~~"), (t_newmethod)vd_tilde_tilde_new, 0,
                          sizeof(t_vd_tilde_tilde), 0, A_DEFSYM, 0);
  class_addmethod(vd_tilde_tilde_class, (t_method)vd_tilde_tilde_dsp, gensym("dsp"), A_CANT, 0);
  CLASS_MAINSIGNALIN(vd_tilde_tilde_class, t_vd_tilde_tilde, x_f);
}

/********************/

void delay_tilde_tilde_setup(void)
{
  delwrite_tilde_tilde_setup();
  delread_tilde_tilde_setup();
  vd_tilde_tilde_setup();
}
