/* Copyright 2002 Benjamin R. Saylor
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "m_pd.h"

#ifdef _MSC_VER
#pragma warning( disable : 4244 )
#pragma warning( disable : 4305 )
#endif

enum { ATTACK, DECAY, RELEASE, LINEAR, LOGARITHMIC };
#define THRESHOLD 0.99
#define SCALE 4600.0
#define CLIP(x) ((x < 0.1) ? 0.1 : x)
#define IS_DENORMAL(f) (((*(unsigned int *)&(f))&0x7f800000) == 0)

static t_class *aenv_class;

typedef struct _aenv {
	t_object x_obj;
	t_float srate;
	t_float a;
	t_float d;
	t_float s;
	t_float r;
	t_float lastval;
	int stage;
	int attacktype;
} t_aenv;

static t_int *aenv_perform(t_int *w)
{
	t_aenv *x = (t_aenv *)(w[1]);
	t_float *out = (t_float *)(w[2]);
	int n = (int)(w[3]);

	t_float lastval = x->lastval;
	t_float a, d, s, r;

	switch (x->stage) {
		case ATTACK:
			if (x->attacktype == LINEAR) {
				a = 1000 / (x->a * x->srate);
				while (n--) {
					*out = lastval + a;
					lastval = *(out++);
					if (lastval > THRESHOLD) {
						x->stage = DECAY;
						break;
					}
				}
			} else {
				a = SCALE / (CLIP(x->a) * x->srate);
				while (n--) {
					*out = lastval + a * (1 - lastval);
					lastval = *(out++);
					if (lastval > THRESHOLD) {
						x->stage = DECAY;
						break;
					}
				}
			}
		case DECAY:
			d = SCALE / (CLIP(x->d) * x->srate);
			s = x->s;
			while (n-- > 0) {
				*out = lastval - d * (lastval - s);
				if (IS_DENORMAL(*out)) *out = 0.0;
				lastval = *(out++);
			}
			break;
		case RELEASE:
			r = SCALE / (CLIP(x->r) * x->srate);
			while (n--) {
				*out = lastval - r * lastval;
				if (IS_DENORMAL(*out)) *out = 0.0;
				lastval = *(out++);
			}
	}

	x->lastval = lastval;

	return (w+4);
}

static void aenv_dsp(t_aenv *x, t_signal **sp)
{
	dsp_add(aenv_perform, 3, x, sp[0]->s_vec, sp[0]->s_n);
}

static void aenv_float(t_aenv *x, t_float f)
{
	if (f == 0)
		x->stage = RELEASE;
	else
		x->stage = ATTACK;
}

static void *aenv_new(t_symbol *s, int argc, t_atom *argv)
{
	t_aenv *x = (t_aenv *)pd_new(aenv_class);
	floatinlet_new(&x->x_obj, &x->a);
	floatinlet_new(&x->x_obj, &x->d);
	floatinlet_new(&x->x_obj, &x->s);
	floatinlet_new(&x->x_obj, &x->r);
	outlet_new(&x->x_obj, gensym("signal"));

	x->srate = sys_getsr();
	x->a = 500;
	x->d = 500;
	x->s = 0.5;
	x->r = 500;
	x->lastval = 0;
	x->stage = RELEASE;
	x->attacktype = LOGARITHMIC;

	if (argc == 4) {
		x->a = atom_getfloat(argv);
		x->d = atom_getfloat(argv+1);
		x->s = atom_getfloat(argv+2);
		x->r = atom_getfloat(argv+3);
	}

	return (x);
}

static void aenv_lina(t_aenv *x)
{
	x->attacktype = LINEAR;
}

static void aenv_loga(t_aenv *x)
{
	x->attacktype = LOGARITHMIC;
}

static void aenv_zero(t_aenv *x)
{
	x->stage = RELEASE;
	x->lastval = 0.0;
}

void aenv_tilde_setup(void)
{
	aenv_class = class_new(gensym("aenv~"), (t_newmethod)aenv_new, 0, sizeof(t_aenv), 0, A_GIMME, 0);
	class_addmethod(aenv_class, (t_method)aenv_dsp, gensym("dsp"), 0);
	class_addfloat(aenv_class, (t_method)aenv_float);
	class_addmethod(aenv_class, (t_method)aenv_lina, gensym("lina"), 0);
	class_addmethod(aenv_class, (t_method)aenv_loga, gensym("loga"), 0);
	class_addmethod(aenv_class, (t_method)aenv_zero, gensym("zero"), 0);
}
