# Copyright (c) 2026
# SPDX-License-Identifier: MIT

DARKSIDE_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
DARKSIDE_SITE = https://github.com/DISTRHO/DPF.git
DARKSIDE_SITE_METHOD = git
DARKSIDE_BUNDLES = darkside.lv2

define DARKSIDE_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>

START_NAMESPACE_DISTRHO

static const float kDsPi = 3.14159265358979323846f;

static inline float dsClamp01(float x)
{
    return std::max(0.0f, std::min(1.0f, x));
}

static inline float dsLerp(float a, float b, float t)
{
    return a + (b - a) * t;
}

class DarksidePlugin : public Plugin
{
public:
    DarksidePlugin()
        : Plugin(kParameterCount, 0, 0),
          fFuzzOn(1.0f), fModOn(1.0f), fOrder(0.0f), fEqProfile(1.0f),
          fFxType(0.0f), fBlend(0.5f), fDepthTime(0.3f), fRateFeedback(0.3f),
          fFuzz(0.5f), fFuzzLevel(0.8f), fFilter(0.0f), fLevel(0.8f),
          fDelayMix(0.5f),
          fDelayTimeMs(0.3f), fDelayFeedback(0.3f),
          fLpState(0.0f), fMidState(0.0f), fFuzzPreLp(0.0f),
          fModBufA(), fModBufAPos(0),
          fModBufB(), fModBufBPos(0),
          fLfoPhase(0.0f), fLfoPhase2(0.0f),
          fTapeBuf(nullptr), fTapeBufLen(0), fTapeWritePos(0)
    {
        for (int i = 0; i < kModBufSize; ++i) { fModBufA[i] = 0.0f; fModBufB[i] = 0.0f; }
        for (int i = 0; i < kPhaserStages; ++i) fPhaserZ[i] = 0.0f;
        for (int i = 0; i < kVibeStages; ++i) fVibeZ[i] = 0.0f;
    }

