/*
  ZynAddSubFX - a software synthesizer
 
  Master.C - It sends Midi Messages to Parts, receives samples from parts,
             process them with system/insertion effects and mix them
  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 "Master.h"

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <unistd.h>

Master::Master(){
    swaplr=0;
    
    busy = false;
    fft=new FFTwrapper(OSCIL_SIZE);

    tmpmixl=new REALTYPE[SOUND_BUFFER_SIZE];
    tmpmixr=new REALTYPE[SOUND_BUFFER_SIZE];
    audiooutl=new REALTYPE[SOUND_BUFFER_SIZE];
    audiooutr=new REALTYPE[SOUND_BUFFER_SIZE];
    
    ksoundbuffersamples=0;
    ksoundbuffersamplelow=0.0;
    oldsamplel=0.0;oldsampler=0.0;
    shutup=0;
    for (int npart=0;npart<NUM_MIDI_PARTS;npart++) {
	vuoutpeakpart[npart]=1e-9;
	fakepeakpart[npart]=0;
    };
    
      memset(audiooutl, 0, sizeof(REALTYPE) * SOUND_BUFFER_SIZE);
      memset(audiooutr, 0, sizeof(REALTYPE) * SOUND_BUFFER_SIZE);

    for (int npart=0;npart<NUM_MIDI_PARTS;npart++)
	part[npart]=new Part(&microtonal,fft,this);
    

    //Insertion Effects init        
    for (int nefx=0;nefx<NUM_INS_EFX;nefx++)
    	insefx[nefx]=new EffectMgr(1,this);

    //System Effects init        
    for (int nefx=0;nefx<NUM_SYS_EFX;nefx++) {
	sysefx[nefx]=new EffectMgr(0,this);
    };

    
    defaults();
};

void Master::defaults(){
    volume=1.0;
    setPvolume(80);
    setPkeyshift(64);
    
    for (int npart=0;npart<NUM_MIDI_PARTS;npart++){
	part[npart]->defaults();
	part[npart]->Prcvchn=npart%NUM_MIDI_CHANNELS;
    };

    partonoff(0,1);//enable the first part

    for (int nefx=0;nefx<NUM_INS_EFX;nefx++) {
    	insefx[nefx]->defaults();
	Pinsparts[nefx]=-1;
    };
    
    //System Effects init        
    for (int nefx=0;nefx<NUM_SYS_EFX;nefx++) {
	sysefx[nefx]->defaults();
	for (int npart=0;npart<NUM_MIDI_PARTS;npart++){
	    if (nefx==0) setPsysefxvol(npart,nefx,64);
	    else setPsysefxvol(npart,nefx,0);
	};
	for (int nefxto=0;nefxto<NUM_SYS_EFX;nefxto++)
	    setPsysefxsend(nefx,nefxto,0);
    };

    sysefx[0]->changeeffect(1);
    microtonal.defaults();
    ShutUp();
};

/*
 * Note On Messages (velocity=0 for NoteOff)
 */
void Master::NoteOn(unsigned char chan,unsigned char note,unsigned char velocity){
    dump.dumpnote(chan,note,velocity);

    noteon(chan,note,velocity);
};

/*
 * Internal Note On (velocity=0 for NoteOff)
 */
void Master::noteon(unsigned char chan,unsigned char note,unsigned char velocity){
    int npart;
    if (velocity!=0){
        for (npart=0;npart<NUM_MIDI_PARTS;npart++){
	    if (chan==part[npart]->Prcvchn){
		fakepeakpart[npart]=velocity*2;
		if (part[npart]->Penabled!=0) part[npart]->NoteOn(note,velocity,keyshift);
	    };
	};
    }else{
	this->NoteOff(chan,note);
    };
    HDDRecorder.triggernow();    
};

/*
 * Note Off Messages
 */
void Master::NoteOff(unsigned char chan,unsigned char note){
    dump.dumpnote(chan,note,0);
    
    noteoff(chan,note);
};

/*
 * Internal Note Off
 */
