//=============================================================================
//  MusE
//  Linux Music Editor
//  $Id:$
//
//  Copyright (C) 2002-2006 by Werner Schweer and others
//
//  ZynAddSubFX - a software synthesizer
//  Copyright (C) 2002-2005 Nasca Octavian Paul
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2.
//
//  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., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#include "Misc/Master.h"
#include "Misc/Util.h"
#include "MasterUI.h"

//=========================================================
//    MESS interface
//=========================================================

#include "synti/libsynti/mess.h"
int instances = -1;

//---------------------------------------------------------
//   Zynadd
//---------------------------------------------------------

class Zynadd : public Mess, public Master 
      {
      virtual void process(float** buffer, int offset, int n);
      virtual bool processEvent(const MidiEvent&);
      virtual void getInitData(int*, const unsigned char**);
      virtual int getControllerInfo(int, const char**, int*, int*, int*);
      virtual const char* getPatchName(int, int, int) const;
      virtual const MidiPatch* getPatchInfo(int, const MidiPatch*) const;
      virtual bool hasGui() const { return true; }
      virtual bool guiVisible() const { return _guiVisible; }
      virtual void showGui(bool val);

      mutable MidiPatch patch;
      mutable int currentBank;

      bool _guiVisible;
      bool loadBank(int);
      char* messPatch[MAX_NUM_BANKS][128];

   public:
      int Pexitprogram;
	MasterUI* ui;
	pthread_t thr;

      Zynadd();
      ~Zynadd();

      enum {
            GUI_NO_CMD, GUI_REFRESH, GUI_HIDE, GUI_SHOW
            };
      int guiCmd;
      };

//---------------------------------------------------------
//    guiThread
//---------------------------------------------------------

void* guiThread(void *arg)
      {
      Zynadd* z = (Zynadd *) arg;
      z->ui = new MasterUI(z, &z->Pexitprogram);
      z->ui->showUI();
      while (z->Pexitprogram == 0) {
            switch(z->guiCmd) {
                  case Zynadd::GUI_REFRESH:
                        z->ui->refresh_master_ui();
                        break;
                  case Zynadd::GUI_HIDE:
                        switch (config.cfg.UserInterfaceMode) {
	                        case 0:   
                                    z->ui->selectuiwindow->hide();
	                              break;
	                        case 1:
                                    z->ui->masterwindow->hide();
	                              break;
	                        case 2:
                                    z->ui->simplemasterwindow->hide();
	                              break;
                              }
                        break;
                  case Zynadd::GUI_SHOW:
                        z->ui->showUI();
                        break;
                  }
            z->guiCmd = Zynadd::GUI_NO_CMD;
            Fl::wait(0.01);
            }
      delete(z->ui);
      Fl::wait(0.01);
      pthread_exit(0);
      return 0;
      }

//---------------------------------------------------------
//   Zynadd
//---------------------------------------------------------

Zynadd::Zynadd() : Mess(2), Master()
      {
      instances++;
      swaplr        = config.cfg.SwapStereo;
      Pexitprogram  = 0;
      currentBank   = -1;
      guiCmd        = 0;

      swaplr = 0;   //1 for left-right swapping
      memset(messPatch, 0, sizeof(messPatch));
      bank.rescanforbanks();
      defaults();

      for (int i = 1; i < MAX_NUM_BANKS; ++i) {
            if (bank.banks[i].dir == 0)
                  break;
            loadBank(i);
            for (unsigned int k = 0; k < 128; ++k) {
            	if (!bank.emptyslot(k)) {
                        messPatch[i][k] = strdup(bank.getname(k));
                        }
                  }
            }

      pthread_create(&thr, NULL, guiThread, this);
      _guiVisible = true;
      }

//---------------------------------------------------------
//   ~Zynadd
//---------------------------------------------------------

Zynadd::~Zynadd()
      {
      Pexitprogram = 1;
      sleep(2);               //wait the thread to finish
      instances--;
      }

//---------------------------------------------------------
//    showGui
//---------------------------------------------------------

void Zynadd::showGui(bool val)
      {
      if (val != _guiVisible)
            guiCmd = val ? GUI_SHOW : GUI_HIDE;
      _guiVisible = val;
      }

//---------------------------------------------------------
//    loadBank
//---------------------------------------------------------

bool Zynadd::loadBank(int n)
      {
      if (n != currentBank) {
            if (bank.banks[n].dir == 0) {
                  printf("Zynaddsubfx: empty bank %d\n", n);
                        return false;
                  }
            bank.loadbank(bank.banks[n].dir);
            currentBank = n;
            }
      return true;
      }

