//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: synth.cpp,v 1.43.2.1 2004/11/08 22:53:51 spamatica Exp $
//  (C) Copyright 2000-2003 Werner Schweer (ws@seh.de)
//=========================================================

#include "config.h"
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <vector>
#include <fcntl.h>

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

#include "app.h"
#include "synth.h"
#include "xml.h"
#include "midi.h"
#include "midiport.h"
#include "mididev.h"
#include "libsynti/mess.h"
#include "song.h"
#include "audio.h"
#include "event.h"
#include "mpevent.h"
#include "audio.h"
#include "midiseq.h"
#include "midictrl.h"

std::vector<Synth*> synthis;  // array of available synthis

extern void connectNodes(AudioTrack*, AudioTrack*);

//---------------------------------------------------------
//   description
//---------------------------------------------------------

const char* MessSynth::description() const
      {
      return descr ? descr->description : "";
      }

//---------------------------------------------------------
//   version
//---------------------------------------------------------

const char* MessSynth::version() const
      {
      return descr ? descr->version : "";
      }

bool MessSynthIF::guiVisible() const
      {
      return _mess ? _mess->guiVisible() : false;
      }

void MessSynthIF::showGui(bool v)
      {
      if (v == guiVisible())
            return;
      if (_mess)
            _mess->showGui(v);
      }

bool MessSynthIF::hasGui() const
      {
      if (_mess)
            return _mess->hasGui();
      return false;
      }

MidiPlayEvent MessSynthIF::receiveEvent()
      {
      if (_mess)
            return _mess->receiveEvent();
      return MidiPlayEvent();
      }

int MessSynthIF::eventsPending() const
      {
      if (_mess)
            return _mess->eventsPending();
      return 0;
      }

void MessSynthIF::getGeometry(int* x, int* y, int* w, int* h) const
      {
      if (_mess)
            _mess->getGeometry(x, y, w, h);
      }

void MessSynthIF::setGeometry(int x, int y, int w, int h)
      {
      if (_mess)
            _mess->setGeometry(x, y, w, h);
      }

//---------------------------------------------------------
//   findSynth
//    search for synthesizer base class
//---------------------------------------------------------

static Synth* findSynth(const QString& sclass)
      {
      for (std::vector<Synth*>::iterator i = synthis.begin();
         i != synthis.end(); ++i) {
            if ((*i)->baseName() == sclass)
                  return *i;
            }
      printf("synthi class <%s> not found\n", sclass.latin1());
      return 0;
      }

//---------------------------------------------------------
//   createSynthI
//    create a synthesizer instance of class "label"
//---------------------------------------------------------

static SynthI* createSynthI(const QString& sclass)
      {
      Synth* s = findSynth(sclass);
      SynthI* si = 0;
      if (s) {
            si = new SynthI();
            QString n;
            n.setNum(s->instances());
            QString instance_name = s->baseName() + "-" + n;
            if (si->initInstance(s, instance_name)) {
                  delete si;
                  return 0;
                  }
            }
      else
            printf("synthi class <%s> not found\n", sclass.latin1());
      return si;
      }

//---------------------------------------------------------
//   Synth
//---------------------------------------------------------

Synth::Synth(const QFileInfo& fi)
   : info(fi)
      {
      _instances = 0;
      }

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

void* MessSynth::instantiate()
      {
      ++_instances;
      QString n;
      n.setNum(_instances);
      QString instanceName = baseName() + "-" + n;
      doSetuid();
      const char* path = info.filePath().latin1();

      // load Synti dll
      void* handle = dlopen(path, RTLD_NOW);
      if (handle == 0) {
            fprintf(stderr, "Synth::instantiate: dlopen(%s) failed: %s\n",
               path, dlerror());
            undoSetuid();
            return 0;
            }
      typedef const MESS* (*MESS_Function)();
      MESS_Function msynth = (MESS_Function)dlsym(handle, "mess_descriptor");

      if (!msynth) {
            const char *txt = dlerror();
            if (txt) {
                  fprintf(stderr,
                     "Unable to find msynth_descriptor() function in plugin "
                     "library file \"%s\": %s.\n"
                     "Are you sure this is a MESS plugin file?\n",
                     info.filePath().ascii(), txt);
                  undoSetuid();
                  return 0;
                  }
            }
      descr = msynth();
      if (descr == 0) {
            fprintf(stderr, "Synth::instantiate: no MESS descr found\n");
            undoSetuid();
            return 0;
            }
      Mess* mess = descr->instantiate(sampleRate, muse, instanceName.latin1());
      undoSetuid();
      return mess;
      }

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

