/*
 * MusE FLUID Synth softsynth plugin
 *
 * Copyright (C) 2004 Mathias Lundgren (lunar_shuttle@users.sourcforge.net)
 *
 * $Id: fluidsynti.cpp,v 1.19.2.1 2005/01/04 21:56:15 spamatica Exp $
 *
 */

#include "fluidsynti.h"
#include "muse/midi.h"

#include <list>
#include <iostream>


FluidCtrl FluidSynth::fluidCtrl[] = {
      { "Gain", FS_GAIN ,0, 127},
      { "Master reverb on/off", FS_REVERB_ON , 0, 1},
      { "Master reverb level", FS_REVERB_LEVEL, 0, 16384 },
      { "Master reverb size", FS_REVERB_ROOMSIZE, 0, 16384 }, // Interval: [0,1]
      { "Master reverb damping", FS_REVERB_DAMPING, 0, 16384 }, // Interval: [0,1]
      { "Master reverb width", FS_REVERB_WIDTH, 0, 16384 }, // Interval: [0,100]
      { "Master chorus on/off", FS_CHORUS_ON, 0, 1},
      { "Master chorus num delay lines", FS_CHORUS_NUM, 0, 10 }, //Default: 3
      { "Master chorus type",  FS_CHORUS_TYPE, 0, 1 },
      { "Master chorus speed", FS_CHORUS_SPEED, 0, 16384 }, // (0.291,5) Hz
      { "Master chorus depth", FS_CHORUS_DEPTH, 0, 16384 }, // [0,40]
      { "Master chorus level", FS_CHORUS_LEVEL, 0, 16384 }, // [0,1]
      { "Modulation", CTRL_MODULATION, 0, 127 },
      { "Portamento time", CTRL_PORTAMENTO_TIME, 0, 127 },
      { "Volume", CTRL_VOLUME, 0, 127 },
      { "Pan", CTRL_PANPOT, 0, 127 },
      { "Expression", CTRL_EXPRESSION, 0, 127 },
      { "Sustain", CTRL_SUSTAIN, 0, 127 },
      { "Portamento", CTRL_PORTAMENTO, 0, 127 },
      { "Soft Pedal", CTRL_SOFT_PEDAL, 0, 127 },
      { "Variation", CTRL_VARIATION_SEND, 0, 127 },
      { "Channel reverb send", CTRL_REVERB_SEND, 0, 127 },
      { "Channel chorus send", CTRL_CHORUS_SEND, 0, 127 }
    };

static int NUM_CONTROLLER = sizeof(FluidSynth::fluidCtrl)/sizeof(*(FluidSynth::fluidCtrl));

//
// Fluidsynth
//
FluidSynth::FluidSynth(int sr, pthread_mutex_t *_Globalsfloader_mutex) : Mess(2)
      {
      setSampleRate(sr);
      fluid_settings_t* s = new_fluid_settings();
      fluid_settings_setnum(s, "synth.sample-rate", float(sampleRate()));
      fluidsynth = new_fluid_synth(s);
      if (!fluidsynth) {
            printf("Error while creating fluidsynth!\n");
            return;
            }

      //Set up channels:
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            //channels[i].font       = 0;
            channels[i].font_extid = FS_UNSPECIFIED_ID;
            channels[i].font_intid = FS_UNSPECIFIED_ID;
            channels[i].preset     = FS_UNSPECIFIED_PRESET;
            channels[i].drumchannel= false;
      }
      //pthread_mutex_init(&_sfloader_mutex,NULL);
      _sfloader_mutex = _Globalsfloader_mutex;

/*
      buffer = 0;
      bufferlen = 0;
      */
      }

FluidSynth::~FluidSynth()
      {
      int err = delete_fluid_synth (fluidsynth);
      delete gui;

/*      if (buffer)
               delete [] buffer;*/
      if (err == -1) {
            std::cerr << DEBUG_ARGS << "error while destroying synth: " << fluid_synth_error(fluidsynth) << std::endl;
            return;
            }
      //Destroy the mutex
/*      if (pthread_mutex_destroy(&_sfloader_mutex) != 0)
            std::cerr << DEBUG_ARGS << "Strange, mutex busy! Should not be!" << std::endl;*/
            
      }

