/* 
 * drive.c
 *
 * example implementation of a LADSPA v1.2 plugin, with special consideration
 * to documenting how the LADSPA architecture works.
 *
 * the plugin implemented is a simple second-order saturating waveshaper 
 * also offering a linear amplification control. it doesn't even sound so
 * bad.
 *
 * for the sake of simplicity, antialiasing or parameter smoothing 
 * refinements have been omitted.
 *
 * compile with:
 *   gcc -Wall -nostartfiles -shared -lm -o drive.so drive.c
 * 
 * (c) 2004 Tim Goetze, all rights reversed: this code is in the public
 * domain.
 */

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

#include "ladspa.h"

/* this structure holds all the data the plugin needs to be operational. 
 * 
 * our plugin exhibits 4 ports:
 * - audio: 1 in and 1 out, 
 * - control: 1 parameter 'drive' in and 1 'gain' in.
 * 
 * it also stores the 'adding_gain', for more information about this please 
 * be referred to the drive_run_adding() method below.
 */
typedef struct 
{
	LADSPA_Data * ports[4];
	LADSPA_Data adding_gain;
} Drive;

/* the 'instantiate' method creates an instance of the plugin and initializes
 * it to a sane state. in this simple case of memory-less distortion, no 
 * initialization other than allocating a Drive structure is needed.
 *
 * plugins that need to do more initialization work before being able to
 * process should do so in the 'activate' method, but it's not a strict
 * requirement.
 *
 * note that this is the only point in time the plugin instance is told about 
 * the sample rate at which it operates.
 */
static LADSPA_Handle
drive_instantiate (
		const struct _LADSPA_Descriptor * Descriptor,
		unsigned long SampleRate)
{
	Drive * d = (Drive *) calloc (1, sizeof (Drive));
	return (LADSPA_Handle) d;
}

/* the 'connect_port' method is called by the host to tell the plugin where 
 * to fetch port data from, or where to put results in the outbound port case.
 * 
 * our simple plugin only stores the pointer the host passes to the method
 * for later use in the processing methods.
 */
static void
drive_connect_port (
		LADSPA_Handle Instance,
		unsigned long Port,
		LADSPA_Data * DataLocation)
{
	Drive * d = (Drive *) Instance;
	d->ports[Port] = DataLocation;
}

/* the 'run' method is called to execute the plugin for a block of sample
 * data. this call is preceded by 'connect_port' for all ports defined by
 * the plugin, and a call to the 'activate' method of the plugin if it 
 * provides one. 
 *
 * a plugin should keep the number of system calls made from the 'run'
 * methods to an absolute minimum. in particular memory allocation functions
 * like 'malloc' and 'free' imply non-realtime use only, because of their
 * potentially blocking nature.
 *
 * of course, you can decide to write a non-realtime plugin, and use whatever
 * resources you want in run(). in that case, the plugin is required not to set 
 * the flag 'LADSPA_PROPERTY_HARD_RT_CAPABLE' (you'll find it further down in 
 * the descriptor structure for the plugin). but non-realtime is only half the 
 * fun at most. :)
 */
static void
drive_run (
		LADSPA_Handle Instance,
		unsigned long SampleCount)
{
	Drive * d = (Drive *) Instance;
	
	LADSPA_Data * src = d->ports[0];
	LADSPA_Data drive = 0.5f * *(d->ports[1]);
	LADSPA_Data gain = pow (10, 0.05 * *(d->ports[2]));
	LADSPA_Data * dest = d->ports[3];

	LADSPA_Data x;
	unsigned long i;
	
	/* make sure we don't divide by zero */
	if (drive == 1) drive = .9999;
	
	/* correction for attenuation through the drive algorithm */
	gain *= 1.0f / (1.0f - drive);
	
	for (i = 0; i < SampleCount; ++i)
	{
		x = src[i];
		dest[i] = gain * (x - drive * fabsf (x) * x);
	}
}

/* the 'run_adding' method is expected to provide the exact same functionality
 * as the 'run' method, with one exception: the destination sample buffers
 * (outbound audio port data locations) contain sample data that the plugin
 * should mix its output with. 'adding_gain', set by the host via a separate
 * method, tells the plugin by what value to scale its output before adding
 * it to the destination sample buffers.
 */
static void
drive_run_adding (
		LADSPA_Handle Instance,
		unsigned long SampleCount)
{
	Drive * d = (Drive *) Instance;
	
	LADSPA_Data * src = d->ports[0];
	LADSPA_Data drive = 0.5f * fabs (*(d->ports[1]));
	LADSPA_Data gain = pow (10, 0.05 * *(d->ports[2]));
	LADSPA_Data * dest = d->ports[3];

	LADSPA_Data x;
	unsigned long i;
	
	/* make sure we don't divide by zero */
	if (drive == 1) drive = .9999;
	
	/* correction for attenuation through the drive algorithm, note 
	 * the use of 'adding_gain' instead of 1 as in drive_run() */
	gain *= d->adding_gain / (1.0f - drive);
	
	for (i = 0; i < SampleCount; ++i)
	{
		x = src[i];
		/* note the '+=' instead of '=' as in drive_run() */
		dest[i] += gain * (x - drive * fabsf (x) * x);
	}
}

