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

REVERSE_REVERB_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
REVERSE_REVERB_SITE = https://github.com/DISTRHO/DPF.git
REVERSE_REVERB_SITE_METHOD = git
REVERSE_REVERB_BUNDLES = reverse-reverb.lv2

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

START_NAMESPACE_DISTRHO

static const uint32_t kMaxDelaySamples = 192001;  // ~4 s at 48 kHz
static const uint32_t kMaxPreDelaySamples = 24000; // 500 ms at 48 kHz

class ReverseReverbPlugin : public Plugin
{
public:
    ReverseReverbPlugin()
        : Plugin(kParameterCount, 0, 0),
          fSize(0.7f),
          fMix(0.5f),
          fPreDelay(0.05f),
          fDecay(0.6f),
          fBloom(0.5f),
          fSampleRate(48000.0f),
          fWritePos(0),
          fPreWritePosL(0),
          fPreWritePosR(0),
          fPreReadPosL(0),
          fPreReadPosR(0),
          fPreDelaySamples(2400),
          fSegmentSamples(23040),
          fSegmentPos(0)
    {
        std::memset(fRevBufL,  0, sizeof(fRevBufL));
        std::memset(fRevBufR,  0, sizeof(fRevBufR));
        std::memset(fPreBufL,  0, sizeof(fPreBufL));
        std::memset(fPreBufR,  0, sizeof(fPreBufR));
        std::memset(fOutBufL,  0, sizeof(fOutBufL));
        std::memset(fOutBufR,  0, sizeof(fOutBufR));
    }

protected:
    const char* getLabel()       const override { return "ReverseReverb"; }
    const char* getDescription() const override { return "Stereo reverse reverb with size, mix, pre-delay, decay and bloom controls."; }
    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('R', 'v', 'R', 'b'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        switch (index)
        {
        case kSize:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Reverb Size";
            parameter.symbol     = "size";
            parameter.unit       = "%";
            parameter.ranges.def = 70.0f;
            parameter.ranges.min = 10.0f;
            parameter.ranges.max = 100.0f;
            break;
        case kMix:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Mix";
            parameter.symbol     = "mix";
            parameter.unit       = "%";
            parameter.ranges.def = 50.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 100.0f;
            break;
        case kPreDelay:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Pre-Delay";
            parameter.symbol     = "predelay";
            parameter.unit       = "ms";
            parameter.ranges.def = 50.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 500.0f;
            break;
        case kDecay:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Decay";
            parameter.symbol     = "decay";
            parameter.unit       = "%";
            parameter.ranges.def = 60.0f;
            parameter.ranges.min = 10.0f;
            parameter.ranges.max = 100.0f;
            break;
        case kBloom:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Bloom Time";
            parameter.symbol     = "bloom";
            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 kSize:     return fSize     * 100.0f;
        case kMix:      return fMix      * 100.0f;
        case kPreDelay: return fPreDelay * 1000.0f;
        case kDecay:    return fDecay    * 100.0f;
        case kBloom:    return fBloom    * 100.0f;
        default:        return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index)
        {
        case kSize:
            fSize = value / 100.0f;
            fSegmentSamples = (uint32_t)(fSize * 0.48f * fSampleRate);
            if (fSegmentSamples < 480)  fSegmentSamples = 480;
            if (fSegmentSamples > kMaxDelaySamples - 1) fSegmentSamples = kMaxDelaySamples - 1;
            break;
        case kMix:
            fMix = value / 100.0f;
            break;
        case kPreDelay:
            fPreDelay = value / 1000.0f;
            fPreDelaySamples = (uint32_t)(fPreDelay * fSampleRate);
            if (fPreDelaySamples >= kMaxPreDelaySamples) fPreDelaySamples = kMaxPreDelaySamples - 1;
            break;
        case kDecay:
            fDecay = value / 100.0f;
            break;
        case kBloom:
            fBloom = value / 100.0f;
            break;
        }
    }