bool FluidSynth::init(const char* name)
      {
      debug("FluidSynth::init\n");

      gui = new FluidSynthGui();
      gui->show();
      gui->setCaption(name);

      lastdir= "";
      currentlyLoadedFonts = 0;
      nrOfSoundfonts = 0;
      sendChannelData();
      cho_on = false;
      cho_num = FS_PREDEF_CHORUS_NUM;
      cho_type = FS_PREDEF_CHORUS_TYPE;
      cho_level = FS_PREDEF_CHORUS_LEVEL;
      cho_speed = FS_PREDEF_CHORUS_SPEED;
      cho_depth = FS_PREDEF_CHORUS_DEPTH;
      setController(0, FS_GAIN, (int)(fluidCtrl[0].max*FS_PREDEF_VOLUME));
      setController(0, FS_REVERB_ON, 0);
      setController(0, FS_REVERB_LEVEL, (int)(fluidCtrl[2].max*FS_PREDEF_REVERB_LEVEL));
      setController(0, FS_REVERB_ROOMSIZE, (int)(fluidCtrl[3].max*FS_PREDEF_REVERB_ROOMSIZE));
      setController(0, FS_REVERB_DAMPING, (int)(fluidCtrl[4].max*FS_PREDEF_REVERB_DAMPING));
      setController(0, FS_REVERB_WIDTH, (int)(fluidCtrl[5].max*FS_PREDEF_REVERB_WIDTH));
      setController(0, FS_CHORUS_ON, 0);
      setController(0, FS_CHORUS_NUM, FS_PREDEF_CHORUS_NUM);
      //setController(0, FS_CHORUS_TYPE, FS_PREDEF_CHORUS_TYPE); //?
      setController(0, FS_CHORUS_SPEED, (int)(fluidCtrl[9].max*FS_PREDEF_CHORUS_SPEED));
      setController(0, FS_CHORUS_DEPTH, (int)(fluidCtrl[10].max*FS_PREDEF_CHORUS_DEPTH));
      setController(0, FS_CHORUS_LEVEL, (int)(fluidCtrl[11].max*FS_PREDEF_CHORUS_LEVEL));
      return false;
      }

//---------------------------------------------------------
//   process
//    called from host
//---------------------------------------------------------

void FluidSynth::process(float** ports, int offset, int len)
      {
      //Process messages from the gui
      while (gui->fifoSize()) {
            MidiPlayEvent ev = gui->readEvent();
            if (ev.type() == ME_SYSEX) {
                  sysex(ev.len(), ev.data());
                  sendEvent(ev);
                  }
            else if (ev.type() == ME_CONTROLLER) {
                  setController(ev.channel(), ev.dataA(), ev.dataB(), true);
                  sendEvent(ev);
                  }
            else {
                  if (FS_DEBUG)
                        printf("FluidSynth::process(): unknown event, type: %d\n", ev.type());
                  }
            }

      if (fluid_synth_write_float(fluidsynth, len, ports[0], offset, 1, ports[1], offset, 1)) {
            M_ERROR("Error writing from synth!");
            return;
            }
      }

//---------------------------------------------------------
//   getInitData
// Prepare data that will restore the synth's state on load
//---------------------------------------------------------
void FluidSynth::getInitData(int* n, const unsigned char** data) const
      {
      // Data setup:
      // FS_INIT_DATA (1 byte)
      // FluidSynth version (2 bytes, x.y)
      // n = Number of soundfonts (1 byte)
      // Lastdir (variable size)
      //
      // FS_FONTS_BEGIN
      // n blocks with font path (variable size)
      // n bytes with font external id
      //
      // for all channels (16), 1 byte each for external id + 1 byte for preset
      // which is mapped to internal id after all fonts are loaded.
      //
      // reverb + chorus on/off (2 bytes)
      if (FS_DEBUG)
            printf("FluidSynth::getInitData()\n");

      //Calculate length:
      int len = FS_INIT_DATA_HEADER_SIZE + strlen(lastdir.c_str()) + 1; //header size
      for (std::list<FluidSoundFont>::const_iterator it = stack.begin(); it!=stack.end(); it++) {
            len+=strlen(it->filename.c_str()) + 2;
            }
      //Add length for lastdir and channels:
      len+=strlen(lastdir.c_str())+1;
      len+=(FS_MAX_NR_OF_CHANNELS*3); //3 bytes: ext+int id + drumchannel status
      // + reverb
      len+=2;

      if (FS_DEBUG)
            printf("Total length of init sysex: %d\n", len);
      byte* d = new byte[len];

      // Header:
      d[0] = FS_INIT_DATA;
      d[1] = FS_VERSION_MAJOR;
      d[2] = FS_VERSION_MINOR;
      d[3] = stack.size();

      //Lastdir:
      byte* chptr = d + FS_INIT_DATA_HEADER_SIZE;
      memcpy(chptr, lastdir.c_str(), strlen(lastdir.c_str())+1);

      //For each font...
      chptr+=strlen(lastdir.c_str())+1;
      for (std::list<FluidSoundFont>::const_iterator it =stack.begin(); it!=stack.end(); it++) {
            memcpy(chptr, it->filename.c_str(), strlen(it->filename.c_str())+1);
            chptr = chptr + 1 + strlen(it->filename.c_str());
            }

      //For each font again...
      *chptr = FS_INIT_CHANNEL_SECTION;
      chptr++;
      for (std::list<FluidSoundFont>::const_iterator it =stack.begin(); it!=stack.end(); it++) {
            *chptr = it->extid;
            chptr++;
            }

      //External id:s & preset for all channels:
      for(int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            *chptr = channels[i].font_extid; chptr++;
            *chptr = channels[i].preset; chptr++;
            *chptr = channels[i].drumchannel; chptr++;
            }

      //Reverb:
      *chptr = rev_on; chptr++;
      *chptr = cho_on; chptr++;
      if (FS_DEBUG) {
            for (int i=0; i<len; i++)
                  printf("%c ", d[i]);
            printf("\n");
            for (int i=0; i<len; i++)
                  printf("%x ", d[i]);
            printf("\n");
            }
      // Give values to host:
      *data = d;
      *n = len;
      }