    ~DarksidePlugin() override
    {
        delete[] fTapeBuf;
    }

protected:
    const char* getLabel()       const override { return "DarkSide"; }
    const char* getDescription() const override { return "Muff-style fuzz combined with a blendable Flange/Rotary, Delay, and Phase/U-Vibe modulation engine."; }
    const char* getMaker()       const override { return "ModCookbook"; }
    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('D', 'r', 'k', 'S'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        parameter.hints = kParameterIsAutomatable;

        switch (index) {
        case kFuzzOn:
            parameter.name = "Fuzz On";
            parameter.symbol = "fuzz_on";
            parameter.ranges.def = 1.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            parameter.hints |= kParameterIsInteger;
            break;
        case kModOn:
            parameter.name = "Mod/Delay On";
            parameter.symbol = "mod_on";
            parameter.ranges.def = 1.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            parameter.hints |= kParameterIsInteger;
            break;
        case kOrder:
            parameter.name = "Order";
            parameter.symbol = "order";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            parameter.hints |= kParameterIsInteger;
            break;
        case kEqProfile:
            parameter.name = "EQ Profile";
            parameter.symbol = "eq_profile";
            parameter.ranges.def = 1.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 2.0f;
            parameter.hints |= kParameterIsInteger;
            break;
        case kFxType:
            parameter.name = "FX Type";
            parameter.symbol = "fx_type";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 2.0f;
            parameter.hints |= kParameterIsInteger;
            break;
        case kBlend:
            parameter.name = "Blend / Tape Head";
            parameter.symbol = "blend";
            parameter.ranges.def = 0.5f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kDepthTime:
            parameter.name = "Depth / Time";
            parameter.symbol = "depth_time";
            parameter.ranges.def = 0.3f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kRateFeedback:
            parameter.name = "Rate / Feedback";
            parameter.symbol = "rate_feedback";
            parameter.ranges.def = 0.3f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kFuzz:
            parameter.name = "Fuzz";
            parameter.symbol = "fuzz";
            parameter.ranges.def = 0.5f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kFuzzLevel:
            parameter.name = "Fuzz Level";
            parameter.symbol = "fuzz_level";
            parameter.ranges.def = 0.8f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.2f;
            break;
        case kFilter:
            parameter.name = "Filter";
            parameter.symbol = "filter";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = -1.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kLevel:
            parameter.name = "Level";
            parameter.symbol = "level";
            parameter.ranges.def = 0.8f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.2f;
            break;
        case kDelayMix:
            parameter.name = "Delay Mix";
            parameter.symbol = "delay_mix";
            parameter.ranges.def = 0.5f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kDelayTimeMs:
            parameter.name = "Delay Time";
            parameter.symbol = "delay_time_ms";
            parameter.unit = "ms";
            parameter.ranges.def = 0.3f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kDelayFeedback:
            parameter.name = "Delay Feedback";
            parameter.symbol = "delay_feedback";
            parameter.ranges.def = 0.3f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index) {
        case kFuzzOn: return fFuzzOn;
        case kModOn: return fModOn;
        case kOrder: return fOrder;
        case kEqProfile: return fEqProfile;
        case kFxType: return fFxType;
        case kBlend: return fBlend;
        case kDepthTime: return fDepthTime;
        case kRateFeedback: return fRateFeedback;
        case kFuzz: return fFuzz;
        case kFuzzLevel: return fFuzzLevel;
        case kFilter: return fFilter;
        case kLevel: return fLevel;
        case kDelayMix: return fDelayMix;
        case kDelayTimeMs: return fDelayTimeMs;
        case kDelayFeedback: return fDelayFeedback;
        default: return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index) {
        case kFuzzOn: fFuzzOn = value; break;
        case kModOn: fModOn = value; break;
        case kOrder: fOrder = value; break;
        case kEqProfile: fEqProfile = value; break;
        case kFxType: fFxType = value; break;
        case kBlend: fBlend = dsClamp01(value); break;
        case kDepthTime: fDepthTime = dsClamp01(value); break;
        case kRateFeedback: fRateFeedback = dsClamp01(value); break;
        case kFuzz: fFuzz = dsClamp01(value); break;
        case kFuzzLevel: fFuzzLevel = value; break;
        case kFilter: fFilter = value; break;
        case kLevel: fLevel = value; break;
        case kDelayMix: fDelayMix = dsClamp01(value); break;
        case kDelayTimeMs: fDelayTimeMs = dsClamp01(value); break;
        case kDelayFeedback: fDelayFeedback = dsClamp01(value); break;
        }
    }

    void activate() override
    {
        const float sr = static_cast<float>(getSampleRate());
        const float maxDelayMs = 8200.0f;
        fTapeBufLen = static_cast<uint32_t>(maxDelayMs * sr / 1000.0f) + 16;

        delete[] fTapeBuf;
        fTapeBuf = new float[fTapeBufLen];
        for (uint32_t i = 0; i < fTapeBufLen; ++i) fTapeBuf[i] = 0.0f;
        fTapeWritePos = 0;

        for (int i = 0; i < kModBufSize; ++i) { fModBufA[i] = 0.0f; fModBufB[i] = 0.0f; }
        fModBufAPos = 0;
        fModBufBPos = 0;
        fLfoPhase = 0.0f;
        fLfoPhase2 = 0.0f;
        fLpState = 0.0f;
        fMidState = 0.0f;
        fFuzzPreLp = 0.0f;
        for (int i = 0; i < kPhaserStages; ++i) fPhaserZ[i] = 0.0f;
        for (int i = 0; i < kVibeStages; ++i) fVibeZ[i] = 0.0f;
    }

