//=========================================================
//  MusE
//  Linux Music Editor
//
//  Parts of this file taken from:
//      The analogue oscillator from Steve Harris plugin collection.
//      Werner Schweer's organ softsynth for MusE.
//	The music-dsp source archive.
//
//  (C) Copyright 2002 Jotsif Lindman Hörnlund (jotsif@linux.nu)
//
// 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 or point your web browser to http://www.gnu.org.
//=========================================================

#include <math.h>
#include <stdio.h>
#include <sys/time.h>
#include "mess.h"
#include "ladspa.h"
#include "midictrl.h"

#include "vam.h"

// Denormalise floats, only actually needed for PIII and very recent PowerPC
#define DENORMALISE(fv) (((*(unsigned int*)&(fv))&0x7f800000)==0)?0.0f:(fv)

// A fast, truncating towards 0 modulo function. ANSI C doesn't define
// which % will do, most truncate towards -inf
#define MOD(v,m) (v<0?v+m:(v>m?v-m:v))

// Limit values
#define LIMIT(v,l,u) (v<l?l:(v>u?u:v))

#define PI M_PI

//---------------------------------------------------------
//   Oscillator
//---------------------------------------------------------

struct Oscillator {
	float phase;
	float pitchmod;
	float detune;
	float freq;
	float pwm;
	float pw;
	float fm;
	int waveform;
	bool on;
};

struct LPFilter {
	float out[4];
	float in[4];
};

//---------------------------------------------------------
//   Envelope
//---------------------------------------------------------

struct EnvelopeGenerator {
	static const int onStates = 2;
	static const int offStates = 1;

	struct Segment {
		int ticks;
		double incr;
	};
	Segment segment[onStates + offStates];

	int state;
	double env;
	int tick;

	int attack;
	int decay;
	float sustain;
	int release;

	EnvelopeGenerator() {
		segment[0].ticks = 441;
		segment[0].incr = 1.0/441.0;
		segment[1].ticks = 0;
		segment[1].incr = 0.0;
		segment[2].ticks = 441;
		segment[2].incr = -(1.0/441.0);
	}

	void setSegment(int seg, int ticks, double incr) {
		segment[seg].ticks = ticks;
		segment[seg].incr = incr;
	}

	void keyOn() {
//		env = 0.0;
		state = 0;
		if(env) segment[state].incr = (1.0 - env) / segment[state].ticks;
		else env = 0.0;
		tick = segment[state].ticks;
	}
	void keyOff() {
		state = onStates;
		tick = segment[state].ticks;
	}
	bool isOff() {
		return state == (onStates+offStates);
	}
	bool step() {
		if(state >= onStates+offStates)
			return false;
		if (tick == 0)
			return true;
		env +=segment[state].incr;
		if(env < 0.0)
		    env = 0.0;
		--tick;
		while(tick == 0) {
			++state;
			if(state >= onStates+offStates)
				return false;
			if(state == onStates)
				return true;
			tick = segment[state].ticks;
		}
		return true;
	}
};

//---------------------------------------------------------
//   VAM
//---------------------------------------------------------

class VAM : public MessMono {
	static int useCount;
	static const int CB_AMP_SIZE = 961;
	static const int LIN2EXP_SIZE = 256;

	static double cb2amp_tab[CB_AMP_SIZE];
	static double cb2amp(double cb);

	static float lin2exp[LIN2EXP_SIZE];

/*	Synthvariables */
	static float *sin_tbl, *tri_tbl, *saw_tbl, *squ_tbl;
	bool isOn;
	int pitch, channel;
	float velocity;

	EnvelopeGenerator dco1_env;
	EnvelopeGenerator dco2_env;
	EnvelopeGenerator filt_env;

	LPFilter dco1_filter;
	LPFilter dco2_filter;

	Oscillator dco1;
	Oscillator dco2;
	Oscillator lfo;

	bool filt_invert, filt_keytrack;

	double filt_env_mod, filt_res, filt_cutoff, keytrack_cutoff;

	int controller[NUM_CONTROLLER];

