################################################################################
#
# ZOOM_STEREO_BOMBER
#
# Models Zoom's signature "Bomber" impact detonation algorithm.
# Updated with optimized frequency tuning for the 'Nuke' profile.
#
################################################################################

ZOOM_STEREO_BOMBER_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ZOOM_STEREO_BOMBER_SITE = https://github.com/DISTRHO/DPF.git
ZOOM_STEREO_BOMBER_SITE_METHOD = git
ZOOM_STEREO_BOMBER_BUNDLES = zoom-stereo-bomber.lv2

define ZOOM_STEREO_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    "StereoBomber"
#define DISTRHO_PLUGIN_URI     "urn:mod-cookbook:zoom-stereo-bomber"
#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 {
    kBombChar = 0,
    kBombMode,
    kBombSens,
    kBombDecay,
    kBombDensity,
    kBombMix,
    kBombLevel,
    kBombParameterCount
};

#endif
endef

define ZOOM_STEREO_BOMBER_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>

START_NAMESPACE_DISTRHO

class ZoomStereoBomber : public Plugin {
public:
    ZoomStereoBomber()
        : Plugin(kBombParameterCount, 0, 0),
          fChar(0.0f), fMode(0.0f), fSens(40.0f), fDecay(50.0f), fDensity(60.0f), fMix(50.0f), fLevel(100.0f),
          fEnvFollower(0.0f), fBombEnv(0.0f), fNoiseState(123456789UL), fSr(48000.0f)
    {
        fLpY1_L = fLpY2_L = 0.0f;
        fLpY1_R = fLpY2_R = 0.0f;
    }

protected:
    const char* getLabel()       const override { return "StereoBomber"; }
    const char* getDescription() const override { return "Zoom Stereo Multi-Model Bomber Explosion 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', 'S', 'B', 'M'); }

    void initParameter(uint32_t index, Parameter& p) override {
        p.hints = kParameterIsAutomatable;
        switch (index) {
        case kBombChar:
            p.name = "Character Type"; p.symbol = "char"; p.ranges.def = 0.0f; p.ranges.min = 0.0f; p.ranges.max = 3.0f;
            p.hints |= kParameterIsInteger; break;
        case kBombMode:
            p.name = "Trigger Mode"; p.symbol = "mode"; p.ranges.def = 0.0f; p.ranges.min = 0.0f; p.ranges.max = 1.0f;
            p.hints |= kParameterIsInteger; break;
        case kBombSens:
            p.name = "Sensitivity"; p.symbol = "sens"; p.ranges.def = 40.0f; p.ranges.min = 0.0f; p.ranges.max = 100.0f; break;
        case kBombDecay:
            p.name = "Decay"; p.symbol = "decay"; p.ranges.def = 50.0f; p.ranges.min = 10.0f; p.ranges.max = 100.0f; break;
        case kBombDensity:
            p.name = "Density / Distortion"; p.symbol = "density"; p.ranges.def = 60.0f; p.ranges.min = 0.0f; p.ranges.max = 100.0f; break;
        case kBombMix:
            p.name = "Mix"; p.symbol = "mix"; p.ranges.def = 50.0f; p.ranges.min = 0.0f; p.ranges.max = 100.0f; break;
        case kBombLevel:
            p.name = "Level"; p.symbol = "level"; p.ranges.def = 100.0f; p.ranges.min = 0.0f; p.ranges.max = 150.0f; break;
        }
    }

    float getParameterValue(uint32_t index) const override {
        switch (index) {
        case kBombChar:    return fChar;
        case kBombMode:    return fMode;
        case kBombSens:    return fSens;
        case kBombDecay:   return fDecay;
        case kBombDensity: return fDensity;
        case kBombMix:     return fMix;
        case kBombLevel:   return fLevel;
        }
        return 0.f;
    }

    void setParameterValue(uint32_t index, float value) override {
        switch (index) {
        case kBombChar:    fChar = value; break;
        case kBombMode:    fMode = value; break;
        case kBombSens:    fSens = value; break;
        case kBombDecay:   fDecay = value; break;
        case kBombDensity: fDensity = value; break;
        case kBombMix:     fMix = value; break;
        case kBombLevel:   fLevel = value; break;
        }
    }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        if (fSr <= 0.f) fSr = 48000.f;
        fEnvFollower = 0.0f;
        fBombEnv = 0.0f;
        fLpY1_L = fLpY2_L = 0.0f;
        fLpY1_R = fLpY2_R = 0.0f;
    }

    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 int charType = std::max(0, std::min((int)(fChar + 0.5f), 3));
        const int triggerMode = std::max(0, std::min((int)(fMode + 0.5f), 1)); 
        const float targetSens = (105.0f - fSens) / 100.0f; 
        const float decayTime = fDecay / 100.0f;
        const float density = fDensity / 100.0f;
        const float wetMix = fMix / 100.0f;
        const float dryMix = 1.0f - wetMix;
        const float outVol = fLevel / 100.0f;