void Master::noteoff(unsigned char chan,unsigned char note){
    int npart;
    for (npart=0;npart<NUM_MIDI_PARTS;npart++){
	if ((chan==part[npart]->Prcvchn) && (part[npart]->Penabled!=0))
	    part[npart]->NoteOff(note);	
    };
};

/*
 * Controllers 
 */
void Master::SetController(unsigned char chan,unsigned int type,int par){
    dump.dumpcontroller(chan,type,par);
    
    setcontroller(chan,type,par);
};

/*
 * Internal Controllers 
 */
void Master::setcontroller(unsigned char chan,unsigned int type,int par){
    if ((type==C_dataentryhi)||(type==C_dataentrylo)||
       (type==C_nrpnhi)||(type==C_nrpnlo)){//Process RPN and NRPN by the Master (ignore the chan)
	ctl.setparameternumber(type,par);
	
	int parhi=-1,parlo=-1,valhi=-1,vallo=-1;
	if (ctl.getnrpn(&parhi,&parlo,&valhi,&vallo)==0){//this is NRPN
	    //fprintf(stderr,"rcv. NRPN: %d %d %d %d\n",parhi,parlo,valhi,vallo);
	    switch (parhi){
	       case 0x04://System Effects
	    	    if (parlo<NUM_SYS_EFX) {
			sysefx[parlo]->seteffectpar_nolock(valhi,vallo);
		    };
	            break;
	       case 0x08://Insertion Effects
	    	    if (parlo<NUM_INS_EFX) {
			insefx[parlo]->seteffectpar_nolock(valhi,vallo);
		    };
	            break;
	    
	    };
	};
    } else {//other controllers
	for (int npart=0;npart<NUM_MIDI_PARTS;npart++){//Send the controller to all part assigned to the channel
	    if ((chan==part[npart]->Prcvchn) && (part[npart]->Penabled!=0))
		part[npart]->SetController(type,par);
	};
    };
};


/*
 * Enable/Disable a part
 */
void Master::partonoff(int npart,int what){
    if (npart>=NUM_MIDI_PARTS) return;
    if (what==0){//disable part
	fakepeakpart[npart]=0;
	part[npart]->Penabled=0;
	part[npart]->cleanup();
	for (int nefx=0;nefx<NUM_INS_EFX;nefx++){
	    if (Pinsparts[nefx]==npart) {
	    insefx[nefx]->cleanup();
	};
    };
    } else {//enabled
	part[npart]->Penabled=1;
	fakepeakpart[npart]=0;
    };
};

/*
 * Master audio out (the final sound)
 */

