################################################################################
# alesis-quadraverb - stereo model of the Alesis QuadraVerb multi-effect
# Chorus / EQ / Delay / Reverb, each independently bypassable, matching the
# real hardware's documented control counts and algorithm types.
################################################################################

ALESIS_QUADRAVERB_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ALESIS_QUADRAVERB_SITE = https://github.com/DISTRHO/DPF.git
ALESIS_QUADRAVERB_SITE_METHOD = git
ALESIS_QUADRAVERB_BUNDLES = alesis-quadraverb.lv2

define ALESIS_QUADRAVERB_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND   "AustinDSP"
#define DISTRHO_PLUGIN_NAME    "Alesis QuadraVerb Model"
#define DISTRHO_PLUGIN_URI     "https://github.com/austin-dsp/lv2-plugins/alesis-quadraverb"
#define DISTRHO_PLUGIN_CLAP_ID "com.austindsp.alesisquadraverb"

#define DISTRHO_PLUGIN_NUM_INPUTS   2
#define DISTRHO_PLUGIN_NUM_OUTPUTS  2
#define DISTRHO_PLUGIN_IS_RT_SAFE   1
#define DISTRHO_PLUGIN_IS_SYNTH     0
#define DISTRHO_PLUGIN_HAS_UI       0
#define DISTRHO_PLUGIN_WANT_STATE   0
#define DISTRHO_PLUGIN_WANT_LATENCY 0
#define DISTRHO_PLUGIN_WANT_TIMEPOS 0
#define DISTRHO_PLUGIN_WANT_MIDI_INPUT  0
#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0

enum Parameters {
    // Chorus block
    pChorusBypass = 0,
    pChorusType,
    pChorusSpeed,
    pChorusDepth,
    pChorusFeedback,
    pChorusMix,
    // EQ block
    pEqBypass,
    pEqType,
    pEqP1Freq, pEqP1Gain, pEqP1Q,
    pEqP2Freq, pEqP2Gain, pEqP2Q,
    pEqP3Freq, pEqP3Gain, pEqP3Q,
    pEqP4Freq, pEqP4Gain, pEqP4Q,
    pEqP5Freq, pEqP5Gain, pEqP5Q,
    pEqG1, pEqG2, pEqG3, pEqG4, pEqG5, pEqG6, pEqG7, pEqG8, pEqG9, pEqG10, pEqG11,
    pEqMix,
    // Delay block
    pDelayBypass,
    pDelayType,
    pDelayTime,
    pDelayFeedback,
    pDelayTaps,
    pDelayTapSpacing,
    pDelayMix,
    // Reverb block
    pReverbBypass,
    pReverbType,
    pReverbDecay,
    pReverbDiffusion,
    pReverbDensity,
    pReverbLfDecay,
    pReverbHfDecay,
    pReverbGateEnable,
    pReverbGateThreshold,
    pReverbGateHold,
    pReverbGateRelease,
    pReverbGatedLevel,
    pReverbMix,

    kParameterCount
};

#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED

endef

define ALESIS_QUADRAVERB_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>

START_NAMESPACE_DISTRHO

static const float kTwoPi = 6.28318530718f;

static inline float dbToLin(float db) { return std::pow(10.f, db/20.f); }
static inline float clampf(float v, float lo, float hi) { return v<lo?lo:(v>hi?hi:v); }

// ---------------------------------------------------------------------------
// Biquad peaking EQ (RBJ cookbook), used for both parametric and graphic bands
// ---------------------------------------------------------------------------
struct Biquad {
    float b0,b1,b2,a1,a2;
    float z1,z2;

    Biquad() : b0(1.f),b1(0.f),b2(0.f),a1(0.f),a2(0.f),z1(0.f),z2(0.f) {}

    void init() { z1=0.f; z2=0.f; }

    void setPeaking(float freq, float q, float gainDb, float sr) {
        freq = clampf(freq, 20.f, sr*0.45f);
        q = clampf(q, 0.1f, 20.f);
        float A = std::pow(10.f, gainDb/40.f);
        float w0 = kTwoPi*freq/sr;
        float alpha = std::sin(w0)/(2.f*q);
        float cosw0 = std::cos(w0);

        float a0 =  1.f + alpha/A;
        b0 = (1.f + alpha*A)/a0;
        b1 = (-2.f*cosw0)/a0;
        b2 = (1.f - alpha*A)/a0;
        a1 = (-2.f*cosw0)/a0;
        a2 = (1.f - alpha/A)/a0;
    }

    float process(float in) {
        float out = b0*in + z1;
        z1 = b1*in + z2 - a1*out;
        z2 = b2*in - a2*out;
        return out;
    }
};

// ---------------------------------------------------------------------------
// One-pole lowpass used for HF damping in reverb comb feedback paths
// ---------------------------------------------------------------------------
struct OnePoleLP {
    float z;
    OnePoleLP() : z(0.f) {}
    void init() { z=0.f; }
    float process(float in, float coeff) {
        z = in + coeff*(z-in);
        return z;
    }
};

// ---------------------------------------------------------------------------
// Chorus block: modulated delay line, Mono/Stereo type
// ---------------------------------------------------------------------------
struct ChorusChannel {
    float* buf; int size, mask, wpos;
    float lfoPhase;

    ChorusChannel() : buf(nullptr), size(0), mask(0), wpos(0), lfoPhase(0.f) {}
    ~ChorusChannel() { delete[] buf; }

    void init(float sr) {
        int sz=1; int need=(int)(sr*0.06f)+8; while(sz<need) sz<<=1;
        delete[] buf; buf = new float[sz](); size=sz; mask=sz-1; wpos=0;
    }

    float process(float in, float speedHz, float depth01, float fbk01, float sr, float phaseOffset) {
        buf[wpos&mask] = in;
        (void)speedHz;
        float ph = lfoPhase + phaseOffset;
        if(ph>=1.f) ph -= 1.f;
        float lfo = (std::sin(ph*kTwoPi)*0.5f+0.5f);
        float baseMs = 8.f, swingMs = 12.f*depth01;
        float delaySamp = (baseMs + swingMs*lfo) * 0.001f * sr;
        if(delaySamp < 1.f) delaySamp = 1.f;

        float rd = (float)wpos - delaySamp;
        int ri = (int)rd; float fr = rd - (float)ri;
        float d0 = buf[ri&mask];
        float d1 = buf[(ri-1)&mask];
        float delayed = d0 + fr*(d1-d0);

        buf[wpos&mask] = in + delayed*fbk01;
        ++wpos;
        return delayed;
    }