	void write(int, float**, int);
	void noteon(int channel, int pitch, int velo);
	void noteoff(int channel, int pitch);
	void setController(int channel, int ctrl, int val);
	void sysex(const unsigned char* data, int len);
	void parameterRequest(int pn);

	float *wave_tbl(int wave);
	double lowpass_filter(double cutoff, double resonance, double input, LPFilter *f);

    public:
	VAM(const char* classname);
	~VAM();
	bool init();
	virtual int getMidiInitEvent(int id, RawMidiEvent *);
};

float* VAM::sin_tbl;
float* VAM::tri_tbl;
float* VAM::saw_tbl;
float* VAM::squ_tbl;
int VAM::useCount = 0;
double VAM::cb2amp_tab[VAM::CB_AMP_SIZE];
float VAM::lin2exp[VAM::LIN2EXP_SIZE];


//---------------------------------------------------------
//   Organ
//---------------------------------------------------------

VAM::VAM(const char* cn)
   : MessMono(cn, 1)
{
	if(useCount == 0) {
		int i;
		float tmp;
		for(i = 0; i < CB_AMP_SIZE; i++) {
			cb2amp_tab[i] = pow(10.0, double(i) / -300.0);
//			cb2amp_tab[i] = 1.0 - i/(float)CB_AMP_SIZE;
		}
		for(i = 0; i < LIN2EXP_SIZE; i++) {
			tmp = i/255.0;
			lin2exp[i] = 1.5 * tmp * tmp * tmp - 0.69 * tmp * tmp + 0.16 * tmp;
		}
		int sr = sampleRate();
		/* Build up denormalised oscilator wavetables, these are sample_rate
		   long, costs more RAM to create them but makes freqency calcs much
		   cheaper, and means that interpolation isn't that neccesary, esp if
		   you use integer frequncies */

		float *tmp_tbl = new float[sr];
                const int lag = sr/50;
		sin_tbl = new float[sr];
		for (i = 0; i < sr; i++) {
                        tmp = sin(i * 2.0 * PI / sr);
			sin_tbl[i] = DENORMALISE(tmp);
		}
		tri_tbl = new float[sr];
		for (i = 0; i < sr; i++) {
			tmp = acos(cos(i * 2.0 * PI / sr)) / PI * 2.0 - 1.0;
			tri_tbl[i] = DENORMALISE(tmp);
		}
		squ_tbl = new float[sr];
		for (i = 0; i < sr/2; i++) {
			tmp_tbl[i] = -1.0f;
		}
		for (i = sr/2; i < sr; i++) {
			tmp_tbl[i] = +1.0f;
		}
		tmp = -1.0f;
		for (i = (sr/2)-lag; i < (sr/2)+lag; i++) {
			tmp_tbl[i] = tmp;
			tmp += 1.0/(lag * 2.0);
		}
		for (i = 0; i < sr; i++) {
			squ_tbl[i] = (tmp_tbl[MOD(i-lag, sr)] +
			tmp_tbl[MOD(i+lag, sr)]) * 0.5;
		}
		saw_tbl = new float[sr];
		for (i = 0; i < sr; i++) {
			tmp = ((2.0 * i) - (float)sr) / (float)sr;
			tmp_tbl[i] = DENORMALISE(tmp);
		}
		for (i = 0; i < sr; i++) {
			saw_tbl[i] = (tmp_tbl[MOD(i-lag, sr)] +
			tmp_tbl[MOD(i+lag, sr)]) * 0.5;
		}
		delete[] tmp_tbl;

	}
	dco1_filter.out[0] = dco1_filter.out[1] = dco1_filter.out[2] = dco1_filter.out[3] = 0.0;
	dco1_filter.in[0] = dco1_filter.in[1] = dco1_filter.in[2] = dco1_filter.in[3] = 0.0;
	dco2_filter.out[0] = dco2_filter.out[1] = dco2_filter.out[2] = dco2_filter.out[3] = 0.0;
	dco2_filter.in[0] = dco2_filter.in[1] = dco2_filter.in[2] = dco2_filter.in[3] = 0.0;

	++useCount;
	dco1.phase = 0.0;
	dco2.phase = 0.0;
	lfo.phase = 0.0;
	ctrlHi = 0;
	ctrlLo = 0;
	dataHi = 0;
	dataLo = 0;
}