void Master::AudioOut(REALTYPE* outl, REALTYPE* outr)
      {
      int i,npart,nefx;

      //Swaps the Left channel with Right Channel (if it is asked for)
      if (swaplr != 0) {
            REALTYPE *tmp=outl;
            outl = outr;
            outr = tmp;
            }
    
      //clean up the output samples 
      memset(outl, 0, sizeof(REALTYPE) * SOUND_BUFFER_SIZE);
      memset(outr, 0, sizeof(REALTYPE) * SOUND_BUFFER_SIZE);

      //Compute part samples and store them part[npart]->partoutl,partoutr
      for (npart=0; npart<NUM_MIDI_PARTS; npart++)
            if (part[npart]->Penabled != 0) 
                  part[npart]->ComputePartSmps();

      //Insertion effects 
      for (nefx=0;nefx<NUM_INS_EFX;nefx++){
            if (Pinsparts[nefx]>=0) {
                  int efxpart=Pinsparts[nefx];
                  if (part[efxpart]->Penabled!=0) 
                        insefx[nefx]->out(part[efxpart]->partoutl,part[efxpart]->partoutr);
                  }
            }
    
    
      //Apply the part volumes and pannings (after insertion effects)
      for (npart = 0; npart < NUM_MIDI_PARTS; npart++) {
            if (part[npart]->Penabled==0)  
                  continue; 

            REALTYPE newvol_l=part[npart]->volume;
            REALTYPE newvol_r=part[npart]->volume;
            REALTYPE oldvol_l=part[npart]->oldvolumel;
            REALTYPE oldvol_r=part[npart]->oldvolumer;
            REALTYPE pan=part[npart]->panning;
            if (pan < 0.5) 
                  newvol_l *= pan*2.0;
            else 
                  newvol_r *= (1.0-pan)*2.0;
	
            if (ABOVE_AMPLITUDE_THRESHOLD(oldvol_l,newvol_l)||
               ABOVE_AMPLITUDE_THRESHOLD(oldvol_r,newvol_r)){//the volume or the panning has changed and needs interpolation
	    
                  for (i=0;i<SOUND_BUFFER_SIZE;i++) {
                        REALTYPE vol_l = INTERPOLATE_AMPLITUDE(oldvol_l,newvol_l,i,SOUND_BUFFER_SIZE);
                        REALTYPE vol_r = INTERPOLATE_AMPLITUDE(oldvol_r,newvol_r,i,SOUND_BUFFER_SIZE);
		            part[npart]->partoutl[i]*=vol_l;
	                  part[npart]->partoutr[i]*=vol_r;
	                  }
                  part[npart]->oldvolumel=newvol_l;
                  part[npart]->oldvolumer=newvol_r;

                  } 
            else {
                  for (i=0;i<SOUND_BUFFER_SIZE;i++) {//the volume did not changed
                        part[npart]->partoutl[i] *= newvol_l;
                        part[npart]->partoutr[i] *= newvol_r;
                        }
                  }
            }


    //System effects
    for (nefx=0;nefx<NUM_SYS_EFX;nefx++){
	if (sysefx[nefx]->geteffect()==0) continue;//the effect is disabled

    	//Clean up the samples used by the system effects
      memset(tmpmixl, 0, sizeof(REALTYPE) * SOUND_BUFFER_SIZE);
      memset(tmpmixr, 0, sizeof(REALTYPE) * SOUND_BUFFER_SIZE);

	//Mix the channels according to the part settings about System Effect
	for (npart=0;npart<NUM_MIDI_PARTS;npart++){
	    //skip if the part has no output to effect	    
	    if (Psysefxvol[nefx][npart]==0) continue;

	    //skip if the part is disabled
	    if (part[npart]->Penabled==0) continue;

	    //the output volume of each part to system effect
	    REALTYPE vol=sysefxvol[nefx][npart];
	    for (i=0;i<SOUND_BUFFER_SIZE;i++) {
		tmpmixl[i]+=part[npart]->partoutl[i]*vol;
		tmpmixr[i]+=part[npart]->partoutr[i]*vol;
	    };
	};
	
	// system effect send to next ones
	for (int nefxfrom=0;nefxfrom<nefx;nefxfrom++){
	    if (Psysefxsend[nefxfrom][nefx]!=0){
		REALTYPE v=sysefxsend[nefxfrom][nefx];
		for (i=0;i<SOUND_BUFFER_SIZE;i++) {
		    tmpmixl[i]+=sysefx[nefxfrom]->efxoutl[i]*v;
		    tmpmixr[i]+=sysefx[nefxfrom]->efxoutr[i]*v;
		};
	    };
	};

	sysefx[nefx]->out(tmpmixl,tmpmixr);
	
	//Add the System Effect to sound output
	REALTYPE outvol=sysefx[nefx]->sysefxgetvolume();
	for (i=0;i<SOUND_BUFFER_SIZE;i++) {
	    outl[i]+=tmpmixl[i]*outvol;
	    outr[i]+=tmpmixr[i]*outvol;
	};

    };

      //Mix all parts
      for (npart=0;npart<NUM_MIDI_PARTS;npart++){
            for (i=0;i<SOUND_BUFFER_SIZE;i++) { //the volume did not changed
                  outl[i] += part[npart]->partoutl[i];
                  outr[i] += part[npart]->partoutr[i];
                  }
            }
    
      //Insertion effects for Master Out
      for (nefx=0;nefx<NUM_INS_EFX;nefx++) {
            if (Pinsparts[nefx] == -2) 
                  insefx[nefx]->out(outl, outr);
            }

      //Master Volume
      for (i = 0; i < SOUND_BUFFER_SIZE; i++) {
            outl[i] *= volume;
            outr[i] *= volume;
            }

    //Peak computation (for vumeters)
    vuoutpeakl=1e-12;vuoutpeakr=1e-12;
    for (i=0;i<SOUND_BUFFER_SIZE;i++) {
        if (fabs(outl[i])>vuoutpeakl) vuoutpeakl=fabs(outl[i]);
        if (fabs(outr[i])>vuoutpeakr) vuoutpeakr=fabs(outr[i]);
    };
    if ((vuoutpeakl>1.0)||(vuoutpeakr>1.0)) vuclipped=1;
    if (vumaxoutpeakl<vuoutpeakl) vumaxoutpeakl=vuoutpeakl;
    if (vumaxoutpeakr<vuoutpeakr) vumaxoutpeakr=vuoutpeakr;

    //RMS Peak computation (for vumeters)
    vurmspeakl=1e-12;vurmspeakr=1e-12;
    for (i=0;i<SOUND_BUFFER_SIZE;i++) {
        vurmspeakl+=outl[i]*outl[i];
        vurmspeakr+=outr[i]*outr[i];
    };
    vurmspeakl=sqrt(vurmspeakl/SOUND_BUFFER_SIZE);
    vurmspeakr=sqrt(vurmspeakr/SOUND_BUFFER_SIZE);
    
    //Part Peak computation (for Part vumeters or fake part vumeters)
    for (npart=0;npart<NUM_MIDI_PARTS;npart++){
	vuoutpeakpart[npart]=1.0e-12;
	if (part[npart]->Penabled!=0) {
    	    REALTYPE *outl=part[npart]->partoutl,
		     *outr=part[npart]->partoutr;
	    for (i=0;i<SOUND_BUFFER_SIZE;i++) {
		REALTYPE tmp=fabs(outl[i]+outr[i]);
    		if (tmp>vuoutpeakpart[npart]) vuoutpeakpart[npart]=tmp;
	    };
	    vuoutpeakpart[npart]*=volume;
	} else {
	    if (fakepeakpart[npart]>1) fakepeakpart[npart]--;
	};
    };


      //Shutup if it is asked (with fade-out)
      if (shutup != 0) {
            for (i = 0; i < SOUND_BUFFER_SIZE; i++) {
                  REALTYPE tmp=(SOUND_BUFFER_SIZE-i)/(REALTYPE) SOUND_BUFFER_SIZE;
                  outl[i] *= tmp;
                  outr[i] *= tmp;
                  }
            ShutUp();
            }

      //update the LFO's time
      LFOParams::time++;

      if (HDDRecorder.recording()) 
            HDDRecorder.recordbuffer(outl,outr);
      dump.inctick();
      };

