//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: synth.cpp,v 1.1.1.1 2003/10/29 10:06:00 wschweer Exp $
//  (C) Copyright 2000 Werner Schweer (ws@seh.de)
//=========================================================

#include <qdir.h>
#include <dlfcn.h>
#include <qpopupmenu.h>

#include "app.h"
#include "synth.h"
#include "xml.h"
#include "midiport.h"
#include "driver/mididev.h"
#include "mess.h"
#include "ladspa.h"
#include "song.h"
#include "audio.h"
#include "event.h"
#include "mpevent.h"
#include "midithread.h"
#include <sys/mman.h>

#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

int nsynthis = 0;
Synth** synthis;              // static array of available synthis
SynthIList synthiInstances;  // this list is owned by seq thread

extern void connectNodes(AudioNode*, AudioNode*);

//---------------------------------------------------------
//   SynthI
//---------------------------------------------------------

SynthI::SynthI(Synth* s)
   : synthesizer(s)
      {
      _mdev       = 0;
      _mess       = (Mess*)synthesizer->instantiate();
      _mess->euid = euid;
      _mess->ruid = ruid;
      if (! realTimeScheduling)
	      _mess->realTimePriority = 0;
      synthesizer->activate(_mess);
      setIName(QString(_mess->name()));
      _gui     = false;
      _readFd  = -1;
      _writeFd = -1;
      setVolume(1.0);
      setPan(0.0);
      AudioNode::setPorts(s->channels());
      connectOut(&audioOutput);       // default: connect to master
      printf("create Synth instance <%s> channels %d %d\n",
         iname().latin1(), s->channels(), ports());
      }

SynthI::~SynthI()
      {
      synthesizer->incInstances(-1);
      delete _mess;
      }

//---------------------------------------------------------
//   sysexReceived
//    received sysex from synth gui
//    execution context: midiThread
//---------------------------------------------------------

void SynthI::sysexReceived(const unsigned char* p, int n)
      {
      if (!_mdev)
            return;
      MidiPlayEvent event(_mdev->port()|0x100, p, n);
      midiThread->eventReceived(&event);
      _mdev->putEvent(&event);     // echo to softSynth
      }

//---------------------------------------------------------
//   eventReceived
//    received event from synth gui
//    execution context: midiThread
//---------------------------------------------------------

void SynthI::eventReceived(int a, int b, int c)
      {
      if (_mdev == 0)
            return;
      midiThread->eventReceived(_mdev->port()|0x80, a, b, c);
      switch(a & 0xf0) {
            case 0xb0:
                  {
                  MidiPlayEvent event(0, 0, 0xb0, b, c);
                  _mdev->putEvent(&event);   // echo to softSynth
                  }
                  break;
            default:
                  printf("SynthI::eventReceived(0x%02x,0x%02x,0x%02x): not impl.\n", a, b, c);
                  break;
            }
      }

//---------------------------------------------------------
//   showGui
//    execution context: midithread
//---------------------------------------------------------

