# Copyright (c) 2026
# SPDX-License-Identifier: MIT
#
# Pollinator Hazee Delay-style modulated delay: dry signal is run
# through a pre-delay filter or tremolo modulation BEFORE entering the
# delay line, then played back forward/reverse at normal/double speed
# via a two-grain crossfaded engine. 8 modes total. Mono in / mono out.

HAZEEDELAY_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
HAZEEDELAY_SITE = https://github.com/DISTRHO/DPF.git
HAZEEDELAY_SITE_METHOD = git
HAZEEDELAY_BUNDLES = hazeedelay.lv2

define HAZEEDELAY_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <cstdint>

START_NAMESPACE_DISTRHO

class HazeeDelayPlugin : public Plugin
{
public:
    HazeeDelayPlugin()
        : Plugin(kParameterCount, 0, 0),
          fTime(0.35f), fFeedback(0.3f), fMix(0.4f), fMod(0.4f), fMode(0.0f),
          fWriteIdx(0),
          fLfoPhase(0.0f), fPreFilterZ1(0.0f)
    {
        for (int i = 0; i < kBufferSize; ++i)
            fBuffer[i] = 0.0f;
        fAnchor[0] = 0; fAnchor[1] = 0;
        fPhase[0]  = 0.0f; fPhase[1] = 0.0f;
    }

protected:
    const char* getLabel()       const override { return "Hazee Delay"; }
    const char* getDescription() const override { return "Modulated delay: filter/tremolo on the dry signal before forward/reverse, normal/octave-up repeats."; }
    const char* getMaker()       const override { return "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', 'z', 'D', 'l'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        switch (index)
        {
        case kTime:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Time";
            parameter.symbol     = "time";
            parameter.unit       = "s";
            parameter.ranges.def = 0.35f;
            parameter.ranges.min = 0.02f;
            parameter.ranges.max = 1.5f;
            break;
        case kFeedback:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "FDBK";
            parameter.symbol     = "feedback";
            parameter.unit       = "";
            parameter.ranges.def = 0.3f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 0.95f;
            break;
        case kMix:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Mix";
            parameter.symbol     = "mix";
            parameter.unit       = "";
            parameter.ranges.def = 0.4f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kMod:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Mod";
            parameter.symbol     = "mod";
            parameter.unit       = "";
            parameter.ranges.def = 0.4f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kMode:
            parameter.hints      = kParameterIsAutomatable | kParameterIsInteger;
            parameter.name       = "Mode";
            parameter.symbol     = "mode";
            parameter.unit       = "";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 7.0f;
            break;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index)
        {
        case kTime:      return fTime;
        case kFeedback:  return fFeedback;
        case kMix:       return fMix;
        case kMod:       return fMod;
        case kMode:      return fMode;
        default:         return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index)
        {
        case kTime:      fTime = value;     break;
        case kFeedback:  fFeedback = value; break;
        case kMix:       fMix = value;      break;
        case kMod:       fMod = value;      break;
        case kMode:      fMode = value;     break;
        }
    }

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

        const float timeKnob = fTime;
        const float fb       = fFeedback;
        const float mix      = fMix;
        const float modAmt   = fMod;
        const int   mode     = (int)(fMode + 0.5f);

        bool usesFilter = true, forward = true, octaveUp = false, specialBlend = false;
        switch (mode)
        {
        case 0: usesFilter = true;  forward = true;  octaveUp = false; break;
        case 1: usesFilter = true;  forward = true;  octaveUp = true;  break;
        case 2: usesFilter = true;  forward = false; octaveUp = false; break;
        case 3: usesFilter = true;  forward = false; octaveUp = true;  break;
        case 4: usesFilter = false; forward = true;  octaveUp = false; break;
        case 5: usesFilter = false; forward = true;  octaveUp = true;  break;
        case 6: usesFilter = false; forward = false; octaveUp = true;  specialBlend = true; break;
        default: usesFilter = false; forward = false; octaveUp = false; break; /* mode 7 */
        }

        float delaySamples = (float)(timeKnob * sr);
        if (delaySamples < 4.0f) delaySamples = 4.0f;
        if (delaySamples > (float)(kBufferSize - 8)) delaySamples = (float)(kBufferSize - 8);
        const float grainLen = delaySamples;
        const float speed = octaveUp ? 2.0f : 1.0f;

        const float timeNorm = (timeKnob - 0.02f) / (1.5f - 0.02f);
        const float blendAmt = (specialBlend && timeNorm > 0.5f) ? (timeNorm - 0.5f) * 2.0f : 0.0f;

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

            /* ---- pre-delay modulation on the dry signal ---- */
            fLfoPhase += (float)(0.8 / sr);
            if (fLfoPhase >= 1.0f) fLfoPhase -= 1.0f;
            const float lfo = sinf(fLfoPhase * 6.2831853f);

            float premod;
            if (usesFilter)
            {
                const float cutoffHz = 400.0f + (0.5f + 0.5f * lfo) * modAmt * 3000.0f + 200.0f;
                const float w = 1.0f - expf(-2.0f * 3.14159265f * cutoffHz / (float)sr);
                fPreFilterZ1 += (x - fPreFilterZ1) * w;
                premod = fPreFilterZ1;
            }
            else
            {
                const float trem = 1.0f - modAmt * (0.5f - 0.5f * lfo);
                premod = x * trem;
            }

