/*
  Copyright 2024 Loki Davison <loki@polyeffects.com>
  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.
  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/


#include "lv2/core/lv2.h"

/** Include standard C headers */
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#define clamp(x, lower, upper) (fmax(lower, fmin(x, upper)))

/* #define FILTER_URI "http://polyeffects.com/lv2/polyfilter#" */
#define PLUGIN_URI  "http://polyeffects.com/lv2/poly_3_band_eq"
#include "faust/dsp/fastmath.cpp"
#include "fastonebigheader.h"

/**
   In code, ports are referred to by index.  An enumeration of port indices
   should be defined for readability.
*/
typedef enum {
    BASS = 0,
    MID = 1,
    TREBLE = 2,
    INPUT_1 = 3,
    OUTPUT_1 = 4,
} PortIndex;

typedef struct {
	// Port buffers
    const float* bass;
    const float* mid;
    const float* treble;
    const float* input_1;
    float* output_1;

	float fRec0[2];
	float fRec1[2];
	float fRec2[2];
	float fRec3[2];
	float fRec4[2];
	float fRec5[2];
	float fRec6[2];
	float fRec7[2];
	float fRec8[2];
    float fVec0[2];
    float fVec1[2];

} Filter ;

static LV2_Handle
instantiate(const LV2_Descriptor*     descriptor,
            double                    rate,
            const char*               bundle_path,
            const LV2_Feature* const* features)
{
	Filter* amp = (Filter*)calloc(1, sizeof(Filter));

	return (LV2_Handle)amp;
}

static void
connect_port(LV2_Handle instance,
             uint32_t   port,
             void*      data)
{
	Filter* amp = (Filter*)instance;

    switch ((PortIndex)port) {

        case BASS:
            amp->bass = (const float*)data;
            break;
        case MID:
            amp->mid = (const float*)data;
            break;
        case TREBLE:
            amp->treble = (const float*)data;
            break;
        case INPUT_1:
            amp->input_1 = (const float*)data;
            break;
        case OUTPUT_1:
            amp->output_1 = (float*)data;
            break;
		default:
			break;
	}
}

/**
   The `activate()` method is called by the host to initialise and prepare the
   plugin instance for running.  The plugin must reset all internal state
   except for buffer locations set by `connect_port()`.  Since this plugin has
   no other internal state, this method does nothing.
   This method is in the ``instantiation'' threading class, so no other
   methods on this instance will be called concurrently with it.
*/
static void
activate(LV2_Handle instance)
{
}

