/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "ladspaplugin.h"

#include "lrdfmanager.h"

#include "data.h"
#include "partwidget.h"
#include "actionmanager.h"

#include "editapplydialog.h"
#include "controls/slider.h"

#include "sonik_util.h"

#include <kaction.h>
#include <klocale.h>
#include <kgenericfactory.h>
#include <kdialogbase.h>
#include <kstandarddirs.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kxmlguifactory.h>

#include <qvbox.h>
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qsignalmapper.h>
#include <qtimer.h>
#include <qpopupmenu.h>

#include <dlfcn.h>


using Sonik::Ladspa;

static const LADSPA_PortIndex kInvalidPort = static_cast<LADSPA_PortIndex>(-1);

namespace Sonik
{
  QString findLadspaLibrary(const QString& name,
                            const QStringList& searchPath = QStringList())
  {
    QString libname = name;
    QStringList libSearchPath = searchPath;

    if (!libname.endsWith(".so"))
      libname.append(".so");

    libSearchPath += QStringList::split(":", getenv("LADSPA_PATH"));
    libSearchPath += "/usr/lib/ladspa";
    libSearchPath += "/usr/local/lib/ladspa";

    QStringList::const_iterator it = libSearchPath.begin();
    for ( ; it != libSearchPath.end(); ++it)
    {
      QString path = *it + "/" + libname;

      if (QFile::exists(path))
        return path;
    }

    return QString::null;
  }


  LADSPA_LibraryHandle openLadpsaLibrary(const QString& path)
  {
    if (path != QString::null)
      return dlopen(path.latin1(), RTLD_LAZY);
    else
      return 0;
  }


  void closeLadspaLibrary(LADSPA_LibraryHandle lib)
  {
    if (lib)
      dlclose(lib);
  }


  const LADSPA_DescriptorList getLadspaDescriptors(LADSPA_LibraryHandle lib)
  {
    LADSPA_DescriptorList descriptors;

    LADSPA_Descriptor_Function descriptorFunc =
      (LADSPA_Descriptor_Function)dlsym(lib, "ladspa_descriptor");
    if (descriptorFunc)
    {
      for (int i = 0; ; i++)
      {
        const LADSPA_Descriptor* d = descriptorFunc(i);
        if (d)
          descriptors.push_back(d);
        else
          break;
      }
    }

    return descriptors;
  }


  const LADSPA_Descriptor* getLadspaDescriptor(LADSPA_LibraryHandle lib,
                                               const QString& label)
  {
    LADSPA_Descriptor_Function descriptorFunc =
      (LADSPA_Descriptor_Function)dlsym(lib, "ladspa_descriptor");
    if (!descriptorFunc)
      return 0;

    for (int i = 0; ; i++)
    {
      const LADSPA_Descriptor* d = descriptorFunc(i);
      if (d == NULL)
        return 0;
      if (d->Label == label)
        return d;
    }
  }


  LADSPA_PortIndex findLadspaPort(const LADSPA_Descriptor* desc,
                                  const QString& name)
  {
    for (LADSPA_PortIndex i = 0; i < desc->PortCount; ++i)
    {
      if (desc->PortNames[i] == name)
        return i;
    }

    return kInvalidPort;
  }


  // File has C channels, processed M [1..C) at a time
  //
  // LADSPA Plugin has Pi inputs and Po outputs
  //
  // Processor has vectors inputMap[M] and outputMap[M] containing
  // port index ( [0..Pi) or [0..Po) ) that each channel is connected to
  //
  // if a plugin port is not refered to in inputMap or
  // outputMap then it is connected to null or sink
  //
  // N instances of plugin are created where:
  //   N = C/M
  //
  // for TRANSFORM_SAME:
  // Pi == Po == M, C % M == 0
  //
  // for TRANSFORM_MAP_UP, GENERATE:
  // Po == M, C % M == 0
  //
  // for TRANSFORM_MAP_DOWN:
  // Pi == M, C % M == 0
  //
  // TRANSFORM_MAP_UP and GENERATE can be achieved by adding channels before
  // running plugin - plugin does not read from channels > M
  // TRANSFORM_MAP_DOWN can be achieved by removing channels after running
  // plugin - plugin does not write to channels > M

