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

FLASHBACK_DELAY_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
FLASHBACK_DELAY_SITE = https://github.com/DISTRHO/DPF.git
FLASHBACK_DELAY_SITE_METHOD = git
FLASHBACK_DELAY_BUNDLES = flashback-delay.lv2

define FLASHBACK_DELAY_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <string.h>
#include <math.h>

START_NAMESPACE_DISTRHO

static const int kMaxDelaySamples = 192001; // 2000ms @ up to 96kHz

class FlashbackDelayPlugin : public Plugin
{
public:
    FlashbackDelayPlugin()
        : Plugin(kParameterCount, 0, 0),
          fDelayType(0.0f),
          fSubdiv(1.0f),
          fDelayTime(250.0f),
          fFeedback(0.4f),
          fLevel(0.5f),
          fWritePos(0),
          fTapeWobblePhase(0.0f),
          fShimmerPhase(0.0f),
          fReverseReadPos(0),
          fReverseLen(0)
    {
        memset(fBufferL, 0, sizeof(fBufferL));
        memset(fBufferR, 0, sizeof(fBufferR));
    }

protected:
    const char* getLabel()       const override { return "FlashbackDelay"; }
    const char* getDescription() const override { return "Stereo Flashback-style delay with 6 delay types and rhythmic subdivisions."; }
    const char* getMaker()       const override { return "MOD Cookbook"; }
    const char* getHomePage()    const override { return "https://mod.audio"; }
    const char* getLicense()     const override { return "MIT"; }
    uint32_t    getVersion()     const override { return d_version(1, 0, 0); }
    int64_t     getUniqueId()    const override { return d_cconst('F', 'b', 'D', 'l'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        switch (index)
        {
        case kDelayType:
            parameter.hints      = kParameterIsAutomatable | kParameterIsInteger;
            parameter.name       = "Delay Type";
            parameter.symbol     = "delay_type";
            parameter.unit       = "";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 5.0f;
            break;
        case kSubdiv:
            parameter.hints      = kParameterIsAutomatable | kParameterIsInteger;
            parameter.name       = "Subdivision";
            parameter.symbol     = "subdiv";
            parameter.unit       = "";
            parameter.ranges.def = 1.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 5.0f;
            break;
        case kDelayTime:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Delay Time";
            parameter.symbol     = "delay_time";
            parameter.unit       = "ms";
            parameter.ranges.def = 250.0f;
            parameter.ranges.min = 10.0f;
            parameter.ranges.max = 2000.0f;
            break;
        case kFeedback:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Feedback";
            parameter.symbol     = "feedback";
            parameter.unit       = "%";
            parameter.ranges.def = 40.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 100.0f;
            break;
        case kLevel:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Level";
            parameter.symbol     = "level";
            parameter.unit       = "%";
            parameter.ranges.def = 50.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 100.0f;
            break;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index)
        {
        case kDelayType:  return fDelayType;
        case kSubdiv:     return fSubdiv;
        case kDelayTime:  return fDelayTime;
        case kFeedback:   return fFeedback * 100.0f;
        case kLevel:      return fLevel * 100.0f;
        default:          return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index)
        {
        case kDelayType:  fDelayType = value; break;
        case kSubdiv:     fSubdiv    = value; break;
        case kDelayTime:  fDelayTime = value; break;
        case kFeedback:   fFeedback  = value / 100.0f; break;
        case kLevel:      fLevel     = value / 100.0f; break;
        }
    }

    // Compute effective delay in samples after applying subdivision multiplier
    // Subdivisions: 0=1/2, 1=1/4, 2=1/8, 3=1/16, 4=1/4T, 5=1/8T
    float getSubdivMultiplier() const
    {
        int sd = (int)(fSubdiv + 0.5f);
        switch (sd)
        {
        case 0: return 2.0f;         // 1/2
        case 1: return 1.0f;         // 1/4 (base)
        case 2: return 0.5f;         // 1/8
        case 3: return 0.25f;        // 1/16
        case 4: return 0.6667f;      // 1/4 triplet
        case 5: return 0.3333f;      // 1/8 triplet
        default: return 1.0f;
        }
    }

    // Hermite cubic interpolation for fractional delay read
    float readInterp(const float* buf, float pos) const
    {
        int p1 = (int)pos;
        float frac = pos - (float)p1;
        int p0 = (p1 - 1 + kMaxDelaySamples) % kMaxDelaySamples;
        int p2 = (p1 + 1) % kMaxDelaySamples;
        int p3 = (p1 + 2) % kMaxDelaySamples;
        float y0 = buf[p0], y1 = buf[p1], y2 = buf[p2], y3 = buf[p3];
        float c0 = y1;
        float c1 = 0.5f * (y2 - y0);
        float c2 = y0 - 2.5f * y1 + 2.0f * y2 - 0.5f * y3;
        float c3 = 0.5f * (y3 - y0) + 1.5f * (y1 - y2);
        return ((c3 * frac + c2) * frac + c1) * frac + c0;
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override
    {
        const float* inL  = inputs[0];
        const float* inR  = inputs[1];
        float*       outL = outputs[0];
        float*       outR = outputs[1];

        const float sr        = (float)getSampleRate();
        const float wetMix    = fLevel;
        const float dryMix    = 1.0f - wetMix;
        const float feedback  = fFeedback * 0.95f; // cap to avoid runaway
        const int   delayType = (int)(fDelayType + 0.5f);

        // Base delay samples with subdivision
        float baseMs   = fDelayTime * getSubdivMultiplier();
        if (baseMs < 10.0f)  baseMs = 10.0f;
        if (baseMs > 2000.0f) baseMs = 2000.0f;
        float delaySamples = baseMs * sr / 1000.0f;
        if (delaySamples >= (float)(kMaxDelaySamples - 4))
            delaySamples = (float)(kMaxDelaySamples - 4);

        // Analog / tape LPF state
        static float lpfStateL = 0.0f;
        static float lpfStateR = 0.0f;
        const float lpfCoeff = (delayType == 1 || delayType == 2) ? 0.4f : 1.0f;

        // Shimmer pitch shift: a simple single-tap chorus-style pitch offset
        // We add a slowly rising read offset that wraps around to simulate +1 oct
        const float shimmerRate = 1.0f / (delaySamples > 0.0f ? delaySamples : 1.0f);

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

            float delL = 0.0f, delR = 0.0f;

            if (delayType == 3)
            {
                // Reverse: read backwards from a fixed window
                if (fReverseLen < 4)
                {
                    fReverseLen = (int)delaySamples;
                    fReverseReadPos = fWritePos; // will walk backwards
                }
                int rp = ((int)fWritePos - fReverseReadPos + kMaxDelaySamples) % kMaxDelaySamples;
                // read at write - (reverseLen - rp)
                int readAt = ((int)fWritePos - fReverseLen + rp + kMaxDelaySamples * 2) % kMaxDelaySamples;
                delL = fBufferL[readAt];
                delR = fBufferR[readAt];
                fReverseReadPos = (fReverseReadPos + 1) % fReverseLen;
            }
            else if (delayType == 2)
            {
                // Tape: wobble the read position slightly
                fTapeWobblePhase += 2.0f * 3.14159265f * 0.7f / sr;
                if (fTapeWobblePhase >= 2.0f * 3.14159265f)
                    fTapeWobblePhase -= 2.0f * 3.14159265f;
                float wobble = sinf(fTapeWobblePhase) * sr * 0.0005f; // ±0.5ms wobble
                float readPos = (float)fWritePos - delaySamples + wobble;
                while (readPos < 0) readPos += (float)kMaxDelaySamples;
                readPos = fmodf(readPos, (float)kMaxDelaySamples);
                delL = readInterp(fBufferL, readPos);
                delR = readInterp(fBufferR, readPos);
                // Tape LPF on feedback path
                lpfStateL = lpfStateL + lpfCoeff * (delL - lpfStateL);
                lpfStateR = lpfStateR + lpfCoeff * (delR - lpfStateR);
                delL = lpfStateL;
                delR = lpfStateR;
            }
            else if (delayType == 4)
            {
                // Ping-Pong: L delay feeds R, R delay feeds L (crossed)
                float readPos = (float)fWritePos - delaySamples;
                while (readPos < 0) readPos += (float)kMaxDelaySamples;
                readPos = fmodf(readPos, (float)kMaxDelaySamples);
                // swap: left reads right buffer, right reads left buffer
                delL = readInterp(fBufferR, readPos);
                delR = readInterp(fBufferL, readPos);
            }
            else if (delayType == 5)
            {
                // Shimmer: read at normal position + pitch-shifted copy (+1 oct)
                float readPos = (float)fWritePos - delaySamples;
                while (readPos < 0) readPos += (float)kMaxDelaySamples;
                readPos = fmodf(readPos, (float)kMaxDelaySamples);

                fShimmerPhase += 1.0f; // step through buffer at double speed = +1 oct
                if (fShimmerPhase >= delaySamples) fShimmerPhase -= delaySamples;
                float shimReadPos = (float)fWritePos - delaySamples + fShimmerPhase;
                while (shimReadPos < 0) shimReadPos += (float)kMaxDelaySamples;
                shimReadPos = fmodf(shimReadPos, (float)kMaxDelaySamples);

                float dryDel = readInterp(fBufferL, readPos);
                float shimL  = readInterp(fBufferL, shimReadPos);
                float shimR  = readInterp(fBufferR, shimReadPos);
                delL = dryDel * 0.6f + shimL * 0.4f;
                delR = readInterp(fBufferR, readPos) * 0.6f + shimR * 0.4f;
            }
            else
            {
                // Digital (0) and Analog (1)
                float readPos = (float)fWritePos - delaySamples;
                while (readPos < 0) readPos += (float)kMaxDelaySamples;
                readPos = fmodf(readPos, (float)kMaxDelaySamples);
                delL = readInterp(fBufferL, readPos);
                delR = readInterp(fBufferR, readPos);
                if (delayType == 1)
                {
                    // Analog: LPF on output (warm, dark repeats)
                    lpfStateL = lpfStateL + lpfCoeff * (delL - lpfStateL);
                    lpfStateR = lpfStateR + lpfCoeff * (delR - lpfStateR);
                    delL = lpfStateL;
                    delR = lpfStateR;
                }
            }

            // Write to buffer with feedback
            fBufferL[fWritePos] = xl + delL * feedback;
            fBufferR[fWritePos] = xr + delR * feedback;

            fWritePos = (fWritePos + 1) % kMaxDelaySamples;

            outL[i] = xl * dryMix + delL * wetMix;
            outR[i] = xr * dryMix + delR * wetMix;
        }
    }

private:
    float fDelayType;
    float fSubdiv;
    float fDelayTime;
    float fFeedback;
    float fLevel;

    float fBufferL[kMaxDelaySamples];
    float fBufferR[kMaxDelaySamples];
    int   fWritePos;

    float fTapeWobblePhase;
    float fShimmerPhase;
    int   fReverseReadPos;
    int   fReverseLen;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FlashbackDelayPlugin)
};

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