//---------------------------------------------------------
//    GetAudioOutSamples
//---------------------------------------------------------

void Master::GetAudioOutSamples(int nsamples, REALTYPE* outl, REALTYPE* outr)
      {
      int dstOffset = 0;
      while (nsamples) {
            if (ksoundbuffersamples <= 0) {
                  AudioOut(audiooutl, audiooutr);
                  ksoundbuffersamples = SOUND_BUFFER_SIZE;
                  }
            int n  = nsamples > ksoundbuffersamples ? ksoundbuffersamples : nsamples;
            int srcOffset = SOUND_BUFFER_SIZE - ksoundbuffersamples;
            memcpy(outl + dstOffset, audiooutl + srcOffset, n * sizeof(REALTYPE));
            memcpy(outr + dstOffset, audiooutr + srcOffset, n * sizeof(REALTYPE));
            nsamples  -= n;
            dstOffset += n;
            ksoundbuffersamples -= n;
            }
      }

Master::~Master(){
    for (int npart=0;npart<NUM_MIDI_PARTS;npart++) delete (part[npart]);
    for (int nefx=0;nefx<NUM_INS_EFX;nefx++) delete (insefx[nefx]);
    for (int nefx=0;nefx<NUM_SYS_EFX;nefx++) delete (sysefx[nefx]);
    
    delete [] audiooutl;
    delete [] audiooutr;
    delete [] tmpmixl;
    delete [] tmpmixr;
    delete (fft);

};