//-----------------------------------
// parseInitData
//-----------------------------------
void FluidSynth::parseInitData(int n, const byte* d)
      {
      bool load_drumchannels = true;

      if (FS_DEBUG) {
            printf("--- PARSING INIT DATA ---\n");
            for (int i=0; i<n; i++)
                  printf("%c ", d[i]);
            printf("\n");
            }

      byte version_major, version_minor;
      version_major = d[1]; version_minor = d[2];
      if (!(version_major == FS_VERSION_MAJOR && version_minor == FS_VERSION_MINOR)) {
            if (FS_DEBUG) {
                  printf("Project saved with other version of fluidsynth format. Ver: %d.%d\n", version_major, version_minor);
                  }

            if (version_major == 0 && version_minor <= 2) {
                  load_drumchannels = false;
                  }
            else {
               sendError("Initialization data created with different version of FluidSynth Mess, will be ignored.");
               return;
               }
            }

      byte nr_of_fonts = d[3];
      nrOfSoundfonts = nr_of_fonts; //"Global" counter
      const byte* chptr = (d + 4);

      //Get lastdir:
      lastdir = std::string((char*)chptr);
      sendLastdir(lastdir.c_str());

      chptr+=strlen(lastdir.c_str())+1;

      FluidSoundFont fonts[nrOfSoundfonts]; //Just a temp one
      //Fonts:
      for (int i=0; i<nr_of_fonts; i++) {
            fonts[i].filename = (char*)(chptr);
            chptr+=(strlen(fonts[i].filename.c_str())+1);
            }

      if (*chptr != FS_INIT_CHANNEL_SECTION) {
            sendError("Init-data corrupt... Projectfile error. Initdata ignored.\n");
            return;
            }

      chptr++;
      for (int i=0; i<nr_of_fonts; i++) {
            fonts[i].extid = *chptr;
            chptr++;
            //printf("Extid, %d: %d\n",i,fonts[i].extid);
            }

      // All channels external id + preset
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            channels[i].font_extid = *chptr; chptr++;
            channels[i].preset     = *chptr; chptr++;

            if (load_drumchannels) {
                  channels[i].drumchannel = *chptr;
                  chptr++;
                  }
            }

      //Reverb:
      setController(0, FS_REVERB_ON, *chptr); chptr++;
      setController(0, FS_CHORUS_ON, *chptr); chptr++;

      if (FS_DEBUG)
            printf("--- END PARSE INIT DATA ---\n");
      //Load the shit:
      for (int i=0; i<nrOfSoundfonts; i++) {
            pushSoundfont(fonts[i].filename.c_str(), fonts[i].extid);
            }
      }


//---------------------------------------------------------
//   processEvent
//    All events from the sequencer goes here
//---------------------------------------------------------

bool FluidSynth::processEvent(const MidiPlayEvent& ev)
      {
      switch(ev.type()) {
            case ME_CONTROLLER:
                  if (FS_DEBUG_DATA) {
                        printf("*** FluidSynth::process - Controller. Chan: %x dataA: %x dataB: %x\n", ev.channel(), ev.dataA(), ev.dataB());
                        for (int i=0; i< ev.len(); i++)
                              printf("%x ", ev.data()[i]);
                        }
                  setController(ev.channel(), ev.dataA(), ev.dataB(), false);
                  return true;
            case ME_NOTEON:
                  return playNote(ev.channel(), ev.dataA(), ev.dataB());
            case ME_NOTEOFF:
                  return playNote(ev.channel(), ev.dataA(), 0);
            case ME_SYSEX:
                  //Debug print
                  if (FS_DEBUG_DATA) {
                        printf("*** FluidSynth::process - Sysex received\n");
                        for (int i=0; i< ev.len(); i++)
                              printf("%x ", ev.data()[i]);
                        printf("\n");
                        }
                  return sysex(ev.len(), ev.data());
            }
      return false;
      }

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

bool FluidSynth::sysex(int n, const unsigned char* d)
      {
      switch(*d) {
            case FS_LASTDIR_CHANGE: {
                  lastdir = std::string((char*)(d+1));
                  sendLastdir(lastdir.c_str());
                  break;
                  }
            case FS_PUSH_FONT: {
                  int extid = d[1];

                  if (FS_DEBUG)
                        printf("Client: Got push font %s, id: %d\n",(d+1), extid);

                  const char* filename = (const char*)(d+2);
                  if (!pushSoundfont(filename, extid))
                              sendError("Could not load soundfont ");
                  break;
                  }
            case FS_DUMP_INFO: {
                  dumpInfo();
                  break;
                  }
            case FS_SOUNDFONT_CHANNEL_SET: {
                  sfChannelChange(*(d+1), *(d+2));
                  break;
                  }
            case FS_INIT_DATA: {
                  parseInitData(n,d);
                  break;
                  }
            case FS_SOUNDFONT_POP:
                  popSoundfont(*(d+1));
                  break;
            case FS_DRUMCHANNEL_SET: {
                  byte onoff = (*(d+1));
                  byte channel = (*(d+2));
                  channels[channel].drumchannel = onoff;
                  if (FS_DEBUG)
                        printf("Client: Set drumchannel on chan %d to %d\n",channel, onoff);
                  break;
                  }
            default:
                  if (FS_DEBUG)
                        printf("FluidSynth::sysex() : unknown sysex received: %d\n",*d);
                  break;
            }
      return false;
      }