    float processFuzz(float x, float sr)
    {
        const float preCoef = 1.0f - std::exp(-2.0f * kDsPi * 1500.0f / sr);
        fFuzzPreLp += (x - fFuzzPreLp) * preCoef;
        const float preEmph = x + 0.9f * (x - fFuzzPreLp);

        const float drive = 1.0f + fFuzz * 26.0f;
        const float driven = preEmph * drive;

        float y;
        if (driven >= 0.0f) {
            y = std::tanh(driven * 1.7f);
        } else {
            y = std::tanh(driven * 1.05f) * 0.82f;
        }
        y = std::max(-0.97f, std::min(0.97f, y * 1.15f));

        const float lpCoef = 1.0f - std::exp(-2.0f * kDsPi * 700.0f / sr);
        fLpState += (y - fLpState) * lpCoef;
        const float hp = y - fLpState;
        const float tilt = fFilter;
        y = y + tilt * hp - tilt * 0.4f * fLpState;

        const int eq = static_cast<int>(fEqProfile + 0.5f);
        if (eq != 1) {
            const float midCoef = 1.0f - std::exp(-2.0f * kDsPi * 900.0f / sr);
            fMidState += (y - fMidState) * midCoef;
            const float midBand = y - fMidState;
            if (eq == 0) {
                y += 0.25f * midBand;
            } else if (eq == 2) {
                y -= 0.3f * midBand;
            }
        }

        return y * fFuzzLevel * 0.5f;
    }

    float readModBuf(const float* buf, float bufPosF, int bufLen)
    {
        float p = bufPosF;
        while (p < 0.0f) p += static_cast<float>(bufLen);
        const int idx0 = static_cast<int>(p) % bufLen;
        const int idx1 = (idx0 + 1) % bufLen;
        const float frac = p - std::floor(p);
        return buf[idx0] + (buf[idx1] - buf[idx0]) * frac;
    }

    float readTape(float delaySamples)
    {
        float readPos = static_cast<float>(fTapeWritePos) - delaySamples;
        while (readPos < 0.0f) readPos += static_cast<float>(fTapeBufLen);
        const uint32_t idx0 = static_cast<uint32_t>(readPos) % fTapeBufLen;
        const uint32_t idx1 = (idx0 + 1) % fTapeBufLen;
        const float frac = readPos - std::floor(readPos);
        return fTapeBuf[idx0] + (fTapeBuf[idx1] - fTapeBuf[idx0]) * frac;
    }

    float processModDelay(float x, float sr)
    {
        const int fxType = static_cast<int>(fFxType + 0.5f);
        const float depth = fDepthTime;
        const float rateOrFb = fRateFeedback;

        if (fxType == 1) {
            static const float kTapMult[12][3] = {
                { 0.5f,   0.0f,  0.0f  }, // 0: straight 1/8
                { 0.75f,  0.0f,  0.0f  }, // 1: dotted 1/8
                { 1.0f,   0.0f,  0.0f  }, // 2: straight 1/4
                { 1.5f,   0.0f,  0.0f  }, // 3: dotted 1/4
                { 2.0f,   0.0f,  0.0f  }, // 4: half note
                { 4.0f,   0.0f,  0.0f  }, // 5: whole note
                { 0.333f, 0.0f,  0.0f  }, // 6: triplet 1/8
                { 0.667f, 0.0f,  0.0f  }, // 7: triplet 1/4
                { 0.25f,  0.0f,  0.0f  }, // 8: straight 1/16
                { 0.5f,   1.0f,  0.0f  }, // 9: dual-head 1/8 + 1/4
                { 0.75f,  1.5f,  0.0f  }, // 10: dual-head dotted 1/8 + dotted 1/4
                { 0.333f, 0.667f, 1.0f } // 11: triple-head swung triplet
            };
            static const float kTapWeight[12][3] = {
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 1.0f, 0.0f, 0.0f },
                { 0.6f, 0.4f, 0.0f },
                { 0.6f, 0.4f, 0.0f },
                { 0.5f, 0.3f, 0.2f }
            };
            static const int kTapCount[12] = { 1,1,1,1,1,1,1,1,1,2,2,3 };

            int patternIdx = static_cast<int>(fBlend * 11.0f + 0.5f);
            if (patternIdx < 0) patternIdx = 0;
            if (patternIdx > 11) patternIdx = 11;

            const float baseMs = 20.0f + fDelayTimeMs * 1980.0f;
            const float fb = fDelayFeedback * 0.95f;

            float wet = 0.0f;
            for (int t = 0; t < kTapCount[patternIdx]; ++t) {
                const float tapMs = baseMs * kTapMult[patternIdx][t];
                const float tapSamples = (tapMs / 1000.0f) * sr;
                wet += readTape(tapSamples) * kTapWeight[patternIdx][t];
            }

            fTapeBuf[fTapeWritePos] = x + wet * fb;
            fTapeWritePos = (fTapeWritePos + 1) % fTapeBufLen;

            return x * (1.0f - fDelayMix) + wet * fDelayMix;
        }

