/* 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"
void outmidi_noteon(int portno, int channel, int pitch, int velo);
void outmidi_controlchange(int portno, int channel, int ctlno, int value);
void outmidi_programchange(int portno, int channel, int value);
void outmidi_pitchbend(int portno, int channel, int value);
void outmidi_aftertouch(int portno, int channel, int value);
void outmidi_polyaftertouch(int portno, int channel, int pitch, int value);
void outmidi_byte(int portno, int value);

struct _instancemidi
{
    t_symbol *m_midiin_sym;
    t_symbol *m_sysexin_sym;
    t_symbol *m_notein_sym;
    t_symbol *m_ctlin_sym;
    t_symbol *m_pgmin_sym;
    t_symbol *m_bendin_sym;
    t_symbol *m_touchin_sym;
    t_symbol *m_polytouchin_sym;
    t_symbol *m_midirealtimein_sym;
};

/* ----------------------- midiin and sysexin ------------------------- */

static t_class *midiin_class, *sysexin_class;

typedef struct _midiin
{
    t_object x_obj;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
} t_midiin;

static void *midiin_new(void)
{
    t_midiin *x = (t_midiin *)pd_new(midiin_class);
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_midiin_sym);
    return (x);
}

static void midiin_list(t_midiin *x, t_symbol *s, int ac, t_atom *av)
{
    outlet_float(x->x_outlet2, atom_getfloatarg(1, ac, av) + 1);
    outlet_float(x->x_outlet1, atom_getfloatarg(0, ac, av));
}

static void midiin_free(t_midiin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_midiin_sym);
}

static void *sysexin_new(void)
{
    t_midiin *x = (t_midiin *)pd_new(sysexin_class);
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_sysexin_sym);
    return (x);
}

static void sysexin_free(t_midiin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_sysexin_sym);
}

static void midiin_setup(void)
{
    midiin_class = class_new(gensym("midiin"), (t_newmethod)midiin_new,
        (t_method)midiin_free, sizeof(t_midiin),
            CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(midiin_class, midiin_list);
    class_sethelpsymbol(midiin_class, gensym("midi"));

    sysexin_class = class_new(gensym("sysexin"), (t_newmethod)sysexin_new,
        (t_method)sysexin_free, sizeof(t_midiin),
            CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(sysexin_class, midiin_list);
    class_sethelpsymbol(sysexin_class, gensym("midi"));
}

void inmidi_byte(int portno, int byte)
{
    t_atom at[2];
    if (pd_this->pd_midi->m_midiin_sym->s_thing)
    {
        SETFLOAT(at, byte);
        SETFLOAT(at+1, portno);
        pd_list(pd_this->pd_midi->m_midiin_sym->s_thing, 0, 2, at);
    }
}

void inmidi_sysex(int portno, int byte)
{
    t_atom at[2];
    if (pd_this->pd_midi->m_sysexin_sym->s_thing)
    {
        SETFLOAT(at, byte);
        SETFLOAT(at+1, portno);
        pd_list(pd_this->pd_midi->m_sysexin_sym->s_thing, 0, 2, at);
    }
}

/* ----------------------- notein ------------------------- */

static t_class *notein_class;

typedef struct _notein
{
    t_object x_obj;
    t_float x_channel;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
    t_outlet *x_outlet3;
} t_notein;

static void *notein_new(t_floatarg f)
{
    t_notein *x = (t_notein *)pd_new(notein_class);
    x->x_channel = f;
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    if (f == 0) x->x_outlet3 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_notein_sym);
    return (x);
}

static void notein_list(t_notein *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float pitch = atom_getfloatarg(0, argc, argv);
    t_float velo = atom_getfloatarg(1, argc, argv);
    t_float channel = atom_getfloatarg(2, argc, argv);
    if (x->x_channel != 0)
    {
        if (channel != x->x_channel) return;
        outlet_float(x->x_outlet2, velo);
        outlet_float(x->x_outlet1, pitch);
    }
    else
    {
        outlet_float(x->x_outlet3, channel);
        outlet_float(x->x_outlet2, velo);
        outlet_float(x->x_outlet1, pitch);
    }
}

static void notein_free(t_notein *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_notein_sym);
}