//---------------------------------------------------------
//   ~VAM
//---------------------------------------------------------

VAM::~VAM()
{
	--useCount;
	if(useCount == 0) {
		delete[] sin_tbl;
		delete[] tri_tbl;
		delete[] saw_tbl;
		delete[] squ_tbl;
	}
}

//---------------------------------------------------------
//   curTime
//---------------------------------------------------------

double VAM::cb2amp(double cb)
{
	if(cb < 0.0)
		return 1.0;
	if(cb > 960.0)
		return 0.0;
	return cb2amp_tab[int(cb)];
}

double VAM::lowpass_filter(double cutoff, double resonance, double input, LPFilter *f)
{
	double output;
	cutoff *= 1.16;

	input -= f->out[3] * (resonance * 4.0) * (1.0 - 0.15 * cutoff * cutoff);
	input *= 0.35013 * cutoff * cutoff * cutoff * cutoff;

	f->out[0] = input + 0.3 * f->in[0] + (1.0 - cutoff) * f->out[0]; // Pole 1
	f->in[0]  = input;
	f->out[1] = f->out[0] + 0.3 * f->in[1] + (1.0 - cutoff) * f->out[1];  // Pole 2
	f->in[1]  = f->out[0];
	f->out[2] = f->out[1] + 0.3 * f->in[2] + (1.0 - cutoff) * f->out[2];  // Pole 3
	f->in[2]  = f->out[1];
	f->out[3] = f->out[2] + 0.3 * f->in[3] + (1.0 - cutoff) * f->out[3];  // Pole 4
	f->in[3]  = f->out[2];

//	if(f.out[3] > 1.0) f.out[3] = 1.0;

	output = f->out[3];


	return output;
}

float *VAM::wave_tbl(int wave)
{
        if (wave == 0) {
                return sin_tbl;
        }
	else if (wave == 1) {
                return squ_tbl;
	}
	else if (wave == 2) {
	        return saw_tbl;
	}
	else if (wave == 3) {
	        return tri_tbl;
	}
	return sin_tbl;
}

//---------------------------------------------------------
//   init
//---------------------------------------------------------

