/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* graphical inlets and outlets, both for control and signals.  */

/* This code is highly inefficient; messages actually have to be forwarded
* by inlets and outlets.  The outlet is in even worse shape than the inlet;
in order to avoid having a "signal" method in the class, the oulet actually
sprouts an inlet, which forwards the message to the "outlet" object, which
sends it on to the outlet proper. */

#include "m_pd.h"
#include "g_canvas.h"
#include <string.h>

/* ------------------------- vinlet -------------------------- */
t_class *vinlet_class;

typedef struct _vinlet
{
    t_object x_obj;
    t_canvas *x_canvas;
    t_inlet *x_inlet;
    int x_bufsize;
    t_float *x_buf; 	    /* signal buffer; zero if not a signal */
    t_float *x_endbuf;
    t_float *x_fill;
    t_float *x_read;
    int x_hop;
} t_vinlet;

static void *vinlet_new(t_symbol *s)
{
    t_vinlet *x = (t_vinlet *)pd_new(vinlet_class);
    x->x_canvas = canvas_getcurrent();
    x->x_inlet = canvas_addinlet(x->x_canvas, &x->x_obj.ob_pd, 0);
    x->x_bufsize = 0;
    x->x_buf = 0;
    outlet_new(&x->x_obj, 0);
    return (x);
}

static void vinlet_bang(t_vinlet *x)
{
    outlet_bang(x->x_obj.ob_outlet);
}

static void vinlet_pointer(t_vinlet *x, t_gpointer *gp)
{
    outlet_pointer(x->x_obj.ob_outlet, gp);
}

static void vinlet_float(t_vinlet *x, t_float f)
{
    outlet_float(x->x_obj.ob_outlet, f);
}

static void vinlet_symbol(t_vinlet *x, t_symbol *s)
{
    outlet_symbol(x->x_obj.ob_outlet, s);
}

static void vinlet_list(t_vinlet *x, t_symbol *s, int argc, t_atom *argv)
{
    outlet_list(x->x_obj.ob_outlet, s, argc, argv);
}

static void vinlet_anything(t_vinlet *x, t_symbol *s, int argc, t_atom *argv)
{
    outlet_anything(x->x_obj.ob_outlet, s, argc, argv);
}

static void vinlet_free(t_vinlet *x)
{
    canvas_rminlet(x->x_canvas, x->x_inlet);
}

t_inlet *vinlet_getit(t_pd *x)
{
    if (pd_class(x) != vinlet_class) bug("vinlet_getit");
    return (((t_vinlet *)x)->x_inlet);
}

/* ------------------------- signal inlet -------------------------- */
int vinlet_issignal(t_vinlet *x)
{
    return (x->x_buf != 0);
}

static int tot;

t_int *vinlet_perform(t_int *w)
{
    t_vinlet *x = (t_vinlet *)(w[1]);
    t_float *out = (t_float *)(w[2]);
    int n = (int)(w[3]);
    t_float *in = x->x_read;
#if 0
    if (tot < 5) post("-in %x out %x n %d", in, out, n);
    if (tot < 5) post("-buf %x endbuf %x", x->x_buf, x->x_endbuf);
    if (tot < 5) post("in[0] %f in[1] %f in[2] %f", in[0], in[1], in[2]);
#endif
    while (n--) *out++ = *in++;
    if (in == x->x_endbuf) in = x->x_buf;
    x->x_read = in;
    return (w+4);
}

static void vinlet_dsp(t_vinlet *x, t_signal **sp)
{
    t_signal *outsig;
    if (!x->x_buf) return;
    outsig = sp[0];
    dsp_add(vinlet_perform, 3, x, outsig->s_vec, outsig->s_n);
    x->x_read = x->x_buf;
}

    /* prolog code: loads buffer from parent patch */
t_int *vinlet_doprolog(t_int *w)
{
    t_vinlet *x = (t_vinlet *)(w[1]);
    t_float *in = (t_float *)(w[2]);
    int n = (int)(w[3]);
    t_float *out = x->x_fill;
    if (out == x->x_endbuf)
    {
    	t_float *f1 = x->x_buf, *f2 = x->x_buf + x->x_hop;
    	int nshift = x->x_bufsize - x->x_hop;
    	out -= x->x_hop;
    	while (nshift--) *f1++ = *f2++;
    }
#if 0
    if (tot < 5) post("in %x out %x n %x", in, out, n), tot++;
    if (tot < 5) post("in[0] %f in[1] %f in[2] %f", in[0], in[1], in[2]);
#endif
    while (n--) *out++ = *in++;
    x->x_fill = out;
    return (w+4);
}

int inlet_getsignalindex(t_inlet *x);

    	/* set up prolog DSP code  */