static void notein_setup(void)
{
    notein_class = class_new(gensym("notein"), (t_newmethod)notein_new,
        (t_method)notein_free, sizeof(t_notein), CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(notein_class, notein_list);
    class_sethelpsymbol(notein_class, gensym("midi"));
}

void inmidi_noteon(int portno, int channel, int pitch, int velo)
{
    if (pd_this->pd_midi->m_notein_sym->s_thing)
    {
        t_atom at[3];
        SETFLOAT(at, pitch);
        SETFLOAT(at+1, velo);
        SETFLOAT(at+2, (channel + (portno << 4) + 1));
        pd_list(pd_this->pd_midi->m_notein_sym->s_thing, &s_list, 3, at);
    }
}

/* ----------------------- ctlin ------------------------- */

static t_class *ctlin_class;

typedef struct _ctlin
{
    t_object x_obj;
    t_float x_channel;
    t_float x_ctlno;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
    t_outlet *x_outlet3;
} t_ctlin;

static void *ctlin_new(t_symbol *s, int argc, t_atom *argv)
{
    int ctlno, channel;
    t_ctlin *x = (t_ctlin *)pd_new(ctlin_class);
    if (!argc) ctlno = -1;
    else ctlno = atom_getfloatarg(0, argc, argv);
    channel = atom_getfloatarg(1, argc, argv);
    x->x_channel = channel;
    x->x_ctlno = ctlno;
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    if (!channel)
    {
        if (x->x_ctlno < 0) x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
        x->x_outlet3 = outlet_new(&x->x_obj, &s_float);
    }
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_ctlin_sym);
    return (x);
}

static void ctlin_list(t_ctlin *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float ctlnumber = atom_getfloatarg(0, argc, argv);
    t_float value = atom_getfloatarg(1, argc, argv);
    t_float channel = atom_getfloatarg(2, argc, argv);
    if (x->x_ctlno >= 0 && x->x_ctlno != ctlnumber) return;
    if (x->x_channel > 0  && x->x_channel != channel) return;
    if (x->x_channel == 0) outlet_float(x->x_outlet3, channel);
    if (x->x_ctlno < 0) outlet_float(x->x_outlet2, ctlnumber);
    outlet_float(x->x_outlet1, value);
}

static void ctlin_free(t_ctlin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_ctlin_sym);
}

static void ctlin_setup(void)
{
    ctlin_class = class_new(gensym("ctlin"), (t_newmethod)ctlin_new,
        (t_method)ctlin_free, sizeof(t_ctlin),
            CLASS_NOINLET, A_GIMME, 0);
    class_addlist(ctlin_class, ctlin_list);
    class_sethelpsymbol(ctlin_class, gensym("midi"));
}

void inmidi_controlchange(int portno, int channel, int ctlnumber, int value)
{
    if (pd_this->pd_midi->m_ctlin_sym->s_thing)
    {
        t_atom at[3];
        SETFLOAT(at, ctlnumber);
        SETFLOAT(at+1, value);
        SETFLOAT(at+2, (channel + (portno << 4) + 1));
        pd_list(pd_this->pd_midi->m_ctlin_sym->s_thing, &s_list, 3, at);
    }
}

/* ----------------------- pgmin ------------------------- */

static t_class *pgmin_class;

typedef struct _pgmin
{
    t_object x_obj;
    t_float x_channel;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
} t_pgmin;

static void *pgmin_new(t_floatarg f)
{
    t_pgmin *x = (t_pgmin *)pd_new(pgmin_class);
    x->x_channel = f;
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    if (f == 0) x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_pgmin_sym);
    return (x);
}

static void pgmin_list(t_pgmin *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float value = atom_getfloatarg(0, argc, argv);
    t_float channel = atom_getfloatarg(1, argc, argv);
    if (x->x_channel != 0)
    {
        if (channel != x->x_channel) return;
        outlet_float(x->x_outlet1, value);
    }
    else
    {
        outlet_float(x->x_outlet2, channel);
        outlet_float(x->x_outlet1, value);
    }
}

static void pgmin_free(t_pgmin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_pgmin_sym);
}