    void tick(float sr) {
        (void)sr;
    }
};

struct ChorusBlock {
    ChorusChannel chL, chR;
    void init(float sr) { chL.init(sr); chR.init(sr); }

    void process(float& l, float& r, int type, float speed, float depthPct, float fbkPct, float mixPct, float sr) {
        float depth = clampf(depthPct,0.f,100.f)/100.f;
        float fbk = clampf(fbkPct,0.f,90.f)/100.f;
        float mix = clampf(mixPct,0.f,100.f)/100.f;

        float phaseL = 0.f, phaseR = (type==1) ? 0.25f : 0.f; // Stereo = 90deg offset, Mono = in phase

        float wl = chL.process(l, speed, depth, fbk, sr, phaseL);
        float wr = chR.process(r, speed, depth, fbk, sr, phaseR);

        chL.lfoPhase += speed/sr; if(chL.lfoPhase>=1.f) chL.lfoPhase-=1.f;
        chR.lfoPhase = chL.lfoPhase;

        l = l*(1.f-mix) + wl*mix;
        r = r*(1.f-mix) + wr*mix;
    }
};

// ---------------------------------------------------------------------------
// EQ block: 3/5-Band Parametric or 11-Band Graphic
// ---------------------------------------------------------------------------
static const float kGraphicFreqs[11] = {16.f,32.f,62.f,125.f,250.f,500.f,1000.f,2000.f,4000.f,8000.f,16000.f};

struct EqChannel {
    Biquad para[5];
    Biquad graphic[11];

    void init() {
        for(int i=0;i<5;++i) para[i].init();
        for(int i=0;i<11;++i) graphic[i].init();
    }

    float process(float in, int type, int activeParaBands) {
        float x = in;
        if(type==2) {
            for(int i=0;i<11;++i) x = graphic[i].process(x);
        } else {
            for(int i=0;i<activeParaBands;++i) x = para[i].process(x);
        }
        return x;
    }
};

struct EqBlock {
    EqChannel chL, chR;
    void init() { chL.init(); chR.init(); }

    void updateCoeffs(int type, const float* pFreq, const float* pGain, const float* pQ,
                       const float* gGain, float sr) {
        int bands = (type==0) ? 3 : 5;
        for(int i=0;i<bands;++i) {
            chL.para[i].setPeaking(pFreq[i], pQ[i], pGain[i], sr);
            chR.para[i].setPeaking(pFreq[i], pQ[i], pGain[i], sr);
        }
        for(int i=0;i<11;++i) {
            chL.graphic[i].setPeaking(kGraphicFreqs[i], 1.4f, gGain[i], sr);
            chR.graphic[i].setPeaking(kGraphicFreqs[i], 1.4f, gGain[i], sr);
        }
    }

    void process(float& l, float& r, int type, float mixPct) {
        int activeBands = (type==0) ? 3 : 5;
        float mix = clampf(mixPct,0.f,100.f)/100.f;
        float wl = chL.process(l, type, activeBands);
        float wr = chR.process(r, type, activeBands);
        l = l*(1.f-mix) + wl*mix;
        r = r*(1.f-mix) + wr*mix;
    }
};

// ---------------------------------------------------------------------------
// Delay block: Mono / Stereo / Ping-Pong / Multi-Tap
// ---------------------------------------------------------------------------
struct DelayChannel {
    float* buf; int size, mask, wpos;
    DelayChannel() : buf(nullptr), size(0), mask(0), wpos(0) {}
    ~DelayChannel() { delete[] buf; }

    void init(float sr) {
        int need = (int)(sr*4.0f)+16; // headroom: 1.5s time + up to 2s of multi-tap spread
        int sz=1; while(sz<need) sz<<=1;
        delete[] buf; buf=new float[sz](); size=sz; mask=sz-1; wpos=0;
    }

    float readAt(float delaySamp) {
        float rd = (float)wpos - delaySamp;
        int ri=(int)rd; float fr=rd-(float)ri;
        float d0=buf[ri&mask], d1=buf[(ri-1)&mask];
        return d0 + fr*(d1-d0);
    }

    void write(float v) { buf[wpos&mask]=v; ++wpos; }
};

struct DelayBlock {
    DelayChannel chL, chR;
    void init(float sr) { chL.init(sr); chR.init(sr); }

    void process(float& l, float& r, int type, float timeMs, float fbkPct, int taps, float tapSpacingMs,
                 float mixPct, float sr) {
        float fbk = clampf(fbkPct,0.f,95.f)/100.f;
        float mix = clampf(mixPct,0.f,100.f)/100.f;
        float delaySamp = clampf(timeMs,1.f,1500.f)*0.001f*sr;
        float spacingSamp = clampf(tapSpacingMs,10.f,500.f)*0.001f*sr;
        int nTaps = taps<1?1:(taps>4?4:taps);

        if(type==3) {
            // Multi-Tap: sum evenly spaced taps, feedback recirculates the last tap
            float sumL=0.f, sumR=0.f, lastL=0.f, lastR=0.f;
            for(int t=0;t<nTaps;++t) {
                float d = delaySamp + spacingSamp*(float)t;
                float tl = chL.readAt(d);
                float tr = chR.readAt(d);
                sumL += tl; sumR += tr;
                if(t==nTaps-1) { lastL=tl; lastR=tr; }
            }
            sumL /= (float)nTaps; sumR /= (float)nTaps;
            chL.write(l + lastL*fbk);
            chR.write(r + lastR*fbk);
            l = l*(1.f-mix) + sumL*mix;
            r = r*(1.f-mix) + sumR*mix;
        } else if(type==2) {
            // Ping-Pong: L feeds R's tap and vice versa
            float dl = chL.readAt(delaySamp);
            float dr = chR.readAt(delaySamp);
            chL.write(l + dr*fbk);
            chR.write(r + dl*fbk);
            l = l*(1.f-mix) + dl*mix;
            r = r*(1.f-mix) + dr*mix;
        } else if(type==1) {
            // Stereo: independent per-channel delay/feedback
            float dl = chL.readAt(delaySamp);
            float dr = chR.readAt(delaySamp);
            chL.write(l + dl*fbk);
            chR.write(r + dr*fbk);
            l = l*(1.f-mix) + dl*mix;
            r = r*(1.f-mix) + dr*mix;
        } else {
            // Mono: single summed tap fed to both outputs
            float mono = (l+r)*0.5f;
            float d = chL.readAt(delaySamp);
            chL.write(mono + d*fbk);
            chR.write(0.f); // unused in mono mode, kept initialized
            l = l*(1.f-mix) + d*mix;
            r = r*(1.f-mix) + d*mix;
        }
    }
};

