################################################################################
# H90 EQ Compressor - Parametric EQ with pre/post compressor
# Eventide H90 algorithm emulation for the MOD Dwarf
# Upload at https://builder.mod.audio/buildroot with a MOD unit connected.
################################################################################

H90_EQCOMPRESSOR_VERSION     = 61d38eb638449647fb8395a35c5b8dab7e981ba7
H90_EQCOMPRESSOR_SITE        = https://github.com/DISTRHO/DPF.git
H90_EQCOMPRESSOR_SITE_METHOD = git
H90_EQCOMPRESSOR_BUNDLES     = h90-eqcompressor.lv2

define H90_EQCOMPRESSOR_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND       "H90"
#define DISTRHO_PLUGIN_NAME        "EQ Compressor"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:h90-eqcompressor"
#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 {
    kGain1=0, kFreq1, kWidth1,
    kGain2,   kFreq2, kWidth2,
    kBass, kTreble,
    kCompressor, kTrim,
    kParameterCount
};

#endif
endef

define H90_EQCOMPRESSOR_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <cstring>

START_NAMESPACE_DISTRHO

static const float kPi    = 3.14159265359f;
static const float kTwoPi = 6.28318530718f;

// Parametric peak EQ biquad (direct form 1)
// gain_db: +12 boost / -18 cut; Q: bandwidth (1=narrow, 10=wide)
struct PeakEQ {
    float x1,x2,y1,y2;
    PeakEQ():x1(0),x2(0),y1(0),y2(0){}
    float process(float in, float freq, float gain_db, float Q, float sr){
        if(fabsf(gain_db)<0.01f) return in;
        float A     = powf(10.f, gain_db/40.f);
        float w0    = kTwoPi*freq/sr;
        float alpha = sinf(w0)/(2.f*Q);
        float b0=1.f+alpha*A, b1=-2.f*cosf(w0), b2=1.f-alpha*A;
        float a0=1.f+alpha/A, a1=b1,             a2=1.f-alpha/A;
        float out=(b0/a0)*in+(b1/a0)*x1+(b2/a0)*x2-(a1/a0)*y1-(a2/a0)*y2;
        x2=x1; x1=in; y2=y1; y1=out; return out;
    }
};

// Low shelf (centered at 400Hz, 8dB/oct slope approximated by 2nd order)
struct LowShelf {
    float x1,x2,y1,y2;
    LowShelf():x1(0),x2(0),y1(0),y2(0){}
    float process(float in, float gain_db, float sr){
        if(fabsf(gain_db)<0.01f) return in;
        float fc=400.f;
        float A    = powf(10.f,gain_db/40.f);
        float w0   = kTwoPi*fc/sr;
        float cosw = cosf(w0), sinw = sinf(w0);
        float alpha= sinw/2.f*sqrtf((A+1.f/A)*(1.f/0.707f-1.f)+2.f);
        float sqA  = sqrtf(A);
        float b0=  A*((A+1.f)-(A-1.f)*cosw+2.f*sqA*alpha);
        float b1=2.f*A*((A-1.f)-(A+1.f)*cosw);
        float b2=  A*((A+1.f)-(A-1.f)*cosw-2.f*sqA*alpha);
        float a0=    (A+1.f)+(A-1.f)*cosw+2.f*sqA*alpha;
        float a1= -2.f*((A-1.f)+(A+1.f)*cosw);
        float a2=    (A+1.f)+(A-1.f)*cosw-2.f*sqA*alpha;
        float out=(b0/a0)*in+(b1/a0)*x1+(b2/a0)*x2-(a1/a0)*y1-(a2/a0)*y2;
        x2=x1; x1=in; y2=y1; y1=out; return out;
    }
};

