# Hologram Infinite Jets Resynthesizer Recreation - MOD Dwarf
# Blur, Synth, Glitch, Swell, Dual Samplers, Modulation Matrix, Drive
# Upload to https://builder.mod.audio/buildroot

INFINITE_JETS_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
INFINITE_JETS_SITE = https://github.com/DISTRHO/DPF.git
INFINITE_JETS_SITE_METHOD = git
INFINITE_JETS_BUNDLES = infinite-jets.lv2

define INFINITE_JETS_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <cstring>
#include <cstdlib>

START_NAMESPACE_DISTRHO

#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif

// Envelope Follower for Tracking Transient Note Attacks
struct EnvFollower {
    float attack, release, env;
    EnvFollower() : attack(0.005f), release(0.1f), env(0.f) {}
    void init(float sr) {
        attack = expf(-1.f / (0.002f * sr));
        release = expf(-1.f / (0.150f * sr));
    }
    void process(float in) {
        float v = fabsf(in);
        if (v > env) env = attack * env + (1.f - attack) * v;
        else         env = release * env + (1.f - release) * v;
    }
};

// Monophonic/Polyphonic Granular Resynthesis Channel Slot
struct VoiceChannel {
    float buffer[48000];
    int writePtr;
    float readPtr;
    int loopLength;
    int freezeStart;
    bool isFrozen;
    float amplitude;
    float pitchRatio;

    VoiceChannel() : writePtr(0), readPtr(0.f), loopLength(24000), freezeStart(0), isFrozen(false), amplitude(0.f), pitchRatio(1.f) {
        memset(buffer, 0, sizeof(buffer));
    }

    void capture(int currentWp, int samples) {
        freezeStart = (currentWp - samples + 48000) % 48000;
        loopLength = samples;
        readPtr = (float)freezeStart;
        isFrozen = true;
    }

    float process(float in, bool modeGlitch, float dimLen, int mainWp) {
        // Stream into internal circular tape ring
        buffer[writePtr] = in;
        writePtr = (writePtr + 1) % 48000;

        if (!isFrozen) return in;

        // Glitch slice recalculation adjustments based on Dimension control
        int currentLength = loopLength;
        if (modeGlitch) {
            currentLength = (int)(2000.f + (dimLen * 18000.f));
        }

        // Linear playback interpolation
        int idx1 = (int)readPtr;
        int idx2 = (idx1 + 1) % 48000;
        float frac = readPtr - idx1;
        float outSample = buffer[idx1] * (1.f - frac) + buffer[idx2] * frac;

        readPtr += pitchRatio;
        
        // Loop wrapping boundaries
        float endBoundary = (float)((freezeStart + currentLength) % 48000);
        if (pitchRatio > 0.f && readPtr >= 48000.f) readPtr -= 48000.f;

        // Basic boundary reset 
        float progress = readPtr - (float)freezeStart;
        if (progress < 0.f) progress += 48000.f;
        if (progress >= (float)currentLength) {
            readPtr = (float)freezeStart;
        }

        return outSample;
    }
};