  class LadspaProcessor : public Edit::Processor
  {
  public:
    LadspaProcessor(const LADSPA_Descriptor* desc,
                    LADSPA_Data* controlPorts,
                    LADSPA_PortIndexVector inputPorts,
                    LADSPA_PortIndexVector outputPorts,
                    LADSPA_PortIndexVector nullPorts,
                    LADSPA_PortIndexVector sinkPorts)
      : mDesc(desc),
        mControlPorts(controlPorts),
        mInputPorts(inputPorts),
        mOutputPorts(outputPorts),
        mNullPorts(nullPorts),
        mSinkPorts(sinkPorts)
    {
      assert(inputPorts.size() == outputPorts.size());
    }

    virtual ~LadspaProcessor()
    {
      freeBuffers();
    }

    virtual bool prepare(uint8_t channels, std::size_t /*length*/,
                         uint32_t sampleRate, std::size_t blocksize)
    {
      int instances = channels / channelsPerInstance();

      // check integer number of instances required
      if (instances == 0 || instances * channelsPerInstance() != channels)
        return false;

      mNullData = new LADSPA_Data[blocksize];
      memset(mNullData, 0, blocksize * sizeof(LADSPA_Data));

      mSinkData = new LADSPA_Data[blocksize];
      memset(mSinkData, 0, blocksize * sizeof(LADSPA_Data));

      mBuffers.resize(channelsPerInstance());
      for (LADSPA_DataPtrVector::iterator d = mBuffers.begin();
           d != mBuffers.end();
           ++d)
        *d = new LADSPA_Data[blocksize];

      for (int i = 0; i < instances; ++i)
      {
        LADSPA_Handle inst = mDesc->instantiate(mDesc, sampleRate);
        if (!inst)
        {
          cleanup();
          return false;
        }

        connectInstance(inst);

        if (mDesc->activate)
          mDesc->activate(inst);

        mInstances.push_back(inst);
      }

      return true;
    }


    virtual void apply(SampleSegment& seg)
    {
      assert(seg.rows() == channelsPerInstance() * mInstances.size());

      uint8_t cBase = 0;
      for (LADSPA_HandleVector::iterator i = mInstances.begin();
           i != mInstances.end();
           ++i, cBase += channelsPerInstance())
      {
        for (uint8_t c = 0; c < channelsPerInstance(); ++c)
          memcpy(mBuffers[c], seg.data(cBase + c), seg.size()*sizeof(Sample));
        mDesc->run(*i, seg.size());
        for (uint8_t c = 0; c < channelsPerInstance(); ++c)
          memcpy(seg.data(cBase + c), mBuffers[c], seg.size()*sizeof(Sample));
      }
    }


    virtual void cleanup()
    {
      LADSPA_HandleVector::iterator i = mInstances.begin();
      for (; i < mInstances.end(); ++i)
      {
        if (mDesc->deactivate)
          mDesc->deactivate(*i);
        mDesc->cleanup(*i);
      }
      mInstances.clear();

      freeBuffers();
    }

  private:
    typedef QValueVector<LADSPA_Data*>  LADSPA_DataPtrVector;
    typedef QValueVector<LADSPA_Handle> LADSPA_HandleVector;

    uint8_t channelsPerInstance() const
    {
      return mInputPorts.size();
    }

    void connectInstance(LADSPA_Handle inst)
    {
      for (LADSPA_PortIndex i = 0; i < mDesc->PortCount; ++i)
        if (LADSPA_IS_PORT_CONTROL(mDesc->PortDescriptors[i]))
          mDesc->connect_port(inst, i, &mControlPorts[i]);

      LADSPA_PortIndexVector::const_iterator i = mInputPorts.begin();
      LADSPA_PortIndexVector::const_iterator o = mOutputPorts.begin();
      LADSPA_DataPtrVector::const_iterator d = mBuffers.begin();
      for ( ; i != mInputPorts.end() && o != mOutputPorts.end() && d != mBuffers.end();
           ++i, ++o, ++d)
      {
        mDesc->connect_port(inst, *i, *d);
        mDesc->connect_port(inst, *o, *d);
      }

      for (LADSPA_PortIndexVector::const_iterator it = mNullPorts.begin();
           it != mNullPorts.end();
           ++it)
        mDesc->connect_port(inst, *it, mNullData);

      for (LADSPA_PortIndexVector::const_iterator it = mSinkPorts.begin();
           it != mSinkPorts.end();
           ++it)
        mDesc->connect_port(inst, *it, mSinkData);
    }