        if (fxType == 0) {
            const float rateHz = 0.05f + rateOrFb * 4.95f;
            fLfoPhase += rateHz / sr;
            if (fLfoPhase >= 1.0f) fLfoPhase -= 1.0f;
            const float lfoA = std::sin(fLfoPhase * 2.0f * kDsPi);

            const float flangeMs = 0.5f + depth * 7.5f;
            const float flangeSamples = (flangeMs / 1000.0f) * sr * (0.5f + 0.5f * lfoA);
            fModBufA[fModBufAPos] = x + readModBuf(fModBufA, static_cast<float>(fModBufAPos) - flangeSamples, kModBufSize) * 0.35f;
            const float flangerOut = readModBuf(fModBufA, static_cast<float>(fModBufAPos) - flangeSamples, kModBufSize);
            fModBufAPos = (fModBufAPos + 1) % kModBufSize;

            const float rotorRateHz = rateHz * 1.6f;
            fLfoPhase2 += rotorRateHz / sr;
            if (fLfoPhase2 >= 1.0f) fLfoPhase2 -= 1.0f;
            const float lfoB = std::sin(fLfoPhase2 * 2.0f * kDsPi);
            const float rotorMs = 0.3f + depth * 4.0f;
            const float rotorSamples = (rotorMs / 1000.0f) * sr * (0.5f + 0.5f * lfoB);
            fModBufB[fModBufBPos] = x;
            const float rotorChorus = readModBuf(fModBufB, static_cast<float>(fModBufBPos) - rotorSamples, kModBufSize);
            fModBufBPos = (fModBufBPos + 1) % kModBufSize;
            const float trem = 0.7f + 0.3f * std::sin(fLfoPhase * 2.0f * kDsPi * 0.5f);
            const float rotaryOut = rotorChorus * trem;

            return dsLerp(flangerOut, rotaryOut, fBlend);
        }

        const float rateHz = 0.05f + rateOrFb * 4.95f;
        fLfoPhase += rateHz / sr;
        if (fLfoPhase >= 1.0f) fLfoPhase -= 1.0f;
        const float lfo = std::sin(fLfoPhase * 2.0f * kDsPi);
        const float sweep = 0.5f + 0.5f * lfo;
        const float minHz = 200.0f;
        const float maxHz = 3000.0f;
        const float freqHz = minHz * std::exp(std::log(maxHz / minHz) * sweep * depth);
        const float wc = kDsPi * freqHz / sr;
        const float a1 = (std::tan(wc) - 1.0f) / (std::tan(wc) + 1.0f);

        float xp = x;
        for (int s = 0; s < kPhaserStages; ++s) {
            const float o = -xp * a1 + fPhaserZ[s];
            fPhaserZ[s] = xp + o * a1;
            xp = o;
        }
        const float phaserOut = dsLerp(x, xp, 0.6f);

        fLfoPhase2 += (rateHz * 0.9f) / sr;
        if (fLfoPhase2 >= 1.0f) fLfoPhase2 -= 1.0f;
        const float lfo2 = std::sin(fLfoPhase2 * 2.0f * kDsPi);
        const float sweep2 = 0.5f + 0.5f * lfo2;
        const float freq2 = 300.0f * std::exp(std::log(2500.0f / 300.0f) * sweep2 * depth);
        const float wc2 = kDsPi * freq2 / sr;
        const float a2 = (std::tan(wc2) - 1.0f) / (std::tan(wc2) + 1.0f);
        float xv = x;
        for (int s = 0; s < kVibeStages; ++s) {
            const float o = -xv * a2 + fVibeZ[s];
            fVibeZ[s] = xv + o * a2;
            xv = o;
        }
        const float vibeTrem = 0.75f + 0.25f * lfo2;
        const float vibeOut = dsLerp(x, xv, 0.7f) * vibeTrem;

