# Eventide H9 Blackhole Reverb - MOD Dwarf plugin
# Gravity, Size, Feedback, PreDelay, Lo/Hi EQ, Mod, Freeze
# Upload at https://builder.mod.audio/buildroot

H9_BLACKHOLE_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
H9_BLACKHOLE_SITE = https://github.com/DISTRHO/DPF.git
H9_BLACKHOLE_SITE_METHOD = git
H9_BLACKHOLE_BUNDLES = h9-blackhole.lv2

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

START_NAMESPACE_DISTRHO

struct P1{float z;P1():z(0.f){}void reset(){z=0.f;}
    float lp(float x,float a){return z=x*(1.f-a)+z*a;}
    float hp(float x,float a){float y=x*(1.f-a)+z*a;z=y;return x-y;}};

struct Comb{
    float buf[32768];int wp,len;float fb,lz;
    Comb():wp(0),len(8000),fb(0.85f),lz(0.f){memset(buf,0,sizeof(buf));}
    void reset(){memset(buf,0,sizeof(buf));wp=0;lz=0.f;}
    void setLen(int l){len=(l<32767)?l:32767;}
    float tick(float in,float damp){
        int rp=(wp-len)&32767;float d=buf[rp];
        lz=d*(1.f-damp)+lz*damp;
        buf[wp&32767]=in+lz*fb;wp++;return d;}
};

struct AP{float buf[4096];int wp,len;float g;
    AP():wp(0),len(500),g(0.7f){memset(buf,0,sizeof(buf));}
    void reset(){memset(buf,0,sizeof(buf));wp=0;}
    void setLen(int l){len=(l<4095)?l:4095;}
    float tick(float in){int rp=(wp-len)&4095;float d=buf[rp];
        float v=in+d*(-g);buf[wp&4095]=in+d*g;wp++;return d+v*g;}
};

struct PreDL{float buf[65536];int wp;
    PreDL():wp(0){memset(buf,0,sizeof(buf));}
    void reset(){memset(buf,0,sizeof(buf));wp=0;}
    float tick(float in,int d){
        buf[wp&65535]=in;float o=buf[(wp-d)&65535];wp++;return o;}
};

// Blackhole: massive reverb with Gravity (forward/inverse decay) and Size
class H9BlackholePlugin : public Plugin {
public:
    H9BlackholePlugin():Plugin(kParameterCount,0,0),
      fSR(44100.f),pGravity(0.5f),pSize(0.6f),pFeedback(0.3f),
      pPreDelay(0.f),pLo(0.f),pHi(0.f),pQ(0.5f),
      pModDepth(0.3f),pModRate(0.8f),pMix(0.5f),pFreeze(0.f),
      fFrozen(false),lfoPhL(0.f),lfoPhR(0.5f)
    {initBufs(fSR);}
protected:
    const char* getLabel()const override{return"H9Blackhole";}
    const char* getDescription()const override{return"Eventide H9 Blackhole: cosmic reverb with Gravity, Size, Feedback, inverse decay and modulation.";}
    const char* getMaker()const override{return"MOD Cookbook";}
    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','9','B','H');}