// ---------------------------------------------------------------------------
// Reverb block: parallel-comb / series-allpass (Freeverb-style), 5 real
// QuadraVerb types, plus the documented noise gate (Threshold/Hold/Release/
// Gated Level).
// ---------------------------------------------------------------------------
struct CombFilter {
    float* buf; int size, mask, wpos;
    OnePoleLP damp;
    CombFilter() : buf(nullptr), size(0), mask(0), wpos(0) {}
    ~CombFilter() { delete[] buf; }
    void init(int lenSamples) {
        int sz=1; while(sz<lenSamples+4) sz<<=1;
        delete[] buf; buf=new float[sz](); size=sz; mask=sz-1; wpos=0;
        damp.init();
    }
    float process(float in, float feedback, float dampCoeff) {
        float out = buf[wpos&mask];
        float damped = damp.process(out, dampCoeff);
        buf[wpos&mask] = in + damped*feedback;
        ++wpos;
        return out;
    }
};

struct AllpassFilter {
    float* buf; int size, mask, wpos;
    AllpassFilter() : buf(nullptr), size(0), mask(0), wpos(0) {}
    ~AllpassFilter() { delete[] buf; }
    void init(int lenSamples) {
        int sz=1; while(sz<lenSamples+4) sz<<=1;
        delete[] buf; buf=new float[sz](); size=sz; mask=sz-1; wpos=0;
    }
    float process(float in, float g) {
        float buffered = buf[wpos&mask];
        float out = -in*g + buffered;
        buf[wpos&mask] = in + buffered*g;
        ++wpos;
        return out;
    }
};

struct ReverbChannel {
    static const int kCombs = 4;
    static const int kAllpasses = 2;
    CombFilter combs[kCombs];
    AllpassFilter allpasses[kAllpasses];

    void init(float sr, int channelOffset) {
        static const float combMs[kCombs] = {35.3f, 40.1f, 44.9f, 49.7f};
        static const float apMs[kAllpasses] = {5.1f, 12.6f};
        for(int i=0;i<kCombs;++i) {
            float ms = combMs[i] + (float)channelOffset*0.8f;
            combs[i].init((int)(ms*0.001f*sr));
        }
        for(int i=0;i<kAllpasses;++i) {
            float ms = apMs[i] + (float)channelOffset*0.3f;
            allpasses[i].init((int)(ms*0.001f*sr));
        }
    }

    float process(float in, float feedback, float dampCoeff, float diffusion01) {
        float sum=0.f;
        for(int i=0;i<kCombs;++i) sum += combs[i].process(in, feedback, dampCoeff);
        sum *= (1.f/(float)kCombs);
        for(int i=0;i<kAllpasses;++i) sum = allpasses[i].process(sum, diffusion01*0.5f);
        return sum;
    }
};

struct ReverbBlock {
    ReverbChannel chL, chR;
    float gateGain, envDb, holdTimer;
    int gateState; // 0=open,1=hold,2=release,3=closed
    float revEnvL, revEnvR; // reverse-type envelope

    ReverbBlock() : gateGain(1.f), envDb(-100.f), holdTimer(0.f), gateState(0), revEnvL(0.f), revEnvR(0.f) {}

    void init(float sr) {
        chL.init(sr, 0);
        chR.init(sr, 1);
        gateGain=1.f; envDb=-100.f; holdTimer=0.f; gateState=0; revEnvL=0.f; revEnvR=0.f;
    }

    void process(float& l, float& r, int type, float decayS, float diffusionPct, float densityPct,
                 float lfDecayX, float hfDecayPct, bool gateOn, float gateThreshDb, float gateHoldMs,
                 float gateReleaseMs, float gatedLevelPct, float mixPct, float sr) {
        float diffusion = clampf(diffusionPct,0.f,100.f)/100.f;
        float hfDamp = clampf(hfDecayPct,0.f,100.f)/100.f;
        float mix = clampf(mixPct,0.f,100.f)/100.f;

        // Type shapes base decay scaling and comb feedback character
        float typeScale = 1.f;
        switch(type) {
            case 0: typeScale = 0.55f; break; // Room: short & tight
            case 1: typeScale = 0.8f;  break; // Chamber: medium
            case 2: typeScale = 1.15f; break; // Hall: long & lush
            case 3: typeScale = 0.9f;  break; // Plate: bright/dense
            case 4: typeScale = 0.7f;  break; // Reverse: swell, handled below
        }
        float decay = clampf(decayS,0.1f,20.f) * typeScale * (0.5f + 0.5f*lfDecayX);
        // convert desired RT60-ish decay time into per-loop comb feedback gain
        float loopMs = 42.f; // approx average comb loop time
        float feedback = std::pow(10.f, (-3.f*(loopMs*0.001f))/std::max(0.05f,decay));
        feedback = clampf(feedback, 0.f, 0.98f);
        // Density widens/narrows diffusion network engagement
        float density = clampf(densityPct,0.f,100.f)/100.f;
        float diffEff = diffusion*(0.4f+0.6f*density);
        float dampCoeff = hfDamp*0.7f; // 0=bright,1=dark feedback

        float wl = chL.process(l, feedback, dampCoeff, diffEff);
        float wr = chR.process(r, feedback, dampCoeff, diffEff);

        if(type==4) {
            // Reverse: envelope ramps up then cuts, applied to the wet tail
            float rate = 1.f/std::max(0.2f, decay*0.5f);
            revEnvL += rate/sr; if(revEnvL>1.f) revEnvL=0.f;
            revEnvR = revEnvL;
            float shapeL = revEnvL<0.85f ? (revEnvL/0.85f) : (1.f-(revEnvL-0.85f)/0.15f);
            wl *= clampf(shapeL,0.f,1.f);
            wr *= clampf(shapeL,0.f,1.f);
        }

        // Noise gate on the reverb tail (Threshold/Hold/Release/Gated Level)
        if(gateOn) {
            float inLevel = std::fabs(l)+std::fabs(r);
            float inDb = inLevel>0.00001f ? 20.f*std::log10(inLevel) : -100.f;
            envDb += (inDb-envDb)*0.05f;

            float dt = 1000.f/sr;
            if(envDb > gateThreshDb) {
                gateState=0; holdTimer=0.f;
                gateGain += (1.f-gateGain)*0.2f; // fast open
            } else {
                if(gateState==0) { gateState=1; holdTimer=0.f; }
                else if(gateState==1) {
                    holdTimer += dt;
                    if(holdTimer>=gateHoldMs) gateState=2;
                } else if(gateState==2) {
                    float floorGain = clampf(gatedLevelPct,0.f,100.f)/100.f;
                    float relCoeff = 1.f - std::exp(-dt/std::max(1.f,gateReleaseMs));
                    gateGain += (floorGain-gateGain)*relCoeff;
                    if(std::fabs(gateGain-floorGain) < 0.001f) gateState=3;
                }
                // state 3 (closed): hold at current gateGain (== floor)
            }
            wl *= gateGain;
            wr *= gateGain;
        } else {
            gateGain = 1.f;
        }

        l = l*(1.f-mix) + wl*mix;
        r = r*(1.f-mix) + wr*mix;
    }
};