        return dsLerp(phaserOut, vibeOut, fBlend);
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override
    {
        const float* in  = inputs[0];
        float*       out = outputs[0];

        const float sr = static_cast<float>(getSampleRate());
        const bool fuzzOn = (fFuzzOn > 0.5f);
        const bool modOn = (fModOn > 0.5f);
        const bool fuzzFirst = (fOrder < 0.5f);
        const float level = fLevel;

        for (uint32_t i = 0; i < frames; ++i) {
            float x = in[i];

            if (fuzzFirst) {
                if (fuzzOn) x = processFuzz(x, sr);
                if (modOn) x = processModDelay(x, sr);
            } else {
                if (modOn) x = processModDelay(x, sr);
                if (fuzzOn) x = processFuzz(x, sr);
            }

            out[i] = x * level;
        }
    }

private:
    static const int kModBufSize = 8192;
    static const int kPhaserStages = 4;
    static const int kVibeStages = 2;

    float fFuzzOn, fModOn, fOrder, fEqProfile;
    float fFxType, fBlend, fDepthTime, fRateFeedback;
    float fFuzz, fFuzzLevel, fFilter, fLevel;
    float fDelayMix;
    float fDelayTimeMs, fDelayFeedback;

    float fLpState, fMidState;
    float fFuzzPreLp;

    float fModBufA[kModBufSize];
    int fModBufAPos;
    float fModBufB[kModBufSize];
    int fModBufBPos;
    float fLfoPhase, fLfoPhase2;

    float fPhaserZ[kPhaserStages];
    float fVibeZ[kVibeStages];

    float* fTapeBuf;
    uint32_t fTapeBufLen;
    uint32_t fTapeWritePos;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DarksidePlugin)
};

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

END_NAMESPACE_DISTRHO
endef

define DARKSIDE_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND       "ModCookbook"
#define DISTRHO_PLUGIN_NAME        "DarkSide"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:darkside"

#define DISTRHO_PLUGIN_HAS_UI       0
#define DISTRHO_PLUGIN_IS_RT_SAFE   1
#define DISTRHO_PLUGIN_NUM_INPUTS   1
#define DISTRHO_PLUGIN_NUM_OUTPUTS  1

enum Parameters {
    kFuzzOn = 0,
    kModOn,
    kOrder,
    kEqProfile,
    kFxType,
    kBlend,
    kDepthTime,
    kRateFeedback,
    kFuzz,
    kFuzzLevel,
    kFilter,
    kLevel,
    kDelayMix,
    kDelayTimeMs,
    kDelayFeedback,
    kParameterCount
};

#endif
endef

define DARKSIDE_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = darkside
FILES_DSP = DarksidePlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

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

<urn:mod-cookbook:darkside>
    a lv2:Plugin , lv2:DistortionPlugin ;
    lv2:binary <darkside_dsp.so> ;
    rdfs:seeAlso <darkside.ttl> .
endef

define DARKSIDE_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#> .