        const float att = std::exp(-1.0f / (0.002f * fSr));
        const float rel = std::exp(-1.0f / (0.040f * fSr));
        
        float decayLengthMod = 2.5f;
        if (charType == 1) decayLengthMod = 4.5f; 
        const float bombDecayCoeff = std::exp(-1.0f / ((0.1f + decayTime * decayLengthMod) * fSr));

        for (uint32_t i = 0; i < frames; ++i) {
            float curInL = inL[i];
            float curInR = inR[i];
            
            float inputMag = std::max(std::fabs(curInL), std::fabs(curInR));

            if (inputMag > fEnvFollower) {
                fEnvFollower = att * fEnvFollower + (1.0f - att) * inputMag;
            } else {
                fEnvFollower = rel * fEnvFollower + (1.0f - rel) * inputMag;
            }

            bool canTrigger = false;
            if (triggerMode == 1) {
                canTrigger = (inputMag > targetSens && inputMag > (fEnvFollower * 1.12f));
            } else {
                canTrigger = (inputMag > targetSens && inputMag > (fEnvFollower * 1.15f) && fBombEnv < 0.15f);
            }

            if (canTrigger) {
                fBombEnv = 1.0f; 
                if (triggerMode == 1) fNoiseState = 123456789UL; 
            } else {
                fBombEnv *= bombDecayCoeff; 
            }

            fNoiseState = fNoiseState * 1664525UL + 1013904223UL;
            float rawNoise = ((float)(int32_t)fNoiseState / 2147483648.0f);

            float noiseMod = rawNoise;
            float oscFreqMult = 2.0f;

            switch (charType) {
            case 1:
                oscFreqMult = 1.2f; // Tuned up from 0.6f for strong, audible sub translation
                noiseMod *= 0.5f;
                break;
            case 2:
                noiseMod = std::sin(rawNoise * 12.5f) * 0.8f;
                oscFreqMult = 4.5f;
                break;
            case 3:
                if (i % 6 != 0) {
                    noiseMod = (float)((int)(rawNoise * 4.0f)) / 4.0f;
                }
                oscFreqMult = 3.0f;
                break;
            case 0:
            default:
                break;
            }

            float noiseComponent = noiseMod * fBombEnv * (0.2f + density * 0.8f);
            float lowFreqImpact = std::sin(fBombEnv * 3.14159265f * oscFreqMult) * fBombEnv * 0.9f;

            float wetSampleL = lowFreqImpact + noiseComponent;
            float wetSampleR = lowFreqImpact - noiseComponent; 

            float driveVal = 1.0f + (density * (charType == 1 ? 18.0f : 12.0f));
            wetSampleL = std::tanh(wetSampleL * driveVal);
            wetSampleR = std::tanh(wetSampleR * driveVal);

            float baseFc = 60.0f;
            float maxSweep = 1800.0f;
            if (charType == 1) { baseFc = 52.0f; maxSweep = 900.0f; } // Tuned baseline floor from 35Hz to 52Hz
            if (charType == 2) { baseFc = 150.0f; maxSweep = 3200.0f; }

            float targetFc = baseFc + (fBombEnv * fBombEnv * maxSweep);
            float resonance = 0.707f + (density * (charType == 2 ? 4.0f : 2.5f));

            float w0 = 2.0f * 3.14159265f * targetFc / fSr;
            float alpha = std::sin(w0) / (2.0f * resonance);
            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 filteredL = (b0 * wetSampleL + b1 * wetSampleL + b0 * wetSampleL - a1 * fLpY1_L - a2 * fLpY2_L) / a0;
            fLpY2_L = fLpY1_L; fLpY1_L = filteredL;

            float filteredR = (b0 * wetSampleR + b1 * wetSampleR + b0 * wetSampleR - a1 * fLpY1_R - a2 * fLpY2_R) / a0;
            fLpY2_R = fLpY1_R; fLpY1_R = filteredR;

            outL[i] = ((curInL * dryMix) + (filteredL * wetMix)) * outVol;
            outR[i] = ((curInR * dryMix) + (filteredR * wetMix)) * outVol;
        }
    }

