/*
  ZynAddSubFX - a software synthesizer
 
  SUBnote.C - The "subtractive" synthesizer
  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 <math.h>
#include <stdlib.h>
#include <stdio.h>
#include "../globals.h"
#include "SUBnote.h"
#include "../Misc/Util.h"

SUBnote::SUBnote(SUBnoteParameters *parameters,Controller *ctl_,REALTYPE freq,REALTYPE velocity,int portamento_,int midinote){
    ready=0;
    
    tmpsmp=new REALTYPE[SOUND_BUFFER_SIZE];
    tmprnd=new REALTYPE[SOUND_BUFFER_SIZE];
    
    pars=parameters;
    ctl=ctl_;
    portamento=portamento_;
    NoteEnabled=ON;
    volume=pow(0.1,3.0*(1.0-pars->PVolume/96.0));//-60 dB .. 0 dB
    volume*=VelF(velocity,pars->PAmpVelocityScaleFunction);
    if (pars->PPanning!=0) panning=pars->PPanning/127.0;
	else panning=RND;
    numstages=pars->Pnumstages;
    stereo=pars->Pstereo;
    start=pars->Pstart;
    firsttick=1;
    int pos[MAX_SUB_HARMONICS];

    if (pars->Pfixedfreq==0) basefreq=freq;
	else {
	    basefreq=440.0;
	    int fixedfreqET=pars->PfixedfreqET;
	    if (fixedfreqET!=0) {//if the frequency varies according the keyboard note 
		REALTYPE tmp=(midinote-69.0)/12.0*(pow(2.0,(fixedfreqET-1)/63.0)-1.0);
		if (fixedfreqET<=64) basefreq*=pow(2.0,tmp);
		    else basefreq*=pow(3.0,tmp);
	    };

	};
    REALTYPE detune=getdetune(pars->PDetuneType,pars->PCoarseDetune,pars->PDetune);
    basefreq*=pow(2.0,detune/1200.0);//detune
//    basefreq*=ctl->pitchwheel.relfreq;//pitch wheel
    
    //global filter    
    GlobalFilterCenterPitch=pars->GlobalFilter->getfreq()+//center freq 
                        (pars->PGlobalFilterVelocityScale/127.0*6.0)* //velocity sensing
			(VelF(velocity,pars->PGlobalFilterVelocityScaleFunction)-1);

    GlobalFilterL=NULL;GlobalFilterR=NULL;
    GlobalFilterEnvelope=NULL;

    //select only harmonics that desire to compute    
    numharmonics=0;
    for (int n=0;n<MAX_SUB_HARMONICS;n++){
	if (pars->Phmag[n]==0)continue;
	if (n*basefreq>SAMPLE_RATE/2.0) break;//remove the freqs above the Nyquist freq
	pos[numharmonics++]=n;
    };
    
    if (numharmonics==0) {
	NoteEnabled=OFF;
	return;
    };
        
    
    lfilter=new bpfilter[numstages*numharmonics];
    if (stereo!=0) rfilter=new bpfilter[numstages*numharmonics];
    
    //how much the amplitude is normalised (because the harmonics)
    REALTYPE reduceamp=0.0;
    
    for (int n=0;n<numharmonics;n++){

	REALTYPE freq=basefreq*(pos[n]+1);
	
	//the bandwidth is not absolute(Hz); it is relative to frequency
	REALTYPE bw=pow(10,(pars->Pbandwidth-127.0)/127.0*4)*numstages;

	//Bandwidth Scale
	bw*=pow(1000/freq,(pars->Pbwscale-64.0)/64.0*3.0);

	//Relative BandWidth    
	bw*=pow(100,(pars->Phrelbw[pos[n]]-64.0)/64.0);

	if (bw>25.0) bw=25.0;

	//try to keep same amplitude on all freqs and bw. (empirically)
	REALTYPE gain=sqrt(1500.0/(bw*freq));

        REALTYPE hmagnew=1.0-pars->Phmag[pos[n]]/127.0;
	REALTYPE hgain;

        switch(pars->Phmagtype){
	    case 1:hgain=exp(hmagnew*log(0.01)); break;
	    case 2:hgain=exp(hmagnew*log(0.001));break;
	    case 3:hgain=exp(hmagnew*log(0.0001));break;
	    case 4:hgain=exp(hmagnew*log(0.00001));break;
	    default:hgain=1.0-hmagnew;
	};
	gain*=hgain;
        reduceamp+=hgain;

        for (int nph=0;nph<numstages;nph++){
	    REALTYPE amp=1.0;
    	    if (nph==0) amp=gain;
	    initfilter(lfilter[nph+n*numstages],freq,bw,amp,hgain);
	    if (stereo!=0) initfilter(rfilter[nph+n*numstages],freq,bw,amp,hgain);
	};
    };
    
    if (reduceamp<0.001) reduceamp=1.0;
    volume/=reduceamp;
    
    oldpitchwheel=0;
    oldbandwidth=64;
    if (pars->Pfixedfreq==0) initparameters(basefreq);
	else initparameters(basefreq/440.0*freq);

    oldamplitude=newamplitude;
    ready=1;
};

SUBnote::~SUBnote(){
    if (NoteEnabled!=OFF) KillNote();
    delete [] tmpsmp;
    delete [] tmprnd;
};

/*
 * Kill the note
 */