            /* ---- two-grain crossfaded playback engine ---- */
            float wetSum = 0.0f, envSum = 0.0f;
            for (int t = 0; t < 2; ++t)
            {
                fPhase[t] += speed;
                if (fPhase[t] >= grainLen)
                {
                    fPhase[t] -= grainLen;
                    fAnchor[t] = fWriteIdx;
                }

                float idxPos = forward
                    ? ((float)fAnchor[t] - grainLen + fPhase[t])
                    : ((float)fAnchor[t] - fPhase[t]);

                while (idxPos < 0.0f) idxPos += (float)kBufferSize;
                int idx0 = (int)idxPos;
                const float frac = idxPos - (float)idx0;
                idx0 = idx0 % kBufferSize;
                const int idx1 = (idx0 + 1) % kBufferSize;
                const float samp = fBuffer[idx0] + (fBuffer[idx1] - fBuffer[idx0]) * frac;

                const float phaseNorm = fPhase[t] / grainLen;
                float env = 1.0f - fabsf(2.0f * phaseNorm - 1.0f);
                if (env < 0.0f) env = 0.0f;

                wetSum += samp * env;
                envSum += env;
            }

            float wet = (envSum > 0.0001f) ? (wetSum / envSum) : 0.0f;

            if (blendAmt > 0.0f)
                wet = wet * (1.0f - blendAmt) + x * blendAmt;

            fBuffer[fWriteIdx] = premod + wet * fb;
            fWriteIdx = (fWriteIdx + 1) % kBufferSize;

            out[i] = x * (1.0f - mix) + wet * mix;
        }
    }

private:
    static const int kBufferSize = 160000;

    float fTime, fFeedback, fMix, fMod, fMode;

    float fBuffer[kBufferSize];
    int   fWriteIdx;

    int   fAnchor[2];
    float fPhase[2];

    float fLfoPhase, fPreFilterZ1;

    enum Parameters {
        kTime = 0,
        kFeedback,
        kMix,
        kMod,
        kMode,
        kParameterCount
    };

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HazeeDelayPlugin)
};

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

END_NAMESPACE_DISTRHO
endef

define HAZEEDELAY_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND       "Cookbook"
#define DISTRHO_PLUGIN_NAME        "Hazee Delay"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:hazeedelay"

#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

#endif
endef

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

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

<urn:mod-cookbook:hazeedelay>
    a lv2:Plugin , lv2:DelayPlugin ;
    lv2:binary <hazeedelay_dsp.so> ;
    rdfs:seeAlso <hazeedelay.ttl> .
endef

define HAZEEDELAY_PLUGIN_TTL
@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .

<urn:mod-cookbook:hazeedelay>
    a lv2:Plugin , lv2:DelayPlugin ;
    doap:name "Hazee Delay" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "Cookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Modulated delay: filter/tremolo on the dry signal before forward/reverse, normal/octave-up repeats." ;
    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 "time" ;
        lv2:name "Time" ;
        lv2:default 0.35 ;
        lv2:minimum 0.02 ;
        lv2:maximum 1.5 ;
        units:unit units:s
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ;
        lv2:symbol "feedback" ;
        lv2:name "FDBK" ;
        lv2:default 0.3 ;
        lv2:minimum 0.0 ;
        lv2:maximum 0.95
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "mix" ;
        lv2:name "Mix" ;
        lv2:default 0.4 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "mod" ;
        lv2:name "Mod" ;
        lv2:default 0.4 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ;
        lv2:symbol "mode" ;
        lv2:name "Mode" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 7 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "1: Filter Fwd"        ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "2: Filter Fwd Oct"    ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "3: Filter Rev"        ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "4: Filter Rev Oct"    ; rdf:value 3 ] ;
        lv2:scalePoint [ rdfs:label "5: Trem Fwd"          ; rdf:value 4 ] ;
        lv2:scalePoint [ rdfs:label "6: Trem Fwd Oct"      ; rdf:value 5 ] ;
        lv2:scalePoint [ rdfs:label "7: Trem Rev Oct"      ; rdf:value 6 ] ;
        lv2:scalePoint [ rdfs:label "8: Trem Rev"          ; rdf:value 7 ]
    ] .
endef

export HAZEEDELAY_PLUGIN_CPP
export HAZEEDELAY_PLUGIN_INFO_H
export HAZEEDELAY_PLUGIN_MAKEFILE
export HAZEEDELAY_MANIFEST_TTL
export HAZEEDELAY_PLUGIN_TTL

define HAZEEDELAY_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/hazeedelay
	printf '%s' "$$HAZEEDELAY_PLUGIN_CPP"      > $(@D)/examples/hazeedelay/HazeeDelayPlugin.cpp
	printf '%s' "$$HAZEEDELAY_PLUGIN_INFO_H"   > $(@D)/examples/hazeedelay/DistrhoPluginInfo.h
	printf '%s' "$$HAZEEDELAY_PLUGIN_MAKEFILE" > $(@D)/examples/hazeedelay/Makefile
endef

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

define HAZEEDELAY_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/hazeedelay.lv2
	cp $(@D)/bin/hazeedelay.lv2/hazeedelay_dsp.so $($(PKG)_PKGDIR)/hazeedelay.lv2/
	printf '%s' "$$HAZEEDELAY_MANIFEST_TTL" > $($(PKG)_PKGDIR)/hazeedelay.lv2/manifest.ttl
	printf '%s' "$$HAZEEDELAY_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/hazeedelay.lv2/hazeedelay.ttl
endef

$(eval $(generic-package))