    void freeBuffers()
    {
      for (LADSPA_DataPtrVector::const_iterator d = mBuffers.begin();
           d != mBuffers.end();
           ++d)
        delete[] (*d);

      mBuffers.clear();

      delete[] mNullData;
      mNullData = 0;

      delete[] mSinkData;
      mSinkData = 0;
    }

    const LADSPA_Descriptor* mDesc;

    LADSPA_Data*             mControlPorts;
    LADSPA_PortIndexVector   mInputPorts;
    LADSPA_PortIndexVector   mOutputPorts;
    LADSPA_PortIndexVector   mNullPorts;
    LADSPA_PortIndexVector   mSinkPorts;

    LADSPA_Data*             mNullData;
    LADSPA_Data*             mSinkData;
    LADSPA_DataPtrVector     mBuffers;

    LADSPA_HandleVector      mInstances;
  };
}


Ladspa::LadspaPluginContext::~LadspaPluginContext()
{
  closeLadspaLibrary(lib);
}


Ladspa::Ladspa(QObject* parent, const char* name, const QStringList& args)
  : Sonik::Edit("ladspa", i18n("LADSPA"), parent, name, args)
{
  QTimer::singleShot(0, this, SLOT(setupActions()));
}


Ladspa::~Ladspa()
{
}


QWidget* Ladspa::makeConfigPage(QWidget*)
{
  return 0;
}


void Ladspa::applyConfigPage()
{
}


void Ladspa::handleAction(const QString& action)
{
  QString actionName = action;
  LadspaPluginContext* context = new LadspaPluginContext;

  //strip off "transform_ladspa_"
  if (actionName.find("transform_ladspa_") == 0)
    actionName.remove(0, sizeof("transform_ladspa_"));

  LadspaPluginMap::const_iterator plugin = mPlugins.find(actionName);
  if (plugin == mPlugins.end())
  {
    KMessageBox::error(0, i18n("Cannot find plugin for action %1").arg(actionName));
    return;
  }
  context->plugin = *plugin;

  QString libName = context->plugin.lib;
  if (QDir::isRelativePath(libName))
      libName = findLadspaLibrary(context->plugin.lib, QStringList::split(':', mSearchPath));
  if (libName == QString::null)
  {
    KMessageBox::error(0, i18n("Cannot find LADSPA plugin library %1")
                       .arg(context->plugin.lib));
    return;
  }

  context->lib = openLadpsaLibrary(libName);
  if (!context->lib)
  {
    KMessageBox::error(0, i18n("Error loading LADSPA plugin library %1")
                       .arg(context->plugin.lib));
    return;
  }

  context->descriptor = getLadspaDescriptor(context->lib,
                                            context->plugin.label);
  if (!context->descriptor)
  {
    KMessageBox::error(0, i18n("Cannot locate %1 in LADSPA plugin library %2")
                       .arg(context->plugin.label)
                       .arg(context->plugin.lib));
    closeLadspaLibrary(context->lib);
    return;
  }

  // check plugin type is supported
  if (context->plugin.type != TRANSFORM_SAME)
  {
    KMessageBox::error(0, i18n("Plugins that change the number of channels are not currently supported"));
    // TODO: plugin mapper dialog

    closeLadspaLibrary(context->lib);
    return;
  }

  // Check channel numbers are valid for plugin
  // TODO: grey out invalid plugins on channel no change
  if (((context->plugin.type == TRANSFORM_MAP_UP ||
        context->plugin.type == GENERATE) &&
       (mData->channels() % context->plugin.outputPorts.size() != 0)) ||
      ((context->plugin.type == TRANSFORM_SAME ||
        context->plugin.type == TRANSFORM_MAP_DOWN) &&
       (mData->channels() % context->plugin.inputPorts.size() != 0)))
  {
      KMessageBox::error(0, i18n("This plugin operates on %1 channels at a time, but this file has %2 channels")
                         .arg(context->plugin.inputPorts.size())
                         .arg(mData->channels()));

    closeLadspaLibrary(context->lib);
    return;
  }

  // connect ports (with dummy for output control ports)
  context->inputPorts.resize(context->plugin.inputPorts.size(), kInvalidPort);
  context->outputPorts.resize(context->plugin.outputPorts.size(), kInvalidPort);
  context->nullPorts.clear();
  context->sinkPorts.clear();
  QVBox *box = new QVBox;
  for (LADSPA_PortIndex i = 0; i < context->descriptor->PortCount; ++i)
  {
    QString portName = QString(context->descriptor->PortNames[i]).lower();
    if (LADSPA_IS_PORT_AUDIO(context->descriptor->PortDescriptors[i]))
    {
      if (LADSPA_IS_PORT_INPUT(context->descriptor->PortDescriptors[i]))
      {
        int pluginPort = context->plugin.inputPorts.findIndex(portName);

        if (pluginPort != -1)
          context->inputPorts[pluginPort] = i;
        else
          context->nullPorts.push_back(i);
      }
      else if (LADSPA_IS_PORT_OUTPUT(context->descriptor->PortDescriptors[i]))
      {
        int pluginPort = context->plugin.outputPorts.findIndex(portName);

        if (pluginPort != -1)
          context->outputPorts[pluginPort] = i;
        else
          context->sinkPorts.push_back(i);
      }
    }
    else if (LADSPA_IS_PORT_CONTROL(context->descriptor->PortDescriptors[i]))
    {
      if (LADSPA_IS_PORT_INPUT(context->descriptor->PortDescriptors[i]))
      {
        context->controls.append(
          processControl(context->plugin, context->descriptor, portName, i, box)
          );
      }
    }
  }

  context->controls.alignAllHorizontalSliders();

  if (context->controls.empty())
  {
    applyPlugin(context);
    delete context;
  }
  else
    pluginDialog("ladspa_" + actionName, box, context->plugin.name,
                 SLOT(applyPlugin(Sonik::Edit::PluginContext*)),
                 context);
}


