# MOD Plugin Cookbook recipe
# DOD FX57 "Hard Rock Distortion" emulation (distortion + BBD doubler)
# SPDX-License-Identifier: MIT

FX57_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
FX57_SITE = https://github.com/DISTRHO/DPF.git
FX57_SITE_METHOD = git
FX57_BUNDLES = fx57.lv2

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

#define FX57_MAX_DELAY_SAMPLES 16384u

START_NAMESPACE_DISTRHO

class Fx57Plugin : public Plugin
{
public:
    Fx57Plugin()
        : Plugin(kParameterCount, 0, 0),
          fDistortion(0.5f),
          fDelay(0.3f),
          fTone(0.5f),
          fLevel(0.7f),
          fHpPrevIn(0.0f),
          fHpPrevOut(0.0f),
          fBbdLpState(0.0f),
          fToneLpState(0.0f),
          fToneHpLpState(0.0f),
          fWriteIndex(0)
    {
        for (uint32_t i = 0; i < FX57_MAX_DELAY_SAMPLES; ++i)
            fDelayBuffer[i] = 0.0f;
    }

protected:
    const char* getLabel()       const override { return "FX57 Hard Rock Distortion"; }
    const char* getDescription() const override { return "Aggressive distortion with a built-in BBD-style short delay doubler, modeled on the DOD FX57 Hard Rock Distortion."; }
    const char* getMaker()       const override { return "DOD"; }
    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('F', 'x', '5', '7'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        switch (index) {
        case kDistortion:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Distortion";
            parameter.symbol     = "distortion";
            parameter.unit       = "";
            parameter.ranges.def = 0.5f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kDelay:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Delay";
            parameter.symbol     = "delay";
            parameter.unit       = "";
            parameter.ranges.def = 0.3f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kTone:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Bass_Presence";
            parameter.symbol     = "tone";
            parameter.unit       = "";
            parameter.ranges.def = 0.5f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        case kLevel:
            parameter.hints      = kParameterIsAutomatable;
            parameter.name       = "Level";
            parameter.symbol     = "level";
            parameter.unit       = "";
            parameter.ranges.def = 0.7f;
            parameter.ranges.min = 0.0f;
            parameter.ranges.max = 1.0f;
            break;
        default:
            break;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index) {
        case kDistortion: return fDistortion;
        case kDelay:       return fDelay;
        case kTone:        return fTone;
        case kLevel:       return fLevel;
        default:           return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index) {
        case kDistortion: fDistortion = value; break;
        case kDelay:       fDelay       = value; break;
        case kTone:        fTone        = value; break;
        case kLevel:       fLevel       = value; break;
        default: break;
        }
    }

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

        const double sr    = getSampleRate();
        const double twoPi = 6.283185307179586;

        // Input DC-blocking highpass, ~35 Hz, mirrors C1/R ahead of U1A.
        const float hpA = (float)std::exp(-twoPi * 35.0 / sr);

        // Distortion knob (P4) sets pre-gain into the diode clipper
        // (D1-D4), pushed harder than the FX53/FX54 for the "hard rock"
        // edge, then a tight extra clamp.
        const float driveGain  = 1.0f + fDistortion * 59.0f;
        const float distMakeup = 1.0f / (1.0f + fDistortion * 2.0f);

        // Delay knob (P1) sets the BBD clock rate, i.e. the short
        // doubling delay time, here mapped 3 ms..40 ms.
        const double delayTimeSec  = 0.003 + (double)fDelay * 0.037;
        float        delaySamples  = (float)(delayTimeSec * sr);
        if (delaySamples > (float)(FX57_MAX_DELAY_SAMPLES - 4))
            delaySamples = (float)(FX57_MAX_DELAY_SAMPLES - 4);

        // BBD chips band-limit what passes through them; a fixed
        // lowpass on the wet tap stands in for that warmth/darkening.
        const float bbdLpA = (float)std::exp(-twoPi * 3500.0 / sr);

        // Fixed wet/dry blend, matching the fixed resistor summing
        // network the real circuit uses (not panel-adjustable).
        const float wetMix = 0.35f;

        // Bass/Presence knob (P3): blend of a dark lowpass branch and a
        // bright highpass branch, after the delay return (U2B stage).
        const float toneLpA = (float)std::exp(-twoPi * 300.0  / sr);
        const float toneHpA = (float)std::exp(-twoPi * 2500.0 / sr);

        const float level = fLevel * fLevel * 2.0f;

        float hpPrevIn   = fHpPrevIn;
        float hpPrevOut  = fHpPrevOut;
        float bbdLp      = fBbdLpState;
        float toneLp     = fToneLpState;
        float toneHpLp   = fToneHpLpState;
        uint32_t writeIdx = fWriteIndex;

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

            // 1) DC-blocking input highpass
            const float hp = (x - hpPrevIn) + hpA * hpPrevOut;
            hpPrevIn  = x;
            hpPrevOut = hp;

