################################################################################
#
# ZOOM_MS70CDR_02_STEREO_CHORUS
#
# LV2 plugin built from the MOD Audio Cookbook workflow: DPF source is embedded
# below and compiled against a pinned DPF checkout, producing an .lv2 bundle
# that the builder.mod.audio cloud builder can compile and package for the
# MOD Dwarf.
#
################################################################################

ZOOM_MS70CDR_02_STEREO_CHORUS_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
ZOOM_MS70CDR_02_STEREO_CHORUS_SITE = https://github.com/DISTRHO/DPF.git
ZOOM_MS70CDR_02_STEREO_CHORUS_SITE_METHOD = git
ZOOM_MS70CDR_02_STEREO_CHORUS_GIT_SUBMODULES = YES
ZOOM_MS70CDR_02_STEREO_CHORUS_INSTALL_STAGING = NO
ZOOM_MS70CDR_02_STEREO_CHORUS_BUNDLES = zoom-ms70cdr-02-stereo-chorus.lv2

define ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>

START_NAMESPACE_DISTRHO

class ZoomMS70CDR02StereoChorus : public Plugin {
public:
    ZoomMS70CDR02StereoChorus()
        : Plugin(5, 0, 0)
    {
        sampleRateChanged(getSampleRate());
    }

protected:
    const char* getLabel() const override { return "ZoomStereoChorus"; }
    const char* getDescription() const override { return "Zoom MS-70CDR Stereo Chorus - MOD Audio Cookbook"; }
    const char* getMaker() const override { return "MODAudioCookbook"; }
    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','C','2'); }

    void initParameter(uint32_t index, Parameter& parameter) override {
        switch (index) {
        case 0:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Depth";
            parameter.symbol = "depth";
            parameter.ranges.def = 50.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 100.0f;
            break;
        case 1:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Rate";
            parameter.symbol = "rate";
            parameter.ranges.def = 25.0f; parameter.ranges.min = 1.0f; parameter.ranges.max = 50.0f;
            break;
        case 2:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Mix";
            parameter.symbol = "mix";
            parameter.ranges.def = 50.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 100.0f;
            break;
        case 3:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Tone";
            parameter.symbol = "tone";
            parameter.ranges.def = 5.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 10.0f;
            break;
        case 4:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Level";
            parameter.symbol = "level";
            parameter.ranges.def = 100.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 150.0f;
            break;
        }
    }

    float getParameterValue(uint32_t index) const override { return params[index]; }
    void  setParameterValue(uint32_t index, float value) override { params[index] = value; }

    void sampleRateChanged(double newSampleRate) override {
        sr = (float)newSampleRate;
    }

    void activate() override {
        lfoPhaseL = 0.0f;
        lfoPhaseR = 0.25f; // 90 deg phase split for wide spatial separation
        writePos = 0;
        for(int i = 0; i < 8192; ++i) { bufferL[i] = 0.0f; bufferR[i] = 0.0f; }
        lpL = 0.0f; lpR = 0.0f;
    }