void Ladspa::setupActions()
{
  QSignalMapper* mapper = new QSignalMapper(this, "ladspa_mapper");
  QMap<QString, QPtrList<KAction> > transformActionLists;
  QPtrList<KAction> generate;

  scanPlugins();

  LadspaPluginMap::const_iterator it = mPlugins.begin();
  for ( ; it != mPlugins.end(); ++it)
  {
    if ((*it).ignore)
      continue;

    KAction* action;

    if ((*it).type == GENERATE)
    {
      action = new KAction((*it).name, 0,
                           mapper, SLOT(map()),
                           actionCollection(),
                           "generate_ladspa_" + it.key());
      generate.append(action);
    }
    else
    {
      action = new KAction((*it).name, 0,
                           mapper, SLOT(map()),
                           actionCollection(),
                           "transform_ladspa_" + it.key());

      // map ladspa categories
      if ((*it).category.empty())
        transformActionLists["transform_other_ladspa"].append(action);
      else if ((*it).category.size() == 1)
      {
        if ((*it).category[0] == "Amplitude")
          transformActionLists["transform_amplitude_ladspa"].append(action);
        else if ((*it).category.front() == "Frequency")
          transformActionLists["transform_pitch_ladspa"].append(action);
        else if ((*it).category.front() == "Delay" ||
                 (*it).category.front() == "Delays")
          transformActionLists["transform_delay_ladspa"].append(action);
        else
          transformActionLists["transform_other_ladspa"].append(action);
      }
      else if ((*it).category[0] == "Amplitude")
      {
        if ((*it).category[1] == "Distortions" ||
            (*it).category[1] == "Waveshapers")
          transformActionLists["transform_distortion_ladspa"].append(action);
        else if ((*it).category[1] == "Amplifiers" ||
                 (*it).category[1] == "Modulators" ||
                 (*it).category[1] == "Dynamics" )
          transformActionLists["transform_amplitude_ladspa"].append(action);
        else
          transformActionLists["transform_other_ladspa"].append(action);
      }
      else if ((*it).category[0] == "Frequency")
      {
        if ((*it).category[1] == "EQs")
          transformActionLists["transform_eq_ladspa"].append(action);
        else if ((*it).category[1] == "Filters")
          transformActionLists["transform_filter_ladspa"].append(action);
        else if ((*it).category[1] == "Pitch shifters")
          transformActionLists["transform_pitch_ladspa"].append(action);
        else
          transformActionLists["transform_other_ladspa"].append(action);
      }
      else if ((*it).category[0] == "Time" &&
               ((*it).category[1] == "Reverbs" ||
                (*it).category[1] == "Delays"  ||
                (*it).category[1] == "Flangers"))
        transformActionLists["transform_delay_ladspa"].append(action);
      else
        transformActionLists["transform_other_ladspa"].append(action);

    }

    mapper->setMapping(action, it.key());
  }

  connect(mapper, SIGNAL(mapped(const QString&)),
          this, SLOT(handleAction(const QString&)));


  QMap<QString, QPtrList<KAction> >::const_iterator actionList = transformActionLists.begin();
  for ( ; actionList != transformActionLists.end(); ++actionList)
  {
    unplugActionList(actionList.key());
    plugActionList(actionList.key(), actionList.data());
  }

  unplugActionList("generate_ladspa");
  plugActionList("generate_ladspa", generate);
}