<urn:mod-cookbook:darkside>
    a lv2:Plugin , lv2:DistortionPlugin ;
    doap:name "DarkSide" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "ModCookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Muff-style fuzz combined with a blendable Flange/Rotary, Delay, and Phase/U-Vibe modulation engine, inspired by the Keeley Dark Side." ;
    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ;
        lv2:symbol "in" ;
        lv2:name "Audio In"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 1 ;
        lv2:symbol "out" ;
        lv2:name "Audio Out"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 2 ;
        lv2:symbol "fuzz_on" ;
        lv2:name "Fuzz On" ;
        lv2:default 1 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:integer ;
        lv2:portProperty lv2:toggled
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ;
        lv2:symbol "mod_on" ;
        lv2:name "Mod/Delay On" ;
        lv2:default 1 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:integer ;
        lv2:portProperty lv2:toggled
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "order" ;
        lv2:name "Order" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 1 ;
        lv2:portProperty lv2:integer ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Fuzz first"       ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Mod/Delay first"  ; rdf:value 1 ]
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "eq_profile" ;
        lv2:name "EQ Profile" ;
        lv2:default 1 ;
        lv2:minimum 0 ;
        lv2:maximum 2 ;
        lv2:portProperty lv2:integer ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Full"  ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Flat"  ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Scoop" ; rdf:value 2 ]
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ;
        lv2:symbol "fx_type" ;
        lv2:name "FX Type" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 2 ;
        lv2:portProperty lv2:integer ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Flange / Rotary" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Delay"           ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Phase / U-Vibe"  ; rdf:value 2 ]
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ;
        lv2:symbol "blend" ;
        lv2:name "Blend / Tape Head" ;
        lv2:comment "Crossfades the FX-Type pair in Flange/Rotary and Phase/U-Vibe modes. In Delay mode this selects one of 12 rhythmic tap patterns (snaps to 12 positions)." ;
        lv2:default 0.5 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ;
        lv2:symbol "depth_time" ;
        lv2:name "Depth / Time" ;
        lv2:default 0.3 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ;
        lv2:symbol "rate_feedback" ;
        lv2:name "Rate / Feedback" ;
        lv2:default 0.3 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 10 ;
        lv2:symbol "fuzz" ;
        lv2:name "Fuzz" ;
        lv2:default 0.5 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 11 ;
        lv2:symbol "fuzz_level" ;
        lv2:name "Fuzz Level" ;
        lv2:default 0.8 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.2
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 12 ;
        lv2:symbol "filter" ;
        lv2:name "Filter" ;
        lv2:default 0.0 ;
        lv2:minimum -1.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 13 ;
        lv2:symbol "level" ;
        lv2:name "Level" ;
        lv2:default 0.8 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.2
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 14 ;
        lv2:symbol "delay_mix" ;
        lv2:name "Delay Mix" ;
        lv2:comment "Wet/dry mix used only in Delay mode (FX Type = Delay)." ;
        lv2:default 0.5 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 15 ;
        lv2:symbol "delay_time_ms" ;
        lv2:name "Delay Time" ;
        lv2:comment "Dedicated delay time for Delay mode (20ms-2000ms base unit), independent of Depth/Time." ;
        lv2:default 0.3 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 16 ;
        lv2:symbol "delay_feedback" ;
        lv2:name "Delay Feedback" ;
        lv2:comment "Dedicated feedback amount for Delay mode, independent of Rate/Feedback." ;
        lv2:default 0.3 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] .
endef

export DARKSIDE_PLUGIN_CPP
export DARKSIDE_PLUGIN_INFO_H
export DARKSIDE_PLUGIN_MAKEFILE
export DARKSIDE_MANIFEST_TTL
export DARKSIDE_PLUGIN_TTL

define DARKSIDE_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/darkside
	printf '%s' "$$DARKSIDE_PLUGIN_CPP"      > $(@D)/examples/darkside/DarksidePlugin.cpp
	printf '%s' "$$DARKSIDE_PLUGIN_INFO_H"   > $(@D)/examples/darkside/DistrhoPluginInfo.h
	printf '%s' "$$DARKSIDE_PLUGIN_MAKEFILE" > $(@D)/examples/darkside/Makefile
endef

define DARKSIDE_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/darkside lv2_dsp
endef

define DARKSIDE_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/darkside.lv2
	cp $(@D)/bin/darkside.lv2/darkside_dsp.so $($(PKG)_PKGDIR)/darkside.lv2/
	printf '%s' "$$DARKSIDE_MANIFEST_TTL" > $($(PKG)_PKGDIR)/darkside.lv2/manifest.ttl
	printf '%s' "$$DARKSIDE_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/darkside.lv2/darkside.ttl
endef

$(eval $(generic-package))