//---------------------------------------------------------
//   getControllerInfo
//---------------------------------------------------------

struct ZynCtrl {
      const char* name;
      int num;
      int min;
      int max;
      };

//
//  NRPN
//    0x04 0     // System Effects
//    0x04 1
//    0x04 2
//    0x04 3
//
//    0x08 0     // Insertion Effects
//    0x08 1
//    0x08 2
//    0x08 3
//    0x08 4
//    0x08 5
//    0x08 6
//    0x08 7
//    0x08 8

int Zynadd::getControllerInfo(int i, const char** name, int* num, int* min, int* max)
      {
      static const ZynCtrl ctrl[] = {
            { "Pitch",          0x40000, -8191, 8190 },
            { "ProgramChange",  0x40001,  0, 0xffffff },
            { "Modulation",           1,  0, 127     },
            { "MainVolume",           7,  0, 127     },
            { "Pan",                 10,  0, 127     },
            { "Expression",          11,  0, 127     },
            { "Sustain",             64,  0, 127     },
            { "Portamento",          65,  0, 127     },
            { "FilterQ",	       71,  0, 127     },
            { "FilterCutoff",	       74,  0, 127     },
            { "Bandwidth",           75,  0, 127     },
            { "ModulationAmp",       76,  0, 127     },
            { "ResonanceCenter",     77,  0, 127     },
            { "ResonanceBandwidth",  78,  0, 127     },
            { "AllSoundsOff",       120,  0, 127     },
            { "ResetAllController", 121,  0, 127     },
            { "AllNotesOff",        123,  0, 127     },
            };
      if ((unsigned)i >= sizeof(ctrl)/sizeof(*ctrl))
            return 0;
      *name = ctrl[i].name;
      *num  = ctrl[i].num;
      *min  = ctrl[i].min;
      *max  = ctrl[i].max;
      return i+1;;
      }

//---------------------------------------------------------
//   getPatchName
//---------------------------------------------------------

const char* Zynadd::getPatchName(int, int val, int) const
      {     
      int bankNo = (val >> 8) + 1;
      int program = val & 0x7f;
      return messPatch[bankNo][program];
      }

//---------------------------------------------------------
//   getPatchInfo
//---------------------------------------------------------

const MidiPatch* Zynadd::getPatchInfo(int, const MidiPatch* p) const {
  if(p) {
    patch.hbank = p->hbank;
    patch.lbank = p->lbank;
    patch.prog = p->prog;
    int bn = ((patch.hbank << 7) + patch.lbank); //7 because lbank is signed
    switch(p->typ) {
    case MP_TYPE_LBANK :
      patch.typ = 0;
      patch.name  = messPatch[bn + 1][patch.prog];
      if(patch.name) return &patch;
      else return getPatchInfo(0, &patch);
     break;
    default :
      if(patch.prog + 1 < 128) {
	patch.prog++;
	patch.name = messPatch[bn + 1][patch.prog];
	if(patch.name) return &patch;
	else return getPatchInfo(0, &patch);
      }
      else {
	patch.prog = 0;
	if(bn + 1 < MAX_NUM_BANKS - 1) {
	  bn++;
	  patch.name = bank.banks[bn + 1].name;
	  patch.hbank = bn / 128;
	  patch.lbank = bn % 128;
	  patch.typ = MP_TYPE_LBANK;
	  if(patch.name) return &patch;
	  else return getPatchInfo(0, &patch);
	}
	else return NULL;
      }
      break;
    }
  } 
  else {
    patch.typ = MP_TYPE_LBANK;
    patch.hbank = 0;
    patch.lbank = 0;
    patch.prog = 0;
    patch.name = bank.banks[(patch.hbank << 7) + patch.lbank + 1].name;
    if(patch.name) return &patch;
    else {
      patch.typ = 0;
      patch.prog = 127; //hack to go faster
      return getPatchInfo(0, &patch);
    }
  }
}

//---------------------------------------------------------
//    getInitData
//---------------------------------------------------------

void Zynadd::getInitData(int* n, const unsigned char** data)
      {
      *n = getalldata((char **)data);
      }

//---------------------------------------------------------
//   process
//    synthesize n samples into buffer+offset
//---------------------------------------------------------

void Zynadd::process(float** outputs, int offset, int n)
      {
      float* outl = outputs[0] + offset;
      float* outr = outputs[1] + offset;
      if (busy) {
            memset(outl, 0, sizeof(float) * n);
            memset(outr, 0, sizeof(float) * n);
            return;
            }
      GetAudioOutSamples(n, outl, outr);
      }