void SynthI::showGui(bool f)
      {
      if (!hasGui() || (_gui == f))
            return;
      if (_gui) {
            kill(_guiPid, SIGKILL);
            int status;
            int n;

            do {
                  n = waitpid (_guiPid, &status, 0);
                  } while (n == -1 && errno == EINTR);
            if (n != _guiPid)
                  printf("__waitpid failed\n");
            _gui = false;
            }
      else {
            const char* classname = _mess->className();
            const char* instname  = _mess->name();
            QString p = synthesizer->path();
            p += "/";
            p += classname;

            struct sigaction sa, intr, quit;
            sa.sa_handler = SIG_IGN;
            sa.sa_flags = 0;
            __sigemptyset(&sa.sa_mask);

            if (sigaction (SIGINT, &sa, &intr) < 0)
                  return;
            if (sigaction (SIGQUIT, &sa, &quit) < 0) {
                  sigaction (SIGINT, &intr, 0);
                  return;
                  }
            sigset_t block, omask;
            __sigemptyset (&block);
            __sigaddset (&block, SIGCHLD);
            int save = errno;
            if (sigprocmask (SIG_BLOCK, &block, &omask) < 0) {
                  if (errno == ENOSYS)
                        errno = save;
                  else {
                        sigaction (SIGINT, &intr, 0);
                        sigaction (SIGQUIT, &quit, 0);
                        return;
                        }
                  }

            int rfiledes[2];
            int wfiledes[2];
            int rv = pipe(rfiledes);
            if (rv < 0) {
                  printf("pipe failed\n");
                  return;
                  }
            rv = pipe(wfiledes);
            if (rv < 0) {
                  printf("pipe failed\n");
                  return;
                  }
            _guiPid = fork();
            if (_guiPid == 0) {       // Child side
                  dup2(rfiledes[1], 1);    // connect stdout to pipe
                  close(rfiledes[0]);
                  close(rfiledes[1]);
                  dup2(wfiledes[0], 0);    // connect stdin to pipe
                  close(wfiledes[0]);
                  close(wfiledes[1]);

                  for (int i = 3; i < 50; ++i)
                        close(i);
                  const char* const new_argv[4] = {
                        classname,
                        instname,
                        museProject.latin1(),
                        0
                        };

                  // Restore the signals
                  sigaction (SIGINT, &intr, 0);
                  sigaction (SIGQUIT, &quit, 0);
                  sigprocmask (SIG_SETMASK, &omask, 0);

                  //-------------------------------------
                  // make sure:
                  //    a) gui does not get realtime priorities
                  //    b) gui does not lock memory
                  //    c) gui does not run uid root

                  struct sched_param sp;
                  memset(&sp, 0, sizeof(sp));
                  // sp.sched_priority = 0;
	            sched_setscheduler(0, SCHED_OTHER, &sp);
                  munlockall();
                  setuid(getuid());

                  execve(p.latin1(), (char*const*)new_argv, __environ);
                  _exit (127);
                  }
            else if (_guiPid < 0)   /* The fork failed.  */
                  ;
            _readFd  = rfiledes[0];
            _writeFd = wfiledes[1];

            if ((sigaction (SIGINT, &intr, 0)
               | sigaction (SIGQUIT, &quit, 0)
               | sigprocmask (SIG_SETMASK, &omask, 0)) != 0) {
                  ;
                  }
            _gui = true;
            }
      }

//---------------------------------------------------------
//   hasGui
//---------------------------------------------------------

bool SynthI::hasGui() const
      {
      const char* classname = _mess->className();
      QString p = synthesizer->path();
      p += "/";
      p += classname;
      return access(p.latin1(), X_OK) == 0;
      }

//---------------------------------------------------------
//   getData
//    - connect LADSPA ports
//    - run LADSPA ports
//---------------------------------------------------------

bool SynthI::getData(int ports, unsigned long nframes, float** buffer)
      {
      for (int i = 0; i < ports; ++i)
            synthesizer->connect((LADSPA_Handle*)_mess, i, buffer[i]);
      synthesizer->run((LADSPA_Handle*)_mess, nframes);
      return true;
      }

//---------------------------------------------------------
//   addSynthi
//    return true if ok
//---------------------------------------------------------