// ---------------------------------------------------------------------------
// Main plugin
// ---------------------------------------------------------------------------
class AlesisQuadraverbPlugin : public Plugin {
public:
    AlesisQuadraverbPlugin() : Plugin(kParameterCount, 0, 0), fSr(48000.f) {
        for(int i=0;i<kParameterCount;++i) fParams[i]=0.f;

        // CHECKLIST: every buffer-owning struct instance below MUST get .init()
        // called here (missing an .init() call leaves a null/garbage buffer
        // pointer and segfaults the very first audio block - see tallon-banshee
        // post-mortem). ChorusBlock, EqBlock, DelayBlock, ReverbBlock each own
        // heap buffers internally.
        fChorus.init(fSr);
        fEq.init();
        fDelay.init(fSr);
        fReverb.init(fSr);

        setDefaultParams();
        sampleRateChanged(getSampleRate());
    }

protected:
    const char* getLabel() const override { return "AlesisQuadraverb"; }
    const char* getDescription() const override {
        return "Stereo model of the Alesis QuadraVerb multi-effect: switchable "
               "Chorus, EQ, Delay and Reverb blocks, each individually bypassable, "
               "matching the real unit's control counts and algorithm types.";
    }
    const char* getMaker() const override { return "AustinDSP"; }
    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('A','Q','V','1'); }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        fChorus.init(fSr);
        fEq.init();
        fDelay.init(fSr);
        fReverb.init(fSr);
        updateEqCoeffs();
    }

    void setDefaultParams() {
        static const float defaults[kParameterCount] = {
            /* chorus */ 1,0,0.8f,50.f,20.f,50.f,
            /* eq */ 1,0, 100.f,0.f,1.f, 300.f,0.f,1.f, 800.f,0.f,1.f, 2500.f,0.f,1.f, 7000.f,0.f,1.f,
                     0.f,0.f,0.f,0.f,0.f,0.f,0.f,0.f,0.f,0.f,0.f, 100.f,
            /* delay */ 1,0,350.f,30.f,3,80.f,35.f,
            /* reverb */ 0,2,2.5f,70.f,70.f,1.f,50.f,0,-40.f,50.f,200.f,10.f,35.f
        };
        for(int i=0;i<kParameterCount;++i) fParams[i]=defaults[i];
    }

    void initParameter(uint32_t index, Parameter& parameter) override {
        parameter.hints = kParameterIsAutomatable;
        switch(index) {
            case pChorusBypass: parameter.name="Chorus Bypass"; parameter.symbol="chorus_bypass";
                parameter.hints|=kParameterIsBoolean; parameter.ranges.def=1; parameter.ranges.min=0; parameter.ranges.max=1; break;
            case pChorusType: parameter.name="Chorus Type"; parameter.symbol="chorus_type";
                parameter.hints|=kParameterIsInteger; parameter.ranges.def=0; parameter.ranges.min=0; parameter.ranges.max=1;
                { static const char* labels[]={"Mono Chorus","Stereo Chorus"}; setEnumHints(parameter,labels,2); } break;
            case pChorusSpeed: parameter.name="Chorus Speed"; parameter.symbol="chorus_speed"; parameter.unit="Hz";
                parameter.ranges.def=0.8f; parameter.ranges.min=0.05f; parameter.ranges.max=5.f; break;
            case pChorusDepth: parameter.name="Chorus Depth"; parameter.symbol="chorus_depth"; parameter.unit="%";
                parameter.ranges.def=50.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;
            case pChorusFeedback: parameter.name="Chorus Feedback"; parameter.symbol="chorus_feedback"; parameter.unit="%";
                parameter.ranges.def=20.f; parameter.ranges.min=0.f; parameter.ranges.max=90.f; break;
            case pChorusMix: parameter.name="Chorus Mix"; parameter.symbol="chorus_mix"; parameter.unit="%";
                parameter.ranges.def=50.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;

            case pEqBypass: parameter.name="EQ Bypass"; parameter.symbol="eq_bypass";
                parameter.hints|=kParameterIsBoolean; parameter.ranges.def=1; parameter.ranges.min=0; parameter.ranges.max=1; break;
            case pEqType: parameter.name="EQ Type"; parameter.symbol="eq_type";
                parameter.hints|=kParameterIsInteger; parameter.ranges.def=0; parameter.ranges.min=0; parameter.ranges.max=2;
                { static const char* labels[]={"3-Band Parametric","5-Band Parametric","11-Band Graphic"}; setEnumHints(parameter,labels,3); } break;

            case pEqP1Freq: parameter.name="EQ Band 1 Freq"; parameter.symbol="eq_p1_freq"; parameter.unit="Hz";
                parameter.ranges.def=100.0f; parameter.ranges.min=20.f; parameter.ranges.max=20000.f; break;
            case pEqP1Gain: parameter.name="EQ Band 1 Gain"; parameter.symbol="eq_p1_gain"; parameter.unit="dB";
                parameter.ranges.def=0.f; parameter.ranges.min=-12.f; parameter.ranges.max=12.f; break;
            case pEqP1Q: parameter.name="EQ Band 1 Q"; parameter.symbol="eq_p1_q";
                parameter.ranges.def=1.f; parameter.ranges.min=0.3f; parameter.ranges.max=10.f; break;
            case pEqP2Freq: parameter.name="EQ Band 2 Freq"; parameter.symbol="eq_p2_freq"; parameter.unit="Hz";
                parameter.ranges.def=300.0f; parameter.ranges.min=20.f; parameter.ranges.max=20000.f; break;
            case pEqP2Gain: parameter.name="EQ Band 2 Gain"; parameter.symbol="eq_p2_gain"; parameter.unit="dB";
                parameter.ranges.def=0.f; parameter.ranges.min=-12.f; parameter.ranges.max=12.f; break;
            case pEqP2Q: parameter.name="EQ Band 2 Q"; parameter.symbol="eq_p2_q";
                parameter.ranges.def=1.f; parameter.ranges.min=0.3f; parameter.ranges.max=10.f; break;
            case pEqP3Freq: parameter.name="EQ Band 3 Freq"; parameter.symbol="eq_p3_freq"; parameter.unit="Hz";
                parameter.ranges.def=800.0f; parameter.ranges.min=20.f; parameter.ranges.max=20000.f; break;
            case pEqP3Gain: parameter.name="EQ Band 3 Gain"; parameter.symbol="eq_p3_gain"; parameter.unit="dB";
                parameter.ranges.def=0.f; parameter.ranges.min=-12.f; parameter.ranges.max=12.f; break;
            case pEqP3Q: parameter.name="EQ Band 3 Q"; parameter.symbol="eq_p3_q";
                parameter.ranges.def=1.f; parameter.ranges.min=0.3f; parameter.ranges.max=10.f; break;
            case pEqP4Freq: parameter.name="EQ Band 4 Freq"; parameter.symbol="eq_p4_freq"; parameter.unit="Hz";
                parameter.ranges.def=2500.0f; parameter.ranges.min=20.f; parameter.ranges.max=20000.f; break;
            case pEqP4Gain: parameter.name="EQ Band 4 Gain"; parameter.symbol="eq_p4_gain"; parameter.unit="dB";
                parameter.ranges.def=0.f; parameter.ranges.min=-12.f; parameter.ranges.max=12.f; break;
            case pEqP4Q: parameter.name="EQ Band 4 Q"; parameter.symbol="eq_p4_q";
                parameter.ranges.def=1.f; parameter.ranges.min=0.3f; parameter.ranges.max=10.f; break;
            case pEqP5Freq: parameter.name="EQ Band 5 Freq"; parameter.symbol="eq_p5_freq"; parameter.unit="Hz";
                parameter.ranges.def=7000.0f; parameter.ranges.min=20.f; parameter.ranges.max=20000.f; break;
            case pEqP5Gain: parameter.name="EQ Band 5 Gain"; parameter.symbol="eq_p5_gain"; parameter.unit="dB";
                parameter.ranges.def=0.f; parameter.ranges.min=-12.f; parameter.ranges.max=12.f; break;
            case pEqP5Q: parameter.name="EQ Band 5 Q"; parameter.symbol="eq_p5_q";
                parameter.ranges.def=1.f; parameter.ranges.min=0.3f; parameter.ranges.max=10.f; break;

            case pEqG1: case pEqG2: case pEqG3: case pEqG4: case pEqG5: case pEqG6:
            case pEqG7: case pEqG8: case pEqG9: case pEqG10: case pEqG11: {
                static const float freqs[11] = {16.f,32.f,62.f,125.f,250.f,500.f,1000.f,2000.f,4000.f,8000.f,16000.f};
                int gi = index - pEqG1;
                char nameBuf[32]; char symBuf[16];
                std::snprintf(nameBuf,sizeof(nameBuf),"EQ Graphic %gHz", (double)freqs[gi]);
                std::snprintf(symBuf,sizeof(symBuf),"eq_g%d_gain", gi+1);
                parameter.name = strdupbuf(nameBuf); parameter.symbol = strdupbuf(symBuf);
                parameter.unit="dB"; parameter.ranges.def=0.f; parameter.ranges.min=-12.f; parameter.ranges.max=12.f;
                break;
            }
            case pEqMix: parameter.name="EQ Mix"; parameter.symbol="eq_mix"; parameter.unit="%";
                parameter.ranges.def=100.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;

            case pDelayBypass: parameter.name="Delay Bypass"; parameter.symbol="delay_bypass";
                parameter.hints|=kParameterIsBoolean; parameter.ranges.def=1; parameter.ranges.min=0; parameter.ranges.max=1; break;
            case pDelayType: parameter.name="Delay Type"; parameter.symbol="delay_type";
                parameter.hints|=kParameterIsInteger; parameter.ranges.def=0; parameter.ranges.min=0; parameter.ranges.max=3;
                { static const char* labels[]={"Mono","Stereo","Ping-Pong","Multi-Tap"}; setEnumHints(parameter,labels,4); } break;
            case pDelayTime: parameter.name="Delay Time"; parameter.symbol="delay_time"; parameter.unit="ms";
                parameter.ranges.def=350.f; parameter.ranges.min=1.f; parameter.ranges.max=1500.f; break;
            case pDelayFeedback: parameter.name="Delay Feedback"; parameter.symbol="delay_feedback"; parameter.unit="%";
                parameter.ranges.def=30.f; parameter.ranges.min=0.f; parameter.ranges.max=95.f; break;
            case pDelayTaps: parameter.name="Delay Taps"; parameter.symbol="delay_taps";
                parameter.hints|=kParameterIsInteger; parameter.ranges.def=3; parameter.ranges.min=1; parameter.ranges.max=4; break;
            case pDelayTapSpacing: parameter.name="Delay Tap Spacing"; parameter.symbol="delay_tap_spacing"; parameter.unit="ms";
                parameter.ranges.def=80.f; parameter.ranges.min=10.f; parameter.ranges.max=500.f; break;
            case pDelayMix: parameter.name="Delay Mix"; parameter.symbol="delay_mix"; parameter.unit="%";
                parameter.ranges.def=35.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;

            case pReverbBypass: parameter.name="Reverb Bypass"; parameter.symbol="reverb_bypass";
                parameter.hints|=kParameterIsBoolean; parameter.ranges.def=0; parameter.ranges.min=0; parameter.ranges.max=1; break;
            case pReverbType: parameter.name="Reverb Type"; parameter.symbol="reverb_type";
                parameter.hints|=kParameterIsInteger; parameter.ranges.def=2; parameter.ranges.min=0; parameter.ranges.max=4;
                { static const char* labels[]={"Room","Chamber","Hall","Plate","Reverse"}; setEnumHints(parameter,labels,5); } break;
            case pReverbDecay: parameter.name="Reverb Decay"; parameter.symbol="reverb_decay"; parameter.unit="s";
                parameter.ranges.def=2.5f; parameter.ranges.min=0.1f; parameter.ranges.max=20.f; break;
            case pReverbDiffusion: parameter.name="Reverb Diffusion"; parameter.symbol="reverb_diffusion"; parameter.unit="%";
                parameter.ranges.def=70.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;
            case pReverbDensity: parameter.name="Reverb Density"; parameter.symbol="reverb_density"; parameter.unit="%";
                parameter.ranges.def=70.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;
            case pReverbLfDecay: parameter.name="Reverb LF Decay"; parameter.symbol="reverb_lf_decay"; parameter.unit="x";
                parameter.ranges.def=1.f; parameter.ranges.min=0.5f; parameter.ranges.max=2.f; break;
            case pReverbHfDecay: parameter.name="Reverb HF Decay"; parameter.symbol="reverb_hf_decay"; parameter.unit="%";
                parameter.ranges.def=50.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;
            case pReverbGateEnable: parameter.name="Reverb Gate Enable"; parameter.symbol="reverb_gate_enable";
                parameter.hints|=kParameterIsBoolean; parameter.ranges.def=0; parameter.ranges.min=0; parameter.ranges.max=1; break;
            case pReverbGateThreshold: parameter.name="Reverb Gate Threshold"; parameter.symbol="reverb_gate_threshold"; parameter.unit="dB";
                parameter.ranges.def=-40.f; parameter.ranges.min=-60.f; parameter.ranges.max=0.f; break;
            case pReverbGateHold: parameter.name="Reverb Gate Hold"; parameter.symbol="reverb_gate_hold"; parameter.unit="ms";
                parameter.ranges.def=50.f; parameter.ranges.min=0.f; parameter.ranges.max=500.f; break;
            case pReverbGateRelease: parameter.name="Reverb Gate Release"; parameter.symbol="reverb_gate_release"; parameter.unit="ms";
                parameter.ranges.def=200.f; parameter.ranges.min=1.f; parameter.ranges.max=2000.f; break;
            case pReverbGatedLevel: parameter.name="Reverb Gated Level"; parameter.symbol="reverb_gated_level"; parameter.unit="%";
                parameter.ranges.def=10.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;
            case pReverbMix: parameter.name="Reverb Mix"; parameter.symbol="reverb_mix"; parameter.unit="%";
                parameter.ranges.def=35.f; parameter.ranges.min=0.f; parameter.ranges.max=100.f; break;
        }
    }

    static void setEnumHints(Parameter& parameter, const char* const* labels, int count) {
        parameter.enumValues.count = count;
        parameter.enumValues.restrictedMode = true;
        ParameterEnumerationValue* values = new ParameterEnumerationValue[count];
        for(int i=0;i<count;++i) { values[i].value=(float)i; values[i].label=labels[i]; }
        parameter.enumValues.values = values;
    }

    static const char* strdupbuf(const char* s) {
        char* out = (char*)std::malloc(std::strlen(s)+1);
        std::strcpy(out, s);
        return out;
    }

    float getParameterValue(uint32_t index) const override { return fParams[index]; }

    void setParameterValue(uint32_t index, float value) override {
        fParams[index] = value;
        if((index>=pEqType && index<=pEqMix)) updateEqCoeffs();
    }

    void updateEqCoeffs() {
        float freqs[5] = { fParams[pEqP1Freq], fParams[pEqP2Freq], fParams[pEqP3Freq], fParams[pEqP4Freq], fParams[pEqP5Freq] };
        float gains[5] = { fParams[pEqP1Gain], fParams[pEqP2Gain], fParams[pEqP3Gain], fParams[pEqP4Gain], fParams[pEqP5Gain] };
        float qs[5]    = { fParams[pEqP1Q],    fParams[pEqP2Q],    fParams[pEqP3Q],    fParams[pEqP4Q],    fParams[pEqP5Q] };
        float ggains[11];
        for(int i=0;i<11;++i) ggains[i] = fParams[pEqG1+i];
        fEq.updateCoeffs((int)fParams[pEqType], freqs, gains, qs, ggains, fSr);
    }

    void activate() override {
        fChorus.init(fSr);
        fEq.init();
        fDelay.init(fSr);
        fReverb.init(fSr);
        updateEqCoeffs();
    }

    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 bool eqOn      = fParams[pEqBypass]     > 0.5f;
        const bool chorusOn  = fParams[pChorusBypass] > 0.5f;
        const bool delayOn   = fParams[pDelayBypass]  > 0.5f;
        const bool reverbOn  = fParams[pReverbBypass] > 0.5f;

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

            // Signal chain follows the real QuadraVerb QuadMode order:
            // EQ -> Chorus (Pitch slot) -> Delay -> Reverb
            if(eqOn) fEq.process(l, r, (int)fParams[pEqType], fParams[pEqMix]);

            if(chorusOn) fChorus.process(l, r, (int)fParams[pChorusType], fParams[pChorusSpeed],
                                          fParams[pChorusDepth], fParams[pChorusFeedback], fParams[pChorusMix], fSr);

            if(delayOn) fDelay.process(l, r, (int)fParams[pDelayType], fParams[pDelayTime], fParams[pDelayFeedback],
                                        (int)fParams[pDelayTaps], fParams[pDelayTapSpacing], fParams[pDelayMix], fSr);

            if(reverbOn) fReverb.process(l, r, (int)fParams[pReverbType], fParams[pReverbDecay], fParams[pReverbDiffusion],
                                          fParams[pReverbDensity], fParams[pReverbLfDecay], fParams[pReverbHfDecay],
                                          fParams[pReverbGateEnable] > 0.5f, fParams[pReverbGateThreshold],
                                          fParams[pReverbGateHold], fParams[pReverbGateRelease], fParams[pReverbGatedLevel],
                                          fParams[pReverbMix], fSr);

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