    void activate() override
    {
        fSampleRate = (float)getSampleRate();
        fSegmentSamples = (uint32_t)(fSize * 0.48f * fSampleRate);
        if (fSegmentSamples < 480) fSegmentSamples = 480;
        if (fSegmentSamples > kMaxDelaySamples - 1) fSegmentSamples = kMaxDelaySamples - 1;
        fPreDelaySamples = (uint32_t)(fPreDelay * fSampleRate);
        if (fPreDelaySamples >= kMaxPreDelaySamples) fPreDelaySamples = kMaxPreDelaySamples - 1;
        fWritePos      = 0;
        fPreWritePosL  = 0;
        fPreWritePosR  = 0;
        fPreReadPosL   = 0;
        fPreReadPosR   = 0;
        fSegmentPos    = 0;
        std::memset(fRevBufL, 0, sizeof(fRevBufL));
        std::memset(fRevBufR, 0, sizeof(fRevBufR));
        std::memset(fPreBufL, 0, sizeof(fPreBufL));
        std::memset(fPreBufR, 0, sizeof(fPreBufR));
        std::memset(fOutBufL, 0, sizeof(fOutBufL));
        std::memset(fOutBufR, 0, sizeof(fOutBufR));
    }

    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  wet   = fMix;
        const float  dry   = 1.0f - fMix;
        const float  decay = fDecay;
        const float  bloom = fBloom;
        const uint32_t seg = fSegmentSamples;
        const uint32_t pre = (fPreDelaySamples == 0) ? 1 : fPreDelaySamples;

        for (uint32_t i = 0; i < frames; ++i)
        {
            // --- pre-delay ---
            fPreBufL[fPreWritePosL] = inL[i];
            fPreBufR[fPreWritePosR] = inR[i];
            fPreWritePosL = (fPreWritePosL + 1) % kMaxPreDelaySamples;
            fPreWritePosR = (fPreWritePosR + 1) % kMaxPreDelaySamples;

            fPreReadPosL = (fPreWritePosL + kMaxPreDelaySamples - pre) % kMaxPreDelaySamples;
            fPreReadPosR = (fPreWritePosR + kMaxPreDelaySamples - pre) % kMaxPreDelaySamples;

            float sL = fPreBufL[fPreReadPosL];
            float sR = fPreBufR[fPreReadPosR];

            // --- write into reverse buffer ---
            fRevBufL[fWritePos] = sL + fRevBufL[fWritePos] * decay * 0.3f;
            fRevBufR[fWritePos] = sR + fRevBufR[fWritePos] * decay * 0.3f;

            // --- read reversed: count back from end of current segment ---
            uint32_t segStart = (fWritePos >= fSegmentPos)
                                ? fWritePos - fSegmentPos
                                : kMaxDelaySamples - (fSegmentPos - fWritePos);
            uint32_t revIdx = (segStart + seg - 1 - fSegmentPos % seg) % kMaxDelaySamples;

            float rL = fRevBufL[revIdx];
            float rR = fRevBufR[revIdx];

            // apply bloom envelope: bloom knob controls how slowly the swell rises
            // bloom=0 -> linear (instant attack), bloom=1 -> power^5 (very slow build)
            float env = (float)(fSegmentPos % seg) / (float)seg;
            {
                int steps = 1 + (int)(bloom * 4.0f);  // 1 to 5
                float e = env;
                for (int b = 1; b < steps; ++b) e *= env;
                env = e;
            }

            // feedback smear for size/density
            fOutBufL[fWritePos] = rL * env * (0.5f + decay * 0.5f);
            fOutBufR[fWritePos] = rR * env * (0.5f + decay * 0.5f);

            outL[i] = dry * inL[i] + wet * fOutBufL[fWritePos];
            outR[i] = dry * inR[i] + wet * fOutBufR[fWritePos];

            fWritePos   = (fWritePos   + 1) % kMaxDelaySamples;
            fSegmentPos = (fSegmentPos + 1);
            if (fSegmentPos >= seg) fSegmentPos = 0;
        }
    }

