################################################################################
#
# ZOOM_SLICER
#
# Models Zoom's signature rhythmic "Slicer / SeqFilter" algorithm.
# Fully expanded with the complete 20-pattern structural sequence matrix.
#
################################################################################

ZOOM_SLICER_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ZOOM_SLICER_SITE = https://github.com/DISTRHO/DPF.git
ZOOM_SLICER_SITE_METHOD = git
ZOOM_SLICER_BUNDLES = zoom-slicer.lv2

define ZOOM_SLICER_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND   "Zoom"
#define DISTRHO_PLUGIN_NAME    "Slicer"
#define DISTRHO_PLUGIN_URI     "urn:mod-cookbook:zoom-slicer"
#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 {
    kSlicerPattern = 0,
    kSlicerSpeed,
    kSlicerGate,
    kSlicerReso,
    kSlicerLevel,
    kSlicerParameterCount
};

#endif
endef

define ZOOM_SLICER_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>

START_NAMESPACE_DISTRHO

// Compact 8-step bitmask array representing the 20 distinct rhythmic profiles
// (1 = Open Gate / Active Step, 0 = Closed Gate / Muted Step)
const uint8_t kSlicerMasks[20] = {
    0b10101010, // 1. Straight 8ths
    0b11011010, // 2. Dotted Feel
    0b10001101, // 3. Syncopated Groove
    0b11100101, // 4. Choppy Stutter
    0b11110000, // 5. Half-Note Pulse
    0b10001000, // 6. Quarter-Note Downbeats
    0b00100010, // 7. Offbeat Quarter Accents
    0b11111111, // 8. Fully Open (Passive Modulation)
    0b10110110, // 9. Double-Step Skip
    0b11001100, // 10. Gallop Subdivision
    0b10010010, // 11. Poly-rhythmic Accent
    0b01100110, // 12. Inverted Gallop
    0b11101110, // 13. Heavy Driving Grid
    0b10101111, // 14. Delayed Burst Pattern
    0b11111010, // 15. Anticipated Release
    0b10000001, // 16. Perimeter Bounds Gate
    0b01010101, // 17. Phase-Inverted 8ths
    0b11010011, // 18. Complex Syncopated Cross
    0b10100000, // 19. Early Triplet Simulation
    0b00001010  // 20. Late Triplet Simulation
};

class ZoomSlicer : public Plugin {
public:
    ZoomSlicer()
        : Plugin(kSlicerParameterCount, 0, 0),
          fPattern(0.0f), fSpeed(4.0f), fGate(50.0f), fReso(40.0f), fLevel(100.0f),
          fPhase(0.0f), fCurrentStepGain(1.0f), fSr(48000.0f)
    {
        fLpY1 = fLpY2 = 0.0f;
    }

protected:
    const char* getLabel()       const override { return "Slicer"; }
    const char* getDescription() const override { return "Zoom Rhythmic 20-Pattern Slicer 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', 'L', 'C'); }

    void initParameter(uint32_t index, Parameter& p) override {
        p.hints = kParameterIsAutomatable;
        switch (index) {
        case kSlicerPattern:
            p.name = "Pattern"; p.symbol = "pattern"; p.ranges.def = 0.0f; p.ranges.min = 0.0f; p.ranges.max = 19.0f;
            p.hints |= kParameterIsInteger; break;
        case kSlicerSpeed:
            p.name = "Speed (Hz)"; p.symbol = "speed"; p.ranges.def = 4.0f; p.ranges.min = 1.0f; p.ranges.max = 15.0f; break;
        case kSlicerGate:
            p.name = "Gate Time"; p.symbol = "gate"; p.ranges.def = 50.0f; p.ranges.min = 5.0f; p.ranges.max = 95.0f; break;
        case kSlicerReso:
            p.name = "Resonance"; p.symbol = "resonance"; p.ranges.def = 40.0f; p.ranges.min = 0.0f; p.ranges.max = 100.0f; break;
        case kSlicerLevel:
            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 kSlicerPattern: return fPattern;
        case kSlicerSpeed:   return fSpeed;
        case kSlicerGate:    return fGate;
        case kSlicerReso:    return fReso;
        case kSlicerLevel:   return fLevel;
        }
        return 0.f;
    }

    void setParameterValue(uint32_t index, float value) override {
        switch (index) {
        case kSlicerPattern: fPattern = value; break;
        case kSlicerSpeed:   fSpeed = value; break;
        case kSlicerGate:    fGate = value; break;
        case kSlicerReso:    fReso = value; break;
        case kSlicerLevel:   fLevel = value; break;
        }
    }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        if (fSr <= 0.f) fSr = 48000.f;
        fPhase = 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 int patternIdx = std::max(0, std::min((int)(fPattern + 0.5f), 19));
        const float outVol = fLevel / 100.0f;
        const float gateWidth = fGate / 100.0f;
        const float qFactor = 0.707f + (fReso / 15.0f);

        // Sequence calculation setup
        const float totalSequenceFreq = fSpeed / 8.0f;
        const float phaseInc = (2.0f * 3.14159265f * totalSequenceFreq) / fSr;
        const float smoothCoeff = std::exp(-1.0f / (0.003f * fSr));

        // Pull active bitmask configuration
        const uint8_t activeMask = kSlicerMasks[patternIdx];

        for (uint32_t i = 0; i < frames; ++i) {
            float curIn = in[i];

            fPhase += phaseInc;
            if (fPhase >= 2.0f * 3.14159265f) fPhase -= 2.0f * 3.14159265f;

            float normalizedPhase = fPhase / (2.0f * 3.14159265f);
            int currentStep = (int)(normalizedPhase * 8.0f) % 8;
            float stepProgress = (normalizedPhase * 8.0f) - (float)currentStep;

            // Extract gate state from indexed sequence bitmask position
            bool stepActive = (activeMask & (1 << (7 - currentStep))) != 0;
            float targetGain = 0.0f;
            
            if (stepActive && (stepProgress < gateWidth)) {
                targetGain = 1.0f;
            }

            fCurrentStepGain = smoothCoeff * fCurrentStepGain + (1.0f - smoothCoeff) * targetGain;

            float wetSample = curIn * fCurrentStepGain;

            // Envelope VCF filter tracking loop
            float targetFc = 350.0f + (3800.0f * fCurrentStepGain);
            
            float w0 = 2.0f * 3.14159265f * targetFc / fSr;
            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;

            out[i] = filtered * outVol;
        }
    }

private:
    float fPattern, fSpeed, fGate, fReso, fLevel;
    float fPhase, fCurrentStepGain, fSr;
    float fLpY1, fLpY2;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZoomSlicer)
};