    inline float readBufferCubic(const float* buffer, float readPos) const {
        int i0 = (int)readPos;
        float frac = readPos - (float)i0;
        
        int idx1 = (i0) & 8191;
        int idx0 = (i0 - 1) & 8191;
        int idx2 = (i0 + 1) & 8191;
        int idx3 = (i0 + 2) & 8191;

        float y0 = buffer[idx0];
        float y1 = buffer[idx1];
        float y2 = buffer[idx2];
        float y3 = buffer[idx3];

        float c0 = y1;
        float c1 = 0.5f * (y2 - y0);
        float c2 = y0 - 2.5f * y1 + 2.0f * y2 - 0.5f * y3;
        float c3 = 0.5f * (y3 - y0) + 1.5f * (y1 - y2);

        return ((c3 * frac + c2) * frac + c1) * frac + c0;
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override {
        const float depthVal = (params[0] / 100.0f);
        const float rateHz   = 0.2f + powf(params[1] / 50.0f, 1.8f) * 3.3f;
        const float mixVal   = params[2] / 100.0f;
        const float toneVal  = params[3] / 10.0f;
        const float levelVal = params[4] / 100.0f;

        const float lfoInc = rateHz / sr;
        const float lpCoeff = 0.05f + toneVal * 0.45f;

        const float baseDelaySec = 0.014f;
        const float maxModSec    = 0.004f * depthVal;

        for (uint32_t i = 0; i < frames; ++i) {
            float inL = inputs[0][i];
            float inR = inputs[1][i];

            bufferL[writePos] = inL;
            bufferR[writePos] = inR;

            float modL = sinf(lfoPhaseL * 6.28318530718f);
            float modR = sinf(lfoPhaseR * 6.28318530718f);

            float delaySamplesL = (baseDelaySec + modL * maxModSec) * sr;
            float delaySamplesR = (baseDelaySec + modR * maxModSec) * sr;

            float readPosL = (float)writePos - delaySamplesL;
            float readPosR = (float)writePos - delaySamplesR;

            while (readPosL < 0.0f) readPosL += 8192.0f;
            while (readPosR < 0.0f) readPosR += 8192.0f;

            float wetL = readBufferCubic(bufferL, readPosL);
            float wetR = readBufferCubic(bufferR, readPosR);

            lpL += lpCoeff * (wetL - lpL);
            lpR += lpCoeff * (wetR - lpR);

            outputs[0][i] = (inL * (1.0f - mixVal) + lpL * mixVal) * levelVal;
            outputs[1][i] = (inR * (1.0f - mixVal) + lpR * mixVal) * levelVal;

            writePos = (writePos + 1) & 8191;
            lfoPhaseL += lfoInc; if (lfoPhaseL >= 1.0f) lfoPhaseL -= 1.0f;
            lfoPhaseR += lfoInc; if (lfoPhaseR >= 1.0f) lfoPhaseR -= 1.0f;
        }
    }

private:
    float sr = 48000.0f;
    float params[5] = {50.0f, 25.0f, 50.0f, 5.0f, 100.0f};

    float bufferL[8192];
    float bufferR[8192];
    int writePos = 0;

    float lfoPhaseL = 0.0f;
    float lfoPhaseR = 0.25f;

    float lpL = 0.0f, lpR = 0.0f;

    ZoomMS70CDR02StereoChorus(const ZoomMS70CDR02StereoChorus&) = delete;
    ZoomMS70CDR02StereoChorus& operator=(const ZoomMS70CDR02StereoChorus&) = delete;
};

Plugin* createPlugin() { return new ZoomMS70CDR02StereoChorus(); }

END_NAMESPACE_DISTRHO

endef
export ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_CPP

define ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND   "MODAudioCookbook"
#define DISTRHO_PLUGIN_NAME    "Zoom Stereo Chorus"
#define DISTRHO_PLUGIN_URI     "http://zoom.jp/plugins/ms70cdr/stereo-chorus"
#define DISTRHO_PLUGIN_NUM_INPUTS  2
#define DISTRHO_PLUGIN_NUM_OUTPUTS 2
#define DISTRHO_PLUGIN_IS_RT_SAFE  1
#define DISTRHO_PLUGIN_WANT_STATE 0
#define DISTRHO_PLUGIN_HAS_UI     0

#endif

endef
export ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_INFO_H

define ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

<http://zoom.jp/plugins/ms70cdr/stereo-chorus>
    a lv2:Plugin , lv2:ChorusPlugin ;
    doap:name "Zoom Stereo Chorus" ;
    doap:license <http://opensource.org/licenses/isc> ;
    lv2:project <http://zoom.jp/plugins/ms70cdr/stereo-chorus> ;
    lv2:optionalFeature lv2:hardRTCapable ;
    lv2:port
    [
        a lv2:AudioPort , lv2:InputPort ;
        lv2:index 0 ;
        lv2:symbol "in_l" ;
        lv2:name "In L" ;
    ] ,
    [
        a lv2:AudioPort , lv2:InputPort ;
        lv2:index 1 ;
        lv2:symbol "in_r" ;
        lv2:name "In R" ;
    ] ,
    [
        a lv2:AudioPort , lv2:OutputPort ;
        lv2:index 2 ;
        lv2:symbol "out_l" ;
        lv2:name "Out L" ;
    ] ,
    [
        a lv2:AudioPort , lv2:OutputPort ;
        lv2:index 3 ;
        lv2:symbol "out_r" ;
        lv2:name "Out R" ;
    ] ,
    [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "depth" ;
        lv2:name "Depth" ;
        lv2:default 50.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 100.0 ;
    ] ,
    [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "rate" ;
        lv2:name "Rate" ;
        lv2:default 25.0 ;
        lv2:minimum 1.0 ;
        lv2:maximum 50.0 ;
    ] ,
    [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ;
        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 7 ;
        lv2:symbol "tone" ;
        lv2:name "Tone" ;
        lv2:default 5.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 10.0 ;
    ] ,
    [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ;
        lv2:symbol "level" ;
        lv2:name "Level" ;
        lv2:default 100.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 150.0 ;
    ] .

endef
export ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_TTL

define ZOOM_MS70CDR_02_STEREO_CHORUS_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix pset: <http://lv2plug.in/ns/ext/presets#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<http://zoom.jp/plugins/ms70cdr/stereo-chorus>
    a lv2:Plugin ;
    lv2:binary <zoom-ms70cdr-02-stereo-chorus_dsp.so> ;
    rdfs:seeAlso <zoom-ms70cdr-02-stereo-chorus.ttl> .

endef
export ZOOM_MS70CDR_02_STEREO_CHORUS_MANIFEST_TTL

define ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_MAKEFILE
#!/usr/bin/make -f
# Makefile for zoom-ms70cdr-02-stereo-chorus (MOD Audio Cookbook / DPF) #
# ------------------------------------------------ #

include ../../Makefile.base.mk

NAME = zoom-ms70cdr-02-stereo-chorus

all: lv2_dsp

BUILD_DIR = ../../build/zoom-ms70cdr-02-stereo-chorus

FILES_DSP = \
	DistrhoPluginZoomMS70CDR02StereoChorus.cpp

include ../../Makefile.plugins.mk

TARGETS += lv2_dsp

all: $(TARGETS)

endef
export ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_MAKEFILE

define ZOOM_MS70CDR_02_STEREO_CHORUS_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/zoom-ms70cdr-02-stereo-chorus
	printf '%s' "$$ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_CPP" > $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/DistrhoPluginZoomMS70CDR02StereoChorus.cpp
	printf '%s' "$$ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_INFO_H" > $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/DistrhoPluginInfo.h
	printf '%s' "$$ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_TTL" > $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/zoom-ms70cdr-02-stereo-chorus.ttl
	printf '%s' "$$ZOOM_MS70CDR_02_STEREO_CHORUS_MANIFEST_TTL" > $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/manifest.ttl
	printf '%s' "$$ZOOM_MS70CDR_02_STEREO_CHORUS_PLUGIN_MAKEFILE" > $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/Makefile
endef

define ZOOM_MS70CDR_02_STEREO_CHORUS_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/zoom-ms70cdr-02-stereo-chorus lv2_dsp
endef

define ZOOM_MS70CDR_02_STEREO_CHORUS_INSTALL_TARGET_CMDS
	cp $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/manifest.ttl $(@D)/bin/zoom-ms70cdr-02-stereo-chorus.lv2/manifest.ttl
	cp $(@D)/examples/zoom-ms70cdr-02-stereo-chorus/zoom-ms70cdr-02-stereo-chorus.ttl $(@D)/bin/zoom-ms70cdr-02-stereo-chorus.lv2/zoom-ms70cdr-02-stereo-chorus.ttl
	mkdir -p $(TARGET_DIR)/usr/lib/lv2
	cp -r $(@D)/bin/zoom-ms70cdr-02-stereo-chorus.lv2 $(TARGET_DIR)/usr/lib/lv2/
endef

$(eval $(generic-package))