// ------------------------------------------------------------------------
// audiofx_ladspa.cpp: Wrapper class for LADSPA plugins
// Copyright (C) 2000-2004,2011,2020 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 3
//
// References:
//     http://www.ladspa.org
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <dlfcn.h>
#include <kvu_utils.h>
#include <kvu_dbc.h>
#include <kvu_numtostr.h>
#include "samplebuffer.h"
#include "audiofx_ladspa.h"
#include "eca-error.h"
#include "eca-logger.h"

EFFECT_LADSPA::EFFECT_LADSPA (const LADSPA_Descriptor *pdesc) throw(ECA_ERROR&)
{
  plugin_desc = pdesc;
  if ((plugin_desc->Properties & LADSPA_PROPERTY_INPLACE_BROKEN) ==
      LADSPA_PROPERTY_INPLACE_BROKEN)
    throw(ECA_ERROR("AUDIOFX_LADSPA", "Inplace-broken plugins not supported."));

  /* FIXME: strip linefeeds and other forbidden characters; write down to
   *        to ECA_OBJECT docs what chars are allowed and what are not... */
  name_rep = string(plugin_desc->Name);
  unique_rep = string(plugin_desc->Label);
  maker_rep = string(plugin_desc->Maker);
  unique_number_rep = static_cast<long int>(plugin_desc->UniqueID);
  buffer_repp = 0;

  init_ports();
}

EFFECT_LADSPA::~EFFECT_LADSPA (void)
{
  release();
  
  if (plugin_desc != 0) {
    for(unsigned int n = 0; n < plugins_rep.size(); n++) {
      if (plugin_desc->deactivate != 0) 
	plugin_desc->deactivate(plugins_rep[n]);
      if (plugin_desc->cleanup != 0)
	plugin_desc->cleanup(plugins_rep[n]);
    }
  }
}

std::string EFFECT_LADSPA::description(void) const
{
  return name_rep + " - Author: '" + maker_rep + "'";
}

EFFECT_LADSPA* EFFECT_LADSPA::clone(void) const
{ 
  EFFECT_LADSPA* result = new EFFECT_LADSPA(plugin_desc);
  for(int n = 0; n < number_of_params(); n++) {
    result->set_parameter(n + 1, get_parameter(n + 1));
  }
  return result;
}

void EFFECT_LADSPA::init_ports(void)
{
  // note: run from plugin constructor

  port_count_rep = plugin_desc->PortCount;
  in_audio_ports = 0;
  out_audio_ports = 0;

  for(unsigned long m = 0; m < port_count_rep; m++) {
    if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_AUDIO) == LADSPA_PORT_AUDIO) {
      if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_INPUT) == LADSPA_PORT_INPUT)
	++in_audio_ports;
      else
	++out_audio_ports;
    }

    if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_CONTROL) == LADSPA_PORT_CONTROL) {

      struct PARAM_DESCRIPTION pd; 
      parse_parameter_hint_information(m, params.size() + 1, &pd);

      params.push_back(pd.default_value);
      param_descs_rep.push_back(pd);
      if (params.size() > 1) param_names_rep += ",";
      string tmp (kvu_string_search_and_replace(string(plugin_desc->PortNames[m]), ",", "\\,"));
      param_names_rep += kvu_string_search_and_replace(tmp, ":", "\\:");
    }
  }
}

