# Copyright (c) 2026
# SPDX-License-Identifier: MIT
#
# Zzombee-style multi-effect: 4-stage gain (Bee), parallel monophonic
# low-octave (Parasite), modulated filter (Ramp/LFO/Mad), and modulated
# amplitude (Tremolo/Swell/Cross-Tremolo). Mono in / mono out.

ZZOMBEE_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ZZOMBEE_SITE = https://github.com/DISTRHO/DPF.git
ZZOMBEE_SITE_METHOD = git
ZZOMBEE_BUNDLES = zzombee.lv2

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

START_NAMESPACE_DISTRHO

class ZzombeePlugin : public Plugin
{
public:
    ZzombeePlugin()
        : Plugin(kParameterCount, 0, 0),
          fBuzz(0.0f), fOctaveMix(0.0f),
          fFilterMode(0.0f), fFilterRate(1.5f), fFilterDepth(0.6f),
          fAmpMode(0.0f), fAmpRate(2.0f), fAmpDepth(0.6f),
          fOutputLevel(1.0f),
          fLastSample(0.0f), fOctSign(1.0f), fEnvFollow(0.0f), fOctLP(0.0f),
          fFiltPhase(0.0f), fFiltZ1(0.0f),
          fMadCounter(0.0f), fMadTarget(0.0f), fMadCurrent(0.0f),
          fRandState(123456789u),
          fAmpPhase(0.0f)
    {
    }

protected:
    const char* getLabel()       const override { return "Zzombee"; }
    const char* getDescription() const override { return "Gain stage, parallel low octave, modulated filter and amplitude effects."; }
    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('Z', 'z', 'm', 'b'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        switch (index)
        {
        case kBuzz:
            parameter.hints      = kParameterIsAutomatable | kParameterIsInteger;
            parameter.name       = "Buzz Level";
            parameter.symbol     = "buzz";
            parameter.unit       = "";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 3.0f;
            break;
        case kOctaveMix:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Octave Mix";
            parameter.symbol     = "octave_mix";
            parameter.unit       = "";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kFilterMode:
            parameter.hints      = kParameterIsAutomatable | kParameterIsInteger;
            parameter.name       = "Filter Mode";
            parameter.symbol     = "filter_mode";
            parameter.unit       = "";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 3.0f;
            break;
        case kFilterRate:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Filter Rate";
            parameter.symbol     = "filter_rate";
            parameter.unit       = "Hz";
            parameter.ranges.def = 1.5f;
            parameter.ranges.min = 0.05f;
            parameter.ranges.max = 8.0f;
            break;
        case kFilterDepth:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Filter Depth";
            parameter.symbol     = "filter_depth";
            parameter.unit       = "";
            parameter.ranges.def = 0.6f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kAmpMode:
            parameter.hints      = kParameterIsAutomatable | kParameterIsInteger;
            parameter.name       = "Amp Mode";
            parameter.symbol     = "amp_mode";
            parameter.unit       = "";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 3.0f;
            break;
        case kAmpRate:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Amp Rate";
            parameter.symbol     = "amp_rate";
            parameter.unit       = "Hz";
            parameter.ranges.def = 2.0f;
            parameter.ranges.min = 0.05f;
            parameter.ranges.max = 8.0f;
            break;
        case kAmpDepth:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Amp Depth";
            parameter.symbol     = "amp_depth";
            parameter.unit       = "";
            parameter.ranges.def = 0.6f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kOutputLevel:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Output Level";
            parameter.symbol     = "output_level";
            parameter.unit       = "x";
            parameter.ranges.def = 1.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 2.0f;
            break;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index)
        {
        case kBuzz:         return fBuzz;
        case kOctaveMix:    return fOctaveMix;
        case kFilterMode:   return fFilterMode;
        case kFilterRate:   return fFilterRate;
        case kFilterDepth:  return fFilterDepth;
        case kAmpMode:      return fAmpMode;
        case kAmpRate:      return fAmpRate;
        case kAmpDepth:     return fAmpDepth;
        case kOutputLevel:  return fOutputLevel;
        default:            return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index)
        {
        case kBuzz:         fBuzz = value;        break;
        case kOctaveMix:    fOctaveMix = value;    break;
        case kFilterMode:   fFilterMode = value;   break;
        case kFilterRate:   fFilterRate = value;   break;
        case kFilterDepth:  fFilterDepth = value;  break;
        case kAmpMode:      fAmpMode = value;      break;
        case kAmpRate:      fAmpRate = value;      break;
        case kAmpDepth:     fAmpDepth = value;     break;
        case kOutputLevel:  fOutputLevel = 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 int   buzzMode  = (int)(fBuzz + 0.5f);
        const float octMix    = fOctaveMix;
        const int   filtMode  = (int)(fFilterMode + 0.5f);
        const float filtRate  = fFilterRate;
        const float filtDepth = fFilterDepth;
        const int   ampMode   = (int)(fAmpMode + 0.5f);
        const float ampRate   = fAmpRate;
        const float ampDepth  = fAmpDepth;
        const float outLevel  = fOutputLevel;

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

            /* ---- Gain stage (Bee / Buzz Level) ---- */
            float fuzz;
            switch (buzzMode)
            {
            case 0:
                fuzz = x;
                break;
            case 1:
                fuzz = tanhf(x * 2.5f) * 0.7f;
                break;
            case 2:
                fuzz = tanhf(x * 6.0f) * 0.85f;
                break;
            default:
            {
                const float d = x * 12.0f;
                const float s = (d < 0.0f) ? -1.0f : 1.0f;
                fuzz = s * (1.0f - expf(-fabsf(d)));
                break;
            }
            }

            /* ---- Low octave (Parasite): zero-crossing divider ---- */
            if (fLastSample <= 0.0f && x > 0.0f)
                fOctSign = -fOctSign;
            fLastSample = x;

            const float absx = fabsf(x);
            if (absx > fEnvFollow) fEnvFollow += (absx - fEnvFollow) * 0.02f;
            else                   fEnvFollow += (absx - fEnvFollow) * 0.001f;

            const float octRaw = fOctSign * fEnvFollow;
            fOctLP += (octRaw - fOctLP) * 0.06f;
            const float octave = fOctLP * octMix * 2.0f;

            /* ---- Filter modulation (acts on the gain-stage signal) ---- */
            float cutoffNorm;
            if (filtMode == 0)
            {
                cutoffNorm = 0.5f;
            }
            else if (filtMode == 1)
            {
                /* Ramp: looping sweep (no momentary footswitch in this schema) */
                fFiltPhase += (float)(filtRate / sr);
                if (fFiltPhase >= 1.0f) fFiltPhase -= 1.0f;
                cutoffNorm = fFiltPhase;
            }
            else if (filtMode == 2)
            {
                /* LFO: continuous sine sweep */
                fFiltPhase += (float)(filtRate / sr);
                if (fFiltPhase >= 1.0f) fFiltPhase -= 1.0f;
                cutoffNorm = 0.5f + 0.5f * sinf(fFiltPhase * 6.2831853f);
            }
            else
            {
                /* Mad: smoothed random-walk target, re-rolled periodically */
                fMadCounter += 1.0f;
                const float holdSamples = (float)(sr / (filtRate * 4.0f + 0.5f));
                if (fMadCounter >= holdSamples)
                {
                    fMadCounter = 0.0f;
                    fRandState = fRandState * 1664525u + 1013904223u;
                    fMadTarget = (float)(fRandState >> 8) / (float)(1 << 24);
                }
                fMadCurrent += (fMadTarget - fMadCurrent) * 0.08f;
                cutoffNorm = fMadCurrent;
            }

            const float cutoffHz = 200.0f + cutoffNorm * filtDepth * 3000.0f;
            const float w = 1.0f - expf(-2.0f * 3.14159265f * cutoffHz / (float)sr);
            fFiltZ1 += (fuzz - fFiltZ1) * w;
            const float filtered = (filtMode == 0) ? fuzz : fFiltZ1;

            /* ---- Amplitude modulation ---- */
            float ampMul = 1.0f, crossA = 1.0f, crossB = 0.0f;
            if (ampMode == 1)
            {
                /* Traditional tremolo */
                fAmpPhase += (float)(ampRate / sr);
                if (fAmpPhase >= 1.0f) fAmpPhase -= 1.0f;
                ampMul = 1.0f - ampDepth * (0.5f - 0.5f * sinf(fAmpPhase * 6.2831853f));
            }
            else if (ampMode == 2)
            {
                /* Volume swell: repeating attack-fade ramp */
                fAmpPhase += (float)(ampRate / sr) * 0.5f;
                if (fAmpPhase >= 1.0f) fAmpPhase -= 1.0f;
                ampMul = (1.0f - ampDepth) + ampDepth * fAmpPhase;
            }
            else if (ampMode == 3)
            {
                /* Cross-tremolo: alternate between fuzz tone and octave */
                fAmpPhase += (float)(ampRate / sr);
                if (fAmpPhase >= 1.0f) fAmpPhase -= 1.0f;
                const float lfo = 0.5f + 0.5f * sinf(fAmpPhase * 6.2831853f);
                crossA = 1.0f - ampDepth * (1.0f - lfo);
                crossB = ampDepth * lfo;
            }

            float mixed;
            if (ampMode == 3)
                mixed = filtered * crossA + octave * crossB;
            else
                mixed = (filtered + octave) * ampMul;

            out[i] = mixed * outLevel;
        }
    }

private:
    float fBuzz, fOctaveMix;
    float fFilterMode, fFilterRate, fFilterDepth;
    float fAmpMode, fAmpRate, fAmpDepth;
    float fOutputLevel;