void SUBnote::KillNote(){
    if (NoteEnabled!=OFF){
	delete [] lfilter;
	lfilter=NULL;
	if (stereo!=0) delete [] rfilter;
	rfilter=NULL;
	delete(AmpEnvelope);
	if (FreqEnvelope!=NULL) delete(FreqEnvelope);
	if (BandWidthEnvelope!=NULL) delete(BandWidthEnvelope);
	NoteEnabled=OFF;
    };
    
};


/*
 * Compute the filters coefficients
 */
void SUBnote::computefiltercoefs(bpfilter &filter,REALTYPE freq,REALTYPE bw,REALTYPE gain){
    if (freq>SAMPLE_RATE/2.0-200.0) {
	freq=SAMPLE_RATE/2.0-200.0;
    };

    REALTYPE omega=2.0*PI*freq/SAMPLE_RATE;
    REALTYPE sn=sin(omega);REALTYPE cs=cos(omega);
    REALTYPE alpha=sn*sinh(LOG_2/2.0*bw*omega/sn);

    if (alpha>1) alpha=1;
    if (alpha>bw) alpha=bw;
    
    filter.b0=alpha/(1.0+alpha)*filter.amp*gain;
    filter.b2=-alpha/(1.0+alpha)*filter.amp*gain;
    filter.a1=-2.0*cs/(1.0+alpha); 
    filter.a2=(1.0-alpha)/(1.0+alpha);

};


/*
 * Initialise the filters
 */
void SUBnote::initfilter(bpfilter &filter,REALTYPE freq,REALTYPE bw,REALTYPE amp,REALTYPE mag){
    filter.xn1=0.0;filter.xn2=0.0;
    
    if (start==0) {
	filter.yn1=0.0;
	filter.yn2=0.0;
    } else {
	REALTYPE a=0.1*mag;//empirically
        REALTYPE p=RND*2.0*PI;
	if (start==1) a*=RND;
	filter.yn1=a*cos(p);
	filter.yn2=a*cos(p+freq*2.0*PI/SAMPLE_RATE);

	//correct the error of computation the start amplitude 
	//at very high frequencies	
	if (freq>SAMPLE_RATE*0.96) {
	    filter.yn1=0.0;
	    filter.yn2=0.0;
	
	};
    };

    filter.amp=amp;   
    filter.freq=freq;
    filter.bw=bw;
    computefiltercoefs(filter,freq,bw,1.0);
};

/*
 * Do the filtering
 */
void SUBnote::filter(bpfilter &filter,REALTYPE *smps){
    int i;
    REALTYPE out;
    for (i=0;i<SOUND_BUFFER_SIZE;i++){
       out=smps[i] * filter.b0 + filter.b2 * filter.xn2
          -filter.a1 * filter.yn1 - filter.a2 * filter.yn2;
       filter.xn2=filter.xn1;
       filter.xn1=smps[i];
       filter.yn2=filter.yn1;
       filter.yn1=out;
       smps[i]=out;

    };
};

