/*                                                     -*- linux-c -*-
    Copyright (C) 2004 Tom Szilagyi
    
    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: tap_echo.c,v 1.7 2004/12/06 09:32:41 tszilagyi Exp $
*/


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

#include <ladspa.h>
#include "tap_utils.h"

/* The Unique ID of the plugin: */

#define ID_STEREO       2143

/* The port numbers for the plugin: */

#define DELAYTIME_L 0
#define FEEDBACK_L  1
#define DELAYTIME_R 2
#define FEEDBACK_R  3
#define STRENGTH_L  4
#define STRENGTH_R  5
#define DRYLEVEL    6
#define MODE        7
#define HAAS        8
#define REV_OUTCH   9

#define INPUT_L     10
#define OUTPUT_L    11
#define INPUT_R     12
#define OUTPUT_R    13

/* Total number of ports */

#define PORTCOUNT_STEREO 14


/* Maximum delay (ms) */

#define MAX_DELAY        2000


/* The structure used to hold port connection information and state */

typedef struct {
	LADSPA_Data * delaytime_L;
	LADSPA_Data * delaytime_R;
	LADSPA_Data * feedback_L;
	LADSPA_Data * feedback_R;
	LADSPA_Data * strength_L;
	LADSPA_Data * strength_R;
	LADSPA_Data * drylevel;
	LADSPA_Data * mode;
	LADSPA_Data * haas;
	LADSPA_Data * rev_outch;

	LADSPA_Data * input_L;
	LADSPA_Data * output_L;
	LADSPA_Data * input_R;
	LADSPA_Data * output_R;

	unsigned long sample_rate;
	LADSPA_Data mpx_out_L;
	LADSPA_Data mpx_out_R;

	LADSPA_Data * ringbuffer_L;
	LADSPA_Data * ringbuffer_R;
	unsigned long * buffer_pos_L;
	unsigned long * buffer_pos_R;

	LADSPA_Data run_adding_gain;
} Echo;




/* Construct a new plugin instance. */
LADSPA_Handle 
instantiate_Echo(const LADSPA_Descriptor * Descriptor,
		 unsigned long             SampleRate) {
	
	LADSPA_Handle * ptr;
	
	if ((ptr = malloc(sizeof(Echo))) != NULL) {
		((Echo *)ptr)->sample_rate = SampleRate;
		((Echo *)ptr)->run_adding_gain = 1.0f;

		/* allocate memory for ringbuffers and related dynamic vars */
		if ((((Echo *)ptr)->ringbuffer_L = 
		     calloc(MAX_DELAY * ((Echo *)ptr)->sample_rate / 1000,
			    sizeof(LADSPA_Data))) == NULL)
			exit(1);
		if ((((Echo *)ptr)->ringbuffer_R = 
		     calloc(MAX_DELAY * ((Echo *)ptr)->sample_rate / 1000,
			    sizeof(LADSPA_Data))) == NULL)
			exit(1);
		if ((((Echo *)ptr)->buffer_pos_L = calloc(1, sizeof(unsigned long))) == NULL)
			exit(1);
		if ((((Echo *)ptr)->buffer_pos_R = calloc(1, sizeof(unsigned long))) == NULL)
			exit(1);
		
		*(((Echo *)ptr)->buffer_pos_L) = 0;
		*(((Echo *)ptr)->buffer_pos_R) = 0;
		
		return ptr;
	}
	
	return NULL;
}


/* activate a plugin instance */
void
activate_Echo(LADSPA_Handle Instance) {

	Echo * ptr = (Echo *)Instance;
	int i;
	
	ptr->mpx_out_L = 0;
	ptr->mpx_out_R = 0;
	
	*(ptr->buffer_pos_L) = 0;
	*(ptr->buffer_pos_R) = 0;

	for (i = 0; i < MAX_DELAY * ptr->sample_rate / 1000; i++) {
		ptr->ringbuffer_L[i] = 0.0f;
		ptr->ringbuffer_R[i] = 0.0f;
	}
}