class InfiniteJetsPlugin : public Plugin {
public:
    InfiniteJetsPlugin() : Plugin(kParameterCount, 0, 0),
        fSR(44100.f), pMode(0.f), pDimension(0.5f), pEnvShape(0.2f),
        pEnvTime(0.4f), pDrive(0.2f), pDryMix(0.5f),
        globalWritePtr(0), activeVoice(0), triggerCooldown(0), lfoPhase(0.f)
    {
        memset(mainCircularBuffer, 0, sizeof(mainCircularBuffer));
    }

protected:
    const char* getLabel() const override { return "InfiniteJets"; }
    const char* getDescription() const override { return "Hologram Infinite Jets Recreation. Features dual-channel resynthesis tracking (Blur, Synth, Glitch, Swell), morphing LFO filter matrix and drive."; }
    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('I', 'J', 'E', 'T'); }

    void initParameter(uint32_t i, Parameter& p) override {
        switch (i) {
        case kMode: p.hints = kParameterIsAutomatable; p.name = "Resynthesis Mode"; p.symbol = "mode";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 3.f; // 0=Blur, 1=Synth, 2=Glitch, 3=Swell
            break;
        case kDimension: p.hints = kParameterIsAutomatable; p.name = "Dimension"; p.symbol = "dimension";
            p.ranges.def = 0.5f; p.ranges.min = 0.f; p.ranges.max = 1.f; break;
        case kEnvShape: p.hints = kParameterIsAutomatable; p.name = "Mod Shape (LFO)"; p.symbol = "shape";
            p.ranges.def = 0.2f; p.ranges.min = 0.f; p.ranges.max = 1.f; break;
        case kEnvTime: p.hints = kParameterIsAutomatable; p.name = "Mod Speed / Time"; p.symbol = "time";
            p.ranges.def = 0.4f; p.ranges.min = 0.05f; p.ranges.max = 2.f; break;
        case kDrive: p.hints = kParameterIsAutomatable; p.name = "Drive"; p.symbol = "drive";
            p.ranges.def = 0.2f; p.ranges.min = 0.f; p.ranges.max = 1.f; break;
        case kDryMix: p.hints = kParameterIsAutomatable; p.name = "Dry/Wet Mix"; p.symbol = "mix";
            p.ranges.def = 0.5f; p.ranges.min = 0.f; p.ranges.max = 1.f; break;
        default: break;
        }
    }

    float getParameterValue(uint32_t i) const override {
        switch (i) {
        case kMode: return pMode; case kDimension: return pDimension; case kEnvShape: return pEnvShape;
        case kEnvTime: return pEnvTime; case kDrive: return pDrive; case kDryMix: return pDryMix;
        default: return 0.f;
        }
    }

    void setParameterValue(uint32_t i, float v) override {
        switch (i) {
        case kMode: pMode = v; break; case kDimension: pDimension = v; break; case kEnvShape: pEnvShape = v; break;
        case kEnvTime: pEnvTime = v; break; case kDrive: pDrive = v; break; case kDryMix: pDryMix = v; break;
        default: break;
        }
    }

    void activate() override {
        fSR = (float)getSampleRate();
        detector.init(fSR);
        voiceA.isFrozen = false; voiceA.amplitude = 0.f;
        voiceB.isFrozen = false; voiceB.amplitude = 0.f;
        globalWritePtr = 0;
        triggerCooldown = 0;
        lfoPhase = 0.f;
        filterZ1 = 0.f;
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override {
        const float* in = inputs[0];
        float* out = outputs[0];

        int currentMode = (int)pMode;
        
        // Map target modulation cycle profiles
        float lfoFreq = pEnvTime * 4.f; 
        float phaseIncr = (lfoFreq / fSR);

        for (uint32_t i = 0; i < frames; ++i) {
            float drySignal = in[i];
            
            // Log global circular input window historical record
            mainCircularBuffer[globalWritePtr] = drySignal;
            
            // Evaluate incoming dynamic envelope thresholds
            detector.process(drySignal);

            if (triggerCooldown > 0) triggerCooldown--;

            // Tracking dynamic auto-trigger logic gate
            if (detector.env > 0.18f && triggerCooldown == 0) {
                // Transient hit detected! Hand-off sampling assignment seamlessly
                int captureLength = (int)(4000.f + (pDimension * 16000.f));
                
                if (activeVoice == 0) {
                    voiceA.capture(globalWritePtr, captureLength);
                    voiceA.pitchRatio = (currentMode == 0 && pDimension > 0.7f) ? 2.0f : 1.0f; // Pitch option for Blur Mode
                    voiceA.amplitude = 0.01f; // begin ramp-up crossfade
                    activeVoice = 1;
                } else {
                    voiceB.capture(globalWritePtr, captureLength);
                    voiceB.pitchRatio = (currentMode == 0 && pDimension > 0.7f) ? 0.5f : 1.0f;
                    voiceB.amplitude = 0.01f;
                    activeVoice = 0;
                }
                triggerCooldown = (int)(0.25f * fSR); // Apply dynamic debounce window
            }

            // Continuous LFO parameter sweep processing matrix
            lfoPhase += phaseIncr;
            if (lfoPhase >= 1.f) lfoPhase -= 1.f;

            float lfoModulator = 0.f;
            if (pEnvShape < 0.5f) {
                // Triangle Wave configuration
                lfoModulator = 2.f * fabsf(2.f * (lfoPhase - 0.5f)) - 1.f;
            } else {
                // Square wave stepped configuration
                lfoModulator = (lfoPhase > 0.5f) ? 1.f : -1.f;
            }

            // Standardize Voice Engine updates
            float sampleA = voiceA.process(drySignal, (currentMode == 2), pDimension, globalWritePtr);
            float sampleB = voiceB.process(drySignal, (currentMode == 2), pDimension, globalWritePtr);

            // Maintain crossfade configurations smoothly between operational voices
            if (activeVoice == 1) {
                if (voiceA.amplitude < 1.f) voiceA.amplitude += 0.002f;
                if (voiceB.amplitude > 0.f) voiceB.amplitude -= 0.002f;
            } else {
                if (voiceB.amplitude < 1.f) voiceB.amplitude += 0.002f;
                if (voiceA.amplitude > 0.f) voiceA.amplitude -= 0.002f;
            }

            float wetSynthesis = (sampleA * voiceA.amplitude) + (sampleB * voiceB.amplitude);

            // Execute specialized target transformations based on mode configurations
            if (currentMode == 1) {
                // SYNTH MODE: Multi-saw dynamic wave-shaper generation
                float hardSaturated = (wetSynthesis > 0.f) ? 1.f : -1.f;
                wetSynthesis = (wetSynthesis * 0.4f) + (hardSaturated * 0.6f);

                // Dynamically route LFO modulation directly into Resonant Cutoff Filters
                float filterCutoffHz = 300.f + ((lfoModulator + 1.f) * 0.5f * 2200.f * pDimension);
                float lpCoeff = expf(-6.283185f * filterCutoffHz / fSR);
                filterZ1 = wetSynthesis * (1.f - lpCoeff) + filterZ1 * lpCoeff;
                wetSynthesis = filterZ1;
            } 
            else if (currentMode == 3) {
                // SWELL MODE: Tracking Envelope shape smoothing
                float swellGain = normalizedSwellEnvelope(pEnvTime);
                wetSynthesis *= swellGain;
            }

            // Analog Output Overdrive Saturation Path
            float drivenWet = wetSynthesis * (1.f + pDrive * 15.f);
            float outWet = tanhf(drivenWet) * (1.f / (1.f + pDrive * 2.5f));

            // Output Mix blending matrix
            out[i] = (drySignal * (1.f - pDryMix)) + (outWet * pDryMix);

            globalWritePtr = (globalWritePtr + 1) % 48000;
        }
    }

private:
    float fSR;
    float pMode, pDimension, pEnvShape, pEnvTime, pDrive, pDryMix;

    float mainCircularBuffer[48000];
    int globalWritePtr;

    EnvFollower detector;
    VoiceChannel voiceA;
    VoiceChannel voiceB;
    int activeVoice;
    int triggerCooldown;

    float lfoPhase;
    float filterZ1;

    float normalizedSwellEnvelope(float rateTime) {
        // Slow curve emulation mapping helper
        static float ampTrace = 0.f;
        if (detector.env > 0.15f) ampTrace += (0.005f / (rateTime + 0.01f));
        else                      ampTrace -= 0.001f;
        if (ampTrace > 1.f) ampTrace = 1.f;
        if (ampTrace < 0.f) ampTrace = 0.f;
        return ampTrace;
    }

    enum Parameters { kMode = 0, kDimension, kEnvShape, kEnvTime, kDrive, kDryMix, kParameterCount };
    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(InfiniteJetsPlugin)
};