void vinlet_dspprolog(t_vinlet *x, t_signal **parentsigs,
    int myvecsize, int phase, int period, int frequency)
{
    t_signal *insig, *outsig;
    int parentvecsize, bufsize, oldbufsize, prologphase;
    	/* the prolog code counts from 0 to period-1; the
    	phase is backed up by one so that AFTER the prolog code
    	runs, the "x_fill" phase is in sync with the "x_read" phase. */
    if (!x->x_buf) return;
    prologphase = (phase - 1) & (period - 1);
    if (parentsigs)
    {
    	insig = parentsigs[inlet_getsignalindex(x->x_inlet)];
    	parentvecsize = insig->s_n;
    }
    else
    {
    	insig = 0;
    	parentvecsize = 1;
    }
    bufsize = parentvecsize;
    if (bufsize < myvecsize) bufsize = myvecsize;
    if (bufsize != (oldbufsize = x->x_bufsize))
    {
    	t_float *buf = x->x_buf;
    	t_freebytes(buf, oldbufsize * sizeof(*buf));
    	buf = (t_float *)t_getbytes(bufsize * sizeof(*buf));
    	memset((char *)buf, 0, bufsize * sizeof(*buf));
    	x->x_bufsize = bufsize;
    	x->x_endbuf = buf + bufsize;
    	x->x_buf = buf;
    }
    if (parentsigs)
    {
    	x->x_hop = period * parentvecsize;
    	x->x_fill = x->x_endbuf -
    	    (x->x_hop - prologphase * parentvecsize);
    	dsp_add(vinlet_doprolog, 3, x, insig->s_vec, parentvecsize);
    }
    else memset((char *)(x->x_buf), 0, bufsize * sizeof(*x->x_buf));
}


static void *vinlet_newsig(void)
{
    t_vinlet *x = (t_vinlet *)pd_new(vinlet_class);
    x->x_canvas = canvas_getcurrent();
    x->x_inlet = canvas_addinlet(x->x_canvas, &x->x_obj.ob_pd, &s_signal);
    x->x_endbuf = x->x_buf = (t_float *)getbytes(0);
    x->x_bufsize = 0;
    outlet_new(&x->x_obj, &s_signal);
    return (x);
}

static void vinlet_setup(void)
{
    vinlet_class = class_new(gensym("inlet"), vinlet_new, vinlet_free,
    	sizeof(t_vinlet), CLASS_NOINLET, A_DEFSYM, 0);
    class_addcreator(vinlet_newsig, gensym("inlet~"), 0);
    class_addbang(vinlet_class, vinlet_bang);
    class_addpointer(vinlet_class, vinlet_pointer);
    class_addfloat(vinlet_class, vinlet_float);
    class_addsymbol(vinlet_class, vinlet_symbol);
    class_addlist(vinlet_class, vinlet_list);
    class_addanything(vinlet_class, vinlet_anything);
    class_addmethod(vinlet_class, vinlet_dsp, gensym("dsp"), 0);
}

/* ------------------------- voutlet -------------------------- */

t_class *voutlet_class;

typedef struct _voutlet
{
    t_object x_obj;
    t_canvas *x_canvas;
    t_outlet *x_parentoutlet;
    int x_bufsize;
    t_float *x_buf; 	    /* signal buffer; zero if not a signal */
    t_float *x_endbuf;
    t_float *x_empty;	    /* next to read out of buffer in epilog code */
    t_float *x_write;	    /* next to write in to buffer */
    int x_hop;	    	    /* hopsize */
} t_voutlet;

static void *voutlet_new(t_symbol *s)
{
    t_voutlet *x = (t_voutlet *)pd_new(voutlet_class);
    x->x_canvas = canvas_getcurrent();
    x->x_parentoutlet = canvas_addoutlet(x->x_canvas, &x->x_obj.ob_pd, 0);
    inlet_new(&x->x_obj, &x->x_obj.ob_pd, 0, 0);
    x->x_bufsize = 0;
    x->x_buf = 0;
    return (x);
}

static void voutlet_bang(t_voutlet *x)
{
    outlet_bang(x->x_parentoutlet);
}

static void voutlet_pointer(t_voutlet *x, t_gpointer *gp)
{
    outlet_pointer(x->x_parentoutlet, gp);
}

static void voutlet_float(t_voutlet *x, t_float f)
{
    outlet_float(x->x_parentoutlet, f);
}

static void voutlet_symbol(t_voutlet *x, t_symbol *s)
{
    outlet_symbol(x->x_parentoutlet, s);
}

static void voutlet_list(t_voutlet *x, t_symbol *s, int argc, t_atom *argv)
{
    outlet_list(x->x_parentoutlet, s, argc, argv);
}

static void voutlet_anything(t_voutlet *x, t_symbol *s, int argc, t_atom *argv)
{
    outlet_anything(x->x_parentoutlet, s, argc, argv);
}

static void voutlet_free(t_voutlet *x)
{
    canvas_rmoutlet(x->x_canvas, x->x_parentoutlet);
}

t_outlet *voutlet_getit(t_pd *x)
{
    if (pd_class(x) != voutlet_class) bug("voutlet_getit");
    return (((t_voutlet *)x)->x_parentoutlet);
}

/* ------------------------- signal outlet -------------------------- */

int voutlet_issignal(t_voutlet *x)
{
    return (x->x_buf != 0);
}

    /* LATER optimise for non-overlapped case where the "+=" isn't needed */
