# Virtual Analog Multi-Model Stereo Filter - Moog, Polivoks, Roland Emulation Engine
# Features: 3 Classic Circuit Models, Variable Slopes (12/24dB), Multi-Mode (LP/HP/BP), Dual Syncable LFOs.

VA_MULTIFILTER_VERSION = main
VA_MULTIFILTER_SITE = https://github.com/DISTRHO/DPF.git
VA_MULTIFILTER_SITE_METHOD = git
VA_MULTIFILTER_BUNDLES = va-multifilter.lv2

# ---------------------------------------------------------------------------
# DistrhoPluginInfo.h
# ---------------------------------------------------------------------------
define VA_MULTIFILTER_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND        "Custom Synth"
#define DISTRHO_PLUGIN_NAME         "VA Multi-Model Filter"
#define DISTRHO_PLUGIN_URI          "urn:mod-cookbook:va-multifilter"
#define DISTRHO_PLUGIN_HAS_UI       0
#define DISTRHO_PLUGIN_IS_RT_SAFE   1
#define DISTRHO_PLUGIN_NUM_INPUTS   2
#define DISTRHO_PLUGIN_NUM_OUTPUTS  2

enum Parameters {
    kCutoff = 0,
    kResonance,
    kFilterModel,  // 0 = Moog, 1 = Polivoks, 2 = Roland
    kFilterMode,   // 0 = LP, 1 = HP, 2 = BP
    kSlope,        // 0 = 12dB (2-Pole), 1 = 24dB (4-Pole)
    kLfo1Rate,
    kLfo1Wave,     // 0=Sine, 1=Tri, 2=Squ, 3=Saw, 4=Ramp, 5=Peak, 6=S&H
    kLfo1Depth,
    kLfo2Rate,
    kLfo2Wave,
    kLfo2Depth,
    kLfoSync,      // 0 = Independent, 1 = Synced to LFO 1
    kParameterCount
};

#endif
endef

# ---------------------------------------------------------------------------
# ElevenPlugin.cpp (Reused as Multi-Model Core)
# ---------------------------------------------------------------------------
define VA_MULTIFILTER_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>

START_NAMESPACE_DISTRHO

// Fast tanh approximation for internal filter stage saturation
static inline float fast_tanh(float x) {
    float x2 = x * x;
    return x * (27.0f + x2) / (27.0f + 9.0f * x2);
}

class Lfo {
public:
    Lfo() : phase(0.0f), lastPhase(0.0f), currentRand(0.0f), nextRand(0.0f), randSeed(123456789U) {}
    
    void reset() { 
        phase = 0.0f; 
        lastPhase = 0.0f;
        currentRand = nextRandomValue();
        nextRand = nextRandomValue();
    }
    
    float process(float rate, int wave, float sr) {
        lastPhase = phase;
        phase += rate / sr;
        if (phase >= 1.0f) {
            phase -= 1.0f;
        }
        if (phase < 0.0f) phase = 0.0f;

        // Detect a new LFO cycle boundary for Sample and Hold steps
        if (phase < lastPhase) {
            currentRand = nextRand;
            nextRand = nextRandomValue();
        }
        
        switch(wave) {
            case 0: // Sine
                return std::sin(phase * 2.0f * 3.14159265359f);
            case 1: // Triangle
                return 1.0f - 4.0f * std::abs(phase - 0.5f);
            case 2: // Square
                return (phase < 0.5f) ? 1.0f : -1.0f;
            case 3: // Sawtooth (Rising)
                return 2.0f * phase - 1.0f;
            case 4: // Ramp (Falling)
                return 1.0f - 2.0f * phase;
            case 5: { // Peak / Trapezoid Shape
                if (phase < 0.25f) return phase * 4.0f;                // Attack stage
                if (phase < 0.50f) return 1.0f;                       // Sustained peak stage
                if (phase < 0.75f) return 1.0f - (phase - 0.5f) * 4.f; // Decay stage
                return 0.0f;                                          // Floor stage
            }
            case 6: { // Sample & Hold (Linear-interpolated random steps to prevent harsh DC pops)
                float t = phase;
                return currentRand + t * (nextRand - currentRand);
            }
            default:
                return 0.0f;
        }
    }
    
    void setPhase(float p) { phase = p; }
    float getPhase() const { return phase; }

private:
    float phase;
    float lastPhase;
    float currentRand;
    float nextRand;
    uint32_t randSeed;