// High shelf (centered at 1800Hz)
struct HighShelf {
    float x1,x2,y1,y2;
    HighShelf():x1(0),x2(0),y1(0),y2(0){}
    float process(float in, float gain_db, float sr){
        if(fabsf(gain_db)<0.01f) return in;
        float fc=1800.f;
        float A    = powf(10.f,gain_db/40.f);
        float w0   = kTwoPi*fc/sr;
        float cosw = cosf(w0), sinw = sinf(w0);
        float alpha= sinw/2.f*sqrtf((A+1.f/A)*(1.f/0.707f-1.f)+2.f);
        float sqA  = sqrtf(A);
        float b0=  A*((A+1.f)+(A-1.f)*cosw+2.f*sqA*alpha);
        float b1=-2.f*A*((A-1.f)+(A+1.f)*cosw);
        float b2=  A*((A+1.f)+(A-1.f)*cosw-2.f*sqA*alpha);
        float a0=    (A+1.f)-(A-1.f)*cosw+2.f*sqA*alpha;
        float a1=  2.f*((A-1.f)-(A+1.f)*cosw);
        float a2=    (A+1.f)-(A-1.f)*cosw-2.f*sqA*alpha;
        float out=(b0/a0)*in+(b1/a0)*x1+(b2/a0)*x2-(a1/a0)*y1-(a2/a0)*y2;
        x2=x1; x1=in; y2=y1; y1=out; return out;
    }
};

// Width param: H90 says 1=narrow, 10=wide -> maps to Q: wide=low Q, narrow=high Q
static float widthToQ(float width){ return 10.f/(width > 0.1f ? width : 0.1f); }

// Compressor: pre-EQ (negative) or post-EQ (positive), -100..+100
struct Compressor {
    float envL,envR,gainL,gainR;
    Compressor():envL(0),envR(0),gainL(1),gainR(1){}
    void process(float& l, float& r, float amt, float sr){
        float atk=expf(-1.f/(0.005f*sr));
        float rel=expf(-1.f/(0.150f*sr));
        float aL=fabsf(l), aR=fabsf(r);
        float cL=(aL>envL)?atk:rel; envL=cL*envL+(1.f-cL)*aL;
        float cR=(aR>envR)?atk:rel; envR=cR*envR+(1.f-cR)*aR;
        float thresh=0.5f-amt*0.4f, ratio=1.f+amt*7.f;
        auto g=[&](float e)->float{
            if(e<thresh||e<0.0001f) return 1.f;
            return (thresh+(e-thresh)/ratio)/e;
        };
        gainL=gainL*0.999f+g(envL)*0.001f;
        gainR=gainR*0.999f+g(envR)*0.001f;
        l*=gainL; r*=gainR;
    }
};