    void initParameter(uint32_t i,Parameter&p)override{
        switch(i){
        case kGravity: p.hints=kParameterIsAutomatable;p.name="Gravity";p.symbol="gravity";
            p.ranges.def=0.5f;p.ranges.min=-1.f;p.ranges.max=1.f;break;
        case kSize: p.hints=kParameterIsAutomatable;p.name="Size";p.symbol="size";
            p.ranges.def=0.6f;p.ranges.min=0.f;p.ranges.max=1.f;break;
        case kFeedback: p.hints=kParameterIsAutomatable;p.name="Feedback";p.symbol="feedback";
            p.ranges.def=0.3f;p.ranges.min=0.f;p.ranges.max=0.99f;break;
        case kPreDelay: p.hints=kParameterIsAutomatable;p.name="Pre-Delay";p.symbol="pre_delay";p.unit="ms";
            p.ranges.def=0.f;p.ranges.min=0.f;p.ranges.max=500.f;break;
        case kLo: p.hints=kParameterIsAutomatable;p.name="Lo EQ";p.symbol="lo_eq";p.unit="dB";
            p.ranges.def=0.f;p.ranges.min=-12.f;p.ranges.max=12.f;break;
        case kHi: p.hints=kParameterIsAutomatable;p.name="Hi EQ";p.symbol="hi_eq";p.unit="dB";
            p.ranges.def=0.f;p.ranges.min=-12.f;p.ranges.max=12.f;break;
        case kQ: p.hints=kParameterIsAutomatable;p.name="Resonance";p.symbol="q";
            p.ranges.def=0.5f;p.ranges.min=0.f;p.ranges.max=1.f;break;
        case kModDepth: p.hints=kParameterIsAutomatable;p.name="Mod Depth";p.symbol="mod_depth";
            p.ranges.def=0.3f;p.ranges.min=0.f;p.ranges.max=1.f;break;
        case kModRate: p.hints=kParameterIsAutomatable;p.name="Mod Rate";p.symbol="mod_rate";p.unit="Hz";
            p.ranges.def=0.8f;p.ranges.min=0.05f;p.ranges.max=5.f;break;
        case kMix: p.hints=kParameterIsAutomatable;p.name="Mix";p.symbol="mix";
            p.ranges.def=0.5f;p.ranges.min=0.f;p.ranges.max=1.f;break;
        case kFreeze: p.hints=kParameterIsAutomatable|kParameterIsBoolean;p.name="Freeze";p.symbol="freeze";
            p.ranges.def=0.f;p.ranges.min=0.f;p.ranges.max=1.f;break;
        default:break;}
    }
    float getParameterValue(uint32_t i)const override{
        switch(i){case kGravity:return pGravity;case kSize:return pSize;
        case kFeedback:return pFeedback;case kPreDelay:return pPreDelay;
        case kLo:return pLo;case kHi:return pHi;case kQ:return pQ;
        case kModDepth:return pModDepth;case kModRate:return pModRate;
        case kMix:return pMix;case kFreeze:return fFrozen?1.f:0.f;default:return 0.f;}
    }
    void setParameterValue(uint32_t i,float v)override{
        switch(i){case kGravity:pGravity=v;updateFb();break;case kSize:pSize=v;updateSize();break;
        case kFeedback:pFeedback=v;updateFb();break;case kPreDelay:pPreDelay=v;break;
        case kLo:pLo=v;break;case kHi:pHi=v;break;case kQ:pQ=v;break;
        case kModDepth:pModDepth=v;break;case kModRate:pModRate=v;break;
        case kMix:pMix=v;break;case kFreeze:fFrozen=(v>=0.5f);break;default:break;}
    }
    void activate()override{
        fSR=(float)getSampleRate();
        initBufs(fSR);
        fPDL.reset();fPDR.reset();
        fLoL.reset();fLoR.reset();fHiL.reset();fHiR.reset();
        lfoPhL=0.f;lfoPhR=0.5f;
    }
    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];
        int preD=(int)(pPreDelay*0.001f*fSR);
        // HF damp: Gravity positive=bright long tail, negative=dark inverse
        float hfDamp=0.3f+(1.f-fabsf(pGravity))*0.5f;
        // Lo/Hi shelving gains
        float loGain=powf(10.f,pLo/20.f);
        float hiGain=powf(10.f,pHi/20.f);
        float aLoLP=expf(-6.2832f*350.f/fSR);
        float aHiHP=expf(-6.2832f*2000.f/fSR);

        for(uint32_t i=0;i<frames;++i){
            float pL=fPDL.tick(inL[i],preD);
            float pR=fPDR.tick(inR[i],preD);
            float fL=fFrozen?0.f:pL;
            float fR=fFrozen?0.f:pR;
            // LFO
            float lfoL=sinf(lfoPhL*6.2832f)*pModDepth*0.003f;
            float lfoR=sinf(lfoPhR*6.2832f)*pModDepth*0.003f;
            lfoPhL+=pModRate/fSR;if(lfoPhL>=1.f)lfoPhL-=1.f;
            lfoPhR+=pModRate/fSR;if(lfoPhR>=1.f)lfoPhR-=1.f;
            // Tail
            float tL=0.f,tR=0.f;
            for(int c=0;c<8;++c)tL+=cL[c].tick(fL+lfoL,hfDamp);
            for(int c=0;c<8;++c)tR+=cR[c].tick(fR+lfoR,hfDamp);
            tL*=0.125f;tR*=0.125f;
            for(int a=0;a<4;++a)tL=aL[a].tick(tL);
            for(int a=0;a<4;++a)tR=aR[a].tick(tR);
            // Gravity inverse: reverse-style by feeding back inverted
            if(pGravity<0.f){
                float inv=-tL*fabsf(pGravity)*0.5f;
                tL=tL*(1.f+pGravity)+inv;
                inv=-tR*fabsf(pGravity)*0.5f;
                tR=tR*(1.f+pGravity)+inv;
            }
            // Lo/Hi EQ on tail
            float loL=fLoL.lp(tL,aLoLP);
            float loR=fLoR.lp(tR,aLoLP);
            float hiL=tL-fHiL.lp(tL,aHiHP);
            float hiR=tR-fHiR.lp(tR,aHiHP);
            tL=loL*loGain+hiL*hiGain+(tL-loL-hiL);
            tR=loR*loGain+hiR*hiGain+(tR-loR-hiR);
            // Width
            float mid=(tL+tR)*0.5f,side=(tL-tR)*0.5f;
            outL[i]=inL[i]*(1.f-pMix)+(mid+side)*pMix;
            outR[i]=inR[i]*(1.f-pMix)+(mid-side)*pMix;
        }
    }