/*
 * Init Parameters
 */
void SUBnote::initparameters(REALTYPE freq){
    AmpEnvelope=new Envelope(pars->AmpEnvelope,freq);
    if (pars->PFreqEnvelopeEnabled!=0) FreqEnvelope=new Envelope(pars->FreqEnvelope,freq);
	    else FreqEnvelope=NULL;
    if (pars->PBandWidthEnvelopeEnabled!=0) BandWidthEnvelope=new Envelope(pars->BandWidthEnvelope,freq);
	    else BandWidthEnvelope=NULL;
    if (pars->PGlobalFilterEnabled!=0){
	globalfiltercenterq=pars->GlobalFilter->getq();
	GlobalFilterL=new Filter(pars->GlobalFilter);
	if (stereo!=0) GlobalFilterR=new Filter(pars->GlobalFilter);
	GlobalFilterEnvelope=new Envelope(pars->GlobalFilterEnvelope,freq);
	GlobalFilterFreqTracking=pars->GlobalFilter->getfreqtracking(basefreq);
    };
    computecurrentparameters();
};


/*
 * Compute Parameters of SUBnote for each tick
 */
void SUBnote::computecurrentparameters(){
    if ((FreqEnvelope!=NULL)||(BandWidthEnvelope!=NULL)||
	(oldpitchwheel!=ctl->pitchwheel.data)||
	(oldbandwidth!=ctl->bandwidth.data)||
	(portamento!=0)){
	REALTYPE envfreq=1.0;
	REALTYPE envbw=1.0;
	REALTYPE gain=1.0;
		
	if (FreqEnvelope!=NULL) {
	    envfreq=FreqEnvelope->envout()/1200;
	    envfreq=pow(2.0,envfreq);
	};
	envfreq*=ctl->pitchwheel.relfreq;//pitch wheel
	if (portamento!=0) {//portamento is used
	    envfreq*=ctl->portamento.freqrap;
	    if (ctl->portamento.used==0){//the portamento has finished
		portamento=0;//this note is no longer "portamented"
	    };
	};
	
	if (BandWidthEnvelope!=NULL) {
	    envbw=BandWidthEnvelope->envout();
	    envbw=pow(2,envbw);	    
	};
	envbw*=ctl->bandwidth.relbw;//bandwidth controller

	REALTYPE tmpgain=1.0/sqrt(envbw*envfreq);

	for (int n=0;n<numharmonics;n++){
	    for (int nph=0;nph<numstages;nph++) {
		if (nph==0) gain=tmpgain;else gain=1.0;
	        computefiltercoefs( lfilter[nph+n*numstages],
	    			    lfilter[nph+n*numstages].freq*envfreq,
				    lfilter[nph+n*numstages].bw*envbw,gain);
	    };
	};
	if (stereo!=0)
	for (int n=0;n<numharmonics;n++){
	    for (int nph=0;nph<numstages;nph++) {
		if (nph==0) gain=tmpgain;else gain=1.0;
	        computefiltercoefs( rfilter[nph+n*numstages],
	    			    rfilter[nph+n*numstages].freq*envfreq,
				    rfilter[nph+n*numstages].bw*envbw,gain);
	    };
	};
	oldbandwidth=ctl->bandwidth.data;
	oldpitchwheel=ctl->pitchwheel.data;
    };
    newamplitude=volume*AmpEnvelope->envout_dB()*2.0;
    
    //Filter
    if (GlobalFilterL!=NULL){
	REALTYPE globalfilterpitch=GlobalFilterCenterPitch+GlobalFilterEnvelope->envout();
	REALTYPE filterfreq=globalfilterpitch+ctl->filtercutoff.relfreq+GlobalFilterFreqTracking;
	filterfreq=GlobalFilterL->getrealfreq(filterfreq);
	
	GlobalFilterL->setfreq_and_q(filterfreq,globalfiltercenterq*ctl->filterq.relq);
	if (GlobalFilterR!=NULL) GlobalFilterR->setfreq_and_q(filterfreq,globalfiltercenterq*ctl->filterq.relq);
    };

};