private:
    float fParams[kParameterCount];
    float fSr;
    ChorusBlock fChorus;
    EqBlock fEq;
    DelayBlock fDelay;
    ReverbBlock fReverb;

    DISTRHO_DECLARE_NON_COPYABLE(AlesisQuadraverbPlugin)
};

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

END_NAMESPACE_DISTRHO

endef

define ALESIS_QUADRAVERB_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = alesis-quadraverb
FILES_DSP = Plugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)

endef

define ALESIS_QUADRAVERB_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://github.com/austin-dsp/lv2-plugins/alesis-quadraverb>
    a lv2:Plugin ;
    lv2:binary <alesis-quadraverb_dsp.so> ;
    rdfs:seeAlso <alesis-quadraverb.ttl> .

endef

define ALESIS_QUADRAVERB_PLUGIN_TTL
@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 doap:  <http://usefulinc.com/ns/doap#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .

<https://github.com/austin-dsp/lv2-plugins/alesis-quadraverb>
    a lv2:Plugin ;
    doap:name "Alesis QuadraVerb Model" ;
    doap:license <https://opensource.org/licenses/MIT> ;
    lv2:project <https://github.com/austin-dsp/lv2-plugins/alesis-quadraverb> ;
    lv2:optionalFeature lv2:hardRTCapable ;
    lv2:port
    [
        a lv2:InputPort, lv2:AudioPort ;
        lv2:index 0 ;
        lv2:symbol "audio_in_l" ;
        lv2:name "Audio Input L" ;
    ] ,
    [
        a lv2:InputPort, lv2:AudioPort ;
        lv2:index 1 ;
        lv2:symbol "audio_in_r" ;
        lv2:name "Audio Input R" ;
    ] ,
    [
        a lv2:OutputPort, lv2:AudioPort ;
        lv2:index 2 ;
        lv2:symbol "audio_out_l" ;
        lv2:name "Audio Output L" ;
    ] ,
    [
        a lv2:OutputPort, lv2:AudioPort ;
        lv2:index 3 ;
        lv2:symbol "audio_out_r" ;
        lv2:name "Audio Output R" ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "chorus_bypass" ;
        lv2:name "Chorus Bypass" ;
        lv2:default 1 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:toggled ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "chorus_type" ;
        lv2:name "Chorus Type" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:integer, lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Mono Chorus" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Stereo Chorus" ; rdf:value 1 ] ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 6 ;
        lv2:symbol "chorus_speed" ;
        lv2:name "Chorus Speed" ;
        lv2:default 0.8 ;
        lv2:minimum 0.05 ;
        lv2:maximum 5.0 ;
        units:unit units:hz ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 7 ;
        lv2:symbol "chorus_depth" ;
        lv2:name "Chorus Depth" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 8 ;
        lv2:symbol "chorus_feedback" ;
        lv2:name "Chorus Feedback" ;
        lv2:default 20.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 90.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 9 ;
        lv2:symbol "chorus_mix" ;
        lv2:name "Chorus Mix" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 10 ;
        lv2:symbol "eq_bypass" ;
        lv2:name "EQ Bypass" ;
        lv2:default 1 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:toggled ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 11 ;
        lv2:symbol "eq_type" ;
        lv2:name "EQ Type" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 2 ;
        lv2:portProperty lv2:integer, lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "3-Band Parametric" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "5-Band Parametric" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "11-Band Graphic" ; rdf:value 2 ] ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 12 ;
        lv2:symbol "eq_p1_freq" ;
        lv2:name "EQ Band 1 Freq" ;
        lv2:default 100.0 ;
        lv2:minimum 20.0 ;
        lv2:maximum 20000.0 ;
        units:unit units:hz ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 13 ;
        lv2:symbol "eq_p1_gain" ;
        lv2:name "EQ Band 1 Gain" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 14 ;
        lv2:symbol "eq_p1_q" ;
        lv2:name "EQ Band 1 Q" ;
        lv2:default 1.0 ;
        lv2:minimum 0.3 ;
        lv2:maximum 10.0 ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 15 ;
        lv2:symbol "eq_p2_freq" ;
        lv2:name "EQ Band 2 Freq" ;
        lv2:default 300.0 ;
        lv2:minimum 20.0 ;
        lv2:maximum 20000.0 ;
        units:unit units:hz ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 16 ;
        lv2:symbol "eq_p2_gain" ;
        lv2:name "EQ Band 2 Gain" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 17 ;
        lv2:symbol "eq_p2_q" ;
        lv2:name "EQ Band 2 Q" ;
        lv2:default 1.0 ;
        lv2:minimum 0.3 ;
        lv2:maximum 10.0 ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 18 ;
        lv2:symbol "eq_p3_freq" ;
        lv2:name "EQ Band 3 Freq" ;
        lv2:default 800.0 ;
        lv2:minimum 20.0 ;
        lv2:maximum 20000.0 ;
        units:unit units:hz ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 19 ;
        lv2:symbol "eq_p3_gain" ;
        lv2:name "EQ Band 3 Gain" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 20 ;
        lv2:symbol "eq_p3_q" ;
        lv2:name "EQ Band 3 Q" ;
        lv2:default 1.0 ;
        lv2:minimum 0.3 ;
        lv2:maximum 10.0 ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 21 ;
        lv2:symbol "eq_p4_freq" ;
        lv2:name "EQ Band 4 Freq" ;
        lv2:default 2500.0 ;
        lv2:minimum 20.0 ;
        lv2:maximum 20000.0 ;
        units:unit units:hz ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 22 ;
        lv2:symbol "eq_p4_gain" ;
        lv2:name "EQ Band 4 Gain" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 23 ;
        lv2:symbol "eq_p4_q" ;
        lv2:name "EQ Band 4 Q" ;
        lv2:default 1.0 ;
        lv2:minimum 0.3 ;
        lv2:maximum 10.0 ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 24 ;
        lv2:symbol "eq_p5_freq" ;
        lv2:name "EQ Band 5 Freq" ;
        lv2:default 7000.0 ;
        lv2:minimum 20.0 ;
        lv2:maximum 20000.0 ;
        units:unit units:hz ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 25 ;
        lv2:symbol "eq_p5_gain" ;
        lv2:name "EQ Band 5 Gain" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 26 ;
        lv2:symbol "eq_p5_q" ;
        lv2:name "EQ Band 5 Q" ;
        lv2:default 1.0 ;
        lv2:minimum 0.3 ;
        lv2:maximum 10.0 ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 27 ;
        lv2:symbol "eq_g1_gain" ;
        lv2:name "EQ Graphic 16Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 28 ;
        lv2:symbol "eq_g2_gain" ;
        lv2:name "EQ Graphic 32Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 29 ;
        lv2:symbol "eq_g3_gain" ;
        lv2:name "EQ Graphic 62Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 30 ;
        lv2:symbol "eq_g4_gain" ;
        lv2:name "EQ Graphic 125Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 31 ;
        lv2:symbol "eq_g5_gain" ;
        lv2:name "EQ Graphic 250Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 32 ;
        lv2:symbol "eq_g6_gain" ;
        lv2:name "EQ Graphic 500Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 33 ;
        lv2:symbol "eq_g7_gain" ;
        lv2:name "EQ Graphic 1000Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 34 ;
        lv2:symbol "eq_g8_gain" ;
        lv2:name "EQ Graphic 2000Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 35 ;
        lv2:symbol "eq_g9_gain" ;
        lv2:name "EQ Graphic 4000Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 36 ;
        lv2:symbol "eq_g10_gain" ;
        lv2:name "EQ Graphic 8000Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 37 ;
        lv2:symbol "eq_g11_gain" ;
        lv2:name "EQ Graphic 16000Hz" ;
        lv2:default 0.0 ;
        lv2:minimum -12.0 ;
        lv2:maximum 12.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 38 ;
        lv2:symbol "eq_mix" ;
        lv2:name "EQ Mix" ;
        lv2:default 100.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 39 ;
        lv2:symbol "delay_bypass" ;
        lv2:name "Delay Bypass" ;
        lv2:default 1 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:toggled ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 40 ;
        lv2:symbol "delay_type" ;
        lv2:name "Delay Type" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 3 ;
        lv2:portProperty lv2:integer, lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Mono" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Stereo" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Ping-Pong" ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Multi-Tap" ; rdf:value 3 ] ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 41 ;
        lv2:symbol "delay_time" ;
        lv2:name "Delay Time" ;
        lv2:default 350.0 ;
        lv2:minimum 1.0 ;
        lv2:maximum 1500.0 ;
        units:unit units:ms ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 42 ;
        lv2:symbol "delay_feedback" ;
        lv2:name "Delay Feedback" ;
        lv2:default 30.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 95.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 43 ;
        lv2:symbol "delay_taps" ;
        lv2:name "Delay Taps" ;
        lv2:default 3 ;
        lv2:minimum 1 ;
        lv2:maximum 4 ;
        lv2:portProperty lv2:integer ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 44 ;
        lv2:symbol "delay_tap_spacing" ;
        lv2:name "Delay Tap Spacing" ;
        lv2:default 80.0 ;
        lv2:minimum 10.0 ;
        lv2:maximum 500.0 ;
        units:unit units:ms ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 45 ;
        lv2:symbol "delay_mix" ;
        lv2:name "Delay Mix" ;
        lv2:default 35.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 46 ;
        lv2:symbol "reverb_bypass" ;
        lv2:name "Reverb Bypass" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:toggled ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 47 ;
        lv2:symbol "reverb_type" ;
        lv2:name "Reverb Type" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 4 ;
        lv2:portProperty lv2:integer, lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Room" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Chamber" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Hall" ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Plate" ; rdf:value 3 ] ;
        lv2:scalePoint [ rdfs:label "Reverse" ; rdf:value 4 ] ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 48 ;
        lv2:symbol "reverb_decay" ;
        lv2:name "Reverb Decay" ;
        lv2:default 2.5 ;
        lv2:minimum 0.1 ;
        lv2:maximum 20.0 ;
        units:unit units:s ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 49 ;
        lv2:symbol "reverb_diffusion" ;
        lv2:name "Reverb Diffusion" ;
        lv2:default 70.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 50 ;
        lv2:symbol "reverb_density" ;
        lv2:name "Reverb Density" ;
        lv2:default 70.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 51 ;
        lv2:symbol "reverb_lf_decay" ;
        lv2:name "Reverb LF Decay" ;
        lv2:default 1.0 ;
        lv2:minimum 0.5 ;
        lv2:maximum 2.0 ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 52 ;
        lv2:symbol "reverb_hf_decay" ;
        lv2:name "Reverb HF Decay" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 53 ;
        lv2:symbol "reverb_gate_enable" ;
        lv2:name "Reverb Gate Enable" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:toggled ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 54 ;
        lv2:symbol "reverb_gate_threshold" ;
        lv2:name "Reverb Gate Threshold" ;
        lv2:default -40.0 ;
        lv2:minimum -60.0 ;
        lv2:maximum 0.0 ;
        units:unit units:db ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 55 ;
        lv2:symbol "reverb_gate_hold" ;
        lv2:name "Reverb Gate Hold" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 500.0 ;
        units:unit units:ms ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 56 ;
        lv2:symbol "reverb_gate_release" ;
        lv2:name "Reverb Gate Release" ;
        lv2:default 200.0 ;
        lv2:minimum 1.0 ;
        lv2:maximum 2000.0 ;
        units:unit units:ms ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 57 ;
        lv2:symbol "reverb_gated_level" ;
        lv2:name "Reverb Gated Level" ;
        lv2:default 10.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] ,
    [
        a lv2:InputPort, lv2:ControlPort ;
        lv2:index 58 ;
        lv2:symbol "reverb_mix" ;
        lv2:name "Reverb Mix" ;
        lv2:default 35.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc ;
    ] .