private:
    void initBufs(float sr){
        float sc=sr/44100.f;
        // Long comb delays for galactic scale
        int cLens[8]={9001,9501,8801,8301,7901,8501,7501,7001};
        for(int i=0;i<8;++i){cL[i].setLen((int)(cLens[i]*sc));cR[i].setLen((int)(cLens[i]*sc*1.001f));}
        int aLens[4]={555,341,441,251};
        for(int i=0;i<4;++i){aL[i].setLen((int)(aLens[i]*sc));aR[i].setLen((int)(aLens[i]*sc));}
        for(int i=0;i<8;++i){cL[i].reset();cR[i].reset();}
        for(int i=0;i<4;++i){aL[i].reset();aR[i].reset();}
        updateFb();updateSize();
    }
    void updateFb(){
        // Gravity 0..1: feedback 0.78..0.98. Negative gravity also increases feedback
        float fb=0.78f+(fabsf(pGravity)*0.5f+pFeedback*0.2f);
        if(fb>0.99f)fb=0.99f;
        for(int i=0;i<8;++i){cL[i].fb=fb;cR[i].fb=fb;}
    }
    void updateSize(){
        // Size 0..1 scales comb delays: small=tight reflections, large=galactic
        float sc=(fSR>0.f?fSR:44100.f)/44100.f;
        float sizeFactor=0.3f+pSize*1.5f;
        int cLens[8]={9001,9501,8801,8301,7901,8501,7501,7001};
        for(int i=0;i<8;++i){
            cL[i].setLen((int)(cLens[i]*sc*sizeFactor));
            cR[i].setLen((int)(cLens[i]*sc*sizeFactor*1.001f));
        }
    }
    float fSR;
    float pGravity,pSize,pFeedback,pPreDelay,pLo,pHi,pQ;
    float pModDepth,pModRate,pMix,pFreeze;
    bool  fFrozen;
    float lfoPhL,lfoPhR;
    Comb  cL[8],cR[8];
    AP    aL[4],aR[4];
    PreDL fPDL,fPDR;
    P1    fLoL,fLoR,fHiL,fHiR;
    enum Parameters{kGravity=0,kSize,kFeedback,kPreDelay,kLo,kHi,kQ,
        kModDepth,kModRate,kMix,kFreeze,kParameterCount};
    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(H9BlackholePlugin)
};
Plugin*createPlugin(){return new H9BlackholePlugin();}
END_NAMESPACE_DISTRHO
endef

define H9_BLACKHOLE_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_BRAND "MOD Cookbook"
#define DISTRHO_PLUGIN_NAME  "H9 Blackhole"
#define DISTRHO_PLUGIN_URI   "urn:mod-cookbook:h9-blackhole"
#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{kGravity=0,kSize,kFeedback,kPreDelay,kLo,kHi,kQ,
    kModDepth,kModRate,kMix,kFreeze,kParameterCount};
#endif
endef

define H9_BLACKHOLE_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = h9-blackhole
FILES_DSP = H9BlackholePlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define H9_BLACKHOLE_MANIFEST_TTL
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:mod-cookbook:h9-blackhole>
    a lv2:Plugin,lv2:ReverbPlugin;
    lv2:binary <h9-blackhole_dsp.so>;
    rdfs:seeAlso <h9-blackhole.ttl>.
endef