/*
 * Note Output
 */
int SUBnote::noteout(REALTYPE *outl,REALTYPE *outr){
    int i;

    for (i=0;i<SOUND_BUFFER_SIZE;i++){
	outl[i]=denormalkillbuf[i];
	outr[i]=denormalkillbuf[i];
    };
    
    if (NoteEnabled==OFF) return(0);

    //left channel
    for (i=0;i<SOUND_BUFFER_SIZE;i++) tmprnd[i]=RND*2.0-1.0;
    for (int n=0;n<numharmonics;n++){
	for (i=0;i<SOUND_BUFFER_SIZE;i++) tmpsmp[i]=tmprnd[i];
	for (int nph=0;nph<numstages;nph++) 
	     filter(lfilter[nph+n*numstages],tmpsmp);     
	for (i=0;i<SOUND_BUFFER_SIZE;i++) outl[i]+=tmpsmp[i];
    };

    if (GlobalFilterL!=NULL) GlobalFilterL->filterout(&outl[0]);     
    
    //right channel
    if (stereo!=0){
	for (i=0;i<SOUND_BUFFER_SIZE;i++) tmprnd[i]=RND*2.0-1.0;
	for (int n=0;n<numharmonics;n++){
	    for (i=0;i<SOUND_BUFFER_SIZE;i++) tmpsmp[i]=tmprnd[i];
	    for (int nph=0;nph<numstages;nph++) 
	        filter(rfilter[nph+n*numstages],tmpsmp);     
	    for (i=0;i<SOUND_BUFFER_SIZE;i++) outr[i]+=tmpsmp[i];
	};
	if (GlobalFilterR!=NULL) GlobalFilterR->filterout(&outr[0]);     
    } else for (i=0;i<SOUND_BUFFER_SIZE;i++) outr[i]=outl[i];
    
    if (firsttick!=0){
	int n=10;if (n>SOUND_BUFFER_SIZE) n=SOUND_BUFFER_SIZE;
	for (i=0;i<n;i++) {
	    REALTYPE ampfadein=0.5-0.5*cos((REALTYPE) i/(REALTYPE) n*PI);
	    outl[i]*=ampfadein;
	    outr[i]*=ampfadein;
	};
	firsttick=0;
    };

    if (ABOVE_AMPLITUDE_THRESHOLD(oldamplitude,newamplitude)){
	// Amplitude interpolation 
        for (i=0;i<SOUND_BUFFER_SIZE;i++){
	   REALTYPE tmpvol=INTERPOLATE_AMPLITUDE(oldamplitude
	          ,newamplitude,i,SOUND_BUFFER_SIZE);
	   outl[i]*=tmpvol*panning; 
	   outr[i]*=tmpvol*(1.0-panning);
        };
    } else {
	for (i=0;i<SOUND_BUFFER_SIZE;i++) {
	    outl[i]*=newamplitude*panning;
	    outr[i]*=newamplitude*(1.0-panning);
	};
    };

    oldamplitude=newamplitude;    
    computecurrentparameters();
    
    // Check if the note needs to be computed more
    if (AmpEnvelope->finished()!=0){
        for (i=0;i<SOUND_BUFFER_SIZE;i++) {//fade-out
		REALTYPE tmp=1.0-(REALTYPE)i/(REALTYPE)SOUND_BUFFER_SIZE;
		outl[i]*=tmp;
		outr[i]*=tmp;
		};
	KillNote();    
    };
    return(1);
};

/*
 * Relase Key (Note Off)
 */
void SUBnote::relasekey(){
    AmpEnvelope->relasekey();
    if (FreqEnvelope!=NULL) FreqEnvelope->relasekey();
    if (BandWidthEnvelope!=NULL) BandWidthEnvelope->relasekey();
    if (GlobalFilterEnvelope!=NULL) GlobalFilterEnvelope->relasekey();
};

/*
 * Check if the note is finished
 */
int SUBnote::finished(){
  if (NoteEnabled==OFF) return(1);
	else return(0);    
};