private:
    float    fSize;
    float    fMix;
    float    fPreDelay;
    float    fDecay;
    float    fBloom;
    float    fSampleRate;

    uint32_t fWritePos;
    uint32_t fPreWritePosL;
    uint32_t fPreWritePosR;
    uint32_t fPreReadPosL;
    uint32_t fPreReadPosR;
    uint32_t fPreDelaySamples;
    uint32_t fSegmentSamples;
    uint32_t fSegmentPos;

    float fRevBufL[kMaxDelaySamples];
    float fRevBufR[kMaxDelaySamples];
    float fPreBufL[kMaxPreDelaySamples];
    float fPreBufR[kMaxPreDelaySamples];
    float fOutBufL[kMaxDelaySamples];
    float fOutBufR[kMaxDelaySamples];

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ReverseReverbPlugin)
};

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

END_NAMESPACE_DISTRHO
endef

define REVERSE_REVERB_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        "Reverse Reverb"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:reverse-reverb"

#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 {
    kSize = 0,
    kMix,
    kPreDelay,
    kDecay,
    kBloom,
    kParameterCount
};

#endif
endef

define REVERSE_REVERB_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = reverse-reverb
FILES_DSP = ReverseReverbPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

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

<urn:mod-cookbook:reverse-reverb>
    a lv2:Plugin , lv2:ReverbPlugin ;
    lv2:binary <reverse-reverb_dsp.so> ;
    rdfs:seeAlso <reverse-reverb.ttl> .
endef

define REVERSE_REVERB_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:reverse-reverb>
    a lv2:Plugin , lv2:ReverbPlugin ;
    doap:name "Reverse Reverb" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "MOD Cookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Stereo reverse reverb with size, mix, pre-delay, decay and bloom controls." ;
    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 "size" ;
        lv2:name "Reverb Size" ;
        lv2:default 70.0 ;
        lv2:minimum 10.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "mix" ;
        lv2:name "Mix" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ;
        lv2:symbol "predelay" ;
        lv2:name "Pre-Delay" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 500.0 ;
        units:unit units:ms
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ;
        lv2:symbol "decay" ;
        lv2:name "Decay" ;
        lv2:default 60.0 ;
        lv2:minimum 10.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ;
        lv2:symbol "bloom" ;
        lv2:name "Bloom Time" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
        units:unit units:pc
    ] .
endef

export REVERSE_REVERB_PLUGIN_CPP
export REVERSE_REVERB_PLUGIN_INFO_H
export REVERSE_REVERB_PLUGIN_MAKEFILE
export REVERSE_REVERB_MANIFEST_TTL
export REVERSE_REVERB_PLUGIN_TTL

define REVERSE_REVERB_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/reverse-reverb
	printf '%s' "$$REVERSE_REVERB_PLUGIN_CPP"      > $(@D)/examples/reverse-reverb/ReverseReverbPlugin.cpp
	printf '%s' "$$REVERSE_REVERB_PLUGIN_INFO_H"   > $(@D)/examples/reverse-reverb/DistrhoPluginInfo.h
	printf '%s' "$$REVERSE_REVERB_PLUGIN_MAKEFILE" > $(@D)/examples/reverse-reverb/Makefile
endef

define REVERSE_REVERB_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/reverse-reverb lv2_dsp
endef

define REVERSE_REVERB_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/reverse-reverb.lv2
	cp $(@D)/bin/reverse-reverb.lv2/reverse-reverb_dsp.so $($(PKG)_PKGDIR)/reverse-reverb.lv2/
	printf '%s' "$$REVERSE_REVERB_MANIFEST_TTL" > $($(PKG)_PKGDIR)/reverse-reverb.lv2/manifest.ttl
	printf '%s' "$$REVERSE_REVERB_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/reverse-reverb.lv2/reverse-reverb.ttl
endef

$(eval $(generic-package))