Plugin* createPlugin() { return new InfiniteJetsPlugin(); }
END_NAMESPACE_DISTRHO
endef

define INFINITE_JETS_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define INFINITE_JETS_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_BRAND "MOD Cookbook"
#define DISTRHO_PLUGIN_NAME  "Infinite Jets Resynth"
#define DISTRHO_PLUGIN_URI   "urn:mod-cookbook:infinite-jets"
#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 { kMode = 0, kDimension, kEnvShape, kEnvTime, kDrive, kDryMix, kParameterCount };
#endif
endef

define INFINITE_JETS_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = infinite-jets
FILES_DSP = InfiniteJetsPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define INFINITE_JETS_MANIFEST_TTL
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:mod-cookbook:infinite-jets>
    a lv2:Plugin, lv2:GranularPlugin;
    lv2:binary <infinite-jets_dsp.so>;
    rdfs:seeAlso <infinite-jets.ttl>.
endef

define INFINITE_JETS_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#> .
<urn:mod-cookbook:infinite-jets>
    a lv2:Plugin, lv2:GranularPlugin;
    doap:name "Infinite Jets Resynth";
    doap:license <http://opensource.org/licenses/MIT>;
    doap:maintainer [foaf:name "MOD Cookbook"; foaf:homepage <https://mod.audio>];
    rdfs:comment "Multi-Voice resynthesis texture tracking machine mimicking the Blur, Synth, Glitch, and Swell modes of the Infinite Jets hardware.";
    lv2:port [
        a lv2:InputPort, lv2:AudioPort; lv2:index 0; lv2:symbol "in_m"; lv2:name "Audio In"
    ],[
        a lv2:OutputPort, lv2:AudioPort; lv2:index 1; lv2:symbol "out_m"; lv2:name "Audio Out"
    ],[
        a lv2:InputPort, lv2:ControlPort; lv2:index 2; lv2:symbol "mode"; lv2:name "Resynthesis Mode";
        lv2:default 0.0; lv2:minimum 0.0; lv2:maximum 3.0
    ],[
        a lv2:InputPort, lv2:ControlPort; lv2:index 3; lv2:symbol "dimension"; lv2:name "Dimension";
        lv2:default 0.5; lv2:minimum 0.0; lv2:maximum 1.0
    ],[
        a lv2:InputPort, lv2:ControlPort; lv2:index 4; lv2:symbol "shape"; lv2:name "Mod Shape (LFO)";
        lv2:default 0.2; lv2:minimum 0.0; lv2:maximum 1.0
    ],[
        a lv2:InputPort, lv2:ControlPort; lv2:index 5; lv2:symbol "time"; lv2:name "Mod Speed / Time";
        lv2:default 0.4; lv2:minimum 0.05; lv2:maximum 2.0
    ],[
        a lv2:InputPort, lv2:ControlPort; lv2:index 6; lv2:symbol "drive"; lv2:name "Drive";
        lv2:default 0.2; lv2:minimum 0.0; lv2:maximum 1.0
    ],[
        a lv2:InputPort, lv2:ControlPort; lv2:index 7; lv2:symbol "mix"; lv2:name "Dry/Wet Mix";
        lv2:default 0.5; lv2:minimum 0.0; lv2:maximum 1.0
    ].
endef

export INFINITE_JETS_PLUGIN_CPP INFINITE_JETS_PLUGIN_INFO_H INFINITE_JETS_PLUGIN_MAKEFILE
export INFINITE_JETS_MANIFEST_TTL INFINITE_JETS_PLUGIN_TTL

define INFINITE_JETS_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/infinite-jets
	printf '%s' "$$INFINITE_JETS_PLUGIN_CPP"      > $(@D)/examples/infinite-jets/InfiniteJetsPlugin.cpp
	printf '%s' "$$INFINITE_JETS_PLUGIN_INFO_H"   > $(@D)/examples/infinite-jets/DistrhoPluginInfo.h
	printf '%s' "$$INFINITE_JETS_PLUGIN_MAKEFILE" > $(@D)/examples/infinite-jets/Makefile
endef

define INFINITE_JETS_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/infinite-jets lv2_dsp
endef

define INFINITE_JETS_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/infinite-jets.lv2
	cp $(@D)/bin/infinite-jets.lv2/infinite-jets_dsp.so $($(PKG)_PKGDIR)/infinite-jets.lv2/
	printf '%s' "$$INFINITE_JETS_MANIFEST_TTL" > $($(PKG)_PKGDIR)/infinite-jets.lv2/manifest.ttl
	printf '%s' "$$INFINITE_JETS_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/infinite-jets.lv2/infinite-jets.ttl
endef

$(eval $(generic-package))