void Ladspa::scanPlugins()
{
  scanPluginLibraries();
  scanPluginRdfs();
  scanPluginOverrides();
  determinePluginTypes();
}


void Ladspa::scanPluginLibraries()
{
  // scan for plugin libs
  QStringList libSearchPath = QStringList::split(':', mSearchPath);

  libSearchPath += QStringList::split(":", getenv("LADSPA_PATH"));
  libSearchPath += "/usr/lib/ladspa";
  libSearchPath += "/usr/local/lib/ladspa";

  QStringList::const_iterator path = libSearchPath.begin();
  for ( ; path != libSearchPath.end(); ++path)
  {
    // TODO: handle different shlib extensions
    QStringList libs = QDir(*path).entryList("*.so", QDir::Files);

    QStringList::const_iterator l = libs.begin();
    for ( ; l != libs.end(); ++l)
    {
      QString lib = *path + "/" + *l;

      LADSPA_LibraryHandle libHandle = openLadpsaLibrary(lib);
      if (libHandle)
      {
        LADSPA_DescriptorList descriptors = getLadspaDescriptors(libHandle);

        LADSPA_DescriptorList::const_iterator d = descriptors.begin();
        for ( ; d != descriptors.end(); ++d)
        {
          LadspaPlugin plugin;
          plugin.id     = (*d)->UniqueID;
          plugin.lib    = lib;
          plugin.label  = (*d)->Label;
          plugin.name   = (*d)->Name;
          plugin.ignore = false;
          plugin.type   = UNKNOWN;

          // TODO: do we actually care about L/R ports?? - assume  L, R order
          for (LADSPA_PortIndex i = 0; i < (*d)->PortCount; ++i)
          {
            if (LADSPA_IS_PORT_AUDIO((*d)->PortDescriptors[i]))
            {
              QString portName = QString((*d)->PortNames[i]).lower();
              if (LADSPA_IS_PORT_INPUT((*d)->PortDescriptors[i]))
              {
                plugin.inputPorts.push_back(portName);
              }
              else if (LADSPA_IS_PORT_OUTPUT((*d)->PortDescriptors[i]))
              {
                plugin.outputPorts.push_back(portName);
              }
            }

            // TODO: maybe process control info here - performance hit at
            // start & memory usage, but scan is currently repeated if run again
          }

          mPlugins[QString::number(plugin.id)] = plugin;
        }

        closeLadspaLibrary(libHandle);
      }
    }
  }
}


void Ladspa::scanPluginRdfs()
{
  // scan for plugin rdfs
  LrdfManager::CategoryMap categoryMap = LrdfManager::instance().categoryMap();

  LadspaPluginMap::iterator plugin = mPlugins.begin();
  // TODO: is this the wrong way round??
  for ( ; plugin != mPlugins.end(); ++plugin)
  {
    LrdfManager::CategoryMap::const_iterator cat = categoryMap.find((*plugin).id);
    if (cat != categoryMap.end())
      (*plugin).category = (*cat);
  }
}


