################################################################################
#
# ZOOM_STEREO_SLICER
#
# Models Zoom's signature rhythmic "Slicer / SeqFilter" algorithm.
# Stereo version with independent channel filtering and dry/wet mix blend.
#
################################################################################

ZOOM_STEREO_SLICER_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ZOOM_STEREO_SLICER_SITE = https://github.com/DISTRHO/DPF.git
ZOOM_STEREO_SLICER_SITE_METHOD = git
ZOOM_STEREO_SLICER_BUNDLES = zoom-stereo-slicer.lv2

define ZOOM_STEREO_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    "StereoSlicer"
#define DISTRHO_PLUGIN_URI     "urn:mod-cookbook:zoom-stereo-slicer"
#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 {
    kSlicerPattern = 0,
    kSlicerSpeed,
    kSlicerGate,
    kSlicerReso,
    kSlicerMix,
    kSlicerLevel,
    kSlicerParameterCount
};

#endif
endef

define ZOOM_STEREO_SLICER_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>

START_NAMESPACE_DISTRHO

const uint8_t kSlicerMasks[20] = {
    0b10101010, 0b11011010, 0b10001101, 0b11100101,
    0b11110000, 0b10001000, 0b00100010, 0b11111111,
    0b10110110, 0b11001100, 0b10010010, 0b01100110,
    0b11101110, 0b10101111, 0b11111010, 0b10000001,
    0b01010101, 0b11010011, 0b10100000, 0b00001010
};

class ZoomStereoSlicer : public Plugin {
public:
    ZoomStereoSlicer()
        : Plugin(kSlicerParameterCount, 0, 0),
          fPattern(0.0f), fSpeed(4.0f), fGate(50.0f), fReso(40.0f), fMix(100.0f), fLevel(100.0f),
          fPhase(0.0f), fCurrentStepGain(1.0f), fSr(48000.0f)
    {
        fLpY1_L = fLpY2_L = 0.0f;
        fLpY1_R = fLpY2_R = 0.0f;
    }

protected:
    const char* getLabel()       const override { return "StereoSlicer"; }
    const char* getDescription() const override { return "Zoom Stereo 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', 'S', 'L'); }

    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 kSlicerMix:
            p.name = "Mix"; p.symbol = "mix"; p.ranges.def = 100.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 kSlicerMix:     return fMix;
        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 kSlicerMix:     fMix = 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_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 patternIdx = std::max(0, std::min((int)(fPattern + 0.5f), 19));
        const float wetMix = fMix / 100.0f;
        const float dryMix = 1.0f - wetMix;
        const float outVol = fLevel / 100.0f;
        const float gateWidth = fGate / 100.0f;
        const float qFactor = 0.707f + (fReso / 15.0f);

        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));
        const uint8_t activeMask = kSlicerMasks[patternIdx];

        for (uint32_t i = 0; i < frames; ++i) {
            float curInL = inL[i];
            float curInR = inR[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;

            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 wetSampleL = curInL * fCurrentStepGain;
            float wetSampleR = curInR * fCurrentStepGain;

            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;

            // Left Channel
            float filteredL = (b0 * wetSampleL + b1 * wetSampleL + b0 * wetSampleL - a1 * fLpY1_L - a2 * fLpY2_L) / a0;
            fLpY2_L = fLpY1_L;
            fLpY1_L = filteredL;

            // Right Channel
            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 fPattern, fSpeed, fGate, fReso, fMix, fLevel;
    float fPhase, fCurrentStepGain, fSr;
    float fLpY1_L, fLpY2_L;
    float fLpY1_R, fLpY2_R;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZoomStereoSlicer)
};

Plugin* createPlugin() { return new ZoomStereoSlicer(); }

END_NAMESPACE_DISTRHO
endef

define ZOOM_STEREO_SLICER_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = zoom-stereo-slicer
FILES_DSP = ZoomStereoSlicerPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

define ZOOM_STEREO_SLICER_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:zoom-stereo-slicer>
    a lv2:Plugin ;
    lv2:binary <zoom-stereo-slicer_dsp.so> ;
    rdfs:seeAlso <zoom-stereo-slicer.ttl> .
endef

define ZOOM_STEREO_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-stereo-slicer>
    a lv2:Plugin , lv2:FilterPlugin ;
    doap:name "StereoSlicer" ;
    doap:license <http://opensource.org/licenses/ISC> ;
    doap:maintainer [ foaf:name "Zoom" ] ;
    rdfs:comment "Zoom MultiStomp Stereo 20-Pattern Step Slicer 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 "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 5 ; 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 6 ; 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 7 ; 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 8 ; lv2:symbol "mix" ; lv2:name "Mix" ;
        lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ; lv2:symbol "level" ; lv2:name "Level" ;
        lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 150.0 ;
    ] .
endef

export ZOOM_STEREO_SLICER_PLUGIN_CPP ZOOM_STEREO_SLICER_PLUGIN_INFO_H ZOOM_STEREO_SLICER_PLUGIN_MAKEFILE
export ZOOM_STEREO_SLICER_MANIFEST_TTL ZOOM_STEREO_SLICER_PLUGIN_TTL

define ZOOM_STEREO_SLICER_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/zoom-stereo-slicer
	printf '%s' "$$ZOOM_STEREO_SLICER_PLUGIN_CPP"      > $(@D)/examples/zoom-stereo-slicer/ZoomStereoSlicerPlugin.cpp
	printf '%s' "$$ZOOM_STEREO_SLICER_PLUGIN_INFO_H"   > $(@D)/examples/zoom-stereo-slicer/DistrhoPluginInfo.h
	printf '%s' "$$ZOOM_STEREO_SLICER_PLUGIN_MAKEFILE" > $(@D)/examples/zoom-stereo-slicer/Makefile
endef

define ZOOM_STEREO_SLICER_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/zoom-stereo-slicer lv2_dsp
endef

define ZOOM_STEREO_SLICER_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/zoom-stereo-slicer.lv2
	cp $(@D)/bin/zoom-stereo-slicer.lv2/zoom-stereo-slicer_dsp.so $($(PKG)_PKGDIR)/zoom-stereo-slicer.lv2/
	printf '%s' "$$ZOOM_STEREO_SLICER_MANIFEST_TTL" > $($(PKG)_PKGDIR)/zoom-stereo-slicer.lv2/manifest.ttl
	printf '%s' "$$ZOOM_STEREO_SLICER_PLUGIN_TTL" > $($(PKG)_PKGDIR)/zoom-stereo-slicer.lv2/zoom-stereo-slicer.ttl
endef

$(eval $(generic-package))