bool VAM::init()
{
	memset(controller, 0, sizeof(controller));

	int maxval = 128*128-1;

	setController(0, DCO1_PITCHMOD, 8191);
	setController(0, DCO2_PITCHMOD, 8191);
	setController(0, DCO1_WAVEFORM, 1);
	setController(0, DCO2_WAVEFORM, 1);
	setController(0, DCO1_FM, 0);
	setController(0, DCO2_FM, 0);
	setController(0, DCO1_PWM, 0);
	setController(0, DCO2_PWM, 0);
	setController(0, DCO1_ATTACK, 0);
	setController(0, DCO2_ATTACK, 0);
	setController(0, DCO1_DECAY, 0);
	setController(0, DCO2_DECAY, 0);
	setController(0, DCO1_SUSTAIN, maxval - 255);
	setController(0, DCO2_SUSTAIN, maxval - 255);
	setController(0, DCO1_RELEASE, 0);
	setController(0, DCO2_RELEASE, 0);
	setController(0, LFO_FREQ, 0);
	setController(0, LFO_WAVEFORM, 0);
	setController(0, FILT_ENV_MOD, 0);
	setController(0, FILT_KEYTRACK, 0);
	setController(0, FILT_RES, 0);
	setController(0, FILT_ATTACK, 0);
	setController(0, FILT_DECAY, 0);
	setController(0, FILT_SUSTAIN, maxval);
	setController(0, FILT_RELEASE, 3);
	setController(0, DCO2ON, 0);
	setController(0, FILT_INVERT, 0);
	setController(0, FILT_CUTOFF, 15000);
	setController(0, DCO1_DETUNE, 8191);
	setController(0, DCO2_DETUNE, 8191);
	setController(0, DCO1_PW, 0);
	setController(0, DCO2_PW, 0);

	
	isOn = false;
	return false;
}

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void VAM::write(int sampleCount, float** ports, int offset)
{
      float* buffer = *ports + offset;
	if (!isOn)
            return;

	float sample, osc, lfol, *dco1_tbl, *dco2_tbl, *lfo_tbl, pw;
	float cutoff;
	int sr = sampleRate();

	dco1_tbl = wave_tbl(dco1.waveform);
	dco2_tbl = wave_tbl(dco2.waveform);
	lfo_tbl = wave_tbl(lfo.waveform);

	cutoff = filt_keytrack ? (dco1.freq /500.0 + filt_cutoff)/2 : filt_cutoff;
	cutoff = LIMIT(cutoff, 0.0, 1.0);

	for (int i = 0; i < sampleCount; i++) {
		if(!(dco1_env.step() + dco2_env.step())) {
			isOn = false;
			break;
		}
		filt_env.step();

		/* DCO 1 */
		lfol = lfo_tbl[(int)lfo.phase];
		pw = dco1.pw + dco1.pwm * lfol * 0.5;
		pw = LIMIT(pw, 0.0, 1.0);
		if(dco1.phase < sr/2 * ( 1.0 - pw))
			osc = dco1_tbl[int(dco1.phase / (1.0 - pw))];
		else
			osc = dco1_tbl[int(dco1.phase / (1.0 + pw))];
		lfol = lfo_tbl[(int)lfo.phase];
		dco1.phase += dco1.freq + dco1.fm * lfol * 1500.0;
		lfo.phase += lfo.freq * 50.0;
		if(!filt_invert)
		sample = lowpass_filter((cb2amp(960.0 * (1.0 - filt_env_mod * filt_env.env))
			+ 1.0 - filt_env_mod) * cutoff,
			filt_res, osc, &dco1_filter) * cb2amp(960.0 * (1.0 - dco1_env.env));
		else sample = lowpass_filter((cb2amp(960.0 * (1.0 - filt_env_mod * (1.0 - filt_env.env)))
			+ 1.0 - filt_env_mod) * cutoff,
			filt_res, osc, &dco1_filter) * cb2amp(960.0 * (1.0 - dco1_env.env));
		while(dco1.phase > sr) dco1.phase -= sr;
		while(dco1.phase < 0.0) dco1.phase += sr;

		/* DCO 2 */
		if(dco2.on) {
			pw = dco2.pw + dco2.pwm * lfol * 0.5;
			pw = LIMIT(pw, 0.0, 1.0);
			if(dco2.phase < sr/2 * (1 - pw))
				osc = dco2_tbl[int(dco2.phase / (1.0 - pw))];
			else
				osc = dco2_tbl[int(dco2.phase / (1.0 + pw))];
			dco2.phase += dco2.freq + dco2.fm * lfol * 1500.0;
			if(!filt_invert)
			sample += lowpass_filter((cb2amp(960.0 * (1.0 - filt_env_mod * filt_env.env)) + 1.0 - filt_env_mod) * cutoff,
				filt_res, osc, &dco2_filter) * cb2amp(960.0 * (1.0 - dco2_env.env));
			else sample += lowpass_filter((cb2amp(960.0 * (1.0 - filt_env_mod * (1.0 - filt_env.env))) + 1.0 - filt_env_mod) * cutoff,
				filt_res, osc, &dco2_filter) * cb2amp(960.0 * (1.0 - dco2_env.env));
			
			while (dco2.phase > sr)  dco2.phase -= sr;
			while (dco2.phase < 0.0) dco2.phase += sr;
		}
		while(lfo.phase > sr)	lfo.phase -= sr;
		while(lfo.phase < 0.0)	lfo.phase += sr;
		sample *= velocity * 0.5;
		sample = LIMIT(sample, -1.0, 1.0);

//		if(sample > 1.0) fprintf(stderr, "oooops %f\n", sample);
		buffer[i] = sample;
	}
}

//---------------------------------------------------------
//   noteon
//---------------------------------------------------------

