# Copyright (c) 2026 MOD Audio Limited
# SPDX-License-Identifier: MIT
#
# Center Filter Resonant: two-knob mono resonant filter for the MOD Online Builder.
# Save this file as center-filter-resonant.mk and upload it at:
# https://builder.mod.audio/buildroot

CENTER_FILTER_RESONANT_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
CENTER_FILTER_RESONANT_SITE = https://github.com/DISTRHO/DPF.git
CENTER_FILTER_RESONANT_SITE_METHOD = git
CENTER_FILTER_RESONANT_BUNDLES = center-filter-resonant.lv2

define CENTER_FILTER_RESONANT_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>

START_NAMESPACE_DISTRHO

class CenterFilterResonantPlugin : public Plugin
{
public:
    CenterFilterResonantPlugin()
        : Plugin(kParameterCount, 0, 0),
          fFilter(0.0f),
          fResonance(0.0f),
          fIc1eq(0.0f),
          fIc2eq(0.0f),
          fCutoffHz(1000.0f),
          fQ(0.70710678f),
          fLastMode(0)
    {
    }

protected:
    const char* getLabel() const override
    {
        return "Center Filter Resonant";
    }

    const char* getDescription() const override
    {
        return "Two-knob mono resonant filter with center-flat sweep. Left is resonant low-pass, right is resonant high-pass.";
    }

    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, 2, 0);
    }

    int64_t getUniqueId() const override
    {
        return d_cconst('C', 'F', 'R', '2');
    }

    void activate() override
    {
        fIc1eq = 0.0f;
        fIc2eq = 0.0f;
        fCutoffHz = 1000.0f;
        fQ = 0.70710678f;
        fLastMode = 0;
    }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        if (index == kFilter)
        {
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Filter";
            parameter.symbol = "filter";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = -1.0f;
            parameter.ranges.max = 1.0f;
        }
        else if (index == kResonance)
        {
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Resonance";
            parameter.symbol = "resonance";
            parameter.ranges.def = 0.0f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index)
        {
        case kFilter:
            return fFilter;
        case kResonance:
            return fResonance;
        default:
            return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index)
        {
        case kFilter:
            fFilter = clamp(value, -1.0f, 1.0f);
            break;
        case kResonance:
            fResonance = clamp(value, 0.0f, 1.0f);
            break;
        default:
            break;
        }
    }

    void run(const float** inputs, float** outputs, uint32_t frames) override
    {
        const float* const in = inputs[0];
        float* const out = outputs[0];

        const float sweep = clamp(fFilter, -1.0f, 1.0f);
        const float deadband = 0.02f;

        int mode = 0;
        float targetCutoffHz = fCutoffHz;

        if (sweep < -deadband)
        {
            mode = -1;
            const float amount = (-sweep - deadband) / (1.0f - deadband);
            targetCutoffHz = logMap(amount, 18000.0f, 50.0f);
        }
        else if (sweep > deadband)
        {
            mode = 1;
            const float amount = (sweep - deadband) / (1.0f - deadband);
            targetCutoffHz = logMap(amount, 20.0f, 8000.0f);
        }

        const float targetQ = resonanceToQ(fResonance);

        if (fLastMode == 0 && mode != 0)
        {
            fCutoffHz = targetCutoffHz;
            fQ = targetQ;
        }

        float cutoffHz = fCutoffHz;
        float q = fQ;
        const float cutoffStep = frames > 0 ? (targetCutoffHz - cutoffHz) / static_cast<float>(frames) : 0.0f;
        const float qStep = frames > 0 ? (targetQ - q) / static_cast<float>(frames) : 0.0f;

        for (uint32_t i = 0; i < frames; ++i)
        {
            const float x = in[i];

            if (mode == 0)
            {
                out[i] = x;
                fIc1eq *= 0.995f;
                fIc2eq *= 0.995f;
                continue;
            }

            cutoffHz += cutoffStep;
            q += qStep;

            float low = 0.0f;
            float high = 0.0f;
            processSvf(x, cutoffHz, q, low, high);

            out[i] = (mode < 0) ? low : high;
        }

        fCutoffHz = targetCutoffHz;
        fQ = targetQ;
        fLastMode = mode;
    }