static void pgmin_setup(void)
{
    pgmin_class = class_new(gensym("pgmin"), (t_newmethod)pgmin_new,
        (t_method)pgmin_free, sizeof(t_pgmin),
            CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(pgmin_class, pgmin_list);
    class_sethelpsymbol(pgmin_class, gensym("midi"));
}

void inmidi_programchange(int portno, int channel, int value)
{
    if (pd_this->pd_midi->m_pgmin_sym->s_thing)
    {
        t_atom at[2];
        SETFLOAT(at, value + 1);
        SETFLOAT(at+1, (channel + (portno << 4) + 1));
        pd_list(pd_this->pd_midi->m_pgmin_sym->s_thing, &s_list, 2, at);
    }
}

/* ----------------------- bendin ------------------------- */

static t_class *bendin_class;

typedef struct _bendin
{
    t_object x_obj;
    t_float x_channel;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
} t_bendin;

static void *bendin_new(t_floatarg f)
{
    t_bendin *x = (t_bendin *)pd_new(bendin_class);
    x->x_channel = f;
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    if (f == 0) x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_bendin_sym);
    return (x);
}

static void bendin_list(t_bendin *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float value = atom_getfloatarg(0, argc, argv);
    t_float channel = atom_getfloatarg(1, argc, argv);
    if (x->x_channel != 0)
    {
        if (channel != x->x_channel) return;
        outlet_float(x->x_outlet1, value);
    }
    else
    {
        outlet_float(x->x_outlet2, channel);
        outlet_float(x->x_outlet1, value);
    }
}

static void bendin_free(t_bendin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_bendin_sym);
}

static void bendin_setup(void)
{
    bendin_class = class_new(gensym("bendin"), (t_newmethod)bendin_new,
        (t_method)bendin_free, sizeof(t_bendin), CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(bendin_class, bendin_list);
    class_sethelpsymbol(bendin_class, gensym("midi"));
}

void inmidi_pitchbend(int portno, int channel, int value)
{
    if (pd_this->pd_midi->m_bendin_sym->s_thing)
    {
        t_atom at[2];
        SETFLOAT(at, value);
        SETFLOAT(at+1, (channel + (portno << 4) + 1));
        pd_list(pd_this->pd_midi->m_bendin_sym->s_thing, &s_list, 2, at);
    }
}

/* ----------------------- touchin ------------------------- */

static t_class *touchin_class;

typedef struct _touchin
{
    t_object x_obj;
    t_float x_channel;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
} t_touchin;

static void *touchin_new(t_floatarg f)
{
    t_touchin *x = (t_touchin *)pd_new(touchin_class);
    x->x_channel = f;
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    if (f == 0) x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_touchin_sym);
    return (x);
}

static void touchin_list(t_touchin *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float value = atom_getfloatarg(0, argc, argv);
    t_float channel = atom_getfloatarg(1, argc, argv);
    if (x->x_channel)
    {
        if (channel != x->x_channel) return;
        outlet_float(x->x_outlet1, value);
    }
    else
    {
        outlet_float(x->x_outlet2, channel);
        outlet_float(x->x_outlet1, value);
    }
}

static void touchin_free(t_touchin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_touchin_sym);
}

static void touchin_setup(void)
{
    touchin_class = class_new(gensym("touchin"), (t_newmethod)touchin_new,
        (t_method)touchin_free, sizeof(t_touchin),
            CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(touchin_class, touchin_list);
    class_sethelpsymbol(touchin_class, gensym("midi"));
}

void inmidi_aftertouch(int portno, int channel, int value)
{
    if (pd_this->pd_midi->m_touchin_sym->s_thing)
    {
        t_atom at[2];
        SETFLOAT(at, value);
        SETFLOAT(at+1, (channel + (portno << 4) + 1));
        pd_list(pd_this->pd_midi->m_touchin_sym->s_thing, &s_list, 2, at);
    }
}

/* ----------------------- polytouchin ------------------------- */

static t_class *polytouchin_class;

typedef struct _polytouchin
{
    t_object x_obj;
    t_float x_channel;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
    t_outlet *x_outlet3;
} t_polytouchin;

static void *polytouchin_new(t_floatarg f)
{
    t_polytouchin *x = (t_polytouchin *)pd_new(polytouchin_class);
    x->x_channel = f;
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    if (f == 0) x->x_outlet3 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_polytouchin_sym);
    return (x);
}