    // Fast, RT-Safe LCG Random float generation (-1.f to 1.f)
    float nextRandomValue() {
        randSeed = randSeed * 1664525U + 1013904223U;
        return ((float)(randSeed & 0x7FFFFFFF) / 1073741824.0f) - 1.0f;
    }
};

// Stable Topology-Preserving Transform (TPT) State Variable Filter Engine
class VirtualAnalogFilterCore {
public:
    VirtualAnalogFilterCore() { reset(); }
    
    void reset() {
        s1[0] = s2[0] = 0.0f;
        s1[1] = s2[1] = 0.0f;
    }

    float process(float in, float cutoffHz, float resonance, int model, int mode, int slope, float sr) {
        cutoffHz = std::max(20.0f, std::min(cutoffHz, sr * 0.49f));
        resonance = std::max(0.0f, std::min(resonance, 0.95f));

        float g = std::tan(3.14159265359f * cutoffHz / sr);
        
        float rCoeff = 0.0f;
        float drive = 1.0f;

        switch(model) {
            case 0: // Moog-Style Ladder Profile
                rCoeff = resonance * 1.85f;
                drive = 1.4f;
                break;
            case 1: // Polivoks-Style High-Grit Profile
                rCoeff = resonance * 2.1f;
                drive = 2.2f;
                break;
            case 2: // Roland-Style Liquid Diode Profile
            default:
                rCoeff = resonance * 1.25f;
                drive = 0.9f;
                break;
        }

        in = fast_tanh(in * drive);

        float outSample = 0.0f;
        int cascades = (slope == 1) ? 2 : 1;

        float currentIn = in;

        for (int c = 0; c < cascades; ++c) {
            float R = 2.0f * (1.0f - rCoeff);
            float h = 1.0f / (1.0f + R * g + g * g);

            float hp = (currentIn - R * s1[c] - g * s1[c] - s2[c]) * h;
            float bp = g * hp + s1[c];
            float lp = g * bp + s2[c];

            s1[c] = fast_tanh(g * hp + bp);
            s2[c] = fast_tanh(g * bp + lp);

            float stageOut = 0.0f;
            if (mode == 0)      stageOut = lp;
            else if (mode == 1) stageOut = hp;
            else                stageOut = bp;

            currentIn = stageOut;
        }

        return currentIn;
    }

private:
    float s1[2];
    float s2[2];
};

class VaFilterPlugin : public Plugin
{
public:
    VaFilterPlugin()
        : Plugin(kParameterCount, 0, 0), fSr(48000.f) {
        for (int i = 0; i < kParameterCount; ++i) fParams[i] = 0.0f;
        fParams[kCutoff] = 1200.0f;
        fParams[kResonance] = 0.2f;
        fParams[kLfo1Rate] = 0.5f;
        fParams[kLfo2Rate] = 1.0f;
    }

protected:
    const char* getLabel()       const override { return "VA Multi Filter"; }
    const char* getDescription() const override { return "Multi-Model Virtual Analog Stereo Filter Engine."; }
    const char* getMaker()       const override { return "Custom Synth"; }
    const char* getHomePage()    const override { return "https://mod.audio"; }
    const char* getLicense()     const override { return "MIT"; }
    uint32_t    getVersion()     const override { return d_version(1,0,0); }
    int64_t     getUniqueId()    const override { return d_cconst('V', 'A', 'M', 'F'); }