/* Connect a port to a data location. */
void 
connect_port_Echo(LADSPA_Handle Instance,
		   unsigned long Port,
		   LADSPA_Data * DataLocation) {
	
	Echo * ptr;
	
	ptr = (Echo *)Instance;
	switch (Port) {
	case DELAYTIME_L:
		ptr->delaytime_L = DataLocation;
		break;
	case DELAYTIME_R:
		ptr->delaytime_R = DataLocation;
		break;
	case FEEDBACK_L:
		ptr->feedback_L = DataLocation;
		break;
	case FEEDBACK_R:
		ptr->feedback_R = DataLocation;
		break;
	case STRENGTH_L:
		ptr->strength_L = DataLocation;
		break;
	case STRENGTH_R:
		ptr->strength_R = DataLocation;
		break;
	case MODE:
		ptr->mode = DataLocation;
		break;
	case HAAS:
		ptr->haas = DataLocation;
		break;
	case REV_OUTCH:
		ptr->rev_outch = DataLocation;
		break;
	case DRYLEVEL:
		ptr->drylevel = DataLocation;
		break;
	case INPUT_L:
		ptr->input_L = DataLocation;
		break;
	case OUTPUT_L:
		ptr->output_L = DataLocation;
		break;
	case INPUT_R:
		ptr->input_R = DataLocation;
		break;
	case OUTPUT_R:
		ptr->output_R = DataLocation;
		break;
	}
}


#define EPS 0.00000001f

static inline float
M(float x) {

        if ((x > EPS) || (x < -EPS))
                return x;
        else
                return 0.0f;
}

void 
run_Echo(LADSPA_Handle Instance,
	 unsigned long SampleCount) {
	
	Echo * ptr;
	unsigned long sample_index;

	LADSPA_Data delaytime_L;
	LADSPA_Data delaytime_R;
	LADSPA_Data feedback_L;
	LADSPA_Data feedback_R;
	LADSPA_Data strength_L;
	LADSPA_Data strength_R;
	LADSPA_Data drylevel;
	LADSPA_Data mode;
	LADSPA_Data haas;
	LADSPA_Data rev_outch;

	LADSPA_Data * input_L;
	LADSPA_Data * output_L;
	LADSPA_Data * input_R;
	LADSPA_Data * output_R;

	unsigned long sample_rate;
	unsigned long buflen_L;
	unsigned long buflen_R;

	LADSPA_Data out_L = 0;
	LADSPA_Data out_R = 0;
	LADSPA_Data in_L = 0;
	LADSPA_Data in_R = 0;

	ptr = (Echo *)Instance;

	delaytime_L = LIMIT(*(ptr->delaytime_L),0.0f,2000.0f);
	delaytime_R = LIMIT(*(ptr->delaytime_R),0.0f,2000.0f);
	feedback_L = LIMIT(*(ptr->feedback_L) / 100.0, 0.0f, 100.0f);
	feedback_R = LIMIT(*(ptr->feedback_R) / 100.0, 0.0f, 100.0f);
        strength_L = db2lin(LIMIT(*(ptr->strength_L),-70.0f,10.0f));
	strength_R = db2lin(LIMIT(*(ptr->strength_R),-70.0f,10.0f));
	drylevel = db2lin(LIMIT(*(ptr->drylevel),-70.0f,10.0f));
	mode = LIMIT(*(ptr->mode),-2.0f,2.0f);
	haas = LIMIT(*(ptr->haas),-2.0f,2.0f);
	rev_outch = LIMIT(*(ptr->rev_outch),-2.0f,2.0f);

      	input_L = ptr->input_L;
	output_L = ptr->output_L;
      	input_R = ptr->input_R;
	output_R = ptr->output_R;

	sample_rate = ptr->sample_rate;
	buflen_L = delaytime_L * sample_rate / 1000;
	buflen_R = delaytime_R * sample_rate / 1000;


	for (sample_index = 0; sample_index < SampleCount; sample_index++) {
		
		in_L = *(input_L++);
		in_R = *(input_R++);

		out_L = in_L * drylevel + ptr->mpx_out_L * strength_L;
		out_R = in_R * drylevel + ptr->mpx_out_R * strength_R;

		if (haas > 0.0f)
			in_R = 0.0f;

		if (mode <= 0.0f) {
			ptr->mpx_out_L = 
				M(push_buffer(in_L + ptr->mpx_out_L * feedback_L,
					      ptr->ringbuffer_L, buflen_L, ptr->buffer_pos_L));
			ptr->mpx_out_R =
				M(push_buffer(in_R + ptr->mpx_out_R * feedback_R,
					      ptr->ringbuffer_R, buflen_R, ptr->buffer_pos_R));
		} else {
			ptr->mpx_out_R =
				M(push_buffer(in_L + ptr->mpx_out_L * feedback_L,
					      ptr->ringbuffer_L, buflen_L, ptr->buffer_pos_L));
			ptr->mpx_out_L =
				M(push_buffer(in_R + ptr->mpx_out_R * feedback_R,
					      ptr->ringbuffer_R, buflen_R, ptr->buffer_pos_R));
		}

		if (rev_outch <= 0.0f) {
			*(output_L++) = out_L;
			*(output_R++) = out_R;
		} else {
			*(output_L++) = out_R;
			*(output_R++) = out_L;
		}
	}
}