/*
 * Parameter control
 */
void Master::setPvolume(char Pvolume_){
    Pvolume=Pvolume_;
    volume=dB2rap((Pvolume-96.0)/96.0*40.0);
};

void Master::setPkeyshift(char Pkeyshift_){
    Pkeyshift=Pkeyshift_;
    keyshift=(int)Pkeyshift-64;
};


void Master::setPsysefxvol(int Ppart,int Pefx,char Pvol){
    Psysefxvol[Pefx][Ppart]=Pvol;
    sysefxvol[Pefx][Ppart]=pow(0.1,(1.0-Pvol/96.0)*2.0);
};

void Master::setPsysefxsend(int Pefxfrom,int Pefxto,char Pvol){
    Psysefxsend[Pefxfrom][Pefxto]=Pvol;
    sysefxsend[Pefxfrom][Pefxto]=pow(0.1,(1.0-Pvol/96.0)*2.0);
};


/*
 * Panic! (Clean up all parts and effects)
 */
void Master::ShutUp(){
    for (int npart=0;npart<NUM_MIDI_PARTS;npart++) {
	part[npart]->cleanup();
	fakepeakpart[npart]=0;    
    };
    for (int nefx=0;nefx<NUM_INS_EFX;nefx++) insefx[nefx]->cleanup();
    for (int nefx=0;nefx<NUM_SYS_EFX;nefx++) sysefx[nefx]->cleanup();
    vuresetpeaks();
    shutup=0;
};


/*
 * Reset peaks and clear the "cliped" flag (for VU-meter)
 */
void Master::vuresetpeaks(){
    vuoutpeakl=1e-9;vuoutpeakr=1e-9;vumaxoutpeakl=1e-9;vumaxoutpeakr=1e-9;
    vuclipped=0;
};



void Master::applyparameters(){
    for (int npart=0;npart<NUM_MIDI_PARTS;npart++){
	part[npart]->applyparameters();
    };
};

void Master::add2XML(XMLwrapper *xml){
    xml->addpar("volume",Pvolume);
    xml->addpar("key_shift",Pkeyshift);
    xml->addparbool("nrpn_receive",ctl.NRPN.receive);

    xml->beginbranch("MICROTONAL");
	microtonal.add2XML(xml);
    xml->endbranch();

    for (int npart=0;npart<NUM_MIDI_PARTS;npart++){
	xml->beginbranch("PART",npart);
	part[npart]->add2XML(xml);
	xml->endbranch();
    };
    
    xml->beginbranch("SYSTEM_EFFECTS");
	for (int nefx=0;nefx<NUM_SYS_EFX;nefx++){
	    xml->beginbranch("SYSTEM_EFFECT",nefx);
		xml->beginbranch("EFFECT");
		    sysefx[nefx]->add2XML(xml);
		xml->endbranch();

		for (int pefx=0;pefx<NUM_MIDI_PARTS;pefx++){
		    xml->beginbranch("VOLUME",pefx);
			xml->addpar("vol",Psysefxvol[nefx][pefx]);
		    xml->endbranch();    
		};

		for (int tonefx=nefx+1;tonefx<NUM_SYS_EFX;tonefx++){
		    xml->beginbranch("SENDTO",tonefx);
			xml->addpar("send_vol",Psysefxsend[nefx][tonefx]);
		    xml->endbranch();    
		};
		
		
	    xml->endbranch();
	};
    xml->endbranch();

    xml->beginbranch("INSERTION_EFFECTS");
	for (int nefx=0;nefx<NUM_INS_EFX;nefx++){
	    xml->beginbranch("INSERTION_EFFECT",nefx);
		xml->addpar("part",Pinsparts[nefx]);

		xml->beginbranch("EFFECT");
		    insefx[nefx]->add2XML(xml);
		xml->endbranch();
	    xml->endbranch();
	};
	
    xml->endbranch();
    
};