//---------------------------------------------------------
//   sendSysex
//---------------------------------------------------------
void FluidSynth::sendSysex(int l, const unsigned char* d)
      {
      MidiPlayEvent ev(0, 0, ME_SYSEX, d, l);
      gui->writeEvent(ev);
      }

//-----------------------------------
// pushSoundfont - load a soundfont
//-----------------------------------
bool FluidSynth::pushSoundfont (const char* filename, int extid)
      {
      pthread_attr_t* attributes = (pthread_attr_t*) malloc(sizeof(pthread_attr_t));
      pthread_attr_init(attributes);
      pthread_attr_setdetachstate(attributes, PTHREAD_CREATE_DETACHED);

      FS_Helper* helper = new FS_Helper;
      helper->fptr = this;
      helper->filename = filename;
      helper->id = extid;

      if (pthread_create(&fontThread, attributes, ::fontLoadThread, (void*) helper))
            perror("creating thread failed:");

      pthread_attr_destroy(attributes);
      return true;
      }

//---------------------------------------------------------
//   fontLoadThread
//    helper thread to load soundfont in the
//    background
//---------------------------------------------------------

static void* fontLoadThread(void* t)
      {
      //Init vars
      FS_Helper* h = (FS_Helper*) t;
      FluidSynth* fptr = h->fptr;
      const char* filename = h->filename.c_str();
      pthread_mutex_t* sfloader_mutex = (fptr->_sfloader_mutex);

      //Let only one loadThread have access to the fluidsynth-object at the time
      pthread_mutex_lock(sfloader_mutex);
      int rv = fluid_synth_sfload(fptr->fluidsynth, filename, 1);

      if (rv ==-1) {
            fptr->sendError(fluid_synth_error(fptr->fluidsynth));
            if (FS_DEBUG)
                  std::cerr << DEBUG_ARGS << "error loading soundfont: " << fluid_synth_error(fptr->fluidsynth) << std::endl;

            //Unlock the mutex, or else we might be stuck here forever...
            pthread_mutex_unlock(sfloader_mutex);
            delete h;
            pthread_exit(0);
            }

      //Deal with internal and external id etc.
      if (FS_DEBUG)
            printf("Soundfont %s loaded, index %d\n", filename, rv);

      FluidSoundFont font;
      font.filename = h->filename;//strdup(filename);

      font.intid = rv;
      if (h->id == FS_UNSPECIFIED_ID) {
            font.extid = fptr->getNextAvailableExternalId();
            if (FS_DEBUG)
                  printf("Font got extid %d\n",font.extid);
            }
      else
            font.extid = h->id;
      if (FS_DEBUG)
            printf("Font has external id: %d int id:%d\n", font.extid, font.intid);

      //Strip off the filename
      QString temp = QString(filename);
      QString name = temp.right(temp.length() - temp.findRev('/',-1) - 1);
      name = name.left(name.length()-4); //Strip off ".sf2"
      font.name = name.latin1();
      fptr->stack.push_front(font);
      fptr->currentlyLoadedFonts++;

      //Cleanup & unlock:
      pthread_mutex_unlock(sfloader_mutex);
      delete h;

      if (FS_DEBUG)
            printf("Currently loaded fonts: %d Nr of soundfonts: %d\n",fptr->currentlyLoadedFonts, fptr->nrOfSoundfonts);
      //Check whether this was the last font or not. If so, run initSynth();
      if (fptr->nrOfSoundfonts <= fptr->currentlyLoadedFonts) {
            if (FS_DEBUG)
                  printf("This was the last font, rewriting channel settings...\n");
            fptr->rewriteChannelSettings();
            //Update data in GUI-window.
            fptr->sendSoundFontData();;
            fptr->sendChannelData();
            }

      pthread_exit(0);
      }

//---------------------------------------------------------
//   playNote
//    called from host
//---------------------------------------------------------

bool FluidSynth::playNote(int channel, int pitch, int velo)
      {
      if (channels[channel].font_intid == FS_UNSPECIFIED_FONT)
            return false;
      if (velo) {
            if (fluid_synth_noteon(fluidsynth, channel, pitch, velo)) {
                  if (FS_DEBUG)
                        std::cerr << DEBUG_ARGS << "error processing noteon event: " << fluid_synth_error(fluidsynth);
                  }
            }
      else {
            if (fluid_synth_noteoff(fluidsynth, channel, pitch))
                  if (FS_DEBUG)
                        std::cerr << DEBUG_ARGS << "error processing noteoff event: " << fluid_synth_error(fluidsynth) << std::endl;
            }
      return false;
      }