void
set_run_adding_gain(LADSPA_Handle Instance, LADSPA_Data gain){

	Echo * ptr;

	ptr = (Echo *)Instance;

	ptr->run_adding_gain = gain;
}


void 
run_adding_gain_Echo(LADSPA_Handle Instance,
	 unsigned long SampleCount) {
	
	Echo * ptr;
	unsigned long sample_index;

	LADSPA_Data delaytime_L;
	LADSPA_Data delaytime_R;
	LADSPA_Data feedback_L;
	LADSPA_Data feedback_R;
	LADSPA_Data strength_L;
	LADSPA_Data strength_R;
	LADSPA_Data drylevel;
	LADSPA_Data mode;
	LADSPA_Data haas;
	LADSPA_Data rev_outch;

	LADSPA_Data * input_L;
	LADSPA_Data * output_L;
	LADSPA_Data * input_R;
	LADSPA_Data * output_R;

	unsigned long sample_rate;
	unsigned long buflen_L;
	unsigned long buflen_R;

	LADSPA_Data out_L = 0;
	LADSPA_Data out_R = 0;
	LADSPA_Data in_L = 0;
	LADSPA_Data in_R = 0;

	ptr = (Echo *)Instance;

	delaytime_L = LIMIT(*(ptr->delaytime_L),0.0f,2000.0f);
	delaytime_R = LIMIT(*(ptr->delaytime_R),0.0f,2000.0f);
	feedback_L = LIMIT(*(ptr->feedback_L) / 100.0, 0.0f, 100.0f);
	feedback_R = LIMIT(*(ptr->feedback_R) / 100.0, 0.0f, 100.0f);
        strength_L = db2lin(LIMIT(*(ptr->strength_L),-70.0f,10.0f));
	strength_R = db2lin(LIMIT(*(ptr->strength_R),-70.0f,10.0f));
	drylevel = db2lin(LIMIT(*(ptr->drylevel),-70.0f,10.0f));
	mode = LIMIT(*(ptr->mode),-2.0f,2.0f);
	haas = LIMIT(*(ptr->haas),-2.0f,2.0f);
	rev_outch = LIMIT(*(ptr->rev_outch),-2.0f,2.0f);

      	input_L = ptr->input_L;
	output_L = ptr->output_L;
      	input_R = ptr->input_R;
	output_R = ptr->output_R;

	sample_rate = ptr->sample_rate;
	buflen_L = delaytime_L * sample_rate / 1000;
	buflen_R = delaytime_R * sample_rate / 1000;


	for (sample_index = 0; sample_index < SampleCount; sample_index++) {
		
		in_L = *(input_L++);
		in_R = *(input_R++);

		out_L = in_L * drylevel + ptr->mpx_out_L * strength_L;
		out_R = in_R * drylevel + ptr->mpx_out_R * strength_R;

		if (haas > 0.0f)
			in_R = 0.0f;

		if (mode <= 0.0f) {
			ptr->mpx_out_L = 
				M(push_buffer(in_L + ptr->mpx_out_L * feedback_L,
					      ptr->ringbuffer_L, buflen_L, ptr->buffer_pos_L));
			ptr->mpx_out_R =
				M(push_buffer(in_R + ptr->mpx_out_R * feedback_R,
					      ptr->ringbuffer_R, buflen_R, ptr->buffer_pos_R));
		} else {
			ptr->mpx_out_R =
				M(push_buffer(in_L + ptr->mpx_out_L * feedback_L,
					      ptr->ringbuffer_L, buflen_L, ptr->buffer_pos_L));
			ptr->mpx_out_L =
				M(push_buffer(in_R + ptr->mpx_out_R * feedback_R,
					      ptr->ringbuffer_R, buflen_R, ptr->buffer_pos_R));
		}

		if (rev_outch <= 0.0f) {
			*(output_L++) += out_L * ptr->run_adding_gain;
			*(output_R++) += out_R * ptr->run_adding_gain;
		} else {
			*(output_L++) += out_R * ptr->run_adding_gain;
			*(output_R++) += out_L * ptr->run_adding_gain;
		}
	}
}