class EQCompressorPlugin : public Plugin {
public:
    EQCompressorPlugin()
        : Plugin(kParameterCount,0,0),
          fGain1(0.f),fFreq1(200.f),fWidth1(5.f),
          fGain2(0.f),fFreq2(3000.f),fWidth2(5.f),
          fBass(0.f),fTreble(0.f),fCompressor(0.f),fTrim(0.f),
          fSr(48000.f)
    { sampleRateChanged(getSampleRate()); }

protected:
    const char* getLabel()       const override { return "EQ Compressor"; }
    const char* getDescription() const override { return "Parametric EQ with pre/post compressor. Eventide H90 EQ Compressor emulation."; }
    const char* getMaker()       const override { return "H90"; }
    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('H','E','Q','C'); }

    void initParameter(uint32_t index, Parameter& p) override {
        switch(index){
        case kGain1:
            p.hints=kParameterIsAutomatable; p.name="Gain 1"; p.symbol="gain1"; p.unit="dB";
            p.ranges.def=0.f; p.ranges.min=-18.f; p.ranges.max=12.f; break;
        case kFreq1:
            p.hints=kParameterIsAutomatable; p.name="Frequency 1"; p.symbol="freq1"; p.unit="Hz";
            p.ranges.def=200.f; p.ranges.min=30.f; p.ranges.max=1500.f; break;
        case kWidth1:
            p.hints=kParameterIsAutomatable; p.name="Width 1"; p.symbol="width1"; p.unit="";
            p.ranges.def=5.f; p.ranges.min=1.f; p.ranges.max=10.f; break;
        case kGain2:
            p.hints=kParameterIsAutomatable; p.name="Gain 2"; p.symbol="gain2"; p.unit="dB";
            p.ranges.def=0.f; p.ranges.min=-18.f; p.ranges.max=12.f; break;
        case kFreq2:
            p.hints=kParameterIsAutomatable; p.name="Frequency 2"; p.symbol="freq2"; p.unit="Hz";
            p.ranges.def=3000.f; p.ranges.min=1000.f; p.ranges.max=9999.f; break;
        case kWidth2:
            p.hints=kParameterIsAutomatable; p.name="Width 2"; p.symbol="width2"; p.unit="";
            p.ranges.def=5.f; p.ranges.min=1.f; p.ranges.max=10.f; break;
        case kBass:
            p.hints=kParameterIsAutomatable; p.name="Bass"; p.symbol="bass"; p.unit="dB";
            p.ranges.def=0.f; p.ranges.min=-18.f; p.ranges.max=12.f; break;
        case kTreble:
            p.hints=kParameterIsAutomatable; p.name="Treble"; p.symbol="treble"; p.unit="dB";
            p.ranges.def=0.f; p.ranges.min=-18.f; p.ranges.max=12.f; break;
        case kCompressor:
            p.hints=kParameterIsAutomatable; p.name="Compressor"; p.symbol="compressor"; p.unit="";
            p.ranges.def=0.f; p.ranges.min=-100.f; p.ranges.max=100.f; break;
        case kTrim:
            p.hints=kParameterIsAutomatable; p.name="Trim"; p.symbol="trim"; p.unit="dB";
            p.ranges.def=0.f; p.ranges.min=-12.f; p.ranges.max=12.f; break;
        }
    }

    float getParameterValue(uint32_t index) const override {
        switch(index){
        case kGain1:      return fGain1;
        case kFreq1:      return fFreq1;
        case kWidth1:     return fWidth1;
        case kGain2:      return fGain2;
        case kFreq2:      return fFreq2;
        case kWidth2:     return fWidth2;
        case kBass:       return fBass;
        case kTreble:     return fTreble;
        case kCompressor: return fCompressor;
        case kTrim:       return fTrim;
        } return 0.f;
    }

    void setParameterValue(uint32_t index, float v) override {
        switch(index){
        case kGain1:      fGain1=v;      break;
        case kFreq1:      fFreq1=v;      break;
        case kWidth1:     fWidth1=v;     break;
        case kGain2:      fGain2=v;      break;
        case kFreq2:      fFreq2=v;      break;
        case kWidth2:     fWidth2=v;     break;
        case kBass:       fBass=v;       break;
        case kTreble:     fTreble=v;     break;
        case kCompressor: fCompressor=v; break;
        case kTrim:       fTrim=v;       break;
        }
    }

    void sampleRateChanged(double newSR) override { fSr=(float)newSR; }

    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];

        const float comp    = fCompressor/100.f; // -1..+1
        const bool  preComp = (comp < 0.f);
        const float compAmt = fabsf(comp);
        const float q1      = widthToQ(fWidth1);
        const float q2      = widthToQ(fWidth2);
        const float trimGain= powf(10.f, fTrim/20.f);

        for(uint32_t i=0;i<frames;++i){
            float l=inL[i], r=inR[i];

            // Pre-EQ compression
            if(preComp) fComp.process(l,r,compAmt,fSr);

            // EQ chain: low shelf -> band 1 -> band 2 -> high shelf
            l=fBassL.process(l,fBass,fSr);
            r=fBassR.process(r,fBass,fSr);
            l=fEQ1L.process(l,fFreq1,fGain1,q1,fSr);
            r=fEQ1R.process(r,fFreq1,fGain1,q1,fSr);
            l=fEQ2L.process(l,fFreq2,fGain2,q2,fSr);
            r=fEQ2R.process(r,fFreq2,fGain2,q2,fSr);
            l=fTrebL.process(l,fTreble,fSr);
            r=fTrebR.process(r,fTreble,fSr);

            // Post-EQ compression
            if(!preComp) fComp.process(l,r,compAmt,fSr);

            // Trim (output level, graceful clip if over)
            l*=trimGain; r*=trimGain;
            // Graceful soft clip at output
            if(l> 1.5f) l= 1.5f; if(l<-1.5f) l=-1.5f;
            if(r> 1.5f) r= 1.5f; if(r<-1.5f) r=-1.5f;

            outL[i]=l; outR[i]=r;
        }
    }

private:
    float fGain1,fFreq1,fWidth1;
    float fGain2,fFreq2,fWidth2;
    float fBass,fTreble,fCompressor,fTrim;
    float fSr;

    LowShelf   fBassL,fBassR;
    PeakEQ     fEQ1L,fEQ1R;
    PeakEQ     fEQ2L,fEQ2R;
    HighShelf  fTrebL,fTrebR;
    Compressor fComp;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EQCompressorPlugin)
};

Plugin* createPlugin() { return new EQCompressorPlugin(); }

END_NAMESPACE_DISTRHO
endef