            // 2) Distortion: driven soft clip plus a tight extra clamp
            const float driven = hp * driveGain;
            float shaped = std::tanh(driven);
            if (shaped >  0.85f) shaped =  0.85f + (shaped - 0.85f) * 0.05f;
            if (shaped < -0.85f) shaped = -0.85f + (shaped + 0.85f) * 0.05f;
            shaped *= distMakeup;

            // 3) BBD-style short delay doubler
            fDelayBuffer[writeIdx] = shaped;

            float readPos = (float)writeIdx - delaySamples;
            if (readPos < 0.0f) readPos += (float)FX57_MAX_DELAY_SAMPLES;
            const uint32_t idx0 = ((uint32_t)readPos) & (FX57_MAX_DELAY_SAMPLES - 1u);
            const uint32_t idx1 = (idx0 + 1u) & (FX57_MAX_DELAY_SAMPLES - 1u);
            const float frac = readPos - (float)((uint32_t)readPos);
            const float delayed = fDelayBuffer[idx0] + frac * (fDelayBuffer[idx1] - fDelayBuffer[idx0]);

            bbdLp = bbdLpA * bbdLp + (1.0f - bbdLpA) * delayed;

            const float mixed = shaped * (1.0f - wetMix) + bbdLp * wetMix;

            writeIdx = (writeIdx + 1u) & (FX57_MAX_DELAY_SAMPLES - 1u);

            // 4) Bass/Presence tone control
            toneLp   = toneLpA * toneLp   + (1.0f - toneLpA) * mixed;
            toneHpLp = toneHpA * toneHpLp + (1.0f - toneHpA) * mixed;
            const float toneHp = mixed - toneHpLp;
            const float toned  = toneLp * (1.0f - fTone) + toneHp * fTone;

            // 5) Output level
            out[i] = toned * level;
        }

        fHpPrevIn      = hpPrevIn;
        fHpPrevOut     = hpPrevOut;
        fBbdLpState    = bbdLp;
        fToneLpState   = toneLp;
        fToneHpLpState = toneHpLp;
        fWriteIndex    = writeIdx;
    }

private:
    float fDistortion, fDelay, fTone, fLevel;
    float fHpPrevIn, fHpPrevOut;
    float fBbdLpState;
    float fToneLpState, fToneHpLpState;
    float fDelayBuffer[FX57_MAX_DELAY_SAMPLES];
    uint32_t fWriteIndex;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Fx57Plugin)
};

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

END_NAMESPACE_DISTRHO
endef

define FX57_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND       "DOD"
#define DISTRHO_PLUGIN_NAME        "FX57 Hard Rock Distortion"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:fx57"

#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 {
    kDistortion = 0,
    kDelay,
    kTone,
    kLevel,
    kParameterCount
};

#endif
endef

define FX57_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME = fx57
FILES_DSP = Fx57Plugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

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

<urn:mod-cookbook:fx57>
    a lv2:Plugin , lv2:DistortionPlugin , lv2:DelayPlugin ;
    lv2:binary <fx57_dsp.so> ;
    rdfs:seeAlso <fx57.ttl> .
endef

define FX57_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#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .

<urn:mod-cookbook:fx57>
    a lv2:Plugin , lv2:DistortionPlugin , lv2:DelayPlugin ;
    doap:name "FX57 Hard Rock Distortion" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "DOD" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Aggressive distortion with a built-in BBD-style short delay doubler, bass/presence tone, and level, modeled on the DOD FX57 Hard Rock Distortion." ;
    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 "distortion" ;
        lv2:name "Distortion" ;
        lv2:default 0.5 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ;
        lv2:symbol "delay" ;
        lv2:name "Delay" ;
        lv2:default 0.3 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ;
        lv2:symbol "tone" ;
        lv2:name "Bass/Presence" ;
        lv2:default 0.5 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ;
        lv2:symbol "level" ;
        lv2:name "Level" ;
        lv2:default 0.7 ;
        lv2:minimum 0.0 ;
        lv2:maximum 1.0
    ] .
endef

export FX57_PLUGIN_CPP
export FX57_PLUGIN_INFO_H
export FX57_PLUGIN_MAKEFILE
export FX57_MANIFEST_TTL
export FX57_PLUGIN_TTL

define FX57_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/fx57
	printf '%s' "$$FX57_PLUGIN_CPP"      > $(@D)/examples/fx57/Fx57Plugin.cpp
	printf '%s' "$$FX57_PLUGIN_INFO_H"   > $(@D)/examples/fx57/DistrhoPluginInfo.h
	printf '%s' "$$FX57_PLUGIN_MAKEFILE" > $(@D)/examples/fx57/Makefile
endef

define FX57_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/fx57 lv2_dsp
endef

define FX57_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/fx57.lv2
	cp $(@D)/bin/fx57.lv2/fx57_dsp.so $($(PKG)_PKGDIR)/fx57.lv2/
	printf '%s' "$$FX57_MANIFEST_TTL" > $($(PKG)_PKGDIR)/fx57.lv2/manifest.ttl
	printf '%s' "$$FX57_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/fx57.lv2/fx57.ttl
endef

$(eval $(generic-package))