/* Throw away an Echo effect instance. */
void 
cleanup_Echo(LADSPA_Handle Instance) {

	Echo * ptr = (Echo *)Instance;

	free(ptr->ringbuffer_L);
	free(ptr->ringbuffer_R);
	free(ptr->buffer_pos_L);
	free(ptr->buffer_pos_R);

	free(Instance);
}



LADSPA_Descriptor * stereo_descriptor = NULL;



/* __attribute__((constructor)) _init() is called automatically when the plugin library is first
   loaded. */
void 
__attribute__((constructor)) _init() {
	
	char ** port_names;
	LADSPA_PortDescriptor * port_descriptors;
	LADSPA_PortRangeHint * port_range_hints;
	
	if ((stereo_descriptor =
	     (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor))) == NULL)
		exit(1);		
	

	/* init the stereo Echo */
	
	stereo_descriptor->UniqueID = ID_STEREO;
	stereo_descriptor->Label = strdup("tap_stereo_echo");
	stereo_descriptor->Properties = LADSPA_PROPERTY_HARD_RT_CAPABLE;
	stereo_descriptor->Name = strdup("TAP Stereo Echo");
	stereo_descriptor->Maker = strdup("Tom Szilagyi");
	stereo_descriptor->Copyright = strdup("GPL");
	stereo_descriptor->PortCount = PORTCOUNT_STEREO;

	if ((port_descriptors = 
	     (LADSPA_PortDescriptor *)calloc(PORTCOUNT_STEREO, sizeof(LADSPA_PortDescriptor))) == NULL)
		exit(1);		

	stereo_descriptor->PortDescriptors = (const LADSPA_PortDescriptor *)port_descriptors;
	port_descriptors[DELAYTIME_L] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[DELAYTIME_R] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[FEEDBACK_L] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[FEEDBACK_R] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[STRENGTH_L] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[STRENGTH_R] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[DRYLEVEL] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[MODE] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[HAAS] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
	port_descriptors[REV_OUTCH] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;

	port_descriptors[INPUT_L] = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
	port_descriptors[OUTPUT_L] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
	port_descriptors[INPUT_R] = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
	port_descriptors[OUTPUT_R] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;

	if ((port_names = 
	     (char **)calloc(PORTCOUNT_STEREO, sizeof(char *))) == NULL)
		exit(1);		

	stereo_descriptor->PortNames = (const char **)port_names;

	port_names[DELAYTIME_L] = strdup("L Delay [ms]");
	port_names[DELAYTIME_R] = strdup("R/Haas Delay [ms]");
	port_names[FEEDBACK_L] = strdup("L Feedback [%]");
	port_names[FEEDBACK_R] = strdup("R/Haas Feedback [%]");
	port_names[STRENGTH_L] = strdup("L Echo Level [dB]");
	port_names[STRENGTH_R] = strdup("R Echo Level [dB]");
	port_names[DRYLEVEL] = strdup("Dry Level [dB]");
	port_names[MODE] = strdup("Cross Mode");
	port_names[HAAS] = strdup("Haas Effect");
	port_names[REV_OUTCH] = strdup("Swap Outputs");

	port_names[INPUT_L] = strdup("Input Left");
	port_names[OUTPUT_L] = strdup("Output Left");
	port_names[INPUT_R] = strdup("Input Right");
	port_names[OUTPUT_R] = strdup("Output Right");

	if ((port_range_hints = 
	     ((LADSPA_PortRangeHint *)calloc(PORTCOUNT_STEREO, sizeof(LADSPA_PortRangeHint)))) == NULL)
		exit(1);		

	stereo_descriptor->PortRangeHints = (const LADSPA_PortRangeHint *)port_range_hints;

	port_range_hints[DELAYTIME_L].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_100);
	port_range_hints[DELAYTIME_L].LowerBound = 0;
	port_range_hints[DELAYTIME_L].UpperBound = MAX_DELAY;

	port_range_hints[DELAYTIME_R].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_100);
	port_range_hints[DELAYTIME_R].LowerBound = 0;
	port_range_hints[DELAYTIME_R].UpperBound = MAX_DELAY;

	port_range_hints[FEEDBACK_L].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_0);
	port_range_hints[FEEDBACK_L].LowerBound = 0;
	port_range_hints[FEEDBACK_L].UpperBound = 100;

	port_range_hints[FEEDBACK_R].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_0);
	port_range_hints[FEEDBACK_R].LowerBound = 0;
	port_range_hints[FEEDBACK_R].UpperBound = 100;

	port_range_hints[STRENGTH_L].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_0);
	port_range_hints[STRENGTH_L].LowerBound = -70;
	port_range_hints[STRENGTH_L].UpperBound = 10;

	port_range_hints[STRENGTH_R].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_0);
	port_range_hints[STRENGTH_R].LowerBound = -70;
	port_range_hints[STRENGTH_R].UpperBound = 10;

	port_range_hints[MODE].HintDescriptor = 
		(LADSPA_HINT_TOGGLED |
		 LADSPA_HINT_DEFAULT_0);

	port_range_hints[HAAS].HintDescriptor = 
		(LADSPA_HINT_TOGGLED |
		 LADSPA_HINT_DEFAULT_0);

	port_range_hints[REV_OUTCH].HintDescriptor = 
		(LADSPA_HINT_TOGGLED |
		 LADSPA_HINT_DEFAULT_0);

	port_range_hints[DRYLEVEL].HintDescriptor = 
		(LADSPA_HINT_BOUNDED_BELOW |
		 LADSPA_HINT_BOUNDED_ABOVE |
		 LADSPA_HINT_DEFAULT_0);
	port_range_hints[DRYLEVEL].LowerBound = -70;
	port_range_hints[DRYLEVEL].UpperBound = 10;


	port_range_hints[INPUT_L].HintDescriptor = 0;
	port_range_hints[OUTPUT_L].HintDescriptor = 0;
	port_range_hints[INPUT_R].HintDescriptor = 0;
	port_range_hints[OUTPUT_R].HintDescriptor = 0;


	stereo_descriptor->instantiate = instantiate_Echo;
	stereo_descriptor->connect_port = connect_port_Echo;
	stereo_descriptor->activate = activate_Echo;
	stereo_descriptor->run = run_Echo;
	stereo_descriptor->run_adding = run_adding_gain_Echo;
	stereo_descriptor->set_run_adding_gain = set_run_adding_gain;
	stereo_descriptor->deactivate = NULL;
	stereo_descriptor->cleanup = cleanup_Echo;
	
}


void
delete_descriptor(LADSPA_Descriptor * descriptor) {
	unsigned long index;
	if (descriptor) {
		free((char *)descriptor->Label);
		free((char *)descriptor->Name);
		free((char *)descriptor->Maker);
		free((char *)descriptor->Copyright);
		free((LADSPA_PortDescriptor *)descriptor->PortDescriptors);
		for (index = 0; index < descriptor->PortCount; index++)
			free((char *)(descriptor->PortNames[index]));
		free((char **)descriptor->PortNames);
		free((LADSPA_PortRangeHint *)descriptor->PortRangeHints);
		free(descriptor);
	}
}


/* __attribute__((destructor)) _fini() is called automatically when the library is unloaded. */
void
__attribute__((destructor)) _fini() {
	delete_descriptor(stereo_descriptor);
}


/* Return a descriptor of the requested plugin type. */

const 
LADSPA_Descriptor * 
ladspa_descriptor(unsigned long Index) {

	switch (Index) {
	case 0:
		return stereo_descriptor;
	default:
		return NULL;
	}
}
