/*
  ZynAddSubFX - a software synthesizer
 
  Envelope.C - Envelope implementation
  Copyright (C) 2002-2005 Nasca Octavian Paul
  Author: Nasca Octavian Paul

  This program is free software; you can redistribute it and/or modify
  it under the terms of version 2 of the GNU General Public License 
  as published by the Free Software Foundation.

  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 (version 2) for more details.

  You should have received a copy of the GNU General Public License (version 2)
  along with this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

*/

#include <stdio.h>
#include "Envelope.h"

Envelope::Envelope(EnvelopeParams *envpars,REALTYPE basefreq){
    int i;
    envpoints=envpars->Penvpoints;
    if (envpoints>MAX_ENVELOPE_POINTS) envpoints=MAX_ENVELOPE_POINTS;
    envsustain=(envpars->Penvsustain==0)?-1:envpars->Penvsustain;
    forcedrelase=envpars->Pforcedrelease;
    envstretch=pow(440.0/basefreq,envpars->Penvstretch/64.0);
    linearenvelope=envpars->Plinearenvelope;

    if (envpars->Pfreemode==0) envpars->converttofree();
	
    REALTYPE bufferdt=SOUND_BUFFER_SIZE/(REALTYPE)SAMPLE_RATE;

    int mode=envpars->Envmode;

    //for amplitude envelopes
    if ((mode==1)&&(linearenvelope==0)) mode=2;//change to log envelope
    if ((mode==2)&&(linearenvelope!=0)) mode=1;//change to linear

    for (i=0;i<MAX_ENVELOPE_POINTS;i++) {
        REALTYPE tmp=envpars->getdt(i)/1000.0*envstretch;
	if (tmp>bufferdt) envdt[i]=bufferdt/tmp;
	    else envdt[i]=2.0;//any value larger than 1

	switch (mode){
	    case 2:envval[i]=(1.0-envpars->Penvval[i]/127.0)*MIN_ENVELOPE_DB;
		   break;
	    case 3:envval[i]=(pow(2,6.0*fabs(envpars->Penvval[i]-64.0)/64.0)-1.0)*100.0;
		   if (envpars->Penvval[i]<64) envval[i]=-envval[i];
		   break;
	    case 4:envval[i]=(envpars->Penvval[i]-64.0)/64.0*6.0;//6 octaves (filtru)
		   break;
	    case 5:envval[i]=(envpars->Penvval[i]-64.0)/64.0*10;
		   break;
	    default:envval[i]=envpars->Penvval[i]/127.0;
	};
	
    };

    envdt[0]=1.0;

    currentpoint=1;//the envelope starts from 1
    keyreleased=0;
    t=0.0;
    envfinish=0;
    inct=envdt[1];
    envoutval=0.0;
};

Envelope::~Envelope(){
};


/*
 * Relase the key (note envelope)
 */
void Envelope::relasekey(){
    if (keyreleased==1) return;
    keyreleased=1;
    if (forcedrelase!=0) t=0.0;
};

/*
 * Envelope Output
 */
REALTYPE Envelope::envout(){
    REALTYPE out;

    if (envfinish!=0) {//if the envelope is finished
	envoutval=envval[envpoints-1];
	return(envoutval);
    };
    if ((currentpoint==envsustain+1)&&(keyreleased==0)) {//if it is sustaining now
	envoutval=envval[envsustain];
	return(envoutval);
    };

    if ((keyreleased!=0) && (forcedrelase!=0)){//do the forced release	
	
        int tmp=(envsustain<0) ? (envpoints-1):(envsustain+1);//if there is no sustain point, use the last point for release

	if (envdt[tmp]<0.00000001) out=envval[tmp];
    	    else out=envoutval+(envval[tmp]-envoutval)*t;
	t+=envdt[tmp]*envstretch;

	if (t>=1.0) {
               currentpoint=envsustain+2;
	       forcedrelase=0;
	       t=0.0;
	       inct=envdt[currentpoint];
	       if ((currentpoint>=envpoints)||(envsustain<0)) envfinish=1;
	    };
	return(out);
    };
    if (inct>=1.0) out=envval[currentpoint];
	else out=envval[currentpoint-1]+(envval[currentpoint]-envval[currentpoint-1])*t;

    t+=inct;
    if (t>=1.0){
	if (currentpoint>=envpoints-1) envfinish=1;
	    else currentpoint++;
	t=0.0;
	inct=envdt[currentpoint];
    };

    envoutval=out;    
    return (out);
};

/*
 * Envelope Output (dB)
 */
REALTYPE Envelope::envout_dB(){
    REALTYPE out;
    if (linearenvelope!=0) return (envout());
    
    if ((currentpoint==1)&&((keyreleased==0)||(forcedrelase==0))) {//first point is always lineary interpolated
	REALTYPE v1=dB2rap(envval[0]);
	REALTYPE v2=dB2rap(envval[1]);
	out=v1+(v2-v1)*t;
	
	t+=inct; 
	if (t>=1.0) {
	    t=0.0;
	    inct=envdt[2];
	    currentpoint++;
	    out=v2;
	};
	
	if (out>0.001) envoutval=rap2dB(out);
		else envoutval=-40.0;
    } else out=dB2rap(envout());

    return(out);
};

int Envelope::finished(){
    return(envfinish);
};