static bool addSynthi(QFileInfo* fi, int idx)
      {
      void* handle = dlopen(fi->filePath().ascii(), RTLD_NOW);
      if (handle == 0) {
            fprintf(stderr, "addSynthi: dlopen(%s) failed: %s\n",
               fi->filePath().ascii(), dlerror());
            return false;
            }
      LADSPA_Descriptor_Function ladspa = (LADSPA_Descriptor_Function)dlsym(handle, "ladspa_descriptor");

      if (!ladspa) {
            const char *txt = dlerror();
            if (txt) {
                  fprintf(stderr,
                     "Unable to find ladspa_descriptor() function in plugin "
                     "library file \"%s\": %s.\n"
                     "Are you sure this is a LADSPA plugin file?\n",
                     fi->filePath().ascii(), txt);
                  return false;
                  }
            }
      const LADSPA_Descriptor* descr = ladspa(0);
      if (descr == 0) {
            fprintf(stderr, "addSynthi: no LADSPA descr found\n");
            return false;
            }
      if (descr->PortCount < 1 || descr->PortCount > 2) {
            fprintf(stderr, "addSynthi: plugin <%s> has bad port count %ld\n",
               descr->Name, descr->PortCount);
            return false;
            }
      LADSPA_PortDescriptor pd0 = descr->PortDescriptors[0];
      if (pd0 & LADSPA_PORT_CONTROL) {
            fprintf(stderr, "addSynthi: plugin <%s> has bad port types\n",
               descr->Name);
            return false;
            }
      if (descr->PortCount == 2) {
            LADSPA_PortDescriptor pd1 = descr->PortDescriptors[1];
            if (pd1 & LADSPA_PORT_CONTROL) {
                  fprintf(stderr, "addSynthi: plugin <%s> has bad port types\n",
                     descr->Name);
                  return false;
                  }
            }
      LADSPA_Properties properties = descr->Properties;
      if (LADSPA_IS_INPLACE_BROKEN(properties)) {
	      fprintf(stderr,
               "Synti \"%s\" is not capable of in-place processing and "
               "therefore cannot be used by this program.\n",
               descr->Name);
            return false;
            }
      if (debugMsg)
            printf("found soft synth <%s> <%s>\n", descr->Label, descr->Name);
      synthis[idx] = new Synth(fi, ladspa, descr);
      return true;
      }

//---------------------------------------------------------
//   initMidiSynth
//    search for software synthis and advertise
//---------------------------------------------------------

void initMidiSynth()
      {
      QString s = museGlobalLib + "/synthi";

      if (!alsaFound)
            return;
      QDir pluginDir(s, QString("*.so"), QDir::Files);
      if (debugMsg)
            printf("searching for software synthesizer in <%s>\n", s.latin1());
      if (pluginDir.exists()) {
            const QFileInfoList* list = pluginDir.entryInfoList();
            nsynthis = list->count();
            if (debugMsg)
                  printf("%d soft synth found\n", nsynthis);
            synthis  = new (Synth*)[nsynthis];
            QFileInfoListIterator it(*list);
            QFileInfo* fi;
            int i = 0;
            while((fi = it.current())) {
                  if (addSynthi(fi, i))
                        ++i;
                  ++it;
                  }
            }
      }

//---------------------------------------------------------
//   createInstance
//    create a synthesizer instance of class "label"
//    associate with MidiPort port
//---------------------------------------------------------

SynthI* Song::createSynthI(const QString& sclass)
      {
//      printf("create SynthI <%s>\n", sclass.latin1());
      if (!alsaFound)
            return 0;
      int i;
      Synth* s = 0;
      for (i = 0; i < nsynthis; ++i) {
            s = synthis[i];
            if (s && s->label() == sclass)
                  break;
            }
      if (i == nsynthis) {
            printf("synthi class <%s> not found\n", sclass.latin1());
            return 0;
            }

      SynthI* si = new SynthI(s);
      return si;
      }

//---------------------------------------------------------
//   attachSynthI
//    attach synthesizer to midi port
//---------------------------------------------------------

void Song::attachSynthI(SynthI* si, int port)
      {
      for (iMidiDevice i = midiDevices.begin(); i != midiDevices.end(); ++i) {
            MidiDevice* d = *i;
            if (d->name() == si->iname()) {
                  midiThread->setMidiDevice(&midiPorts[port], d);
                  midiInstruments.push_back(si);
                  midiPorts[port].setInstrument(si);
                  si->setDevice(d);
                  return;
                  }
            }
printf("attachSynthI <%s> failed: ALSA device not found\n",
   si->iname().latin1());
      }

//---------------------------------------------------------
//   writeSoftSynth
//    write current state of all software synthesizer
//    instances to xml file (MusE project file)
//---------------------------------------------------------