//---------------------------------------------------------
//    processEvent
//---------------------------------------------------------

bool Zynadd::processEvent(const MidiEvent& e)
      {
      if (busy)
            return true;
      int ch = e.channel();
      switch(e.type()) {
            case 0x80:  // note off
	            NoteOff(ch, e.dataA());
                  break;
            case 0x90:  // note on
                  NoteOn(ch, e.dataA(), e.dataB());
		      break;
	      case 0xb0:  // controller
                  switch(e.dataA()) {
                        case 0x40000:     // pitch
	                        SetController(ch, C_pitchwheel, e.dataB());
                              break;
                        case 0x40001:     // program change
                              {
                              int bankNo = (e.dataB() >> 8) + 1;
                              if (!loadBank(bankNo))
                                    return false;
                              int program = e.dataB() & 0x7f;
                              if (bank.emptyslot(program)) {
                                    printf("Zynaddsubfx: programslot %d is empty!\n", program);
                                    break;
                                    }
                              for (int npart = 0; npart < NUM_MIDI_PARTS; npart++) {
                                    Part* p = part[npart];
	                              if ((ch == p->Prcvchn) && (p->Penabled != 0))
	                                    bank.loadfromslot(program, p);
                                    }
                              guiCmd = GUI_REFRESH;
                              }
                              break;
                        default:
                              {
                              int ctl;
                              switch (e.dataA()) {
	                              case 1:    ctl = C_modwheel; break;
                           	      case 7:    ctl = C_volume; break;
                           	      case 10:   ctl = C_panning; break;
                           	      case 11:   ctl = C_expression; break;
                           	      case 64:   ctl = C_sustain; break;
                           	      case 65:   ctl = C_portamento; break;
                           	      case 71:   ctl = C_filterq; break;
                           	      case 74:   ctl = C_filtercutoff; break;
                           	      case 75:   ctl = C_bandwidth; break;
                           	      case 76:   ctl = C_fmamp; break;
                           	      case 77:   ctl = C_resonance_center; break;
                           	      case 78:   ctl = C_resonance_bandwidth; break;
                           	      case 120:  ctl = C_allsoundsoff; break;
                           	      case 121:  ctl = C_resetallcontrollers; break;
                           	      case 123:  ctl = C_allnotesoff; break;
                           	      case 0x06: ctl = C_dataentryhi; break;
                           	      case 0x26: ctl = C_dataentrylo; break;
                           	      case 99:   ctl = C_nrpnhi; break;
                           	      case 98:   ctl = C_nrpnlo; break;
                           	      default:   ctl = C_NULL; break;
                           	      }
	      	            SetController(ch, ctl, e.dataB());
                              guiCmd = GUI_REFRESH;  // does not work
                              }
                              break;
                        }
                  break;

            case 0xf0:
                  putalldata((char*)e.data(), e.len());
                  break;
            }
      return false;
      }

//---------------------------------------------------------
//   instantiate
//---------------------------------------------------------

static Mess* instantiate(int sr, const char*)
      {
      if (instances == -1) {
            config.init();
	      instances = 0;
            srand(time(0));
            // SOUND_BUFFER_SIZE restricts midi resolution
            SOUND_BUFFER_SIZE = 64;
            OSCIL_SIZE        = 256;      // config.cfg.OscilSize;
            SAMPLE_RATE       = sr;
            denormalkillbuf = new REALTYPE [SOUND_BUFFER_SIZE];
            for (int i = 0; i < SOUND_BUFFER_SIZE; i++) 
                  denormalkillbuf[i] = (RND - 0.5) * 1e-16;
    
            OscilGen::tmpsmps = new REALTYPE[OSCIL_SIZE];
                  newFFTFREQS(&OscilGen::outoscilFFTfreqs,OSCIL_SIZE/2);
            }
      if (instances != 0) 
            return 0;   //don't allow multiple instances

      Zynadd* sintetizator = new Zynadd();
      sintetizator->setSampleRate(sr);
      return sintetizator;
      }

extern "C" {
      static MESS descriptor = {
            "Zynaddsubfx",
            "Zynaddsubfx Software Synthesizer",
            "0.1",      // version string
            MESS_MAJOR_VERSION, MESS_MINOR_VERSION,
            instantiate
            };

      // We must compile with -fvisibility=hidden to avoid namespace
      // conflicts with global variables.
      // Only visible symbol is "mess_descriptor".
      // (TODO: all plugins should be compiled this way)

      __attribute__ ((visibility("default")))
         const MESS* mess_descriptor() { return &descriptor; }
      }