endef

export ALESIS_QUADRAVERB_PLUGIN_INFO_H
export ALESIS_QUADRAVERB_PLUGIN_CPP
export ALESIS_QUADRAVERB_PLUGIN_MAKEFILE
export ALESIS_QUADRAVERB_MANIFEST_TTL
export ALESIS_QUADRAVERB_PLUGIN_TTL

define ALESIS_QUADRAVERB_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/alesis-quadraverb
	printf '%s' "$$ALESIS_QUADRAVERB_PLUGIN_INFO_H" > $(@D)/examples/alesis-quadraverb/DistrhoPluginInfo.h
	printf '%s' "$$ALESIS_QUADRAVERB_PLUGIN_CPP" > $(@D)/examples/alesis-quadraverb/Plugin.cpp
	printf '%s' "$$ALESIS_QUADRAVERB_PLUGIN_MAKEFILE" > $(@D)/examples/alesis-quadraverb/Makefile
endef

define ALESIS_QUADRAVERB_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/alesis-quadraverb lv2_dsp
endef

define ALESIS_QUADRAVERB_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/alesis-quadraverb.lv2
	cp $(@D)/bin/alesis-quadraverb.lv2/alesis-quadraverb_dsp.so $($(PKG)_PKGDIR)/alesis-quadraverb.lv2/
	printf '%s' "$$ALESIS_QUADRAVERB_MANIFEST_TTL" > $($(PKG)_PKGDIR)/alesis-quadraverb.lv2/manifest.ttl
	printf '%s' "$$ALESIS_QUADRAVERB_PLUGIN_TTL" > $($(PKG)_PKGDIR)/alesis-quadraverb.lv2/alesis-quadraverb.ttl
endef

$(eval $(generic-package))