void MusE::writeSoftSynth(int level, Xml& xml) const
      {
      iSynthI ii;
      for (ii = synthiInstances.begin(); ii != synthiInstances.end(); ++ii) {
            SynthI* si = *ii;
            if (si == 0) {
                  printf("internal error: synth instance zero\n");
                  continue;
                  }
            xml.tag(level++, "synth");
            xml.strTag(level, "class", si->synth()->label());
            xml.strTag(level, "name", si->iname());

            //---------------------------------------------
            // if soft synth is attached to a midi port,
            // write out port number
            //---------------------------------------------

            MidiDevice* md = si->mdev();
            if (md)
                  xml.intTag(level, "port", md->port());

            xml.intTag(level, "guiVisible", si->guiVisible());

            //---------------------------------------------
            // dump current midi state of synth
            //---------------------------------------------

            xml.tag(level++, "midistate");
            RawMidiEvent ev;
            int id = 0;
            int type;
            while ((id = si->mess()->getMidiInitEvent(id, &ev))) {
                  switch(ev.type()) {
                        case SND_SEQ_EVENT_NONREGPARAM:
                              type = MidiEvent::NRPN;
                              break;
		            case SND_SEQ_EVENT_CONTROLLER:
                              type = MidiEvent::Ctrl7;
                              break;
		            case SND_SEQ_EVENT_SYSEX:
                              type = MidiEvent::Sysex;
                              break;
                        default:
                              printf("writeSoftSynth: event type %d not implemented\n",
                                 ev.type());
                              continue;
                        }

                  unsigned char* p = ev.data();
                  xml.nput(level++, "<event type=\"%d\"", type);
                  if (p) {
                        int l = ev.dataLen();
                        xml.nput(" datalen=\"%d\">\n", l);
                        xml.nput(level, "");
                        for (int i = 0; i < l; ++i) {
                              if (i && ((i % 16) == 0))
                                    xml.nput(level, "\n");
                              xml.nput("%02x ", p[i] & 0xff);
                              }
                        xml.nput("\n");
                        xml.tag(level, "/event");
                        delete[] p;
                        ev.setData(0);
                        }
                  else {
                        xml.put(" a=\"%d\" b=\"%d\"/>", ev.dataA(), ev.dataB());
                        }
                  --level;
                  }
            xml.etag(level--, "midistate");

            //---------------------------------------------
            // dump MusE audio state of synth
            //---------------------------------------------

            si->AudioNode::writeConfiguration(level, xml);
            xml.etag(level--, "synth");
            }
      }

//---------------------------------------------------------
//   readSoftSynth
//---------------------------------------------------------