END_NAMESPACE_DISTRHO
endef

define FLASHBACK_DELAY_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND       "MOD Cookbook"
#define DISTRHO_PLUGIN_NAME        "Flashback Delay"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:flashback-delay"

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

enum Parameters {
    kDelayType = 0,
    kSubdiv,
    kDelayTime,
    kFeedback,
    kLevel,
    kParameterCount
};

#endif
endef

define FLASHBACK_DELAY_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = flashback-delay
FILES_DSP = FlashbackDelayPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

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

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

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

<urn:mod-cookbook:flashback-delay>
    a lv2:Plugin , lv2:DelayPlugin ;
    doap:name "Flashback Delay" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "MOD Cookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Stereo Flashback-style delay with 6 delay types and rhythmic subdivisions." ;
    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ;
        lv2:symbol "in_l" ;
        lv2:name "Audio In L"
    ] , [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 1 ;
        lv2:symbol "in_r" ;
        lv2:name "Audio In R"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 2 ;
        lv2:symbol "out_l" ;
        lv2:name "Audio Out L"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 3 ;
        lv2:symbol "out_r" ;
        lv2:name "Audio Out R"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "delay_type" ;
        lv2:name "Delay Type" ;
        lv2:default 0.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 5.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "subdiv" ;
        lv2:name "Subdivision" ;
        lv2:default 1.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 5.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ;
        lv2:symbol "delay_time" ;
        lv2:name "Delay Time" ;
        lv2:default 250.0 ;
        lv2:minimum 10.0 ;
        lv2:maximum 2000.0 ;
        units:unit units:ms
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ;
        lv2:symbol "feedback" ;
        lv2:name "Feedback" ;
        lv2:default 40.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ;
        lv2:symbol "level" ;
        lv2:name "Level" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc
    ] .