void EFFECT_LADSPA::parse_parameter_hint_information(int portnum, int paramnum, struct PARAM_DESCRIPTION *pd)
{
  LADSPA_PortRangeHintDescriptor hintdescriptor = plugin_desc->PortRangeHints[portnum].HintDescriptor;

  /* if srate not set, use 44.1kHz (used only for calculating
   * param hint values */
  SAMPLE_SPECS::sample_rate_t srate = samples_per_second();
  /* FIXME: this is just ugly! */
  if (srate <= 0) { srate = 44100; }
  
  /** 
   * For LADSPA v1.1, parameter hint information is 
   * used as advertised by the plugin. LADSPA v1.0 
   * API doesn't specify how to initialize control
   * ports to sane initial values, so we just try to 
   * make an educated guess based on the lower and
   * upper bounds.
   *
   * 1) v1.1 hint information available
   * 2) lowb == x and upperb == n/a
   *    a) x < 0, initval = 0
   *    b) x >= 0, initval = x
   * 3) lowb == n/a and upperb == x
   *    a) x > 0, initval = 0
   *    b) x <= 0, initval = x
   * 4) lowb == x and upperb == y 
   *    a) x < 0 and y > 0, initval = 0
   *    b) x < 0 and y < 0, initval = y
   *    c) x > 0 and y > 0, initval = x
   * 5) lowb == n/a and upperb == n/a, initval = 1
   */

  /* parameter name */
  pd->description = string(plugin_desc->PortNames[portnum]);

  /* upper and lower bounds */
  if (LADSPA_IS_HINT_BOUNDED_BELOW(hintdescriptor)) {
    pd->bounded_below = true;

    if (LADSPA_IS_HINT_SAMPLE_RATE(hintdescriptor)) 
      pd->lower_bound = plugin_desc->PortRangeHints[portnum].LowerBound * srate;
    else
      pd->lower_bound = plugin_desc->PortRangeHints[portnum].LowerBound;
  }
  else {
    pd->bounded_below = false;
  }

  if (LADSPA_IS_HINT_BOUNDED_ABOVE(hintdescriptor)) {
    pd->bounded_above = true;

    if (LADSPA_IS_HINT_SAMPLE_RATE(hintdescriptor)) 
      pd->upper_bound = plugin_desc->PortRangeHints[portnum].UpperBound * srate;
    else
      pd->upper_bound = plugin_desc->PortRangeHints[portnum].UpperBound;
  }
  else {
    pd->bounded_above = false;
  }

  /* defaults - case 1 */
  if (LADSPA_IS_HINT_HAS_DEFAULT(hintdescriptor)) {
    if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hintdescriptor)) {
      pd->default_value = pd->lower_bound;
    }

    /* FIXME: add support for logarithmic defaults */

    else if (LADSPA_IS_HINT_DEFAULT_LOW(hintdescriptor)) {
      pd->default_value = pd->lower_bound * 0.75f + pd->upper_bound * 0.25f;
    }
    else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hintdescriptor)) {
      pd->default_value = pd->lower_bound * 0.50f + pd->upper_bound * 0.50f;
    }
    else if (LADSPA_IS_HINT_DEFAULT_HIGH(hintdescriptor)) {
      pd->default_value = pd->lower_bound * 0.25f + pd->upper_bound * 0.75f;
    }
    else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hintdescriptor)) {
      pd->default_value = pd->upper_bound;
    }
    else if (LADSPA_IS_HINT_DEFAULT_0(hintdescriptor)) {
      pd->default_value = 0.0f;
    }
    else if (LADSPA_IS_HINT_DEFAULT_1(hintdescriptor)) {
      pd->default_value = 1.0f;
    }
    else if (LADSPA_IS_HINT_DEFAULT_100(hintdescriptor)) {
      pd->default_value = 100.0f;
    }
    else if (LADSPA_IS_HINT_DEFAULT_440(hintdescriptor)) {
      pd->default_value = 440.0f;
    }
    else {
      ECA_LOG_MSG(ECA_LOGGER::info, 
		  "No LADSPA hint info found for plugin '" + name_rep + "'.");
    }
  }

  /* defaults - case 2 */
  else if (LADSPA_IS_HINT_BOUNDED_BELOW(hintdescriptor) &&
	   !LADSPA_IS_HINT_BOUNDED_ABOVE(hintdescriptor)) {

    if (pd->lower_bound < 0) pd->default_value = 0.0f;
    else pd->default_value = pd->lower_bound;
  }

  /* defaults - case 3 */
  else if (!LADSPA_IS_HINT_BOUNDED_BELOW(hintdescriptor) &&
	   LADSPA_IS_HINT_BOUNDED_ABOVE(hintdescriptor)) {

    if (pd->upper_bound > 0) pd->default_value = 0.0f;
    else pd->default_value = pd->upper_bound;
  }

  /* defaults - case 4 */
  else if (LADSPA_IS_HINT_BOUNDED_BELOW(hintdescriptor) &&
	   LADSPA_IS_HINT_BOUNDED_ABOVE(hintdescriptor)) {

    if (pd->lower_bound < 0 && pd->upper_bound > 0) pd->default_value = 0.0f;
    else if (pd->lower_bound < 0 && pd->upper_bound < 0) pd->default_value = pd->upper_bound;
    else pd->default_value = pd->lower_bound;
  }

  /* defaults - case 5 */
  else {
    DBC_CHECK(!LADSPA_IS_HINT_BOUNDED_BELOW(hintdescriptor) &&
	      !LADSPA_IS_HINT_BOUNDED_ABOVE(hintdescriptor));

    if (LADSPA_IS_HINT_SAMPLE_RATE(hintdescriptor)) 
      pd->default_value = srate;
    else
      pd->default_value = 1.0f;
  }

  if (LADSPA_IS_HINT_TOGGLED(hintdescriptor))
    pd->toggled = true;
  else
    pd->toggled = false;
  
  if (LADSPA_IS_HINT_INTEGER(hintdescriptor))
    pd->integer = true;
  else
    pd->integer = false;
  
  if (LADSPA_IS_HINT_LOGARITHMIC(hintdescriptor))
    pd->logarithmic = true;
  else
    pd->logarithmic = false;

  if ((plugin_desc->PortDescriptors[portnum] & LADSPA_PORT_OUTPUT) == LADSPA_PORT_OUTPUT)
    pd->output = true;
  else
    pd->output = false;
}

void EFFECT_LADSPA::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  DBC_CHECK(param >= 0);
  DBC_CHECK(param <= static_cast<int>(param_descs_rep.size()));
  *pd = param_descs_rep[param - 1];
}