void Ladspa::scanPluginOverrides()
{
  KXMLGUIClient* xmlParent = dynamic_cast<KXMLGUIClient*>(parent());
  assert(xmlParent);

  // scan for Sonik descriptions
  QStringList pluginDocs = xmlParent->instance()->dirs()->findAllResources(
    "data", xmlParent->instance()->instanceName() + "/ladspaplugins/*", true, false
    );

  QStringList::Iterator it = pluginDocs.begin();
  for ( ; it != pluginDocs.end(); ++it)
  {
    QDomDocument doc("ladspapackage");
    QFile file(*it);

    if (!file.open(IO_ReadOnly))
      continue;

    if (!doc.setContent(&file))
    {
      file.close();
      continue;
    }
    file.close();

    QDomElement docElem = doc.documentElement();

    QDomNode n = docElem.firstChild();
    while( !n.isNull() )
    {
      QDomElement e = n.toElement();
      if(!e.isNull() &&
         e.tagName().lower() == "plugin")
      {
        QString pluginId = e.attribute("id");
        if (!pluginId.isNull())
        {
          LadspaPluginMap::iterator plugin = mPlugins.find(pluginId);
          if (plugin != mPlugins.end())
          {
            processPluginOverride(e, (*plugin));
          }

        }
      }
      n = n.nextSibling();
    }
  }
}


void Ladspa::processPluginOverride(QDomElement& e, LadspaPlugin& plugin)
{
  QString name = e.attribute("name");
  if (!name.isNull())
    plugin.name = name;

  QString category = e.attribute("category");
  if (!category.isNull())
    plugin.category = QStringList::split("/", category);

  QString ignore = e.attribute("ignore").lower();
  if (ignore == "yes" || ignore == "true")
    plugin.ignore = true;

  // TODO: connect input to null / output to sink
  // TODO: switch between outputs eg HP & LP

  QDomNodeList inputPorts = e.elementsByTagName("inputPort");
  if (inputPorts.count() != 0)
  {
    plugin.inputPorts.clear();
    for (uint i = 0; i < inputPorts.count(); ++i)
    {
      QString name = inputPorts.item(i).toElement().attribute("name");
      if (!name.isNull())
        plugin.inputPorts.push_back(name.lower());
    }
  }

  QDomNodeList outputPorts = e.elementsByTagName("outputPort");
  if (outputPorts.count() != 0)
  {
    plugin.outputPorts.clear();
    for (uint i = 0; i < outputPorts.count(); ++i)
    {
      QString name = outputPorts.item(i).toElement().attribute("name");
      if (!name.isNull())
        plugin.outputPorts.push_back(name.lower());
    }
  }

  QDomNodeList controlPorts = e.elementsByTagName("controlPort");
  if (controlPorts.count() != 0)
  {
    plugin.controlPorts.clear();
    for (uint i = 0; i < controlPorts.count(); ++i)
    {
      QDomElement e = controlPorts.item(i).toElement();
      QString name = e.attribute("name");
      if (!name.isNull())
      {
        LadspaControlInfo ci;
        bool ok;
        ci.name = name.lower();
        if (e.hasAttribute("value"))
        {
          ci.value  = e.attribute("value").toFloat(&ok);
          if (ok)
            ci.flags |= LadspaControlInfo::VALUE;
        }
        if (e.hasAttribute("lower"))
        {
          ci.lower = e.attribute("lower").toFloat(&ok);
          if (ok)
            ci.flags |= LadspaControlInfo::LOWER;
        }
        if (e.hasAttribute("min"))
        {
          ci.lower = e.attribute("min").toFloat(&ok);
          if (ok)
            ci.flags |= LadspaControlInfo::LOWER;
        }
        if (e.hasAttribute("upper"))
        {
          ci.upper = e.attribute("upper").toFloat(&ok);
          if (ok)
            ci.flags |= LadspaControlInfo::UPPER;
        }
        if (e.hasAttribute("max"))
        {
          ci.upper = e.attribute("max").toFloat(&ok);
          if (ok)
            ci.flags |= LadspaControlInfo::UPPER;
        }
        if (e.hasAttribute("isInteger"))
        {
          QString isInteger = e.attribute("isInteger").lower();
          ci.isInteger = (isInteger == "yes" || isInteger == "true");
          ci.flags |= LadspaControlInfo::ISINTEGER;
        }
        if (e.hasAttribute("isLog"))
        {
          QString isLog = e.attribute("isLog").lower();
          ci.isLog = (isLog == "yes" || isLog == "true");
          ci.flags |= LadspaControlInfo::ISLOG;
        }
        if (e.hasAttribute("uiType"))
        {
          ci.uiType = UiControl::stringToType(e.attribute("uiType").upper());
          ci.flags |= LadspaControlInfo::UITYPE;
        }
        plugin.controlPorts.push_back(ci);
      }
    }
  }
}


