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

/* MIDI. */

#include "m_pd.h"

static t_class *poly_class;

typedef struct voice
{
    t_float v_pitch;
    int v_used;
    unsigned long v_serial;
} t_voice;

typedef struct poly
{
    t_object x_obj;
    int x_n;
    t_voice *x_vec;
    t_float x_vel;
    t_outlet *x_pitchout;
    t_outlet *x_velout;
    unsigned long x_serial;
    int x_steal;
} t_poly;

static void *poly_new(t_float fnvoice, t_float fsteal)
{
    int i, n = fnvoice;
    t_poly *x = (t_poly *)pd_new(poly_class);
    t_voice *v;
    if (n < 1) n = 1;
    x->x_n = n;
    x->x_vec = (t_voice *)getbytes(n * sizeof(*x->x_vec));
    for (v = x->x_vec, i = n; i--; v++)
        v->v_pitch = v->v_used = v->v_serial = 0;
    x->x_vel = 0;
    x->x_steal = (fsteal != 0);
    floatinlet_new(&x->x_obj, &x->x_vel);
    outlet_new(&x->x_obj, &s_float);
    x->x_pitchout = outlet_new(&x->x_obj, &s_float);
    x->x_velout = outlet_new(&x->x_obj, &s_float);
    x->x_serial = 0;
    return (x);
}

static void poly_float(t_poly *x, t_float f)
{
    int i;
    t_voice *v;
    t_voice *firston, *firstoff;
    unsigned int serialon, serialoff, onindex = 0, offindex = 0;
    if (x->x_vel > 0)
    {
            /* note on.  Look for a vacant voice */
        for (v = x->x_vec, i = 0, firston = firstoff = 0,
            serialon = serialoff = 0xffffffff; i < x->x_n; v++, i++)
        {
            if (v->v_used && v->v_serial < serialon)
                    firston = v, serialon = v->v_serial, onindex = i;
            else if (!v->v_used && v->v_serial < serialoff)
                    firstoff = v, serialoff = v->v_serial, offindex = i;
        }
        if (firstoff)
        {
            outlet_float(x->x_velout, x->x_vel);
            outlet_float(x->x_pitchout, firstoff->v_pitch = f);
            outlet_float(x->x_obj.ob_outlet, offindex+1);
            firstoff->v_used = 1;
            firstoff->v_serial = x->x_serial++;
        }
            /* if none, steal one */
        else if (firston && x->x_steal)
        {
            outlet_float(x->x_velout, 0);
            outlet_float(x->x_pitchout, firston->v_pitch);
            outlet_float(x->x_obj.ob_outlet, onindex+1);
            outlet_float(x->x_velout, x->x_vel);
            outlet_float(x->x_pitchout, firston->v_pitch = f);
            outlet_float(x->x_obj.ob_outlet, onindex+1);
            firston->v_serial = x->x_serial++;
        }
    }
    else    /* note off. Turn off oldest match */
    {
        for (v = x->x_vec, i = 0, firston = 0, serialon = 0xffffffff;
            i < x->x_n; v++, i++)
                if (v->v_used && v->v_pitch == f && v->v_serial < serialon)
                    firston = v, serialon = v->v_serial, onindex = i;
        if (firston)
        {
            firston->v_used = 0;
            firston->v_serial = x->x_serial++;
            outlet_float(x->x_velout, 0);
            outlet_float(x->x_pitchout, firston->v_pitch);
            outlet_float(x->x_obj.ob_outlet, onindex+1);
        }
    }
}

static void poly_stop(t_poly *x)
{
    int i;
    t_voice *v;
    for (i = 0, v = x->x_vec; i < x->x_n; i++, v++)
        if (v->v_used)
    {
        outlet_float(x->x_velout, 0L);
        outlet_float(x->x_pitchout, v->v_pitch);
        outlet_float(x->x_obj.ob_outlet, i+1);
        v->v_used = 0;
        v->v_serial = x->x_serial++;
    }
}

static void poly_clear(t_poly *x)
{
    int i;
    t_voice *v;
    for (v = x->x_vec, i = x->x_n; i--; v++) v->v_used = v->v_serial = 0;
}

static void poly_free(t_poly *x)
{
    freebytes(x->x_vec, x->x_n * sizeof (*x->x_vec));
}

void poly_setup(void)
{
    poly_class = class_new(gensym("poly"), 
        (t_newmethod)poly_new, (t_method)poly_free,
        sizeof(t_poly), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addfloat(poly_class, poly_float);
    class_addmethod(poly_class, (t_method)poly_stop, gensym("stop"), 0);
    class_addmethod(poly_class, (t_method)poly_clear, gensym("clear"), 0);
}