endef

export FLASHBACK_DELAY_PLUGIN_CPP
export FLASHBACK_DELAY_PLUGIN_INFO_H
export FLASHBACK_DELAY_PLUGIN_MAKEFILE
export FLASHBACK_DELAY_MANIFEST_TTL
export FLASHBACK_DELAY_PLUGIN_TTL

define FLASHBACK_DELAY_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/flashback-delay
	printf '%s' "$$FLASHBACK_DELAY_PLUGIN_CPP"      > $(@D)/examples/flashback-delay/FlashbackDelayPlugin.cpp
	printf '%s' "$$FLASHBACK_DELAY_PLUGIN_INFO_H"   > $(@D)/examples/flashback-delay/DistrhoPluginInfo.h
	printf '%s' "$$FLASHBACK_DELAY_PLUGIN_MAKEFILE" > $(@D)/examples/flashback-delay/Makefile
endef

define FLASHBACK_DELAY_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/flashback-delay lv2_dsp
endef

define FLASHBACK_DELAY_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/flashback-delay.lv2
	cp $(@D)/bin/flashback-delay.lv2/flashback-delay_dsp.so $($(PKG)_PKGDIR)/flashback-delay.lv2/
	printf '%s' "$$FLASHBACK_DELAY_MANIFEST_TTL" > $($(PKG)_PKGDIR)/flashback-delay.lv2/manifest.ttl
	printf '%s' "$$FLASHBACK_DELAY_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/flashback-delay.lv2/flashback-delay.ttl
endef

$(eval $(generic-package))