SynthI::SynthI()
   : AudioTrack(AUDIO_SOFTSYNTH)
      {
      synthesizer = 0;
      sif         = 0;
      _rwFlags    = 1;
      _openFlags  = 1;
      setVolume(1.0);
      setPan(0.0);
      }

void SynthI::setName(const QString& s)
      {
      AudioTrack::setName(s);
      MidiDevice::setName(s);
      }

//---------------------------------------------------------
//   init
//---------------------------------------------------------

bool MessSynthIF::init(Synth* s)
      {
      _mess = (Mess*)s->instantiate();
      return (_mess == 0);
      }

int MessSynthIF::channels() const
      {
      return _mess->channels();
      }

SynthIF* MessSynth::createSIF() const
      {
      return new MessSynthIF();
      }

//---------------------------------------------------------
//   initInstance
//    returns false on success
//---------------------------------------------------------

bool SynthI::initInstance(Synth* s, const QString& instanceName)
      {
      synthesizer = s;
      sif         = s->createSIF();
      sif->init(s);

      setName(instanceName);    // set midi device name
      setIName(instanceName);   // set instrument name
      AudioTrack::setChannels(sif->channels());

      //---------------------------------------------------
      //  read available controller from synti
      //---------------------------------------------------

      int id = 0;
      MidiControllerList* cl = MidiInstrument::controller();
      for (;;) {
            const char* name;
            int ctrl;
            int min;
            int max;
            id = sif->getControllerInfo(id, &name, &ctrl, &min, &max);
            if (id == 0)
                  break;
            MidiController* c = new MidiController(QString(name), ctrl, min, max, 0);
            cl->push_back(c);
            }

      EventList* iel = midiState();
      if (!iel->empty()) {
            for (iEvent i = iel->begin(); i != iel->end(); ++i) {
                  Event ev = i->second;
                  MidiPlayEvent pev(0, 0, 0, ev);
                  if (sif->putEvent(pev))
                        break;   // try later
                  }
            iel->clear();
            }

      int idx = 0;
      for (std::vector<float>::iterator i = initParams.begin(); i != initParams.end(); ++i, ++idx)
            sif->setParameter(idx, *i);
      return false;
      }

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

int MessSynthIF::getControllerInfo(int id, const char** name, int* ctrl, int* min, int* max)
      {
      return _mess->getControllerInfo(id, name, ctrl, min, max);
      }

//---------------------------------------------------------
//   SynthI::deactivate
//---------------------------------------------------------

void SynthI::deactivate2()
      {
      removeMidiInstrument(this);
      midiDevices.remove(this);
      if (midiPort() != -1) {
            // synthi is attached
            midiPorts[midiPort()].setMidiDevice(0);
            }
      }
//---------------------------------------------------------
//   deactivate3
//---------------------------------------------------------

void SynthI::deactivate3()
      {
      sif->deactivate3();
      synthesizer->incInstances(-1);
      }

void MessSynthIF::deactivate3()
      {
      if (_mess) {
            delete _mess;
            _mess = 0;
            }
      }

//---------------------------------------------------------
//   ~SynthI
//---------------------------------------------------------

SynthI::~SynthI()
      {
      deactivate2();
      deactivate3();
      }

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

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

      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();
            QFileInfoListIterator it(*list);
            QFileInfo* fi;
            while((fi = it.current())) {
                  synthis.push_back(new MessSynth(*fi));
                  ++it;
                  }
            if (debugMsg)
                  printf("%d soft synth found\n", synthis.size());
            }
      }

//---------------------------------------------------------
//   createSynthI
//    create a synthesizer instance of class "label"
//---------------------------------------------------------