private:
    static float clamp(float value, float minValue, float maxValue)
    {
        if (value < minValue)
            return minValue;
        if (value > maxValue)
            return maxValue;
        return value;
    }

    static float logMap(float amount, float startHz, float endHz)
    {
        const float a = clamp(amount, 0.0f, 1.0f);
        return startHz * std::pow(endHz / startHz, a);
    }

    static float resonanceToQ(float resonance)
    {
        const float r = clamp(resonance, 0.0f, 1.0f);
        const float curved = r * r;
        return 0.70710678f + curved * (8.0f - 0.70710678f);
    }

    void processSvf(float input, float cutoffHz, float q, float& low, float& high)
    {
        const float sampleRate = static_cast<float>(getSampleRate());
        const float safeRate = sampleRate > 1.0f ? sampleRate : 48000.0f;
        const float safeCutoff = clamp(cutoffHz, 5.0f, safeRate * 0.45f);
        const float safeQ = clamp(q, 0.5f, 8.0f);
        const float pi = 3.14159265358979323846f;

        const float g = std::tan((pi * safeCutoff) / safeRate);
        const float r = 1.0f / (2.0f * safeQ);
        const float h = 1.0f / (1.0f + 2.0f * r * g + g * g);

        high = (input - (2.0f * r + g) * fIc1eq - fIc2eq) * h;
        const float band = g * high + fIc1eq;
        low = g * band + fIc2eq;

        fIc1eq = 2.0f * band - fIc1eq;
        fIc2eq = 2.0f * low - fIc2eq;
    }

    float fFilter;
    float fResonance;
    float fIc1eq;
    float fIc2eq;
    float fCutoffHz;
    float fQ;
    int fLastMode;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CenterFilterResonantPlugin)
};

Plugin* createPlugin()
{
    return new CenterFilterResonantPlugin();
}

END_NAMESPACE_DISTRHO
endef

define CENTER_FILTER_RESONANT_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND "MOD Cookbook"
#define DISTRHO_PLUGIN_NAME "Center Filter Resonant"
#define DISTRHO_PLUGIN_URI "urn:mod-cookbook:center-filter-resonant"

#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 {
    kFilter = 0,
    kResonance,
    kParameterCount
};

#endif
endef

define CENTER_FILTER_RESONANT_PLUGIN_MAKEFILE
#!/usr/bin/make -f

NAME = center-filter-resonant
FILES_DSP = CenterFilterPlugin.cpp

include ../../Makefile.plugins.mk

TARGETS = lv2_dsp

all: $$(TARGETS)
endef

define CENTER_FILTER_RESONANT_MANIFEST_TTL
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:center-filter-resonant>
    a lv2:Plugin , lv2:FilterPlugin ;
    lv2:binary <center-filter-resonant_dsp.so> ;
    rdfs:seeAlso <center-filter-resonant.ttl> .
endef

define CENTER_FILTER_RESONANT_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:center-filter-resonant>
    a lv2:Plugin , lv2:FilterPlugin ;
    doap:name "Center Filter Resonant" ;
    doap:license <http://opensource.org/licenses/mit> ;
    doap:maintainer [
        foaf:name "MOD Cookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Two-knob mono resonant filter with center-flat sweep. Left is resonant low-pass, right is resonant high-pass." ;
    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ;
        lv2:symbol "in" ;
        lv2:name "Audio In"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 1 ;
        lv2:symbol "out" ;
        lv2:name "Audio Out"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 2 ;
        lv2:symbol "filter" ;
        lv2:name "Filter" ;
        lv2:default 0.0 ;
        lv2:minimum -1.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ;
        lv2:symbol "resonance" ;
        lv2:name "Resonance" ;
        lv2:default 0.0 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] .
endef

export CENTER_FILTER_RESONANT_PLUGIN_CPP
export CENTER_FILTER_RESONANT_PLUGIN_INFO_H
export CENTER_FILTER_RESONANT_PLUGIN_MAKEFILE
export CENTER_FILTER_RESONANT_MANIFEST_TTL
export CENTER_FILTER_RESONANT_PLUGIN_TTL

define CENTER_FILTER_RESONANT_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/center-filter-resonant
	printf '%s' "$$CENTER_FILTER_RESONANT_PLUGIN_CPP" > $(@D)/examples/center-filter-resonant/CenterFilterPlugin.cpp
	printf '%s' "$$CENTER_FILTER_RESONANT_PLUGIN_INFO_H" > $(@D)/examples/center-filter-resonant/DistrhoPluginInfo.h
	printf '%s' "$$CENTER_FILTER_RESONANT_PLUGIN_MAKEFILE" > $(@D)/examples/center-filter-resonant/Makefile
endef

define CENTER_FILTER_RESONANT_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/center-filter-resonant lv2_dsp
endef

define CENTER_FILTER_RESONANT_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/center-filter-resonant.lv2
	cp $(@D)/bin/center-filter-resonant.lv2/center-filter-resonant_dsp.so $($(PKG)_PKGDIR)/center-filter-resonant.lv2/
	printf '%s' "$$CENTER_FILTER_RESONANT_MANIFEST_TTL" > $($(PKG)_PKGDIR)/center-filter-resonant.lv2/manifest.ttl
	printf '%s' "$$CENTER_FILTER_RESONANT_PLUGIN_TTL" > $($(PKG)_PKGDIR)/center-filter-resonant.lv2/center-filter-resonant.ttl
endef

$(eval $(generic-package))