define H90_EQCOMPRESSOR_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = h90-eqcompressor
FILES_DSP = EQCompressorPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define H90_EQCOMPRESSOR_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:mod-cookbook:h90-eqcompressor>
    a lv2:Plugin , lv2:EQPlugin ;
    lv2:binary <h90-eqcompressor_dsp.so> ;
    rdfs:seeAlso <h90-eqcompressor.ttl> .
endef

define H90_EQCOMPRESSOR_PLUGIN_TTL
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
<urn:mod-cookbook:h90-eqcompressor>
    a lv2:Plugin , lv2:EQPlugin ;
    doap:name "H90 EQ Compressor" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [ foaf:name "H90" ] ;
    rdfs:comment "Parametric EQ with pre/post compressor. Eventide H90 EQ Compressor emulation." ;
    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 "gain1" ; lv2:name "Gain 1" ;
        lv2:default 0.0 ; lv2:minimum -18.0 ; lv2:maximum 12.0 ; units:unit units:db
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 5 ; lv2:symbol "freq1" ; lv2:name "Frequency 1" ;
        lv2:default 200.0 ; lv2:minimum 30.0 ; lv2:maximum 1500.0 ; units:unit units:hz
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 6 ; lv2:symbol "width1" ; lv2:name "Width 1" ;
        lv2:default 5.0 ; lv2:minimum 1.0 ; lv2:maximum 10.0
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 7 ; lv2:symbol "gain2" ; lv2:name "Gain 2" ;
        lv2:default 0.0 ; lv2:minimum -18.0 ; lv2:maximum 12.0 ; units:unit units:db
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 8 ; lv2:symbol "freq2" ; lv2:name "Frequency 2" ;
        lv2:default 3000.0 ; lv2:minimum 1000.0 ; lv2:maximum 9999.0 ; units:unit units:hz
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 9 ; lv2:symbol "width2" ; lv2:name "Width 2" ;
        lv2:default 5.0 ; lv2:minimum 1.0 ; lv2:maximum 10.0
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 10 ; lv2:symbol "bass" ; lv2:name "Bass" ;
        lv2:default 0.0 ; lv2:minimum -18.0 ; lv2:maximum 12.0 ; units:unit units:db
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 11 ; lv2:symbol "treble" ; lv2:name "Treble" ;
        lv2:default 0.0 ; lv2:minimum -18.0 ; lv2:maximum 12.0 ; units:unit units:db
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 12 ; lv2:symbol "compressor" ; lv2:name "Compressor" ;
        lv2:default 0.0 ; lv2:minimum -100.0 ; lv2:maximum 100.0
    ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 13 ; lv2:symbol "trim" ; lv2:name "Trim" ;
        lv2:default 0.0 ; lv2:minimum -12.0 ; lv2:maximum 12.0 ; units:unit units:db
    ] .
endef

export H90_EQCOMPRESSOR_PLUGIN_INFO_H
export H90_EQCOMPRESSOR_PLUGIN_CPP
export H90_EQCOMPRESSOR_PLUGIN_MAKEFILE
export H90_EQCOMPRESSOR_MANIFEST_TTL
export H90_EQCOMPRESSOR_PLUGIN_TTL

define H90_EQCOMPRESSOR_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/h90-eqcompressor
	@echo "$$H90_EQCOMPRESSOR_PLUGIN_INFO_H"  > $(@D)/examples/h90-eqcompressor/DistrhoPluginInfo.h
	@echo "$$H90_EQCOMPRESSOR_PLUGIN_CPP"      > $(@D)/examples/h90-eqcompressor/EQCompressorPlugin.cpp
	@echo "$$H90_EQCOMPRESSOR_PLUGIN_MAKEFILE" > $(@D)/examples/h90-eqcompressor/Makefile
endef

define H90_EQCOMPRESSOR_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true \
		-C $(@D)/examples/h90-eqcompressor lv2_dsp
endef

define H90_EQCOMPRESSOR_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/h90-eqcompressor.lv2
	cp $(@D)/bin/h90-eqcompressor.lv2/h90-eqcompressor_dsp.so \
		$($(PKG)_PKGDIR)/h90-eqcompressor.lv2/
	@echo "$$H90_EQCOMPRESSOR_MANIFEST_TTL" > $($(PKG)_PKGDIR)/h90-eqcompressor.lv2/manifest.ttl
	@echo "$$H90_EQCOMPRESSOR_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/h90-eqcompressor.lv2/h90-eqcompressor.ttl
endef

$(eval $(generic-package))