void Ladspa::determinePluginTypes()
{
  QStringList deleteIds;

  LadspaPluginMap::iterator itPlugin = mPlugins.begin();
  for ( ; itPlugin != mPlugins.end(); ++itPlugin)
  {
    LadspaPlugin &plugin = *itPlugin;

    if (plugin.outputPorts.empty())
      // sink plugins are of no use to us
      deleteIds.push_back(itPlugin.key());
    else if (plugin.inputPorts.empty())
    {
      // Output only => generator
      plugin.type = GENERATE;
      deleteIds.push_back(itPlugin.key()); // not currently supported
    }
    else if (plugin.inputPorts.size() == plugin.outputPorts.size())
      plugin.type = TRANSFORM_SAME;
    else if (plugin.inputPorts.size() < plugin.outputPorts.size())
    {
      plugin.type = TRANSFORM_MAP_UP;
      deleteIds.push_back(itPlugin.key()); // not currently supported
    }
    else
    {
      plugin.type = TRANSFORM_MAP_DOWN;
      deleteIds.push_back(itPlugin.key()); // not currently supported
    }
  }

  // remove unwanted plugins
  QStringList::iterator id = deleteIds.begin();
  for ( ; id != deleteIds.end(); ++id)
    mPlugins.erase(*id);
}

Sonik::UiControl* Ladspa::processControl(const LadspaPlugin& plugin,
                                         const LADSPA_Descriptor* descriptor,
                                         const QString& portName,
                                         LADSPA_PortIndex i,
                                         QWidget* parent)
{
  // get default value from plugin, overriding with XML file if
  // present
  uint32_t        controlFlags = 0;
  LADSPA_Data     l            = -1.0e9f;
  LADSPA_Data     u            = 1.0e9f;
  LADSPA_Data     v            = 0.0f;
  int             prec         = 0;
  bool            isLog        = false;
  UiControl::Type uiType       = UiControl::HSLIDER;

  LadspaControlInfoList::const_iterator ci =
    plugin.controlPorts.findByName(portName);

  if (ci != plugin.controlPorts.end())
    controlFlags = (*ci).flags;

  LADSPA_PortRangeHintDescriptor h =
    descriptor->PortRangeHints[i].HintDescriptor;

  // bounds
  if (controlFlags & LadspaControlInfo::LOWER)
    l = (*ci).lower;
  else if (LADSPA_IS_HINT_BOUNDED_BELOW(h))
    l = descriptor->PortRangeHints[i].LowerBound;

  if (controlFlags & LadspaControlInfo::UPPER)
    u = (*ci).upper;
  else if (LADSPA_IS_HINT_BOUNDED_ABOVE(h))
    u = descriptor->PortRangeHints[i].UpperBound;

  if (LADSPA_IS_HINT_SAMPLE_RATE(h))
  {
    l *= mData->sampleRate();
    u *= mData->sampleRate();
  }

  if (u < l)
    u = l;

  // ui type
  if (controlFlags & LadspaControlInfo::UITYPE)
    uiType = (*ci).uiType;

  // logarithmic
  if (controlFlags & LadspaControlInfo::ISLOG)
    isLog = (*ci).isLog;
  else
    isLog = LADSPA_IS_HINT_LOGARITHMIC(h);

  // integer
  if (controlFlags & LadspaControlInfo::ISINTEGER)
    prec = (*ci).isInteger ? 0 : 2;
  else
    prec = LADSPA_IS_HINT_INTEGER(h) ? 0 : 2;

  // value
  if (controlFlags & LadspaControlInfo::VALUE)
  {
    v = (*ci).value;
    if (LADSPA_IS_HINT_SAMPLE_RATE(h))
      v *= mData->sampleRate();
  }
  else if (LADSPA_IS_HINT_HAS_DEFAULT(h))
  {
    if (LADSPA_IS_HINT_DEFAULT_MINIMUM(h))
      v = l;
    else if (LADSPA_IS_HINT_DEFAULT_LOW(h))
    {
      if (isLog)
        v = exp(log(l) * 0.75 + log(u) * 0.25);
      else
        v = l * 0.75 + u * 0.25;
    }
    else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(h))
    {
      if (isLog)
        v = exp(log(l) * 0.5 + log(u) * 0.5);
      else
        v = l * 0.5 + u * 0.5;
    }
    else if (LADSPA_IS_HINT_DEFAULT_HIGH(h))
    {
      if (isLog)
        v = exp(log(l) * 0.25 + log(u) * 0.75);
      else
        v = l * 0.25 + u * 0.75;
    }
    else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(h))
      v = u;
    else if (LADSPA_IS_HINT_DEFAULT_0(h))
      v = 0.0f;
    else if (LADSPA_IS_HINT_DEFAULT_1(h))
      v = 1.0f;
    else if (LADSPA_IS_HINT_DEFAULT_100(h))
      v = 100.0f;
    else if (LADSPA_IS_HINT_DEFAULT_440(h))
      v = 440.0f;
  }

  if (v < l)
    v = l;
  else if (v > u)
    v = u;

  // reduce precision for large ranges
  //   (controls should probably use log scale insteal)
  if (QMAX(fabs(l), fabs(u)) > 1e6)
  {
    prec = 0;
  }

  kdDebug(60606) << "Ladspa::handleAction: "
                 << descriptor->PortNames[i] << ", "
                 << v << ", " << l << ", " << u << ", "
                 << uiType << ", " << isLog
                 << "\n";

  UiControl* c =
    new UiControl(QString::number(i), descriptor->PortNames[i], uiType,
                  v, l, u, prec, isLog, parent);

  return c;
}