Plugin* createPlugin() { return new ZoomSlicer(); }

END_NAMESPACE_DISTRHO
endef

define ZOOM_SLICER_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = zoom-slicer
FILES_DSP = ZoomSlicerPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define ZOOM_SLICER_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:zoom-slicer>
    a lv2:Plugin ;
    lv2:binary <zoom-slicer_dsp.so> ;
    rdfs:seeAlso <zoom-slicer.ttl> .
endef

define ZOOM_SLICER_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-slicer>
    a lv2:Plugin , lv2:FilterPlugin ;
    doap:name "Slicer" ;
    doap:license <http://opensource.org/licenses/ISC> ;
    doap:maintainer [ foaf:name "Zoom" ] ;
    rdfs:comment "Zoom MultiStomp 20-Pattern Step Slicer 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 "pattern" ; lv2:name "Pattern" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 19 ;
        lv2:portProperty lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Pattern 1 (Straight)" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 2 (Dotted)" ; rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 3 (Groove)" ; rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 4 (Stutter)" ; rdf:value 3 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 5 (Half Pulse)" ; rdf:value 4 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 6 (Downbeats)" ; rdf:value 5 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 7 (Offbeats)" ; rdf:value 6 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 8 (Mod Open)" ; rdf:value 7 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 9 (Double Skip)" ; rdf:value 8 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 10 (Gallop)" ; rdf:value 9 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 11 (Poly Accent)" ; rdf:value 10 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 12 (Inv Gallop)" ; rdf:value 11 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 13 (Heavy Drive)" ; rdf:value 12 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 14 (Delayed Burst)" ; rdf:value 13 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 15 (Anticipated)" ; rdf:value 14 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 16 (Perimeter)" ; rdf:value 15 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 17 (Inv 8ths)" ; rdf:value 16 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 18 (Cross Sync)" ; rdf:value 17 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 19 (Early Trip)" ; rdf:value 18 ] ;
        lv2:scalePoint [ rdfs:label "Pattern 20 (Late Trip)" ; rdf:value 19 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ; lv2:symbol "speed" ; lv2:name "Speed (Hz)" ;
        lv2:default 4.0 ; lv2:minimum 1.0 ; lv2:maximum 15.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ; lv2:symbol "gate" ; lv2:name "Gate Time" ;
        lv2:default 50.0 ; lv2:minimum 5.0 ; lv2:maximum 95.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ; lv2:symbol "resonance" ; lv2:name "Resonance" ;
        lv2:default 40.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ; lv2:symbol "level" ; lv2:name "Level" ;
        lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 150.0 ;
    ] .
endef

export ZOOM_SLICER_PLUGIN_CPP ZOOM_SLICER_PLUGIN_INFO_H ZOOM_SLICER_PLUGIN_MAKEFILE
export ZOOM_SLICER_MANIFEST_TTL ZOOM_SLICER_PLUGIN_TTL

define ZOOM_SLICER_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/zoom-slicer
	printf '%s' "$$ZOOM_SLICER_PLUGIN_CPP"      > $(@D)/examples/zoom-slicer/ZoomSlicerPlugin.cpp
	printf '%s' "$$ZOOM_SLICER_PLUGIN_INFO_H"   > $(@D)/examples/zoom-slicer/DistrhoPluginInfo.h
	printf '%s' "$$ZOOM_SLICER_PLUGIN_MAKEFILE" > $(@D)/examples/zoom-slicer/Makefile
endef

define ZOOM_SLICER_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/zoom-slicer lv2_dsp
endef

define ZOOM_SLICER_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/zoom-slicer.lv2
	cp $(@D)/bin/zoom-slicer.lv2/zoom-slicer_dsp.so $($(PKG)_PKGDIR)/zoom-slicer.lv2/
	printf '%s' "$$ZOOM_SLICER_MANIFEST_TTL" > $($(PKG)_PKGDIR)/zoom-slicer.lv2/manifest.ttl
	printf '%s' "$$ZOOM_SLICER_PLUGIN_TTL" > $($(PKG)_PKGDIR)/zoom-slicer.lv2/zoom-slicer.ttl
endef

$(eval $(generic-package))