int Master::getalldata(char **data){
    XMLwrapper *xml=new XMLwrapper();

    xml->beginbranch("MASTER");

    busy = true;
    add2XML(xml);
    busy = false;

    xml->endbranch();

    *data=xml->getXMLdata();
    delete (xml);
    return(strlen(*data)+1);
};

void Master::putalldata(char *data,int size){
    XMLwrapper *xml=new XMLwrapper();
    if (!xml->putXMLdata(data)) {
	delete(xml);
	return;
    };
    
    if (xml->enterbranch("MASTER")==0) return;

    busy = true;
	getfromXML(xml);
    busy = false;

    xml->exitbranch();
    
    delete(xml);
};

int Master::saveXML(char *filename){
    XMLwrapper *xml=new XMLwrapper();

    xml->beginbranch("MASTER");
    add2XML(xml);
    xml->endbranch();

    int result=xml->saveXMLfile(filename);
    delete (xml);
    return(result);
};



int Master::loadXML(char *filename){
    XMLwrapper *xml=new XMLwrapper();
    if (xml->loadXMLfile(filename)<0) {
	delete(xml);
	return(-1);
    };
    
    if (xml->enterbranch("MASTER")==0) return(-10);
	getfromXML(xml);
    xml->exitbranch();
    
    delete(xml);
    return(0);
};

void Master::getfromXML(XMLwrapper *xml){
    setPvolume(xml->getpar127("volume",Pvolume));
    setPkeyshift(xml->getpar127("key_shift",Pkeyshift));
    ctl.NRPN.receive=xml->getparbool("nrpn_receive",ctl.NRPN.receive);
	
    
    part[0]->Penabled=0;
    for (int npart=0;npart<NUM_MIDI_PARTS;npart++){
	if (xml->enterbranch("PART",npart)==0) continue;
    	part[npart]->getfromXML(xml);
	xml->exitbranch();
    };

    if (xml->enterbranch("MICROTONAL")){
        microtonal.getfromXML(xml);
        xml->exitbranch();
    };
	
    sysefx[0]->changeeffect(0);
    if (xml->enterbranch("SYSTEM_EFFECTS")){
	for (int nefx=0;nefx<NUM_SYS_EFX;nefx++){
	    if (xml->enterbranch("SYSTEM_EFFECT",nefx)==0) continue;
		if (xml->enterbranch("EFFECT")){
		    sysefx[nefx]->getfromXML(xml);
		    xml->exitbranch();
		};

		for (int partefx=0;partefx<NUM_MIDI_PARTS;partefx++){
		    if (xml->enterbranch("VOLUME",partefx)==0) continue;
		    setPsysefxvol(partefx,nefx,xml->getpar127("vol",Psysefxvol[partefx][nefx]));
		    xml->exitbranch();    
		};

		for (int tonefx=nefx+1;tonefx<NUM_SYS_EFX;tonefx++){
		    if (xml->enterbranch("SENDTO",tonefx)==0) continue;
		    setPsysefxsend(nefx,tonefx,xml->getpar127("send_vol",Psysefxsend[nefx][tonefx]));
		    xml->exitbranch();    
		};
	    xml->exitbranch();
	};
	xml->exitbranch();
    };


    if (xml->enterbranch("INSERTION_EFFECTS")){
	for (int nefx=0;nefx<NUM_INS_EFX;nefx++){
	
	    if (xml->enterbranch("INSERTION_EFFECT",nefx)==0) continue;
		Pinsparts[nefx]=xml->getpar("part",Pinsparts[nefx],-2,NUM_MIDI_PARTS);
		if (xml->enterbranch("EFFECT")){
		    insefx[nefx]->getfromXML(xml);
		    xml->exitbranch();
		};
	    xml->exitbranch();

	};
	
	xml->exitbranch();
    };
	
};