private:
    float fChar, fMode, fSens, fDecay, fDensity, fMix, fLevel;
    float fEnvFollower, fBombEnv, fSr;
    uint32_t fNoiseState;
    float fLpY1_L, fLpY2_L;
    float fLpY1_R, fLpY2_R;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZoomStereoBomber)
};

Plugin* createPlugin() { return new ZoomStereoBomber(); }

END_NAMESPACE_DISTRHO
endef

define ZOOM_STEREO_BOMBER_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = zoom-stereo-bomber
FILES_DSP = ZoomStereoBomberPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define ZOOM_STEREO_BOMBER_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:zoom-stereo-bomber>
    a lv2:Plugin ;
    lv2:binary <zoom-stereo-bomber_dsp.so> ;
    rdfs:seeAlso <zoom-stereo-bomber.ttl> .
endef

define ZOOM_STEREO_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-stereo-bomber>
    a lv2:Plugin , lv2:DynamicsPlugin ;
    doap:name "StereoBomber" ;
    doap:license <http://opensource.org/licenses/ISC> ;
    doap:maintainer [ foaf:name "Zoom" ] ;
    rdfs:comment "Zoom MultiStomp Stereo Multi-Model Trigger Behavior Bomber Emulation" ;

    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ; lv2:symbol "in_l" ; lv2:name "Input Left"
    ] , [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 1 ; lv2:symbol "in_r" ; lv2:name "Input Right"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 2 ; lv2:symbol "out_l" ; lv2:name "Output Left"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 3 ; lv2:symbol "out_r" ; lv2:name "Output Right"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ; lv2:symbol "char" ; lv2:name "Character Type" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 3 ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Standard Bomb" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Nuke (Audible Deep)" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Cluster (Lo-Fi)" ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Pyated (Crushed)" ; rdf:value 3 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ; lv2:symbol "mode" ; lv2:name "Trigger Mode" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Play Fully" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Instant Retrigger" ; rdf:value 1 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ; lv2:symbol "sens" ; lv2:name "Sensitivity" ;
        lv2:default 40.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ; lv2:symbol "decay" ; lv2:name "Decay" ;
        lv2:default 50.0 ; lv2:minimum 10.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ; lv2:symbol "density" ; lv2:name "Density" ;
        lv2:default 60.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ; lv2:symbol "mix" ; lv2:name "Mix" ;
        lv2:default 50.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 10 ; lv2:symbol "level" ; lv2:name "Level" ;
        lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 150.0 ;
    ] .
endef

export ZOOM_STEREO_BOMBER_PLUGIN_CPP ZOOM_STEREO_BOMBER_PLUGIN_INFO_H ZOOM_STEREO_BOMBER_PLUGIN_MAKEFILE
export ZOOM_STEREO_BOMBER_MANIFEST_TTL ZOOM_STEREO_BOMBER_PLUGIN_TTL

define ZOOM_STEREO_BOMBER_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/zoom-stereo-bomber
	printf '%s' "$$ZOOM_STEREO_BOMBER_PLUGIN_CPP"      > $(@D)/examples/zoom-stereo-bomber/ZoomStereoBomberPlugin.cpp
	printf '%s' "$$ZOOM_STEREO_BOMBER_PLUGIN_INFO_H"   > $(@D)/examples/zoom-stereo-bomber/DistrhoPluginInfo.h
	printf '%s' "$$ZOOM_STEREO_BOMBER_PLUGIN_MAKEFILE" > $(@D)/examples/zoom-stereo-bomber/Makefile
endef

define ZOOM_STEREO_BOMBER_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/zoom-stereo-bomber lv2_dsp
endef

define ZOOM_STEREO_BOMBER_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/zoom-stereo-bomber.lv2
	cp $(@D)/bin/zoom-stereo-bomber.lv2/zoom-stereo-bomber_dsp.so $($(PKG)_PKGDIR)/zoom-stereo-bomber.lv2/
	printf '%s' "$$ZOOM_STEREO_BOMBER_MANIFEST_TTL" > $($(PKG)_PKGDIR)/zoom-stereo-bomber.lv2/manifest.ttl
	printf '%s' "$$ZOOM_STEREO_BOMBER_PLUGIN_TTL" > $($(PKG)_PKGDIR)/zoom-stereo-bomber.lv2/zoom-stereo-bomber.ttl
endef

$(eval $(generic-package))