//---------------------------------------------------------
//   sendSoundFontData
//---------------------------------------------------------
void FluidSynth::sendSoundFontData()
      {
      int ndatalen = 2; //2 bytes for command and length

      //Calculate length in chars of all strings in the soundfontstack in one string
      for (std::list<FluidSoundFont>::iterator it = stack.begin(); it != stack.end(); it++) {
            ndatalen += 1 + strlen(it->name.c_str());
            ndatalen += FS_SFDATALEN; //unsigned char for ID
            }
      byte ndata[ndatalen];
      *ndata = FS_SEND_SOUNDFONTDATA; //The command
      *(ndata + 1) = (unsigned char)stack.size (); //Nr of Soundfonts

      // Copy the stuff to ndatalen:
      char* chunk_start = (char*)(ndata + 2);
      int chunk_len, name_len;
      for (std::list<FluidSoundFont>::iterator it = stack.begin(); it != stack.end();  ++it) {
            name_len = strlen(it->name.c_str()) + 1;
            chunk_len = name_len + FS_SFDATALEN;
            memcpy(chunk_start, it->name.c_str(), name_len); //First, store the fontname
            *(chunk_start + name_len) = it->extid; //The GUI only needs to know about the external id, store that here
            chunk_start += chunk_len;
            }
      sendSysex(ndatalen, ndata);
      }

//---------------------------------------------------------
//   sendChannelData
//---------------------------------------------------------
void FluidSynth::sendChannelData()
      {
      int chunk_size = 2;
      int chdata_length = (chunk_size * FS_MAX_NR_OF_CHANNELS) +1 ; //Command and the 2 channels * 16
      byte chdata[chdata_length];
      byte* chdptr;
      chdata[0] = FS_SEND_CHANNELINFO;
      chdptr = (chdata + 1);
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            *(chdptr)    = channels[i].font_extid; //Font external id
            *(chdptr+1)  = i; //Channel nr
            chdptr += chunk_size;
            }
      sendSysex(chdata_length, chdata);
      // Send drum channel info afterwards (later addition, not very neat, but works...)

      int drumchdata_length = FS_MAX_NR_OF_CHANNELS + 1; //1 byte for the command, one byte for each channel
      byte drumchdata[drumchdata_length ];
      byte* drumchdataptr = drumchdata;
      *drumchdata = FS_SEND_DRUMCHANNELINFO;

      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            drumchdataptr++;
            *drumchdataptr = channels[i].drumchannel;
            }
      sendSysex(drumchdata_length, drumchdata);
      }

//---------------------------------------------------------
//   dumpInfo
//---------------------------------------------------------

void FluidSynth::dumpInfo()
      {
      printf("-----------------------------------------------------\n");
      printf("Dumping info...\n");
      printf("Last dir: %s\n", lastdir.c_str());
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++)
            printf("Chan %d\tFont extid:%d\tintid:%d\tdrumchan:%d\tpreset: %d\n", i, channels[i].font_extid, channels[i].font_intid, channels[i].drumchannel, channels[i].preset);

      printf("\n");
      for (std::list<FluidSoundFont>::iterator it = stack.begin(); it != stack.end(); it++)
            printf("Font: %s\tintid: %d\textid %d\tfilename:%s\n", it->name.c_str(), it->intid, it->extid, it->filename.c_str());
      printf("Reverb on: %d, width: %f, size: %f level: %f damp: %f\n",rev_on, rev_width, rev_size, rev_level, rev_damping);
      printf("-----------------------------------------------------\n");
      }

//---------------------------------------------------------
//   guiVisible
//---------------------------------------------------------

bool FluidSynth::guiVisible() const
      {
      return gui->isVisible();
      }


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

void FluidSynth::showGui(bool val)
      {
      gui->setShown(val);
      }

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

bool FluidSynth::setController(int channel, int id, int val)
      {
      setController(channel, id, val, false);
      return false;
      }

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