void Ladspa::applyPlugin(Sonik::Edit::PluginContext* context)
{
  LadspaPluginContext* ladspaContext =
    static_cast<LadspaPluginContext *>(context);

  LADSPA_Data* controlPorts = new LADSPA_Data[ladspaContext->descriptor->PortCount];

  UiControlPtrList::const_iterator itUi = ladspaContext->controls.begin();
  for (LADSPA_PortIndex i = 0; i < ladspaContext->descriptor->PortCount; ++i)
  {
    if (LADSPA_IS_PORT_CONTROL(ladspaContext->descriptor->PortDescriptors[i]) &&
        LADSPA_IS_PORT_INPUT(ladspaContext->descriptor->PortDescriptors[i]))
    {
      controlPorts[i] = (*itUi)->value();
      ++itUi;
    }
  }

  if (ladspaContext->plugin.type == TRANSFORM_SAME)
  {
    assert(ladspaContext->inputPorts.size() == ladspaContext->outputPorts.size());
    assert(mData->channels() % ladspaContext->inputPorts.size() == 0);

    mActionManager->beginCompoundAction(ladspaContext->plugin.name);

    QValueVector<uint8_t> channels = Sonik::sequence((uint8_t)0, mData->channels());

    LadspaProcessor proc(ladspaContext->descriptor,
                         controlPorts,
                         ladspaContext->inputPorts,
                         ladspaContext->outputPorts,
                         ladspaContext->nullPorts,
                         ladspaContext->sinkPorts);
    if (!apply(proc))
    {
      KMessageBox::error(
        0,
        i18n("Error applying %1 in LADSPA plugin library %2")
        .arg(ladspaContext->plugin.label)
        .arg(ladspaContext->plugin.lib)
        );
    }

    mActionManager->endCompoundAction();
  }
  // TODO: channel adding / removing plugins
  // TODO: generator plugins

  delete[] controlPorts;
}


//
// Factory definition
//
K_EXPORT_COMPONENT_FACTORY(libsonik_editladspa,
                           KGenericFactory<Ladspa>(
                             "sonikpart-edit-ladspa")
                           );