SynthI* Song::createSynthI(const QString& sclass)
      {
      SynthI* si = ::createSynthI(sclass);
      insertTrack1(si, -1);
      msgInsertTrack(si, -1, true);       // add to instance list
      insertTrack3(si, -1);

      OutputList* ol = song->outputs();
      // add default route to master (first audio output)
      if (!ol->empty()) {
            AudioOutput* ao = ol->front();
            audio->msgAddRoute(Route(si, -1), Route(ao, -1));
            }
      return si;
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void SynthI::write(int level, Xml& xml) const
      {
      xml.tag(level++, "SynthI");
      AudioTrack::writeProperties(level, xml);
      xml.strTag(level, "class", synth()->baseName());

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

      if (midiPort() != -1)
            xml.intTag(level, "port", midiPort());

      if (hasGui()) {
            xml.intTag(level, "guiVisible", guiVisible());
            int x, y, w, h;
            w = 0;
            h = 0;
            getGeometry(&x, &y, &w, &h);
            if (h || w)
                  xml.qrectTag(level, "geometry", QRect(x, y, w, h));
            }

      sif->write(level, xml);
      xml.etag(level, "SynthI");
      }

void MessSynthIF::write(int level, Xml& xml) const
      {
      //---------------------------------------------
      // dump current state of synth
      //---------------------------------------------

      int len = 0;
      const unsigned char* p;
      _mess->getInitData(&len, &p);
      if (len) {
            xml.tag(level++, "midistate");
            xml.nput(level++, "<event type=\"%d\"", Sysex);
            xml.nput(" datalen=\"%d\">\n", len);
            xml.nput(level, "");
            for (int i = 0; i < len; ++i) {
                  if (i && ((i % 16) == 0)) {
                        xml.nput("\n");
                        xml.nput(level, "");
                        }
                  xml.nput("%02x ", p[i] & 0xff);
                  }
            xml.nput("\n");
            xml.tag(level--, "/event");
            xml.etag(level--, "midistate");
            }
      }

//---------------------------------------------------------
//   SynthI::read
//---------------------------------------------------------

void SynthI::read(Xml& xml)
      {
      QString sclass;
      int port = -1;
      bool startGui = false;
      QRect r;

      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 == "class")
                              sclass = xml.parse1();
                        else if (tag == "port")
                              port  = xml.parseInt();
                        else if (tag == "guiVisible")
                              startGui = xml.parseInt();
                        else if (tag == "midistate")
                              readMidiState(xml);
                        else if (tag == "param") {
                              float val = xml.parseFloat();
                              initParams.push_back(val);
                              }
                        else if (tag == "geometry")
                              r = readGeometry(xml, tag);
                        else if (AudioTrack::readProperties(xml, tag))
                              xml.unknown("softSynth");
                        break;
                  case Xml::TagEnd:
                        if (tag == "SynthI") {
                              Synth* s = findSynth(sclass);
                              if (s == 0)
                                    return;
                              if (initInstance(s, name()))
                                    return;
                              song->insertTrack0(this, -1);
                              if (port != -1 && port < MIDI_PORTS)
                                    midiPorts[port].setMidiDevice(this);
                              showGui(startGui);
                              setGeometry(r.x(), r.y(), r.width(), r.height());
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

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

const char* MessSynthIF::getPatchName(int channel, int prog, MType type)
      {
      if (_mess)
            return _mess->getPatchName(channel, prog, type);
      return "";
      }

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

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

bool SynthI::getData(unsigned pos, int ports, unsigned n, float** buffer)
      {
      int p = midiPort();
      MidiPort* mp = (p != -1) ? &midiPorts[p] : 0;
      MPEventList* el = playEvents();

      for (int i = 0; i < ports; ++i)
            memset(buffer[i], 0, n * sizeof(float));

      iMPEvent ie = nextPlayEvent();
      ie = sif->getData(mp, el, ie, pos, ports, n, buffer);
      setNextPlayEvent(ie);
      return true;
      }

iMPEvent MessSynthIF::getData(MidiPort* mp, MPEventList* el, iMPEvent i, unsigned pos, int ports, unsigned n, float** buffer)
      {
      int curPos      = pos;
      int endPos      = pos + n;
      int off         = pos;
      int frameOffset = audio->getFrameOffset();

      for (; i != el->end(); ++i) {
            int frame = i->time() - frameOffset;
            if (frame >= endPos)
                  break;
            if (frame > curPos) {
                  if (frame < pos)
                        printf("should not happen: missed event %d\n", pos -frame);
                  else
                        _mess->process(buffer, curPos-pos, frame - curPos);
                  curPos = frame;
                  }
            if (mp)
                  mp->sendEvent(*i);
            else {
                  if (putEvent(*i))
                        break;
                  }
            }
      if (endPos - curPos) {
            _mess->process(buffer, curPos - off, endPos - curPos);
            }
      return i;
      }

//---------------------------------------------------------
//   putEvent
//    return true on error (busy)
//---------------------------------------------------------

bool MessSynthIF::putEvent(const MidiPlayEvent& ev)
      {
      if (midiOutputTrace)
            ev.dump();
      if (_mess)
            return _mess->processEvent(ev);
      return true;
      }