void VAM::noteon(int chan, int newpitch, int velo)
{
	if (velo == 0) {
		noteoff(chan, newpitch);
		return;
	}
	isOn = true;
	channel = chan;
	pitch = newpitch;
	velocity = velo / 127.0;
	dco1.freq = 8.176 * exp(float(pitch + dco1.pitchmod + dco1.detune)*log(2.0)/12.0);
	dco2.freq = 8.176 * exp(float(pitch + dco2.pitchmod + dco2.detune)*log(2.0)/12.0);
	keytrack_cutoff = 16.0 * dco1.freq / sampleRate();
	if(keytrack_cutoff > 1.0) keytrack_cutoff = 1.0;
        dco1_env.setSegment(0, dco1_env.attack,    1.0/dco1_env.attack);
        dco1_env.setSegment(1, dco1_env.decay, -((1.0-dco1_env.sustain)/dco1_env.decay));
        dco2_env.setSegment(0, dco2_env.attack,    1.0/dco2_env.attack);
        dco2_env.setSegment(1, dco2_env.decay, -((1.0-dco2_env.sustain)/dco2_env.decay));
        filt_env.setSegment(0, filt_env.attack,    1.0/filt_env.attack);
        filt_env.setSegment(1, filt_env.decay, -((1.0-filt_env.sustain)/filt_env.decay));
	dco1_env.keyOn();
	dco2_env.keyOn();
	filt_env.env = 0.0;
	filt_env.keyOn();
//	dco1.phase = 0.0;
//	dco2.phase = 0.0;
//	lfo.phase = 0.0;
}

//---------------------------------------------------------
//   noteoff
//---------------------------------------------------------

void VAM::noteoff(int chan, int offpitch)
{
	if(isOn && (pitch == offpitch) && (channel == chan)) {
		dco1_env.keyOff();
		dco2_env.keyOff();
		filt_env.keyOff();
	}
}

//---------------------------------------------------------
//   setController
//---------------------------------------------------------

void VAM::setController(int /*channel*/, int ctrl, int data)
{
//	fprintf(stderr, "ctrl: %d data: %d\n", ctrl, data);
	int maxval = 128*128-1;
	double normval = double(data) / double(maxval);
	switch(ctrl) {
		case DCO1_PITCHMOD:
			dco1.pitchmod = (data  - 8191) / 341.333;
			break;
		case DCO1_WAVEFORM:
			dco1.waveform = data;
			break;
		case DCO1_FM:
			dco1.fm = lin2exp[int(normval * 255.0)];
			break;
		case DCO1_PWM:
			dco1.pwm = normval;
			break;
		case DCO1_ATTACK:
			dco1_env.attack = int(lin2exp[int(normval * 255.0)] * 5.0 * sampleRate()) + 1;
			break;
		case DCO1_DECAY:
			dco1_env.decay = (data * sampleRate() * 5) / maxval + 1;
			break;
		case DCO1_SUSTAIN:
			dco1_env.sustain = normval;
			break;
		case DCO1_RELEASE:
			dco1_env.release = int(lin2exp[int(normval * 255.0)] * 10.0 * sampleRate()) + 1;
			dco1_env.setSegment(2, dco1_env.release, -(1.0/dco1_env.release));
			break;

		case DCO2_PITCHMOD:
			dco2.pitchmod = (data - 8191) / 341.333;
			break;
		case DCO2_WAVEFORM:
			dco2.waveform = data;
			break;
		case DCO2_FM:
			dco2.fm = normval;
			break;
		case DCO2_PWM:
			dco2.pwm = normval;
			break;
		case DCO2_ATTACK:
			dco2_env.attack = int(lin2exp[int(normval * 255.0)] * 5.0 * sampleRate()) + 1;
			break;
		case DCO2_DECAY:
			dco2_env.decay = (data * sampleRate() * 5) / maxval + 1;
			break;
		case DCO2_SUSTAIN:
			dco2_env.sustain = normval;
			break;
		case DCO2_RELEASE:
			dco2_env.release = int(lin2exp[int(normval * 255.0)] * 10.0 * sampleRate()) + 1;
			dco2_env.setSegment(2, dco2_env.release, -(1.0/dco2_env.release));
			break;
		case LFO_FREQ:
			lfo.freq = lin2exp[int(normval * 255.0)];
//			fprintf(stderr, "%f\n", lfo.freq);
			break;
		case LFO_WAVEFORM:
			lfo.waveform = data;
			break;
		case FILT_ENV_MOD:
			filt_env_mod = 1.0 - lin2exp[int(255.0 - normval * 255.0)];
			break;
		case FILT_KEYTRACK:
			filt_keytrack = data;
			break;
		case FILT_RES:
			filt_res = normval;
			break;
		case FILT_ATTACK:
			filt_env.attack = int(lin2exp[int(normval * 255.0)] * 5.0 * sampleRate());
			break;
		case FILT_DECAY:
			filt_env.decay = (data * sampleRate() * 5) / maxval + 1;
			break;
		case FILT_SUSTAIN:
			filt_env.sustain = normval;
			break;
		case FILT_RELEASE:
			filt_env.release = int(lin2exp[int(normval * 255.0)] * 10.0 * sampleRate()) + 1;
			filt_env.setSegment(2, filt_env.release, -(1.0/filt_env.release));
			break;
		case DCO2ON:
			dco2.on = data;
			break;
		case FILT_INVERT:
			filt_invert = data;
			break;
		case FILT_CUTOFF:
			filt_cutoff = normval;
//			fprintf(stderr, "%f\n", filt_cutoff);
			break;
		case DCO1_DETUNE:
			dco1.detune = (data - 8191) / 16384.0;
			break;
		case DCO2_DETUNE:
			dco2.detune = (data - 8191) / 16384.0;
			break;
		case DCO1_PW:
			dco1.pw = normval;
			if(dco1.pw == 1.0) dco1.pw = 0.99;

			break;
		case DCO2_PW:
			dco2.pw = normval;
			if(dco2.pw == 1.0) dco2.pw = 0.99;
			break;
		default:
			printf("VAM: set unknown Ctrl %d to %d\n", ctrl, data);
			break;
	}
	controller[ctrl] = data;
}