static void polytouchin_list(t_polytouchin *x, t_symbol *s, int argc,
    t_atom *argv)
{
    t_float pitch = atom_getfloatarg(0, argc, argv);
    t_float value = atom_getfloatarg(1, argc, argv);
    t_float channel = atom_getfloatarg(2, argc, argv);
    if (x->x_channel != 0)
    {
        if (channel != x->x_channel) return;
        outlet_float(x->x_outlet2, pitch);
        outlet_float(x->x_outlet1, value);
    }
    else
    {
        outlet_float(x->x_outlet3, channel);
        outlet_float(x->x_outlet2, pitch);
        outlet_float(x->x_outlet1, value);
    }
}

static void polytouchin_free(t_polytouchin *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_polytouchin_sym);
}

static void polytouchin_setup(void)
{
    polytouchin_class = class_new(gensym("polytouchin"),
        (t_newmethod)polytouchin_new, (t_method)polytouchin_free,
        sizeof(t_polytouchin), CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(polytouchin_class, polytouchin_list);
    class_sethelpsymbol(polytouchin_class, gensym("midi"));
}

void inmidi_polyaftertouch(int portno, int channel, int pitch, int value)
{
    if (pd_this->pd_midi->m_polytouchin_sym->s_thing)
    {
        t_atom at[3];
        SETFLOAT(at, pitch);
        SETFLOAT(at+1, value);
        SETFLOAT(at+2, (channel + (portno << 4) + 1));
        pd_list(pd_this->pd_midi->m_polytouchin_sym->s_thing, &s_list, 3, at);
    }
}

/*----------midirealtimein (midi F8,FA,FB,FC,FE,FF message)-----------------*/

static t_class *midirealtimein_class;

typedef struct _midirealtimein
{
    t_object x_obj;
    t_outlet *x_outlet1;
    t_outlet *x_outlet2;
} t_midirealtimein;

static void *midirealtimein_new(void)
{
    t_midirealtimein *x = (t_midirealtimein *)pd_new(midirealtimein_class);
    x->x_outlet1 = outlet_new(&x->x_obj, &s_float);
    x->x_outlet2 = outlet_new(&x->x_obj, &s_float);
    pd_bind(&x->x_obj.ob_pd, pd_this->pd_midi->m_midirealtimein_sym);
    return (x);
}

static void midirealtimein_list(t_midirealtimein *x, t_symbol *s,
    int argc, t_atom *argv)
{
    t_float portno = atom_getfloatarg(0, argc, argv);
    t_float byte = atom_getfloatarg(1, argc, argv);

    outlet_float(x->x_outlet2, portno);
    outlet_float(x->x_outlet1, byte);
}

static void midirealtimein_free(t_midirealtimein *x)
{
    pd_unbind(&x->x_obj.ob_pd, pd_this->pd_midi->m_midirealtimein_sym);
}

static void midirealtimein_setup(void)
{
    midirealtimein_class = class_new(gensym("midirealtimein"),
        (t_newmethod)midirealtimein_new, (t_method)midirealtimein_free,
            sizeof(t_midirealtimein), CLASS_NOINLET, A_DEFFLOAT, 0);
    class_addlist(midirealtimein_class, midirealtimein_list);
        class_sethelpsymbol(midirealtimein_class, gensym("midi"));
}

void inmidi_realtimein(int portno, int SysMsg)
{
    if (pd_this->pd_midi->m_midirealtimein_sym->s_thing)
    {
        t_atom at[2];
        SETFLOAT(at, portno);
        SETFLOAT(at+1, SysMsg);
        pd_list(pd_this->pd_midi->m_midirealtimein_sym->s_thing, &s_list, 2, at);
    }
}

/* -------------------------- midiout -------------------------- */

static t_class *midiout_class;

void sys_putmidibyte(int portno, int byte);

typedef struct _midiout
{
    t_object x_obj;
    t_float x_portno;
} t_midiout;

static void *midiout_new(t_floatarg portno)
{
    t_midiout *x = (t_midiout *)pd_new(midiout_class);
    if (portno <= 0) portno = 1;
    x->x_portno = portno;
    floatinlet_new(&x->x_obj, &x->x_portno);
    return (x);
}

static void midiout_float(t_midiout *x, t_floatarg f)
{
    outmidi_byte(x->x_portno - 1, f);
}

static void midiout_list(t_midiout *x, t_symbol *s, int ac, t_atom *av)
{
    int i;
    for (i = 0; i < ac; ++i)
    {
        if(av[i].a_type == A_FLOAT)
            outmidi_byte(x->x_portno - 1, av[i].a_w.w_float);
    }
}

static void midiout_setup(void)
{
    midiout_class = class_new(gensym("midiout"), (t_newmethod)midiout_new, 0,
        sizeof(t_midiout), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addfloat(midiout_class, midiout_float);
    class_addlist(midiout_class, midiout_list);
    class_sethelpsymbol(midiout_class, gensym("midi"));
}

/* -------------------------- noteout -------------------------- */

static t_class *noteout_class;

typedef struct _noteout
{
    t_object x_obj;
    t_float x_velo;
    t_float x_channel;
} t_noteout;

static void *noteout_new(t_floatarg channel)
{
    t_noteout *x = (t_noteout *)pd_new(noteout_class);
    x->x_velo = 0;
    if (channel < 1) channel = 1;
    x->x_channel = channel;
    floatinlet_new(&x->x_obj, &x->x_velo);
    floatinlet_new(&x->x_obj, &x->x_channel);
    return (x);
}

static void noteout_float(t_noteout *x, t_float f)
{
    int binchan = x->x_channel - 1;
    if (binchan < 0)
        binchan = 0;
    outmidi_noteon((binchan >> 4),
        (binchan & 15), (int)f, (int)x->x_velo);
}

static void noteout_setup(void)
{
    noteout_class = class_new(gensym("noteout"), (t_newmethod)noteout_new, 0,
        sizeof(t_noteout), 0, A_DEFFLOAT, 0);
    class_addfloat(noteout_class, noteout_float);
    class_sethelpsymbol(noteout_class, gensym("midi"));
}


/* -------------------------- ctlout -------------------------- */

static t_class *ctlout_class;

typedef struct _ctlout
{
    t_object x_obj;
    t_float x_ctl;
    t_float x_channel;
} t_ctlout;

static void *ctlout_new(t_floatarg ctl, t_floatarg channel)
{
    t_ctlout *x = (t_ctlout *)pd_new(ctlout_class);
    x->x_ctl = ctl;
    if (channel <= 0) channel = 1;
    x->x_channel = channel;
    floatinlet_new(&x->x_obj, &x->x_ctl);
    floatinlet_new(&x->x_obj, &x->x_channel);
    return (x);
}

static void ctlout_float(t_ctlout *x, t_float f)
{
    int binchan = x->x_channel - 1;
    if (binchan < 0)
        binchan = 0;
    outmidi_controlchange((binchan >> 4),
        (binchan & 15), (int)(x->x_ctl), (int)f);
}

static void ctlout_setup(void)
{
    ctlout_class = class_new(gensym("ctlout"), (t_newmethod)ctlout_new, 0,
        sizeof(t_ctlout), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addfloat(ctlout_class, ctlout_float);
    class_sethelpsymbol(ctlout_class, gensym("midi"));
}


/* -------------------------- pgmout -------------------------- */

static t_class *pgmout_class;

typedef struct _pgmout
{
    t_object x_obj;
    t_float x_channel;
} t_pgmout;

static void *pgmout_new(t_floatarg channel)
{
    t_pgmout *x = (t_pgmout *)pd_new(pgmout_class);
    if (channel <= 0) channel = 1;
    x->x_channel = channel;
    floatinlet_new(&x->x_obj, &x->x_channel);
    return (x);
}

static void pgmout_float(t_pgmout *x, t_floatarg f)
{
    int binchan = x->x_channel - 1;
    int n = f - 1;
    if (binchan < 0)
        binchan = 0;
    if (n < 0) n = 0;
    else if (n > 127) n = 127;
    outmidi_programchange((binchan >> 4),
        (binchan & 15), n);
}

static void pgmout_setup(void)
{
    pgmout_class = class_new(gensym("pgmout"), (t_newmethod)pgmout_new, 0,
        sizeof(t_pgmout), 0, A_DEFFLOAT, 0);
    class_addfloat(pgmout_class, pgmout_float);
    class_sethelpsymbol(pgmout_class, gensym("midi"));
}


/* -------------------------- bendout -------------------------- */

static t_class *bendout_class;

typedef struct _bendout
{
    t_object x_obj;
    t_float x_channel;
} t_bendout;

static void *bendout_new(t_floatarg channel)
{
    t_bendout *x = (t_bendout *)pd_new(bendout_class);
    if (channel <= 0) channel = 1;
    x->x_channel = channel;
    floatinlet_new(&x->x_obj, &x->x_channel);
    return (x);
}

static void bendout_float(t_bendout *x, t_float f)
{
    int binchan = x->x_channel - 1;
    int n = (int)f +  8192;
    if (binchan < 0)
        binchan = 0;
    outmidi_pitchbend((binchan >> 4), (binchan & 15), n);
}

static void bendout_setup(void)
{
    bendout_class = class_new(gensym("bendout"), (t_newmethod)bendout_new, 0,
        sizeof(t_bendout), 0, A_DEFFLOAT, 0);
    class_addfloat(bendout_class, bendout_float);
    class_sethelpsymbol(bendout_class, gensym("midi"));
}

/* -------------------------- touch -------------------------- */

static t_class *touchout_class;

typedef struct _touchout
{
    t_object x_obj;
    t_float x_channel;
} t_touchout;

static void *touchout_new(t_floatarg channel)
{
    t_touchout *x = (t_touchout *)pd_new(touchout_class);
    if (channel <= 0) channel = 1;
    x->x_channel = channel;
    floatinlet_new(&x->x_obj, &x->x_channel);
    return (x);
}

static void touchout_float(t_touchout *x, t_float f)
{
    int binchan = x->x_channel - 1;
    if (binchan < 0)
        binchan = 0;
    outmidi_aftertouch((binchan >> 4), (binchan & 15), (int)f);
}

static void touchout_setup(void)
{
    touchout_class = class_new(gensym("touchout"), (t_newmethod)touchout_new, 0,
        sizeof(t_touchout), 0, A_DEFFLOAT, 0);
    class_addfloat(touchout_class, touchout_float);
    class_sethelpsymbol(touchout_class, gensym("midi"));
}

/* -------------------------- polytouch -------------------------- */

static t_class *polytouchout_class;

typedef struct _polytouchout
{
    t_object x_obj;
    t_float x_channel;
    t_float x_pitch;
} t_polytouchout;

static void *polytouchout_new(t_floatarg channel)
{
    t_polytouchout *x = (t_polytouchout *)pd_new(polytouchout_class);
    if (channel <= 0) channel = 1;
    x->x_channel = channel;
    x->x_pitch = 0;
    floatinlet_new(&x->x_obj, &x->x_pitch);
    floatinlet_new(&x->x_obj, &x->x_channel);
    return (x);
}

static void polytouchout_float(t_polytouchout *x, t_float n)
{
    int binchan = x->x_channel - 1;
    if (binchan < 0)
        binchan = 0;
    outmidi_polyaftertouch((binchan >> 4), (binchan & 15), x->x_pitch, n);
}

static void polytouchout_setup(void)
{
    polytouchout_class = class_new(gensym("polytouchout"),
        (t_newmethod)polytouchout_new, 0,
        sizeof(t_polytouchout), 0, A_DEFFLOAT, 0);
    class_addfloat(polytouchout_class, polytouchout_float);
    class_sethelpsymbol(polytouchout_class, gensym("midi"));
}

/* -------------------------- makenote -------------------------- */

static t_class *makenote_class;

typedef struct _hang
{
    t_clock *h_clock;
    struct _hang *h_next;
    t_float h_pitch;
    struct _makenote *h_owner;
} t_hang;

typedef struct _makenote
{
    t_object x_obj;
    t_float x_velo;
    t_float x_dur;
    t_outlet *x_pitchout;
    t_outlet *x_velout;
    t_hang *x_hang;
} t_makenote;

static void *makenote_new(t_floatarg velo, t_floatarg dur)
{
    t_makenote *x = (t_makenote *)pd_new(makenote_class);
    x->x_velo = velo;
    x->x_dur = dur;
    floatinlet_new(&x->x_obj, &x->x_velo);
    floatinlet_new(&x->x_obj, &x->x_dur);
    x->x_pitchout = outlet_new(&x->x_obj, &s_float);
    x->x_velout = outlet_new(&x->x_obj, &s_float);
    x->x_hang = 0;
    return (x);
}

static void makenote_tick(t_hang *hang)
{
    t_makenote *x = hang->h_owner;
    t_hang *h2, *h3;
    outlet_float(x->x_velout, 0);
    outlet_float(x->x_pitchout, hang->h_pitch);
    if (x->x_hang == hang) x->x_hang = hang->h_next;
    else for (h2 = x->x_hang; (h3 = h2->h_next); h2 = h3)
    {
        if (h3 == hang)
        {
            h2->h_next = h3->h_next;
            break;
        }
    }
    clock_free(hang->h_clock);
    freebytes(hang, sizeof(*hang));
}

static void makenote_float(t_makenote *x, t_float f)
{
    t_hang *hang;
    if (!x->x_velo) return;
    outlet_float(x->x_velout, x->x_velo);
    outlet_float(x->x_pitchout, f);
    hang = (t_hang *)getbytes(sizeof *hang);
    hang->h_next = x->x_hang;
    x->x_hang = hang;
    hang->h_pitch = f;
    hang->h_owner = x;
    hang->h_clock = clock_new(hang, (t_method)makenote_tick);
    clock_delay(hang->h_clock, (x->x_dur >= 0 ? x->x_dur : 0));
}

static void makenote_stop(t_makenote *x)
{
    t_hang *hang;
    while ((hang = x->x_hang))
    {
        outlet_float(x->x_velout, 0);
        outlet_float(x->x_pitchout, hang->h_pitch);
        x->x_hang = hang->h_next;
        clock_free(hang->h_clock);
        freebytes(hang, sizeof(*hang));
    }
}

static void makenote_clear(t_makenote *x)
{
    t_hang *hang;
    while ((hang = x->x_hang))
    {
        x->x_hang = hang->h_next;
        clock_free(hang->h_clock);
        freebytes(hang, sizeof(*hang));
    }
}

static void makenote_setup(void)
{
    makenote_class = class_new(gensym("makenote"),
        (t_newmethod)makenote_new, (t_method)makenote_clear,
        sizeof(t_makenote), 0, A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addfloat(makenote_class, makenote_float);
    class_addmethod(makenote_class, (t_method)makenote_stop, gensym("stop"),
        0);
    class_addmethod(makenote_class, (t_method)makenote_clear, gensym("clear"),
        0);
}

/* -------------------------- stripnote -------------------------- */

static t_class *stripnote_class;

typedef struct _stripnote
{
    t_object x_obj;
    t_float x_velo;
    t_outlet *x_pitchout;
    t_outlet *x_velout;
} t_stripnote;

static void *stripnote_new(void)
{
    t_stripnote *x = (t_stripnote *)pd_new(stripnote_class);
    floatinlet_new(&x->x_obj, &x->x_velo);
    x->x_pitchout = outlet_new(&x->x_obj, &s_float);
    x->x_velout = outlet_new(&x->x_obj, &s_float);
    return (x);
}

static void stripnote_float(t_stripnote *x, t_float f)
{
    if (!x->x_velo) return;
    outlet_float(x->x_velout, x->x_velo);
    outlet_float(x->x_pitchout, f);
}

static void stripnote_setup(void)
{
    stripnote_class = class_new(gensym("stripnote"),
        (t_newmethod)stripnote_new, 0, sizeof(t_stripnote), 0, 0);
    class_addfloat(stripnote_class, stripnote_float);
}

/* -------------------------- poly -------------------------- */

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 = (unsigned int)v->v_serial, onindex = i;
            else if (!v->v_used && v->v_serial < serialoff)
                    firstoff = v, serialoff = (unsigned int)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 = (unsigned int)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));
}

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

/* -------------------------- bag -------------------------- */

static t_class *bag_class;

typedef struct _bagelem
{
    struct _bagelem *e_next;
    t_float e_value;
} t_bagelem;

typedef struct _bag
{
    t_object x_obj;
    t_float x_velo;
    t_bagelem *x_first;
} t_bag;

static void *bag_new(void)
{
    t_bag *x = (t_bag *)pd_new(bag_class);
    x->x_velo = 0;
    floatinlet_new(&x->x_obj, &x->x_velo);
    outlet_new(&x->x_obj, &s_float);
    x->x_first = 0;
    return (x);
}

static void bag_float(t_bag *x, t_float f)
{
    t_bagelem *bagelem, *e2, *e3;
    if (x->x_velo != 0)
    {
        bagelem = (t_bagelem *)getbytes(sizeof *bagelem);
        bagelem->e_next = 0;
        bagelem->e_value = f;
        if (!x->x_first) x->x_first = bagelem;
        else    /* LATER replace with a faster algorithm */
        {
            for (e2 = x->x_first; (e3 = e2->e_next); e2 = e3)
                ;
            e2->e_next = bagelem;
        }
    }
    else
    {
        if (!x->x_first) return;
        if (x->x_first->e_value == f)
        {
            bagelem = x->x_first;
            x->x_first = x->x_first->e_next;
            freebytes(bagelem, sizeof(*bagelem));
            return;
        }
        for (e2 = x->x_first; (e3 = e2->e_next); e2 = e3)
            if (e3->e_value == f)
        {
            e2->e_next = e3->e_next;
            freebytes(e3, sizeof(*e3));
            return;
        }
    }
}

static void bag_flush(t_bag *x)
{
    t_bagelem *bagelem;
    while ((bagelem = x->x_first))
    {
        outlet_float(x->x_obj.ob_outlet, bagelem->e_value);
        x->x_first = bagelem->e_next;
        freebytes(bagelem, sizeof(*bagelem));
    }
}

static void bag_clear(t_bag *x)
{
    t_bagelem *bagelem;
    while ((bagelem = x->x_first))
    {
        x->x_first = bagelem->e_next;
        freebytes(bagelem, sizeof(*bagelem));
    }
}

static void bag_setup(void)
{
    bag_class = class_new(gensym("bag"),
        (t_newmethod)bag_new, (t_method)bag_clear,
        sizeof(t_bag), 0, 0);
    class_addfloat(bag_class, bag_float);
    class_addmethod(bag_class, (t_method)bag_flush, gensym("flush"), 0);
    class_addmethod(bag_class, (t_method)bag_clear, gensym("clear"), 0);
}

void x_midi_setup(void)
{
    midiin_setup();
    midirealtimein_setup();
    notein_setup();
    ctlin_setup();
    pgmin_setup();
    bendin_setup();
    touchin_setup();
    polytouchin_setup();
    midiout_setup();
    noteout_setup();
    ctlout_setup();
    pgmout_setup();
    bendout_setup();
    touchout_setup();
    polytouchout_setup();
    makenote_setup();
    stripnote_setup();
    poly_setup();
    bag_setup();
}

void x_midi_newpdinstance(void)
{
    pd_this->pd_midi = getbytes(sizeof(t_instancemidi));
    pd_this->pd_midi->m_midiin_sym = gensym("#midiin");
    pd_this->pd_midi->m_sysexin_sym = gensym("#sysexin");
    pd_this->pd_midi->m_notein_sym = gensym("#notein");
    pd_this->pd_midi->m_ctlin_sym = gensym("#ctlin");
    pd_this->pd_midi->m_pgmin_sym = gensym("#pgmin");
    pd_this->pd_midi->m_bendin_sym = gensym("#bendin");
    pd_this->pd_midi->m_touchin_sym = gensym("#touchin");
    pd_this->pd_midi->m_polytouchin_sym = gensym("#polytouchin");
    pd_this->pd_midi->m_midirealtimein_sym = gensym("#midirealtimein");
}

void x_midi_freepdinstance(void)
{
    freebytes(pd_this->pd_midi, sizeof(t_instancemidi));
}