void EFFECT_LADSPA::set_parameter(int param, CHAIN_OPERATOR::parameter_t value)
{
  if (param > 0 && (param - 1 < static_cast<int>(params.size()))) {
    //  cerr << "ladspa: setting param " << param << " to " << value << "." << endl;
    params[param - 1] = value;
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_LADSPA::get_parameter(int param) const 
{
  if (param > 0 && (param - 1 < static_cast<int>(params.size()))) {
    //  cerr << "ladspa: getting param " << param << " with value " << params[param - 1] << "." << endl;
    return(params[param - 1]);
  }
  return(0.0);
}

int EFFECT_LADSPA::output_channels(int i_channels) const
{
  // note: We have two separate cases: either one plugin 
  //       is instantiated for each channel, or one plugin
  //       per chain. See EFFECT_LADSPA::init().

  if (in_audio_ports > 1 ||
      out_audio_ports > 1) {

    return out_audio_ports;
  }
  
  return i_channels;
}

void EFFECT_LADSPA::init(SAMPLE_BUFFER *insample)
{ 
  EFFECT_BASE::init(insample);

  DBC_CHECK(samples_per_second() > 0);

  if (buffer_repp != insample) {
    release();
    buffer_repp = insample;
    buffer_repp->get_pointer_reflock();
  }

  if (plugin_desc != 0) {
    for(unsigned int n = 0; n < plugins_rep.size(); n++) {
      if (plugin_desc->deactivate != 0) plugin_desc->deactivate(plugins_rep[n]);
      plugin_desc->cleanup(plugins_rep[n]);
    }
  }

  // NOTE: the fancy definition :)
  //       if ((in_audio_ports > 1 &&
  //            in_audio_ports <= channels() &&
  //            out_audio_ports <= channels()) ||
  //           (out_audio_ports > 1 &&
  //            in_audio_ports <= channels() &&
  //            out_audio_ports <= channels())) {}

  if (in_audio_ports > 1 ||
      out_audio_ports > 1) {
    plugins_rep.resize(1);
    plugins_rep[0] = reinterpret_cast<LADSPA_Handle*>(plugin_desc->instantiate(plugin_desc, samples_per_second()));
    int inport = 0;
    int outport = 0;
    for(unsigned long m = 0; m < port_count_rep; m++) {
      if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_AUDIO) == LADSPA_PORT_AUDIO) {
	if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_INPUT) == LADSPA_PORT_INPUT) {
	  if (inport < channels())
	    plugin_desc->connect_port(plugins_rep[0], m, buffer_repp->buffer[inport]);
	  ++inport;
	}
	else {
	  if (outport < channels())
	    plugin_desc->connect_port(plugins_rep[0], m, buffer_repp->buffer[outport]);
	  ++outport;
	}
      }
    }
    
    if (inport > channels())
      ECA_LOG_MSG(ECA_LOGGER::info, 
		  "WARNING: chain has less channels than plugin has input ports ("
		  + name() + ").");
    if (outport > channels())
      ECA_LOG_MSG(ECA_LOGGER::info, 
		  "WARNING: chain has less channels than plugin has output ports ("
		  + name() + ").");
  } 
  else {
    plugins_rep.resize(channels());
    for(unsigned int n = 0; n < plugins_rep.size(); n++) {
      plugins_rep[n] = reinterpret_cast<LADSPA_Handle*>(plugin_desc->instantiate(plugin_desc, samples_per_second()));

      for(unsigned long m = 0; m < port_count_rep; m++) {
	if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_AUDIO) == LADSPA_PORT_AUDIO) {
	  plugin_desc->connect_port(plugins_rep[n], m, buffer_repp->buffer[n]);
	}
      }
    }
  }

  ECA_LOG_MSG(ECA_LOGGER::system_objects, 
		"Instantiated " +
		kvu_numtostr(plugins_rep.size()) + 
		" LADSPA plugin(s), each with " + 
		kvu_numtostr(in_audio_ports) + 
		" audio input port(s) and " +
		kvu_numtostr(out_audio_ports) +
		" output port(s), to chain with " +
		kvu_numtostr(channels()) +
		" channel(s) and srate of " +
		kvu_numtostr(samples_per_second()) +
		".");

  int data_index = 0;
  for(unsigned long m = 0; m < port_count_rep; m++) {
    if ((plugin_desc->PortDescriptors[m] & LADSPA_PORT_CONTROL) ==
	LADSPA_PORT_CONTROL) {
      for(unsigned int n = 0; n < plugins_rep.size(); n++) {
	plugin_desc->connect_port(plugins_rep[n], m, &(params[data_index]));
      }
      ++data_index;
    }
  }
  for(unsigned long m = 0; m < plugins_rep.size(); m++)
    if (plugin_desc->activate != 0) plugin_desc->activate(plugins_rep[m]);
}

void EFFECT_LADSPA::release(void)
{
  if (buffer_repp != 0) {
    buffer_repp->release_pointer_reflock();
  }
  buffer_repp = 0;
}

void EFFECT_LADSPA::process(void)
{
  for(unsigned long m = 0; m < plugins_rep.size(); m++)
    plugin_desc->run(plugins_rep[m], buffer_repp->length_in_samples());
}
