################################################################################
#
# ZOOM_BOMBER
#
# Models Zoom's signature "Bomber" special effect algorithm from the MS-50G.
# Uses an envelope trigger to initiate an explosive pitch/resonant sweep.
#
################################################################################

ZOOM_BOMBER_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ZOOM_BOMBER_SITE = https://github.com/DISTRHO/DPF.git
ZOOM_BOMBER_SITE_METHOD = git
ZOOM_BOMBER_BUNDLES = zoom-bomber.lv2

define ZOOM_BOMBER_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND   "Zoom"
#define DISTRHO_PLUGIN_NAME    "Bomber"
#define DISTRHO_PLUGIN_URI     "urn:mod-cookbook:zoom-bomber"
#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 {
    kGain = 0,
    kCharacter,
    kMix,
    kParameterCount
};

#endif
endef

define ZOOM_BOMBER_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>
#include <cstdlib>

START_NAMESPACE_DISTRHO

class ZoomBomber : public Plugin {
public:
    ZoomBomber()
        : Plugin(kParameterCount, 0, 0),
          fGain(50.0f), fCharacter(1.0f), fMix(50.0f),
          fEnv(0.0f), fTriggerPhase(-1.0f), fSr(48000.0f), fRandState(1)
    {
        fLpY1 = fLpY2 = 0.0f;
    }

protected:
    const char* getLabel()       const override { return "Bomber"; }
    const char* getDescription() const override { return "Zoom Bomber Special Effect Emulation"; }
    const char* getMaker()       const override { return "Zoom"; }
    const char* getHomePage()    const override { return "https://mod.audio"; }
    const char* getLicense()     const override { return "ISC"; }
    uint32_t    getVersion()     const override { return d_version(1, 0, 0); }
    int64_t     getUniqueId()    const override { return d_cconst('Z', 'B', 'M', 'B'); }

    void initParameter(uint32_t index, Parameter& p) override {
        p.hints = kParameterIsAutomatable;
        switch (index) {
        case kGain:
            p.name = "Gain"; p.symbol = "gain"; p.ranges.def = 50.0f; p.ranges.min = 0.0f; p.ranges.max = 100.0f; break;
        case kCharacter:
            p.name = "Character"; p.symbol = "character"; p.ranges.def = 1.0f; p.ranges.min = 0.0f; p.ranges.max = 2.0f;
            p.hints |= kParameterIsInteger; break;
        case kMix:
            p.name = "Mix"; p.symbol = "mix"; p.ranges.def = 50.0f; p.ranges.min = 0.0f; p.ranges.max = 100.0f; break;
        }
    }

    float getParameterValue(uint32_t index) const override {
        switch (index) {
        case kGain:      return fGain;
        case kCharacter: return fCharacter;
        case kMix:       return fMix;
        }
        return 0.f;
    }

    void setParameterValue(uint32_t index, float value) override {
        switch (index) {
        case kGain:      fGain = value; break;
        case kCharacter: fCharacter = value; break;
        case kMix:       fMix = value; break;
        }
    }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        if (fSr <= 0.f) fSr = 48000.f;
        fEnv = 0.0f;
        fLpY1 = fLpY2 = 0.0f;
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override {
        const float* in = inputs[0];
        float* out = outputs[0];

        const float inputGain = std::pow(10.0f, (fGain * 0.24f) / 20.0f);
        const int charMode = (int)(fCharacter + 0.5f);
        const float wetMix = fMix / 100.0f;

        // Envelope speed variables
        const float att = std::exp(-1.0f / (0.005f * fSr));
        const float rel = std::exp(-1.0f / (0.100f * fSr));

        // Threshold configuration
        const float triggerThresh = 0.08f; 
        const float rearmThresh = 0.02f;

        // Dynamic sweep rates based directly on character mode selection
        float sweepDuration = 0.40f; 
        if (charMode == 0)      sweepDuration = 0.18f; 
        else if (charMode == 2) sweepDuration = 0.85f; 

        const float sweepRate = 1.0f / (sweepDuration * fSr);

        for (uint32_t i = 0; i < frames; ++i) {
            float curIn = in[i];
            float absIn = std::fabs(curIn);

            // Envelope tracking
            if (absIn > fEnv) fEnv = att * fEnv + (1.0f - att) * absIn;
            else              fEnv = rel * fEnv + (1.0f - rel) * absIn;

            // Trigger state machine logic
            if (fTriggerPhase < 0.0f) {
                if (fEnv > triggerThresh) {
                    fTriggerPhase = 0.0f; // Ignite!
                }
            }

            float wetSample = curIn * inputGain;

            if (fTriggerPhase >= 0.0f) {
                float progress = fTriggerPhase;

                // Linear Congruential Generator for deterministic real-time safe noise
                fRandState = fRandState * 1103515245 + 12345;
                float noise = ((float)(fRandState & 0x7FFFFFFF) / 2147483648.0f) * 2.0f - 1.0f;

                // Inject exploding noise payload decaying over time
                float burstEnvelope = std::pow(1.0f - progress, 2.0f);
                wetSample += noise * burstEnvelope * 0.7f;

                // Infinite hard clipping saturation stage to create distortion square wave
                wetSample = (wetSample > 0.0f) ? 0.8f : -0.8f;
                wetSample *= burstEnvelope;

                // Resonant Lowpass VCF Sweep calculation
                float baseFc = (charMode == 0) ? 2500.0f : ((charMode == 2) ? 800.0f : 1500.0f);
                float targetFc = 60.0f + (baseFc * std::pow(1.0f - progress, 3.0f));

                float w0 = 2.0f * 3.14159265f * targetFc / fSr;
                float qFactor = (charMode == 2) ? 4.5f : 2.5f; // Deep mode increases resonance 
                float alpha = std::sin(w0) / (2.0f * qFactor);
                float cosw0 = std::cos(w0);

                float b1 = 1.0f - cosw0;
                float b0 = b1 * 0.5f;
                float a0 = 1.0f + alpha;
                float a1 = -2.0f * cosw0;
                float a2 = 1.0f - alpha;

                float filtered = (b0 * wetSample + b1 * wetSample + b0 * wetSample - a1 * fLpY1 - a2 * fLpY2) / a0;
                fLpY2 = fLpY1;
                fLpY1 = filtered;
                wetSample = filtered;

                // Advance trigger
                fTriggerPhase += sweepRate;
                if (fTriggerPhase >= 1.0f) {
                    fTriggerPhase = -2.0f; // Put into cooldown lock
                }
            } else if (fTriggerPhase == -2.0f) {
                // Hard reset filter memory blocks during cooldown phase
                fLpY1 = fLpY2 = 0.0f;
                wetSample = std::tanh(wetSample);

                // Rearm only once input audio profile cools below floor limit
                if (fEnv < rearmThresh) {
                    fTriggerPhase = -1.0f;
                }
            } else {
                // Idle state processing: subtle warm saturation clipping
                wetSample = std::tanh(wetSample);
            }

            out[i] = (curIn * (1.0f - wetMix)) + (wetSample * wetMix);
        }
    }

private:
    float fGain, fCharacter, fMix;
    float fEnv, fTriggerPhase, fSr;
    float fLpY1, fLpY2;
    unsigned int fRandState;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZoomBomber)
};