//---------------------------------------------------------
//   parameterRequest
//---------------------------------------------------------

void VAM::parameterRequest(int ctrl)
{
	if(ctrl >= NUM_CONTROLLER) {
		fprintf(stderr, "VAM: illegal controller %d request\n", ctrl);
		return;
	}
	unsigned char data[] = { 0x7c, 0x2, 0x2, 0x0, 0x0, 0x0 };
	data[3] = ctrl;
	data[4] = controller[ctrl] & 0x7f;
	data[5] = (controller[ctrl] >> 7) & 0x7f;
	sendSysex(data, sizeof(data));
}

//---------------------------------------------------------
//   sysex
//---------------------------------------------------------

void VAM::sysex(const unsigned char* data, int len)
{
	if (len >= 6 && data[0] == 0xf0 && data[len-1] == 0xf7) {
		//---------------------------------------------
		//  MusE Soft Synth
		//---------------------------------------------

		if (data[1] == 0x7c) {
			int n = len - 5;
			if (n < 1) {
				printf("vam: bad sysEx:\n");
				return;
			}
			if (data[2] == 2) {     // vam voice
				if (data[3] == 1) {  // get parameter
					parameterRequest(data[4]);
					return;
				}
			}
		}
	}
	printf("vam: unknown sysex received, len %d:\n", len);
	for (int i = 0; i < len; ++i)
		printf("%02x ", data[i]);

	printf("\n");
}

int VAM::getMidiInitEvent(int id, RawMidiEvent *ev)
{
	if(id >= NUM_CONTROLLER)
		return 0;
	ev->setType(SND_SEQ_EVENT_NONREGPARAM);
	ev->setChan(0);
	ev->setDataA(id);
	ev->setDataB(controller[id]);
	return ++id;
}

//---------------------------------------------------------
//   inst
//---------------------------------------------------------

static Mess* inst(const char* name)
      {
      return new VAM(name);
      }

//---------------------------------------------------------
//   initMess
//---------------------------------------------------------

void initMess()
      {
      Mess::initMess(
          1224,
         "vam",
         "Werner Schweer, Josef Lindman Hörnlund",
         "vam soft synth",
         "None",
         1,
         inst);
      }
