/* canyondelay.cpp

   Canyon Delay - Deep Stereo Cross Delay
   Copyright (c) 1999, 2000 David A. Bartold

   Computer Music Toolkit - a library of LADSPA plugins. Copyright (C)
   2000 Richard W.E. Furse. The author may be contacted at
   richard@muse.demon.co.uk.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public Licence as
   published by the Free Software Foundation; either version 2 of the
   Licence, or (at your option) any later version.

   This library 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 library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA. */

/*****************************************************************************/


#include <stdlib.h>
#include <math.h>
#include "cmt.h"

#define PORT_IN_LEFT      0
#define PORT_IN_RIGHT     1
#define PORT_OUT_LEFT     2
#define PORT_OUT_RIGHT    3
#define PORT_LTR_TIME     4
#define PORT_LTR_FEEDBACK 5
#define PORT_RTL_TIME     6
#define PORT_RTL_FEEDBACK 7
#define PORT_CUTOFF       8

#define NUM_PORTS         9

#ifndef PI
#define PI 3.14159265358979
#endif

class CanyonDelay : public CMT_PluginInstance {
  LADSPA_Data  sample_rate;

  long         datasize;
  LADSPA_Data *data_l;
  LADSPA_Data *data_r;
  LADSPA_Data  accum_l;
  LADSPA_Data  accum_r;

  int pos;

public:
  CanyonDelay(const LADSPA_Descriptor *,
              unsigned long s_rate)
    : CMT_PluginInstance(NUM_PORTS),
      sample_rate(s_rate),
      datasize(s_rate),
      data_l(new LADSPA_Data[datasize]),
      data_r(new LADSPA_Data[datasize]),
      accum_l(0.0),
      accum_r(0.0),
      pos(0) {
    for (long i = 0; i < datasize; i++)
      data_l[i] = data_r[i] = 0.0;
  }

  ~CanyonDelay() {
    delete[] data_l;
    delete[] data_r;
  }

  static void
  activate(LADSPA_Handle Instance) {
    CanyonDelay *delay = (CanyonDelay*) Instance;

    for (long i = 0; i < delay->datasize; i++)
      delay->data_l[i] = delay->data_r[i] = 0.0;

    delay->accum_l = 0.0;
    delay->accum_r = 0.0;
    delay->pos = 0;
  }

  static void
  run(LADSPA_Handle Instance,
      unsigned long SampleCount) {
    CanyonDelay *delay = (CanyonDelay*) Instance;
    LADSPA_Data **ports;
    unsigned long i;
    int l_to_r_offset, r_to_l_offset;
    LADSPA_Data ltr_invmag, rtl_invmag;
    LADSPA_Data filter_mag, filter_invmag;

    ports = delay->m_ppfPorts;

    l_to_r_offset = (int) (*ports[PORT_LTR_TIME] * delay->sample_rate);
    r_to_l_offset = (int) (*ports[PORT_RTL_TIME] * delay->sample_rate);

    ltr_invmag = 1.0 - fabs (*ports[PORT_LTR_FEEDBACK]);
    rtl_invmag = 1.0 - fabs (*ports[PORT_RTL_FEEDBACK]);

    filter_invmag = pow (0.5, (4.0 * PI * *ports[PORT_CUTOFF]) / delay->sample_rate);
    filter_mag = 1.0 - filter_invmag;

    for (i = 0; i < SampleCount; i++)
      {
        LADSPA_Data accum_l, accum_r;
        int pos1, pos2;

        accum_l = ports[PORT_IN_LEFT][i];
        accum_r = ports[PORT_IN_RIGHT][i];

        pos1 = delay->pos - r_to_l_offset + delay->datasize;
        while (pos1 >= delay->datasize)
          pos1 -= delay->datasize;
      
        pos2 = delay->pos - l_to_r_offset + delay->datasize;
        while (pos2 >= delay->datasize)
          pos2 -= delay->datasize;

        /* Mix channels with past samples. */
        accum_l = accum_l * rtl_invmag + delay->data_r[pos1] * *ports[PORT_RTL_FEEDBACK];
        accum_r = accum_r * ltr_invmag + delay->data_l[pos2] * *ports[PORT_LTR_FEEDBACK];

        /* Low-pass filter output. */
        accum_l = delay->accum_l * filter_invmag + accum_l * filter_mag;
        accum_r = delay->accum_r * filter_invmag + accum_r * filter_mag;

        /* Store IIR samples. */
        delay->accum_l = accum_l;
        delay->accum_r = accum_r;
      
        /* Store samples in arrays. */
        delay->data_l[delay->pos] = accum_l;
        delay->data_r[delay->pos] = accum_r;

        ports[PORT_OUT_LEFT][i] = accum_l;
        ports[PORT_OUT_RIGHT][i] = accum_r;

        delay->pos++;
        if (delay->pos >= delay->datasize)
          delay->pos -= delay->datasize;
      }
  }
};


static LADSPA_PortDescriptor g_psPortDescriptors[] =
{
  LADSPA_PORT_AUDIO | LADSPA_PORT_INPUT,
  LADSPA_PORT_AUDIO | LADSPA_PORT_INPUT,
  LADSPA_PORT_AUDIO | LADSPA_PORT_OUTPUT,
  LADSPA_PORT_AUDIO | LADSPA_PORT_OUTPUT,
  LADSPA_PORT_CONTROL | LADSPA_PORT_INPUT,
  LADSPA_PORT_CONTROL | LADSPA_PORT_INPUT,
  LADSPA_PORT_CONTROL | LADSPA_PORT_INPUT,
  LADSPA_PORT_CONTROL | LADSPA_PORT_INPUT,
  LADSPA_PORT_CONTROL | LADSPA_PORT_INPUT
};

static const char * const g_psPortNames[] =
{
  "In (Left)",
  "In (Right)",
  "Out (Left)",
  "Out (Right)",
  "Left to Right Time (Seconds)",
  "Left to Right Feedback (Percent)",
  "Right to Left Time (Seconds)",
  "Right to Left Feedback (Percent)",
  "Low-Pass Cutoff (Hz)"
};

static LADSPA_PortRangeHint g_psPortRangeHints[] =
{
  /* Hints, Lower bound, Upper bound */
  { 0, 0.0, 0.0 },
  { 0, 0.0, 0.0 },
  { 0, 0.0, 0.0 },
  { 0, 0.0, 0.0 },
  { LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW, 0.01, 0.99 },
  { LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW, -1.0, 1.0 },
  { LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW, 0.01, 0.99 },
  { LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW, -1.0, 1.0 },
  { LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW, 1.0, 5000.0 }
};

void
initialise_canyondelay() {
  CMT_Descriptor * psDescriptor;

  psDescriptor = new CMT_Descriptor
      (1225,
       "canyon_delay",
       LADSPA_PROPERTY_HARD_RT_CAPABLE,
       "Canyon Delay",
       CMT_MAKER("David A. Bartold"),
       CMT_COPYRIGHT("1999, 2000", "David A. Bartold"),
       NULL,
       CMT_Instantiate<CanyonDelay>,
       CanyonDelay::activate,
       CanyonDelay::run,
       NULL,
       NULL,
       NULL);

  for (int i = 0; i < NUM_PORTS; i++)
    psDescriptor->addPort(
      g_psPortDescriptors[i],
      g_psPortNames[i],
      g_psPortRangeHints[i].HintDescriptor,
      g_psPortRangeHints[i].LowerBound,
      g_psPortRangeHints[i].UpperBound);

  registerNewPluginDescriptor(psDescriptor);
}