/* this method is used to communicate the scaling ratio to be used by the
 * 'run_adding' method. 
 */
static void
drive_set_run_adding_gain (
		LADSPA_Handle Instance,
		LADSPA_Data adding_gain)
{
	Drive * d = (Drive *) Instance;
	d->adding_gain = adding_gain;
}

/* at the end of the plugin instance lifecycle, the 'cleanup' method is
 * called to free all memory and other resources used by the plugin.
 */
static void
drive_cleanup (
		LADSPA_Handle Instance)
{
	free ((void *) Instance);
}

/* LADSPA v1.1 port description structures ***********************************/

/* for each port: the port name. 
 *
 * we'll refer to these in the following documentation of port properties. 
 */
static const char *
const port_names [] = {
	"input",
	"drive",
	"gain",
	"output"
};

/* this array describes the most basic port features: whether a port is an
 * input or output, and whether it points to a sample buffer (audio) or to
 * a single value (control).
 */
static int
port_descriptors [] = {
	LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO,   /* "input" */
	LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, /* "drive" */
	LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, /* "gain" */
	LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO   /* "output" */
};

/* the member structures of this array tell the host more about port 
 * properties. the nature of the parameter controlled through the port is 
 * expressed via 'hint' bits: a toggling switch would be indicated by 
 * LADSPA_HINT_TOGGLED, or an n-value option by LADSPA_HINT_INTEGER etc.
 * in addition, upper and lower bounds for a parameter value will be
 * documented here.
 * 
 * LADSPA v1.1 default port value hints are expressed here, too.
 */
static const LADSPA_PortRangeHint 
port_range_hints [] = {
	/* "input" */	
	{0, -1, 1}, 
	/* "drive" */
	{
	      /* hints */
	      LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | 
		  LADSPA_HINT_DEFAULT_LOW,
	      /* lower, upper bound */
	      0, 1
	},
	/* "gain" */
	{
	      /* hints */
	      LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | 
		  LADSPA_HINT_DEFAULT_0,
	      /* lower, upper bound */
	      -36, 36
	},
	/* "output" */
	{0, -1, 1}
};

#if defined (LADSPA_PROPERTY_HAVE_VERSION) /* means >= v1.2 */

/* our example plugin chooses to document the meaning of the minimum and
 * maximum values (0, 1) the "drive" port supports.
 */
static const LADSPA_PortValueEnum 
drive_port_enum [] = {
	{"clean", 0},
	{"full",  1},
	{ } /* NULL label marks end of the enumeration */ 
};

/* additional useful information about each port on the plugin is communicated
 * to the host via this array. the members are:
 * 
 * - the default port value.
 * - a string indicating the unit of the port parameter, or NULL if the
 *   parameter is dimension-less.
 * - an optional pointer to an array like the one a few lines above.
 */
static const LADSPA_PortInfo
port_info [] = {
	{ },                          /* audio in, nothing to do */
	{0.1, NULL, drive_port_enum}, /* default 0.1, no unit, drive markers */
	{0.0, "dB"},                  /* "gain", default 0, deciBels */
	{ },                          /* audio out, nothing to do */
};

#endif /* LADSPA >= v1.2 */

/* the LADSPA_Descriptor structure puts together everything we have defined
 * so far.
 */
static LADSPA_Descriptor 
drive_descriptor =
{
	/* this is *not* a valid UniqueID for a distribution-ready plugin. 
	 * it's ok for testing purposes however. */
	.UniqueID = 1,
	.Label = "Drive",

#if defined (LADSPA_PROPERTY_HAVE_VERSION)
	.Properties = 
	    LADSPA_PROPERTY_HARD_RT_CAPABLE | LADSPA_PROPERTY_HAVE_VERSION,
#else
	.Properties = LADSPA_PROPERTY_HARD_RT_CAPABLE,
#endif

	.Name = "2nd order saturating waveshaper",
	.Maker = "Your Name Here <email@address>",
	.Copyright = "Public Domain, 2004",
	
	.PortCount = 4,
	.PortDescriptors = port_descriptors,
	.PortNames = port_names,
	.PortRangeHints = port_range_hints,
	.ImplementationData = 0, 
	
	.instantiate = drive_instantiate,
	.connect_port = drive_connect_port,
	.activate = 0,
	.run = drive_run,
	.run_adding = drive_run_adding,
	.set_run_adding_gain = drive_set_run_adding_gain,
	.deactivate = 0,
	.cleanup = drive_cleanup,

#if defined (LADSPA_PROPERTY_HAVE_VERSION)
	.Version = {1, 2},
	.PortInfo = port_info,
#endif 
};

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

/* this function is how the host gets to the plugin. you'll notice it is 
 * the only symbol not declared 'static', because it's the only thing a
 * host needs to 'see' when accessing the plugin.
 */
const LADSPA_Descriptor *
ladspa_descriptor (unsigned long index)
{
	/* this collection has only one plugin, so any index but 0 returns
	 * NULL. 
	 */
	if (index == 0)
		return &drive_descriptor;

	return NULL;
}