void FluidSynth::setController(int channel, int id, int val, bool fromGui)
      {
      //
      // Channelless controllers
      //
      int err = 0;
      switch (id) {
            case FS_GAIN: {
                  fluid_synth_set_gain(fluidsynth, (float) val/25); //gives val an interval of approximately[0,5]
                  //Forward to gui if not from Gui
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_GAIN, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_REVERB_ON: {
                  rev_on = val;
                  fluid_synth_set_reverb_on(fluidsynth, val); // 0 or 1
                  //if (rev_on)
                  //      fluid_synth_set_reverb(fluidsynth, rev_size, rev_damping, rev_width, rev_level);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_REVERB_ON, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_REVERB_LEVEL:
                  //Interval: 0-2
                  rev_level = (double)2*val/16384; //[0,2]
                  //if (rev_on)
                        fluid_synth_set_reverb(fluidsynth, rev_size, rev_damping, rev_width, rev_level);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_REVERB_LEVEL, val);
                        gui->writeEvent(ev);
                        }
                  break;
            case FS_REVERB_WIDTH: //
                  rev_width = (double)val/164; //[0,100]
                  //if (rev_on)
                        fluid_synth_set_reverb(fluidsynth, rev_size, rev_damping, rev_width, rev_level);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_REVERB_WIDTH, val);
                        gui->writeEvent(ev);
                        }
                  break;
            case FS_REVERB_DAMPING: //[0,1]
                  rev_damping = (double)val/16384;
                  //if (rev_on)
                        fluid_synth_set_reverb(fluidsynth, rev_size, rev_damping, rev_width, rev_level);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_REVERB_DAMPING, val);
                        gui->writeEvent(ev);
                        }
                  break;
            case FS_REVERB_ROOMSIZE: //[0,1]
                  rev_size = (double)val/16384;
                  //if (rev_on)
                        fluid_synth_set_reverb(fluidsynth, rev_size, rev_damping, rev_width, rev_level);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_REVERB_ROOMSIZE, val);
                        gui->writeEvent(ev);
                        }
                  break;
            case FS_CHORUS_ON: {// 0 or 1
                  cho_on = val;
                  fluid_synth_set_chorus_on(fluidsynth, val);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_CHORUS_ON, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_CHORUS_NUM: {//Number of delay lines
                  cho_num = val;
                  fluid_synth_set_chorus(fluidsynth, cho_num, cho_level, cho_speed, cho_depth, cho_type);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_CHORUS_NUM, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_CHORUS_TYPE: {//?
                  cho_type = val;
                  fluid_synth_set_chorus(fluidsynth, cho_num, cho_level, cho_speed, cho_depth, cho_type);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_CHORUS_TYPE, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_CHORUS_SPEED: {//(0.291,5) Hz
                  cho_speed = (double)(0.291 + (double)val/3479);
                  fluid_synth_set_chorus(fluidsynth, cho_num, cho_level, cho_speed, cho_depth, cho_type);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_CHORUS_SPEED, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_CHORUS_DEPTH: { //[0,40]
                  cho_depth = (double) val*40/16383;
                  fluid_synth_set_chorus(fluidsynth, cho_num, cho_level, cho_speed, cho_depth, cho_type);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_CHORUS_DEPTH, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            case FS_CHORUS_LEVEL: { //[0,1]
                  cho_level = (double) val/16383;
                  fluid_synth_set_chorus(fluidsynth, cho_num, cho_level, cho_speed, cho_depth, cho_type);
                  if (!fromGui) {
                        MidiPlayEvent ev(0, 0, 0, ME_CONTROLLER, FS_CHORUS_LEVEL, val);
                        gui->writeEvent(ev);
                        }
                  break;
                  }
            //
            // Controllers that depend on channels
            //
            case CTRL_PITCH:
                  err = fluid_synth_pitch_bend (fluidsynth, channel, val);
                        break;
            case CTRL_PROGRAM: {
                  //Check if MusE is trying to set a preset on an unspecified font. If so, ignore.
                  if (FS_DEBUG)
                        printf("Program select : channel %d val %d\n",channel, val);
                  byte font_intid = channels[channel].font_intid;
                  byte banknum = 0;
                  if (font_intid == FS_UNSPECIFIED_ID || font_intid == FS_UNSPECIFIED_FONT)
                        return;

                  // Check if this is a drum patch change (equiv midi chan 10)
                  if (val & 0x8000) { //This is a drumpatch
                        //printf("Got a drumbank program change!\n");
                        banknum = 128;
                        val-=0x8000;
                        }

                  err = fluid_synth_program_select(fluidsynth, channel, font_intid , banknum, val);
                  if (err)
                        printf("FluidSynth::setController() - Error changing program on soundfont %s, channel: %d\n", fluid_synth_error(fluidsynth), channel);
                  else {
                        channels[channel].preset = val;//setChannelPreset(val, channel);
                        }
                  break;
                  }
            default:
                  if (FS_DEBUG)
                        printf("Setting controller on channel: %d with id: 0x%x to val: %d\n",channel, id, val);
                  err = fluid_synth_cc(fluidsynth, channel, id, val);
                  break;
            }

      if (err)
            printf ("FluidSynth::setController() - error processing controller event: %s\n", fluid_synth_error(fluidsynth));
      }

//---------------------------------------------------------
//   getControllerInfo
//---------------------------------------------------------
int FluidSynth::getControllerInfo(int id, const char** name, int* controller, int* min, int* max) const
      {
      if (id >= NUM_CONTROLLER)
            return 0;
      *controller = fluidCtrl[id].num;
      *name       = fluidCtrl[id].name;
      *min        = fluidCtrl[id].min;
      *max        = fluidCtrl[id].max;
      if (FS_DEBUG)
            printf("FluidSynth::getControllerInfo() id: %d name: %s controller: %d min: %d max: %d\n",id,*name,*controller,*min,*max);
      return ++id;
      }

//---------------------------------------------------------
//   sendError
//---------------------------------------------------------
void FluidSynth::sendError(const char *errorMessage)
      {
      int len = 2 + strlen(errorMessage);
      unsigned char data[len];
      *data = FS_ERROR;
      memcpy(data + 1, errorMessage, len - 1);
      sendSysex(len, data);
      }

//---------------------------------------------------------
//   getNextAvailableExternalId
//---------------------------------------------------------

int FluidSynth::getNextAvailableExternalId()
      {
      unsigned char place[FS_MAX_NR_OF_CHANNELS];
      for(int i=0; i<FS_MAX_NR_OF_CHANNELS; i++)
            place[i] = 0;
      for (std::list<FluidSoundFont>::iterator it = stack.begin(); it != stack.end(); it++)
            place[it->extid] = 1;

      int i=0;
      while (i < FS_MAX_NR_OF_CHANNELS && place[i] == 1)
            i++;

      return i;
      }

//---------------------------------------------------------
//   sfChannelChange
//---------------------------------------------------------

void FluidSynth::sfChannelChange(byte extid, byte channel)
      {
      if (FS_DEBUG)
            printf("FluidSynth::sfChannelChange()-Setting channel %d to font with extid %d intid %d\n",channel, extid, getFontInternalIdByExtId(extid));
      channels[channel].font_extid = extid;
      channels[channel].font_intid = getFontInternalIdByExtId(extid);
      }

//---------------------------------------------------------
//   getFontInternalIdByExtId
//---------------------------------------------------------
byte FluidSynth::getFontInternalIdByExtId(byte ext_id)
      {
      for (std::list<FluidSoundFont>::iterator it = stack.begin(); it !=stack.end(); it++) {
            if (it->extid == ext_id)
                  return it->intid;
            }
      return FS_UNSPECIFIED_FONT;
      }

//---------------------------------------------------------
//   sendLastDir
//---------------------------------------------------------
void FluidSynth::sendLastdir(const char* lastdir)
      {
      int n = strlen(lastdir) + 2;
      byte d[n];
      d[0] = FS_LASTDIR_CHANGE;
      memcpy(d+1,lastdir, strlen(lastdir)+1);

      MidiPlayEvent ev(0,0, ME_SYSEX, d, n);
      gui->writeEvent(ev);
      }


//---------------------------------------------------------
//   rewriteChannelSettings
//---------------------------------------------------------
void FluidSynth::rewriteChannelSettings()
      {
      //Walk through the channels, remap internal ID:s to external ID:s (something that actually only needs to be done at
      //startup, since the fonts aren't loaded yet at that time and it isn't possible to give them a correct internal id
      //since they don't have any at that time, this can probably be fixed in a smarter way (but it works..))
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            int ext_id = channels[i].font_extid;//getFontExternalIdByChannel(i);
            if (ext_id != FS_UNSPECIFIED_ID) //Check if ext_id is set to any sane font
                  {
                  channels[i].font_intid = getFontInternalIdByExtId(ext_id);//(getFontInternalIdByExtId(ext_id));//if so, get value from the stack
                  }
            else
                  channels[i].font_intid = FS_UNSPECIFIED_FONT; //if not, set it to unspecified
            }

      //Assign correct presets to all channels
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            int preset = channels[i].preset;
            int int_id = channels[i].font_intid;
            byte banknum = 0;

            if (channels[i].drumchannel)
                  banknum = 128;

            //printf("Channel %d, font int-id %d ext_id %d, preset %d\n",i, int_id, getFontExternalIdByChannel(i), preset);
            if (!(preset == FS_UNSPECIFIED_PRESET || int_id == FS_UNSPECIFIED_FONT)) {
                  int rv = fluid_synth_program_select(fluidsynth, i, int_id, banknum, preset);
                  if (rv)
                        std::cerr << DEBUG_ARGS << "Error changing preset! " << fluid_synth_error(fluidsynth) << std::endl;
                  }
            }
      }

//---------------------------------------------------------
//   getPatchName
//---------------------------------------------------------
const char* FluidSynth::getPatchName(int i, int, int) const
      {
      if (channels[i].font_intid == FS_UNSPECIFIED_FONT)
            return "no preset";
      else if (channels[i].preset == FS_UNSPECIFIED_PRESET)
            return "no preset";
      else {
            //printf("Getpatchname, channel: %d\n",channel);
            fluid_preset_t *preset = fluid_synth_get_channel_preset(fluidsynth, i);
            if (!preset) return "no preset";
            return preset->get_name(preset);
            }
      }

//---------------------------------------------------------
//   getPatchInfo
//---------------------------------------------------------
const MidiPatch* FluidSynth::getPatchInfo(int i, const MidiPatch* patch) const
      {
      if (channels[i].font_intid == FS_UNSPECIFIED_FONT)
            return 0;
      //else if (channels[i].preset == FS_UNSPECIFIED_PRESET)
      //      return 0;
      else {
            //printf("Getpatchname, channel: %d\n",channel);
            if (!patch)
                  //Deliver first patch
                  return getFirstPatch(i);
            else
                  //Deliver next patch
                  return getNextPatch(i, patch);
            }
      }

//---------------------------------------------------------
//   getFirstPatch
//---------------------------------------------------------
const MidiPatch* FluidSynth::getFirstPatch (int channel) const
      {
      static MidiPatch midiPatch;

      midiPatch.typ = 0;
      midiPatch.hbank = 0;

      fluid_preset_t* preset;
      int font_id = channels[channel].font_intid;
      if (font_id == FS_UNSPECIFIED_FONT)
            return 0;

      fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(fluidsynth, font_id);

      if (!channels[channel].drumchannel) {
            for (unsigned int bank = 0; bank < 128; ++bank) {
                  for (unsigned int patch = 0; patch < 128; ++patch) {
                        preset = sfont->get_preset (sfont, bank, patch);
                        if (preset) {
                              midiPatch.lbank = bank;
                              midiPatch.prog = patch;
                              midiPatch.name = preset->get_name (preset);
                              return &midiPatch;
                              }
                        }
                  }
            return 0;
            }
      else { //This is a drumchannel
            int bank = 128;
            for (unsigned int patch = 0; patch < 128; ++patch) {
                  preset = sfont->get_preset (sfont, bank, patch);
                  if (preset) {
                        midiPatch.lbank = bank;
                        midiPatch.prog = patch;
                        midiPatch.name = preset->get_name(preset);
                        return &midiPatch;
                        }
                  }
            }
      return 0;
      }

//---------------------------------------------------------
//   getNextPatch
//---------------------------------------------------------
const MidiPatch* FluidSynth::getNextPatch (int channel, const MidiPatch* patch) const
      {
      static MidiPatch midiPatch;
      //First check if there actually is any soundfont associated to the channel. If not, don't bother
      int font_id = channels[channel].font_intid;
      if (font_id == FS_UNSPECIFIED_FONT)
            return 0;
      if (patch == 0)
            return getFirstPatch(channel);

      midiPatch.typ = 0;
      midiPatch.hbank = 0;

      if (font_id == FS_UNSPECIFIED_FONT)
            return 0;
      //printf("Font has internal id: %d\n",font_id);
      fluid_preset_t* preset;
      fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(fluidsynth, font_id);

      if (!channels[channel].drumchannel) {
            unsigned int prog = patch->prog;
            for (unsigned int bank = patch->lbank; bank < 128; ++bank) {
                  for (prog = patch->prog + 1; prog < 128; ++prog) {
                        preset = sfont->get_preset (sfont, bank, prog);
                        if (preset) {
                              //printf("Preset info: bank: %d prog: %d name: %s\n",bank, prog, preset->get_name(preset));
                              midiPatch.lbank = bank;
                              midiPatch.prog = prog;
                              midiPatch.name = preset->get_name (preset);
                              return &midiPatch;
                              }
                        }
                  }
            }
      else { //This is a drum channel
            unsigned int bank = 128;
            unsigned int prog = patch->prog;
            for (prog = patch->prog + 1; prog < 128; ++prog) {
                  preset = sfont->get_preset (sfont, bank, prog);
                  if (preset) {
                        //printf("Preset info: bank: %d prog: %d name: %s\n",bank, prog, preset->get_name(preset));
                        midiPatch.lbank = bank;
                        midiPatch.prog = prog;
                        midiPatch.name = preset->get_name (preset);
                        return &midiPatch;
                        }
                  }
            }
      return 0;
      }

//---------------------------------------------------------
//   popSoundfont
//---------------------------------------------------------

bool FluidSynth::popSoundfont (int ext_id)
      {
         bool success = false;
         int int_id = getFontInternalIdByExtId(ext_id);

         if (int_id == FS_UNSPECIFIED_FONT) {
               std::cerr << DEBUG_ARGS << "Internal error! Request for deletion of Soundfont that is not registered!" << std::endl;
               }
         else
         {
         //Try to unload soundfont
         int err = fluid_synth_sfunload(fluidsynth, int_id, 0);
         if (err != -1) {//Success
               //Check all channels that the font is used in
               for (int i=0; i<FS_MAX_NR_OF_CHANNELS;  i++) {
                     //Set them to unspecified and reset preset settings
                     if (channels[i].font_intid == int_id) {
                           channels[i].font_intid = FS_UNSPECIFIED_ID;
                           channels[i].font_extid = FS_UNSPECIFIED_ID;
                           channels[i].preset = FS_UNSPECIFIED_PRESET;
                           }
                     }
               //Remove it from soundfont stack
               for (std::list<FluidSoundFont>::iterator it =stack.begin(); it !=stack.end(); it++) {
                     if (it->intid == int_id) {
                           stack.erase(it);
                           break;
                           }
                     }
               //Resend fontdata & re-initialize
               sendSoundFontData();
               sendChannelData();
               rewriteChannelSettings();
               success = true;
               currentlyLoadedFonts--;
            }
         else //OK, there was trouble
               std::cerr << DEBUG_ARGS << "Error unloading soundfont!" << fluid_synth_error(fluidsynth) << std::endl;
      }
      if (FS_DEBUG)
            printf("Removed soundfont with ext it: %d\n",ext_id);
      return success;
      }

//---------------------------------------------------------
//   instantiate
//    construct a new synthesizer instance
//---------------------------------------------------------

class QWidget;
static  pthread_mutex_t globalMutex;
static bool mutexEnabled = false;

static Mess* instantiate(int sr, QWidget*, const char* name)
      {
printf("fluidsynth sampleRate %d\n", sr);

      if (!mutexEnabled) {
          pthread_mutex_init(&globalMutex,NULL);
          mutexEnabled = true;
          }

      FluidSynth* synth = new FluidSynth(sr, &globalMutex);
      if (synth->init(name)) {
            delete synth;
            synth = 0;
            }
      return synth;
      }

extern "C"
      {
      static MESS descriptor = {
            "FluidSynth",
            "Mathias Lundgren (lunar_shuttle@users.sf.net)",
            "0.1",      //Version string
            MESS_MAJOR_VERSION, MESS_MINOR_VERSION,
            instantiate,
            };
      const MESS* mess_descriptor() { return &descriptor; }
      }