define H9_BLACKHOLE_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 rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
<urn:mod-cookbook:h9-blackhole>
    a lv2:Plugin,lv2:ReverbPlugin;
    doap:name "H9 Blackhole";
    doap:license <http://opensource.org/licenses/MIT>;
    doap:maintainer [foaf:name "MOD Cookbook";foaf:homepage <https://mod.audio>];
    rdfs:comment "Eventide H9 Blackhole: cosmic reverb with Gravity (forward/inverse), Size, Feedback, modulation and freeze.";
    lv2:port [
        a lv2:InputPort,lv2:AudioPort;lv2:index 0;lv2:symbol "in_l";lv2:name "Audio In L"
    ],[a lv2:InputPort,lv2:AudioPort;lv2:index 1;lv2:symbol "in_r";lv2:name "Audio In R"
    ],[a lv2:OutputPort,lv2:AudioPort;lv2:index 2;lv2:symbol "out_l";lv2:name "Audio Out L"
    ],[a lv2:OutputPort,lv2:AudioPort;lv2:index 3;lv2:symbol "out_r";lv2:name "Audio Out R"
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 4;lv2:symbol "gravity";lv2:name "Gravity";
        lv2:default 0.5;lv2:minimum -1.0;lv2:maximum 1.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 5;lv2:symbol "size";lv2:name "Size";
        lv2:default 0.6;lv2:minimum 0.0;lv2:maximum 1.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 6;lv2:symbol "feedback";lv2:name "Feedback";
        lv2:default 0.3;lv2:minimum 0.0;lv2:maximum 0.99
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 7;lv2:symbol "pre_delay";lv2:name "Pre-Delay";
        lv2:default 0.0;lv2:minimum 0.0;lv2:maximum 500.0;units:unit units:ms
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 8;lv2:symbol "lo_eq";lv2:name "Lo EQ";
        lv2:default 0.0;lv2:minimum -12.0;lv2:maximum 12.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 9;lv2:symbol "hi_eq";lv2:name "Hi EQ";
        lv2:default 0.0;lv2:minimum -12.0;lv2:maximum 12.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 10;lv2:symbol "q";lv2:name "Resonance";
        lv2:default 0.5;lv2:minimum 0.0;lv2:maximum 1.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 11;lv2:symbol "mod_depth";lv2:name "Mod Depth";
        lv2:default 0.3;lv2:minimum 0.0;lv2:maximum 1.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 12;lv2:symbol "mod_rate";lv2:name "Mod Rate";
        lv2:default 0.8;lv2:minimum 0.05;lv2:maximum 5.0;units:unit units:hz
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 13;lv2:symbol "mix";lv2:name "Mix";
        lv2:default 0.5;lv2:minimum 0.0;lv2:maximum 1.0
    ],[a lv2:InputPort,lv2:ControlPort;lv2:index 14;lv2:symbol "freeze";lv2:name "Freeze";
        lv2:default 0.0;lv2:minimum 0.0;lv2:maximum 1.0
    ].
endef

export H9_BLACKHOLE_PLUGIN_CPP H9_BLACKHOLE_PLUGIN_INFO_H H9_BLACKHOLE_PLUGIN_MAKEFILE
export H9_BLACKHOLE_MANIFEST_TTL H9_BLACKHOLE_PLUGIN_TTL

define H9_BLACKHOLE_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/h9-blackhole
	printf '%s' "$$H9_BLACKHOLE_PLUGIN_CPP"      > $(@D)/examples/h9-blackhole/H9BlackholePlugin.cpp
	printf '%s' "$$H9_BLACKHOLE_PLUGIN_INFO_H"   > $(@D)/examples/h9-blackhole/DistrhoPluginInfo.h
	printf '%s' "$$H9_BLACKHOLE_PLUGIN_MAKEFILE" > $(@D)/examples/h9-blackhole/Makefile
endef
define H9_BLACKHOLE_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/h9-blackhole lv2_dsp
endef
define H9_BLACKHOLE_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/h9-blackhole.lv2
	cp $(@D)/bin/h9-blackhole.lv2/h9-blackhole_dsp.so $($(PKG)_PKGDIR)/h9-blackhole.lv2/
	printf '%s' "$$H9_BLACKHOLE_MANIFEST_TTL" > $($(PKG)_PKGDIR)/h9-blackhole.lv2/manifest.ttl
	printf '%s' "$$H9_BLACKHOLE_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/h9-blackhole.lv2/h9-blackhole.ttl
endef
$(eval $(generic-package))