    float fLastSample, fOctSign, fEnvFollow, fOctLP;
    float fFiltPhase, fFiltZ1;
    float fMadCounter, fMadTarget, fMadCurrent;
    uint32_t fRandState;
    float fAmpPhase;

    enum Parameters {
        kBuzz = 0,
        kOctaveMix,
        kFilterMode,
        kFilterRate,
        kFilterDepth,
        kAmpMode,
        kAmpRate,
        kAmpDepth,
        kOutputLevel,
        kParameterCount
    };

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZzombeePlugin)
};

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

END_NAMESPACE_DISTRHO
endef

define ZZOMBEE_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND       "Cookbook"
#define DISTRHO_PLUGIN_NAME        "Zzombee"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:zzombee"

#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 ZZOMBEE_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = zzombee
FILES_DSP = ZzombeePlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

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

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

define ZZOMBEE_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:zzombee>
    a lv2:Plugin , lv2:DistortionPlugin ;
    doap:name "Zzombee" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "Cookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Gain stage, parallel low octave, and modulated filter/amplitude effects." ;
    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 "buzz" ;
        lv2:name "Buzz Level" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 3 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Bypass"    ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Light OD"  ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Heavy OD"  ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Fuzz"      ; rdf:value 3 ]
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ;
        lv2:symbol "octave_mix" ;
        lv2:name "Octave Mix" ;
        lv2:default 0.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "filter_mode" ;
        lv2:name "Filter Mode" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 3 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Off"  ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Ramp" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "LFO"  ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Mad"  ; rdf:value 3 ]
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "filter_rate" ;
        lv2:name "Filter Rate" ;
        lv2:default 1.5 ;
        lv2:minimum 0.05 ;
        lv2:maximum 8.0 ;
        units:unit units:hz
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ;
        lv2:symbol "filter_depth" ;
        lv2:name "Filter Depth" ;
        lv2:default 0.6 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ;
        lv2:symbol "amp_mode" ;
        lv2:name "Amp Mode" ;
        lv2:default 0 ;
        lv2:minimum 0 ;
        lv2:maximum 3 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Off"           ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Tremolo"        ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Swell"          ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Cross-Tremolo"  ; rdf:value 3 ]
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ;
        lv2:symbol "amp_rate" ;
        lv2:name "Amp Rate" ;
        lv2:default 2.0 ;
        lv2:minimum 0.05 ;
        lv2:maximum 8.0 ;
        units:unit units:hz
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ;
        lv2:symbol "amp_depth" ;
        lv2:name "Amp Depth" ;
        lv2:default 0.6 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 10 ;
        lv2:symbol "output_level" ;
        lv2:name "Output Level" ;
        lv2:default 1.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 2.0 ;
        units:unit units:coef
    ] .
endef

export ZZOMBEE_PLUGIN_CPP
export ZZOMBEE_PLUGIN_INFO_H
export ZZOMBEE_PLUGIN_MAKEFILE
export ZZOMBEE_MANIFEST_TTL
export ZZOMBEE_PLUGIN_TTL

define ZZOMBEE_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/zzombee
	printf '%s' "$$ZZOMBEE_PLUGIN_CPP"      > $(@D)/examples/zzombee/ZzombeePlugin.cpp
	printf '%s' "$$ZZOMBEE_PLUGIN_INFO_H"   > $(@D)/examples/zzombee/DistrhoPluginInfo.h
	printf '%s' "$$ZZOMBEE_PLUGIN_MAKEFILE" > $(@D)/examples/zzombee/Makefile
endef

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

define ZZOMBEE_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/zzombee.lv2
	cp $(@D)/bin/zzombee.lv2/zzombee_dsp.so $($(PKG)_PKGDIR)/zzombee.lv2/
	printf '%s' "$$ZZOMBEE_MANIFEST_TTL" > $($(PKG)_PKGDIR)/zzombee.lv2/manifest.ttl
	printf '%s' "$$ZZOMBEE_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/zzombee.lv2/zzombee.ttl
endef

$(eval $(generic-package))