void MusE::readSoftSynth(Xml& xml)
      {
      QString name;
      QString sclass;
      int port = -1;
      SynthI* synth = 0;
      bool guiVisible = false;

      for (;;) {
            Xml::Token token = xml.parse();
            const QString tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "name") {
                              name = xml.parse1();
                              synth = song->createSynthI(sclass);

                              // HACK: sequencer not running
                              //       during load phase
                              midiThread->msgScanAlsaMidiPorts();

                              if (port != -1 && synth)
                                    song->attachSynthI(synth, port);
                              }
                        else if (tag == "class")
                              sclass = xml.parse1();
                        else if (tag == "port") {
                              port  = xml.parseInt();
                              if (synth && port != -1)
                                    song->attachSynthI(synth, port);
                              }
                        else if (tag == "guiVisible")
                              guiVisible = xml.parseInt();
                        else if (tag == "midistate") {
                              if (synth)
                                    synth->readMidiState(xml);
                              else
                                    xml.unknown("no synth");
                              }
                        else if (tag == "audionode") {
                              if (synth)
                                    synth->readConfiguration(xml);
                              else
                                    xml.unknown("no synth");
                              }
                        else
                              xml.unknown("softSynth");
                        break;
                  case Xml::TagEnd:
                        if (tag == "synth") {
                              if (synth == 0)
                                    return;
                              // synth is not yet connected:
                              audio->msgAddSynthI(synth);
                              midiThread->msgAddSynthI(synth);
                              midiThread->msgShowInstrumentGui(synth, guiVisible);
                              sleep(2);  // TODO: wait for GUI
                              if (port != -1) {
                                    EventList* el = synth->midiState();
                                    MPEventList mpl;
                                    int tick = 0;
                                    // we must use a tick to get the right
                                    // event sorting order
                                    for (iEvent i = el->begin(); i != el->end(); ++i) {
                                          MidiEvent* ev = (MidiEvent*)(i->second);
                                          mpl.add(tick, ev, port, 0);
                                          // might add more than one raw event
                                          tick += 16;
                                          }
                                    for (iMPEvent i = mpl.begin(); i != mpl.end(); ++i) {
                                          midiThread->playMidiEvent(&(i->second));
                                          synth->writeToGui(&(i->second));
                                          }
                                    mpl.clear();
                                    }
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

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

const char* SynthI::getPatchName(int channel, int hbank, int lbank,
   int prog, MType type)
      {
      return mess()->getPatchName(channel, hbank, lbank, prog, type);
      }

//---------------------------------------------------------
//   populatePatchPopup
//---------------------------------------------------------

void SynthI::populatePatchPopup(QPopupMenu* menu, int ch, MType)
      {
      menu->clear();
      const MidiPatch* mp = _mess->getNextPatch(ch, 0);
      while (mp) {
            int id = ((mp->hbank & 0xff) << 16)
                      + ((mp->lbank & 0xff) << 8) + mp->prog;
            menu->insertItem(QString(mp->name), id);
            mp = _mess->getNextPatch(ch, mp);
            }
      }

//---------------------------------------------------------
//   processInput
//    midi Input from gui
//    execution context: midiThread
//---------------------------------------------------------

void SynthI::processInput()
      {
      unsigned char buffer[128];
      int n = read(_readFd, buffer, 128);
// printf("from GUI %d bytes\n", n);
      dataInput(buffer, n);
      }

//---------------------------------------------------------
//   removeSoftSynth
//---------------------------------------------------------

void Song::removeSoftSynth()
      {
      iSynthI ii;
      for (ii = synthiInstances.begin(); ii != synthiInstances.end();) {
            iSynthI ni = ii;
            ++ni;
            audio->msgRemoveSynthI(*ii);
            delete *ii;
            ii = ni;
            }
      }

//---------------------------------------------------------
//   writeToGui
//---------------------------------------------------------

void SynthI::writeToGui(const MidiPlayEvent* e)
      {
      if (!_gui)
            return;

      int chn = e->channel();
      int a   = e->dataA();
      int b   = e->dataB();
      int typ = e->type();

      char data[3];
      data[0] = typ + chn;
      data[1] = a;
      data[2] = b;

      switch(typ) {
            case 0x90:
            case 0x80:
            case 0xb0:
            case 0xa0:
            case 0xe0:
                  {
                  int nn = write(_writeFd, data, 3);
                  if (nn != 3)
                        perror("write to gui failed");
                  }
                  break;
            case 0xc0:
            case 0xd0:
                  {
                  int nn = write(_writeFd, data, 2);
                  if (nn != 3)
                        perror("write to gui failed");
                  }
                  break;
            case 0xf0:
                  {
                  unsigned char c = 0xf0;
                  write(_writeFd, &c, 1);
                  write(_writeFd, e->data(), e->len());
                  c = 0xf7;
                  write(_writeFd, &c, 1);
                  }
                  break;
            default:
                  printf("Synti: GUI write: illegal event type %d\n", typ);
                  break;
            }
      }

//---------------------------------------------------------
//   terminateSynthGuis
//---------------------------------------------------------

void terminateSynthGuis()
      {
      iSynthI ii;
      for (ii = synthiInstances.begin(); ii != synthiInstances.end(); ++ii) {
            midiThread->msgShowInstrumentGui(*ii, false);
            }
      }

//---------------------------------------------------------
//   guiExited
//---------------------------------------------------------

void SynthI::guiExited()
      {
      _gui = false;
      _guiPid = -1;
      }