/**
   The `run()` method is the main process function of the plugin.  It processes
   a block of audio in the audio context.  Since this plugin is
   `lv2:hardRTCapable`, `run()` must be real-time safe, so blocking (e.g. with
   a mutex) or memory allocation are not allowed.
*/
static void
run(LV2_Handle instance, uint32_t n_samples)
{
	Filter* const amp = (Filter*)instance;

    const float* input0 = amp->input_1;
    float* output0 = amp->output_1;

    const float bass = *(amp->bass);
    const float mid = *(amp->mid);
    const float treble = *(amp->treble);

    float* fRec0 = amp->fRec0;
    float* fRec1 = amp->fRec1;
    float* fRec2 = amp->fRec2;
    float* fRec3 = amp->fRec3;
    float* fRec4 = amp->fRec4;
    float* fRec5 = amp->fRec5;
    float* fRec6 = amp->fRec6;
    float* fRec7 = amp->fRec7;
    float* fRec8 = amp->fRec8;
    float* fVec0 = amp->fVec0;
    float* fVec1 = amp->fVec1;

    float fSlow0 = fasterpow(10.0f, (0.0500000007f * float(bass)));
    float fSlow1 = float(mid);
    int iSlow2 = (fSlow1 > 0.0f);
    float fSlow3 = (0.50143075f * fasterpow(10.0f, (0.0500000007f * fast_fabsf(fSlow1))));
    float fSlow4 = (15.2570515f * (iSlow2 ? 0.50143075f : fSlow3));
    float fSlow5 = (233.777634f - fSlow4);
    float fSlow6 = (fSlow4 + 233.777634f);
    float fSlow7 = (0.0f - (463.555267f / fSlow6));
    float fSlow8 = (15.2570515f * (iSlow2 ? fSlow3 : 0.50143075f));
    float fSlow9 = (fSlow8 + 233.777634f);
    float fSlow10 = (233.777634f - fSlow8);
    float fSlow11 = fasterpow(10.0f, (0.0500000007f * float(treble)));

    for (uint32_t i = 0; (i < n_samples); i = (i + 1)) {
        float fTemp0 = float(input0[i]);
        fVec0[0] = fTemp0;
        fRec4[0] = ((0.980555296f * fRec4[1]) - (0.990277648f * (fVec0[1] - fTemp0)));
        fRec3[0] = ((fRec4[0] + (1.98017538f * fRec3[1])) - (0.980557203f * fRec3[2]));
        fRec6[0] = ((0.980555296f * fRec6[1]) + (0.00972234085f * (fTemp0 + fVec0[1])));
        fRec5[0] = ((fRec6[0] + (1.98017538f * fRec5[1])) - (0.980557203f * fRec5[2]));
        float fTemp1 = (fRec2[1] * fSlow7);
        fRec2[0] = (((0.990183115f * (fRec3[0] + fRec3[2])) + (fSlow0 * ((0.000190885621f * fRec5[1]) + (9.54428106e-05f * (fRec5[0] + fRec5[2]))))) - (((fRec2[2] * fSlow5) / fSlow6) + ((1.98036623f * fRec3[1]) + fTemp1)));
        float fTemp2 = (fTemp1 + (((fRec2[0] * fSlow9) + (fRec2[2] * fSlow10)) / fSlow6));
        fVec1[0] = fTemp2;
        fRec1[0] = ((0.668178618f * fRec1[1]) + (0.165910676f * (fTemp2 + fVec1[1])));
        fRec0[0] = ((fRec1[0] + (1.55098999f * fRec0[1])) - (0.678779483f * fRec0[2]));
        fRec8[0] = ((0.668178618f * fRec8[1]) + (0.834089339f * (fTemp2 - fVec1[1])));
        fRec7[0] = ((fRec8[0] + (1.55098999f * fRec7[1])) - (0.678779483f * fRec7[2]));
        output0[i] = float(((0.0319473706f * (fRec0[0] + fRec0[2])) + ((fSlow11 * ((0.807442367f * (fRec7[0] + fRec7[2])) - (1.61488473f * fRec7[1]))) + (0.0638947412f * fRec0[1]))));
        fVec0[1] = fVec0[0];
        fRec4[1] = fRec4[0];
        fRec3[2] = fRec3[1];
        fRec3[1] = fRec3[0];
        fRec6[1] = fRec6[0];
        fRec5[2] = fRec5[1];
        fRec5[1] = fRec5[0];
        fRec2[2] = fRec2[1];
        fRec2[1] = fRec2[0];
        fVec1[1] = fVec1[0];
        fRec1[1] = fRec1[0];
        fRec0[2] = fRec0[1];
        fRec0[1] = fRec0[0];
        fRec8[1] = fRec8[0];
        fRec7[2] = fRec7[1];
        fRec7[1] = fRec7[0];
    }
}
/**
   The `deactivate()` method is the counterpart to `activate()`, and is called by
   the host after running the plugin.  It indicates that the host will not call
   `run()` again until another call to `activate()` and is mainly useful for more
   advanced plugins with ``live'' characteristics such as those with auxiliary
   processing threads.  As with `activate()`, this plugin has no use for this
   information so this method does nothing.
   This method is in the ``instantiation'' threading class, so no other
   methods on this instance will be called concurrently with it.
*/
static void
deactivate(LV2_Handle instance)
{
}

/**
   Destroy a plugin instance (counterpart to `instantiate()`).
   This method is in the ``instantiation'' threading class, so no other
   methods on this instance will be called concurrently with it.
*/
static void
cleanup(LV2_Handle instance)
{
	Filter* const amp = (Filter*)instance;
	free(instance);
}

/**
   The `extension_data()` function returns any extension data supported by the
   plugin.  Note that this is not an instance method, but a function on the
   plugin descriptor.  It is usually used by plugins to implement additional
   interfaces.  This plugin does not have any extension data, so this function
   returns NULL.
   This method is in the ``discovery'' threading class, so no other functions
   or methods in this plugin library will be called concurrently with it.
*/
static const void*
extension_data(const char* uri)
{
	return NULL;
}

/**
   Every plugin must define an `LV2_Descriptor`.  It is best to define
   descriptors statically to avoid leaking memory and non-portable shared
   library constructors and destructors to clean up properly.








*/

static const LV2_Descriptor descriptor0 = {
	PLUGIN_URI,
	instantiate,
	connect_port,
	activate,
	run,
	deactivate,
	cleanup,
	extension_data
};


/**
   The `lv2_descriptor()` function is the entry point to the plugin library.  The
   host will load the library and call this function repeatedly with increasing
   indices to find all the plugins defined in the library.  The index is not an
   indentifier, the URI of the returned descriptor is used to determine the
   identify of the plugin.
   This method is in the ``discovery'' threading class, so no other functions
   or methods in this plugin library will be called concurrently with it.
*/
LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor (uint32_t index)
{
	switch (index) {
		case 0:
			return &descriptor0;
		default:
			return NULL;
	}
}