Plugin* createPlugin() { return new ZoomBomber(); }

END_NAMESPACE_DISTRHO
endef

define ZOOM_BOMBER_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = zoom-bomber
FILES_DSP = ZoomBomberPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define ZOOM_BOMBER_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:zoom-bomber>
    a lv2:Plugin ;
    lv2:binary <zoom-bomber_dsp.so> ;
    rdfs:seeAlso <zoom-bomber.ttl> .
endef

define ZOOM_BOMBER_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:zoom-bomber>
    a lv2:Plugin , lv2:DistortionPlugin ;
    doap:name "Bomber" ;
    doap:license <http://opensource.org/licenses/ISC> ;
    doap:maintainer [ foaf:name "Zoom" ] ;
    rdfs:comment "Zoom Bomber Special Effect Emulation" ;

    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ; lv2:symbol "in" ; lv2:name "Input"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 1 ; lv2:symbol "out" ; lv2:name "Output"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 2 ; lv2:symbol "gain" ; lv2:name "Gain" ;
        lv2:default 50.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ; lv2:symbol "character" ; lv2:name "Character" ;
        lv2:default 1 ; lv2:minimum 0 ; lv2:maximum 2 ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Fast Burst" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Standard" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Deep Detonate" ; rdf:value 2 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ; lv2:symbol "mix" ; lv2:name "Mix" ;
        lv2:default 50.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] .
endef

export ZOOM_BOMBER_PLUGIN_CPP ZOOM_BOMBER_PLUGIN_INFO_H ZOOM_BOMBER_PLUGIN_MAKEFILE
export ZOOM_BOMBER_MANIFEST_TTL ZOOM_BOMBER_PLUGIN_TTL

define ZOOM_BOMBER_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/zoom-bomber
	printf '%s' "$$ZOOM_BOMBER_PLUGIN_CPP"      > $(@D)/examples/zoom-bomber/ZoomBomberPlugin.cpp
	printf '%s' "$$ZOOM_BOMBER_PLUGIN_INFO_H"   > $(@D)/examples/zoom-bomber/DistrhoPluginInfo.h
	printf '%s' "$$ZOOM_BOMBER_PLUGIN_MAKEFILE" > $(@D)/examples/zoom-bomber/Makefile
endef

define ZOOM_BOMBER_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/zoom-bomber lv2_dsp
endef

define ZOOM_BOMBER_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/zoom-bomber.lv2
	cp $(@D)/bin/zoom-bomber.lv2/zoom-bomber_dsp.so $($(PKG)_PKGDIR)/zoom-bomber.lv2/
	printf '%s' "$$ZOOM_BOMBER_MANIFEST_TTL" > $($(PKG)_PKGDIR)/zoom-bomber.lv2/manifest.ttl
	printf '%s' "$$ZOOM_BOMBER_PLUGIN_TTL" > $($(PKG)_PKGDIR)/zoom-bomber.lv2/zoom-bomber.ttl
endef

$(eval $(generic-package))