    void initParameter(uint32_t index, Parameter& p) override {
        p.hints = kParameterIsAutomatable;
        switch(index) {
        case kCutoff:
            p.name = "Cutoff Frequency"; p.symbol = "cutoff"; p.unit = "Hz";
            p.ranges.def = 1200.f; p.ranges.min = 20.f; p.ranges.max = 16000.f; p.hints |= kParameterIsLogarithmic; break;
        case kResonance:
            p.name = "Resonance Peak"; p.symbol = "resonance";
            p.ranges.def = 0.2f; p.ranges.min = 0.f; p.ranges.max = 1.f; break;
        case kFilterModel:
            p.name = "Circuit Model"; p.symbol = "model";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 2.f; p.hints |= kParameterIsInteger; break;
        case kFilterMode:
            p.name = "Filter Mode"; p.symbol = "mode";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 2.f; p.hints |= kParameterIsInteger; break;
        case kSlope:
            p.name = "Filter Slope Attenuation"; p.symbol = "slope";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 1.f; p.hints |= kParameterIsInteger; break;
        case kLfo1Rate:
            p.name = "LFO 1 Frequency"; p.symbol = "lfo1_rate"; p.unit = "Hz";
            p.ranges.def = 0.5f; p.ranges.min = 0.02f; p.ranges.max = 30.f; p.hints |= kParameterIsLogarithmic; break;
        case kLfo1Wave:
            p.name = "LFO 1 Waveform"; p.symbol = "lfo1_wave";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 6.f; p.hints |= kParameterIsInteger; break;
        case kLfo1Depth:
            p.name = "LFO 1 Depth -> Cutoff"; p.symbol = "lfo1_depth";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 8000.f; break;
        case kLfo2Rate:
            p.name = "LFO 2 Frequency"; p.symbol = "lfo2_rate"; p.unit = "Hz";
            p.ranges.def = 1.0f; p.ranges.min = 0.02f; p.ranges.max = 30.f; p.hints |= kParameterIsLogarithmic; break;
        case kLfo2Wave:
            p.name = "LFO 2 Waveform"; p.symbol = "lfo2_wave";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 6.f; p.hints |= kParameterIsInteger; break;
        case kLfo2Depth:
            p.name = "LFO 2 Depth -> Res"; p.symbol = "lfo2_depth";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 0.7f; break;
        case kLfoSync:
            p.name = "LFO Phase Sync"; p.symbol = "lfo_sync";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 1.f; p.hints |= kParameterIsInteger; break;
        }
    }

    float getParameterValue(uint32_t index) const override { return fParams[index]; }
    void setParameterValue(uint32_t index, float value) override { fParams[index] = value; }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        fLeftFilter.reset();
        fRightFilter.reset();
        fLfo1.reset();
        fLfo2.reset();
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override {
        const float* inL = inputs[0];
        const float* inR = inputs[1];
        float* outL = outputs[0];
        float* outR = outputs[1];

        float baseCutoff = fParams[kCutoff];
        float baseRes = fParams[kResonance];
        int model = (int)fParams[kFilterModel];
        int mode = (int)fParams[kFilterMode];
        int slope = (int)fParams[kSlope];
        
        float lfo1R = fParams[kLfo1Rate];
        int lfo1W = (int)fParams[kLfo1Wave];
        float lfo1D = fParams[kLfo1Depth];
        
        int lfoSync = (int)fParams[kLfoSync];
        float lfo2R = lfoSync ? lfo1R : fParams[kLfo2Rate];
        int lfo2W = lfoSync ? lfo1W : (int)fParams[kLfo2Wave];
        float lfo2D = fParams[kLfo2Depth];

        for (uint32_t i = 0; i < frames; ++i) {
            float mod1 = fLfo1.process(lfo1R, lfo1W, fSr);
            if (lfoSync) {
                fLfo2.setPhase(fLfo1.getPhase());
            }
            float mod2 = fLfo2.process(lfo2R, lfo2W, fSr);

            float targetCutoff = baseCutoff + (mod1 * lfo1D);
            float targetRes = baseRes + (mod2 * lfo2D);

            outL[i] = fLeftFilter.process(inL[i], targetCutoff, targetRes, model, mode, slope, fSr);
            outR[i] = fRightFilter.process(inR[i], targetCutoff, targetRes, model, mode, slope, fSr);
        }
    }

private:
    float fParams[kParameterCount];
    float fSr;
    VirtualAnalogFilterCore fLeftFilter, fRightFilter;
    Lfo fLfo1, fLfo2;
    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VaFilterPlugin)
};

Plugin* createPlugin() { return new VaFilterPlugin(); }

END_NAMESPACE_DISTRHO
endef

# ---------------------------------------------------------------------------
# Local Makefile
# ---------------------------------------------------------------------------
define VA_MULTIFILTER_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = va-multifilter
FILES_DSP = ElevenPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

# ---------------------------------------------------------------------------
# LV2 TTL Descriptor
# ---------------------------------------------------------------------------
define VA_MULTIFILTER_PLUGIN_TTL
@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .

<urn:mod-cookbook:va-multifilter>
    a lv2:Plugin , lv2:FilterPlugin ;
    doap:name "VA Multi-Model Filter" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [ foaf:name "Custom Synth" ] ;
    rdfs:comment "Multi-Model Virtual Analog stereo filter offering selectable circuit profiles, slopes, filter configurations, and syncable LFO modulators." ;

    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ; lv2:symbol "in_l" ; lv2:name "Input L"
    ] , [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 1 ; lv2:symbol "in_r" ; lv2:name "Input R"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 2 ; lv2:symbol "out_l" ; lv2:name "Output L"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 3 ; lv2:symbol "out_r" ; lv2:name "Output R"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ; lv2:symbol "cutoff" ; lv2:name "Cutoff" ;
        lv2:default 1200.0 ; lv2:minimum 20.0 ; lv2:maximum 16000.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ; lv2:symbol "resonance" ; lv2:name "Resonance" ;
        lv2:default 0.2 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ; lv2:symbol "model" ; lv2:name "Circuit Model" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 2 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Moog (Ladder Peak)" ;  rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Polivoks (Op-Amp Grit)" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Roland (Smooth Liquid)" ; rdf:value 2 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ; lv2:symbol "mode" ; lv2:name "Filter Mode" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 2 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Low Pass (LP)" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "High Pass (HP)" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Band Pass (BP)" ; rdf:value 2 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ; lv2:symbol "slope" ; lv2:name "Filter Slope" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "12dB / oct (2-Pole)" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "24dB / oct (4-Pole)" ; rdf:value 1 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ; lv2:symbol "lfo1_rate" ; lv2:name "LFO 1 Rate" ;
        lv2:default 0.5 ; lv2:minimum 0.02 ; lv2:maximum 30.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 10 ; lv2:symbol "lfo1_wave" ; lv2:name "LFO 1 Waveform" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 6 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Sine" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Triangle" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Square" ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Sawtooth" ; rdf:value 3 ] ;
        lv2:scalePoint [ rdfs:label "Ramp" ; rdf:value 4 ] ;
        lv2:scalePoint [ rdfs:label "Peak Trapezoid" ; rdf:value 5 ] ;
        lv2:scalePoint [ rdfs:label "Sample & Hold" ; rdf:value 6 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 11 ; lv2:symbol "lfo1_depth" ; lv2:name "LFO 1 -> Cutoff" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 8000.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 12 ; lv2:symbol "lfo2_rate" ; lv2:name "LFO 2 Rate" ;
        lv2:default 1.0 ; lv2:minimum 0.02 ; lv2:maximum 30.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 13 ; lv2:symbol "lfo2_wave" ; lv2:name "LFO 2 Waveform" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 6 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Sine" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Triangle" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Square" ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Sawtooth" ; rdf:value 3 ] ;
        lv2:scalePoint [ rdfs:label "Ramp" ; rdf:value 4 ] ;
        lv2:scalePoint [ rdfs:label "Peak Trapezoid" ; rdf:value 5 ] ;
        lv2:scalePoint [ rdfs:label "Sample & Hold" ; rdf:value 6 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 14 ; lv2:symbol "lfo2_depth" ; lv2:name "LFO 2 -> Resonance" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 0.7 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 15 ; lv2:symbol "lfo_sync" ; lv2:name "LFO Sync Lock" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ;
        lv2:portProperty lv2:integer , lv2:toggled ;
    ] .
endef

define VA_MULTIFILTER_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:va-multifilter>
    a lv2:Plugin ;
    lv2:binary <va-multifilter_dsp.so> ;
    rdfs:seeAlso <va-multifilter.ttl> .
endef

export VA_MULTIFILTER_PLUGIN_CPP VA_MULTIFILTER_PLUGIN_INFO_H VA_MULTIFILTER_PLUGIN_MAKEFILE VA_MULTIFILTER_MANIFEST_TTL VA_MULTIFILTER_PLUGIN_TTL

define VA_MULTIFILTER_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/eleven
	printf '%s' "$$VA_MULTIFILTER_PLUGIN_CPP"      > $(@D)/examples/eleven/ElevenPlugin.cpp
	printf '%s' "$$VA_MULTIFILTER_PLUGIN_INFO_H"   > $(@D)/examples/eleven/DistrhoPluginInfo.h
	printf '%s' "$$VA_MULTIFILTER_PLUGIN_MAKEFILE" > $(@D)/examples/eleven/Makefile
endef

define VA_MULTIFILTER_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/eleven lv2_dsp
endef

define VA_MULTIFILTER_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/va-multifilter.lv2
	cp $(@D)/bin/va-multifilter.lv2/va-multifilter_dsp.so $($(PKG)_PKGDIR)/va-multifilter.lv2/
	printf '%s' "$$VA_MULTIFILTER_MANIFEST_TTL" > $($(PKG)_PKGDIR)/va-multifilter.lv2/manifest.ttl
	printf '%s' "$$VA_MULTIFILTER_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/va-multifilter.lv2/va-multifilter.ttl
endef

$(eval $(generic-package))