t_int *voutlet_perform(t_int *w)
{
    t_voutlet *x = (t_voutlet *)(w[1]);
    t_float *in = (t_float *)(w[2]);
    int n = (int)(w[3]);
    t_float *out = x->x_write, *outwas = out;
#if 0
    if (tot < 5) post("-in %x out %x n %d", in, out, n);
    if (tot < 5) post("-buf %x endbuf %x", x->x_buf, x->x_endbuf);
#endif
    while (n--)
    {
    	*out++ += *in++;
    	if (out == x->x_endbuf) out = x->x_buf;
    }
    outwas += x->x_hop;
    if (outwas >= x->x_endbuf) outwas = x->x_buf;
    x->x_write = outwas;
    return (w+4);
}

static void voutlet_dsp(t_voutlet *x, t_signal **sp)
{
    t_signal *insig;
    if (!x->x_buf) return;
    insig = sp[0];
    dsp_add(voutlet_perform, 3, x, insig->s_vec, insig->s_n);
}

    /* epilog code: write buffer to parent patch */
static t_int *voutlet_doepilog(t_int *w)
{
    t_voutlet *x = (t_voutlet *)(w[1]);
    t_float *out = (t_float *)(w[2]);
    int n = (int)(w[3]);
    t_float *in = x->x_empty;
#if 0
    if (tot < 5) post("outlet in %x out %x n %x", in, out, n), tot++;
#endif
    for (; n--; in++) *out++ = *in, *in = 0;
    if (in == x->x_endbuf) in = x->x_buf;
    x->x_empty = in;
    return (w+4);
}

int outlet_getsignalindex(t_outlet *x);

    	/* set up epilog DSP code */
void voutlet_dspepilog(t_voutlet *x, t_signal **parentsigs,
    int myvecsize, int phase, int period, int frequency)
{
    t_signal *insig, *outsig;
    int parentvecsize, bufsize, oldbufsize;
    int bigperiod, epilogphase, blockphase;
    if (!x->x_buf) return;
    if (parentsigs)
    {
    	outsig = parentsigs[outlet_getsignalindex(x->x_parentoutlet)];
    	parentvecsize = outsig->s_n;
    }
    else
    {
    	outsig = 0;
    	parentvecsize = 1;
    }
    bigperiod = myvecsize/parentvecsize;
    if (!bigperiod) bigperiod = 1;
    epilogphase = phase & (bigperiod - 1);
    blockphase = (phase + period - 1) & (bigperiod - 1) & (- period);
    bufsize = parentvecsize;
    if (bufsize < myvecsize) bufsize = myvecsize;
    if (bufsize != (oldbufsize = x->x_bufsize))
    {
    	t_float *buf = x->x_buf;
    	t_freebytes(buf, oldbufsize * sizeof(*buf));
    	buf = (t_float *)t_getbytes(bufsize * sizeof(*buf));
    	memset((char *)buf, 0, bufsize * sizeof(*buf));
    	x->x_bufsize = bufsize;
    	x->x_endbuf = buf + bufsize;
    	x->x_buf = buf;
    }
    if (parentvecsize * period > bufsize) bug("voutlet_dspepilog");
    x->x_write = x->x_buf + parentvecsize * blockphase;
    x->x_hop = period * parentvecsize;
    /* post("phase %d, block %d, parent %d", phase & 63,
    	parentvecsize * blockphase, parentvecsize * epilogphase); */

    if (parentsigs)
    {
    	    /* set epilog pointer and schedule it */
    	x->x_empty = x->x_buf + parentvecsize * epilogphase;
    	dsp_add(voutlet_doepilog, 3, x, outsig->s_vec, parentvecsize);
    }
}

static void *voutlet_newsig(t_symbol *s)
{
    t_voutlet *x = (t_voutlet *)pd_new(voutlet_class);
    x->x_canvas = canvas_getcurrent();
    x->x_parentoutlet = canvas_addoutlet(x->x_canvas,
    	&x->x_obj.ob_pd, &s_signal);
    inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
    x->x_endbuf = x->x_buf = (t_float *)getbytes(0);
    x->x_bufsize = 0;
    return (x);
}


static void voutlet_setup(void)
{
    voutlet_class = class_new(gensym("outlet"), voutlet_new, voutlet_free,
    	sizeof(t_voutlet), CLASS_NOINLET, A_DEFSYM, 0);
    class_addcreator(voutlet_newsig, gensym("outlet~"), 0);
    class_addbang(voutlet_class, voutlet_bang);
    class_addpointer(voutlet_class, voutlet_pointer);
    class_addfloat(voutlet_class, (t_method)voutlet_float);
    class_addsymbol(voutlet_class, voutlet_symbol);
    class_addlist(voutlet_class, voutlet_list);
    class_addanything(voutlet_class, voutlet_anything);
    class_addmethod(voutlet_class, voutlet_dsp, gensym("dsp"), 0);
}


/* ---------------------------- overall setup ----------------------------- */

void g_io_setup(void)
{
    vinlet_setup();
    voutlet_setup();
}
