# MXR Bass Octave Deluxe Emulation (M288 Spec - Mid+ & Parameter Fix)
# Accurate to the pedal with Mid+, Dry, Growl, Girth controls
# Upload at https://builder.mod.audio/buildroot

MXR_BASS_OCTAVE_DELUXE_VERSION = develop
MXR_BASS_OCTAVE_DELUXE_SITE = https://github.com/DISTRHO/DPF.git
MXR_BASS_OCTAVE_DELUXE_SITE_METHOD = git
MXR_BASS_OCTAVE_DELUXE_BUNDLES = mxr-bass-octave-deluxe.lv2

# ---------------------------------------------------------------------------
# DistrhoPluginInfo.h
# ---------------------------------------------------------------------------
define MXR_BASS_OCTAVE_DELUXE_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND        "MXR"
#define DISTRHO_PLUGIN_NAME         "Bass Octave Deluxe"
#define DISTRHO_PLUGIN_URI          "urn:mod-cookbook:mxr-bass-octave-deluxe"
#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 {
    kMidPlus = 0,
    kMidFreqMode,
    kDry,
    kGrowl,
    kGirth,
    kVolume,
    kParameterCount
};

#endif
endef

# ---------------------------------------------------------------------------
# MXRBassOctaveDeluxePlugin.cpp
# ---------------------------------------------------------------------------
define MXR_BASS_OCTAVE_DELUXE_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <algorithm>

START_NAMESPACE_DISTRHO

struct Biquad {
    float b0, b1, b2, a1, a2;
    float x1, x2, y1, y2;

    Biquad() { reset(); }
    void reset() { x1 = x2 = y1 = y2 = 0.f; }

    void setPeaking(float freq, float samplerate, float gainDB, float Q) {
        float A = std::pow(10.f, gainDB / 40.f);
        float omega = 2.f * 3.14159265359f * freq / samplerate;
        float alpha = std::sin(omega) / (2.f * Q);
        b0 = 1.f + alpha * A;
        b1 = -2.f * std::cos(omega);
        b2 = 1.f - alpha * A;
        float a0 = 1.f + alpha / A;
        a1 = -2.f * std::cos(omega);
        a2 = 1.f - alpha / A;

        b0 /= a0; b1 /= a0; b2 /= a0; a1 /= a0; a2 /= a0;
    }

    void setLowPass(float freq, float samplerate, float Q) {
        float omega = 2.f * 3.14159265359f * freq / samplerate;
        float alpha = std::sin(omega) / (2.f * Q);
        b1 = 1.f - std::cos(omega);
        b0 = b1 * 0.5f;
        b2 = b0;
        float a0 = 1.f + alpha;
        a1 = -2.f * std::cos(omega);
        a2 = 1.f - alpha;

        b0 /= a0; b1 /= a0; b2 /= a0; a1 /= a0; a2 /= a0;
    }

    inline float process(float in) {
        float out = b0 * in + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
        x2 = x1; x1 = in;
        y2 = y1; y1 = out;
        return out;
    }
};

class MXRBassOctaveDeluxePlugin : public Plugin
{
public:
    MXRBassOctaveDeluxePlugin()
        : Plugin(kParameterCount, 0, 0),
          fMidPlus(0.f), fMidFreqMode(0.f), fDry(100.f), fGrowl(50.f), fGirth(70.f), fVolume(80.f),
          fSr(48000.f), fFlipFlop(false), fLastState(0), fTrackLP(0.f), fTrackEnv(0.f)
    {
        updateFilters();
    }

protected:
    const char* getLabel()       const override { return "MXR Bass Octave Deluxe"; }
    const char* getDescription() const override { return "Dual-voice analog-style tracking bass octaver with Mid+ boost."; }
    const char* getMaker()       const override { return "MXR"; }
    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,6,0); }
    int64_t     getUniqueId()    const override { return d_cconst('M','X','B','O'); }

    void initParameter(uint32_t index, Parameter& p) override {
        p.hints = kParameterIsAutomatable;
        switch (index) {
        case kMidPlus: 
            p.name = "Mid+ Boost"; p.symbol = "midplus"; p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kMidFreqMode:
            p.hints |= kParameterIsInteger;
            p.name = "Mid Freq (400/850)"; p.symbol = "midfreqmode"; p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 1.f; break;
        case kDry:     
            p.name = "Dry Level";  p.symbol = "dry";     p.ranges.def = 100.f; p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kGrowl:   
            p.name = "Growl Mix";  p.symbol = "growl";   p.ranges.def = 50.f;  p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kGirth:   
            p.name = "Girth Mix";  p.symbol = "girth";   p.ranges.def = 70.f;  p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kVolume:  
            p.name = "Master Vol"; p.symbol = "volume";  p.ranges.def = 80.f;  p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        }
    }

    float getParameterValue(uint32_t index) const override {
        switch (index) {
        case kMidPlus:     return fMidPlus;
        case kMidFreqMode: return fMidFreqMode;
        case kDry:         return fDry;
        case kGrowl:       return fGrowl;
        case kGirth:       return fGirth;
        case kVolume:      return fVolume;
        }
        return 0.f;
    }

    void setParameterValue(uint32_t index, float value) override {
        switch (index) {
        case kMidPlus:     fMidPlus = value; updateFilters(); break;
        case kMidFreqMode: fMidFreqMode = value; updateFilters(); break;
        case kDry:         fDry = value; break;
        case kGrowl:       fGrowl = value; break;
        case kGirth:       fGirth = value; break;
        case kVolume:      fVolume = value; break;
        }
    }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        if (fSr <= 0.f) fSr = 48000.f;
        fMidEQ_L.reset(); fMidEQ_R.reset();
        fGirthLP_L.reset(); fGirthLP_R.reset();
        fGrowlLP_L.reset(); fGrowlLP_R.reset();
        updateFilters();
    }

    void updateFilters() {
        float targetFreq = (fMidFreqMode >= 0.5f) ? 850.f : 400.f;
        // Map 0-100% parameter linearly to a significant boost (up to +15dB)
        float midGain = (fMidPlus / 100.f) * 15.f; 
        fMidEQ_L.setPeaking(targetFreq, fSr, midGain, 1.3f);
        fMidEQ_R.setPeaking(targetFreq, fSr, midGain, 1.3f);

        fGirthLP_L.setLowPass(80.f, fSr, 0.9f);
        fGirthLP_R.setLowPass(80.f, fSr, 0.9f);

        fGrowlLP_L.setLowPass(550.f, fSr, 0.6f);
        fGrowlLP_R.setLowPass(550.f, fSr, 0.6f);
    }

    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];

        float dryAmt   = fDry / 100.f;
        float growlAmt = (fGrowl / 100.f) * 3.5f; 
        float girthAmt = (fGirth / 100.f) * 4.5f; 
        float master   = fVolume / 100.f;

        float trackLP_coef = std::exp(-2.f * 3.14159265f * 200.f / fSr);

        for (uint32_t i = 0; i < frames; ++i) {
            float monoIn = (inL[i] + inR[i]) * 0.5f;

            fTrackEnv = 0.008f * std::abs(monoIn) + 0.992f * fTrackEnv;
            fTrackLP = (1.f - trackLP_coef) * monoIn + trackLP_coef * fTrackLP;

            float hysteresisThresh = std::max(0.002f, fTrackEnv * 0.1f);
            if (fTrackLP > hysteresisThresh && fLastState != 1) {
                fLastState = 1;
                fFlipFlop = !fFlipFlop; 
            } else if (fTrackLP < -hysteresisThresh && fLastState != -1) {
                fLastState = -1;
            }

            float squareClock = fFlipFlop ? 1.0f : -1.0f;

            // Growl Channel
            float growlSigL = std::tanh((inL[i] * squareClock) * 1.8f);
            float growlSigR = std::tanh((inR[i] * squareClock) * 1.8f);
            float growlOutL = fGrowlLP_L.process(growlSigL) * growlAmt;
            float growlOutR = fGrowlLP_R.process(growlSigR) * growlAmt;

            // Girth Channel
            float subWaveL = fGirthLP_L.process(squareClock);
            float subWaveR = fGirthLP_R.process(squareClock);
            float girthOutL = subWaveL * fTrackEnv * 3.0f * girthAmt;
            float girthOutR = subWaveR * fTrackEnv * 3.0f * girthAmt;

            // Dry Path processing with parametric dynamic EQ active filter
            float dryL = fMidEQ_L.process(inL[i]) * dryAmt;
            float dryR = fMidEQ_R.process(inR[i]) * dryAmt;

            // Combine channels together
            outL[i] = (dryL + growlOutL + girthOutL) * master;
            outR[i] = (dryR + growlOutR + girthOutR) * master;
        }
    }

private:
    float fMidPlus, fMidFreqMode, fDry, fGrowl, fGirth, fVolume;
    float fSr;

    bool  fFlipFlop;
    int   fLastState;
    float fTrackLP;
    float fTrackEnv;

    Biquad fMidEQ_L, fMidEQ_R;
    Biquad fGirthLP_L, fGirthLP_R;
    Biquad fGrowlLP_L, fGrowlLP_R;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MXRBassOctaveDeluxePlugin)
};

Plugin* createPlugin() { return new MXRBassOctaveDeluxePlugin(); }

END_NAMESPACE_DISTRHO
endef

# ---------------------------------------------------------------------------
# Makefile
# ---------------------------------------------------------------------------
define MXR_BASS_OCTAVE_DELUXE_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = mxr-bass-octave-deluxe
FILES_DSP = MXRBassOctaveDeluxePlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

# ---------------------------------------------------------------------------
# LV2 TTL Manifest
# ---------------------------------------------------------------------------
define MXR_BASS_OCTAVE_DELUXE_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:mxr-bass-octave-deluxe>
    a lv2:Plugin ;
    lv2:binary <mxr-bass-octave-deluxe_dsp.so> ;
    rdfs:seeAlso <mxr-bass-octave-deluxe.ttl> .
endef

# ---------------------------------------------------------------------------
# LV2 TTL Plugin Configuration
# ---------------------------------------------------------------------------
define MXR_BASS_OCTAVE_DELUXE_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#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .

<urn:mod-cookbook:mxr-bass-octave-deluxe>
    a lv2:Plugin , lv2:PitchShifterPlugin ;
    doap:name "MXR Bass Octave Deluxe" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [ foaf:name "MXR" ] ;
    rdfs:comment "MXR Bass Octave Deluxe with Mid+, Dry, Growl, Girth" ;
    lv2:port [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 0 ; lv2:symbol "in_l" ; lv2:name "Input L"
    ] , [
        a lv2:InputPort , lv2:AudioPort ;
        lv2:index 1 ; lv2:symbol "in_r" ; lv2:name "Input R"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 2 ; lv2:symbol "out_l" ; lv2:name "Output L"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 3 ; lv2:symbol "out_r" ; lv2:name "Output R"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ; lv2:symbol "midplus" ; lv2:name "Mid+ Boost" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ; lv2:symbol "midfreqmode" ; lv2:name "Mid Internal Freq (0=400Hz, 1=850Hz)" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ; lv2:symbol "dry" ; lv2:name "Dry Level" ;
        lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ; lv2:symbol "growl" ; lv2:name "Growl Mix" ;
        lv2:default 50.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ; lv2:symbol "girth" ; lv2:name "Girth Mix" ;
        lv2:default 70.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ; lv2:symbol "volume" ; lv2:name "Master Vol" ;
        lv2:default 80.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] .
endef

export MXR_BASS_OCTAVE_DELUXE_PLUGIN_CPP MXR_BASS_OCTAVE_DELUXE_PLUGIN_INFO_H MXR_BASS_OCTAVE_DELUXE_PLUGIN_MAKEFILE
export MXR_BASS_OCTAVE_DELUXE_MANIFEST_TTL MXR_BASS_OCTAVE_DELUXE_PLUGIN_TTL

# ---------------------------------------------------------------------------
# Buildroot Platform Compilation Commands
# ---------------------------------------------------------------------------
define MXR_BASS_OCTAVE_DELUXE_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/mxr-bass-octave-deluxe
	printf '%s' "$$MXR_BASS_OCTAVE_DELUXE_PLUGIN_CPP"      > $(@D)/examples/mxr-bass-octave-deluxe/MXRBassOctaveDeluxePlugin.cpp
	printf '%s' "$$MXR_BASS_OCTAVE_DELUXE_PLUGIN_INFO_H"   > $(@D)/examples/mxr-bass-octave-deluxe/DistrhoPluginInfo.h
	printf '%s' "$$MXR_BASS_OCTAVE_DELUXE_PLUGIN_MAKEFILE" > $(@D)/examples/mxr-bass-octave-deluxe/Makefile
endef

define MXR_BASS_OCTAVE_DELUXE_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/mxr-bass-octave-deluxe lv2_dsp
endef

define MXR_BASS_OCTAVE_DELUXE_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/mxr-bass-octave-deluxe.lv2
	cp $(@D)/bin/mxr-bass-octave-deluxe.lv2/mxr-bass-octave-deluxe_dsp.so $($(PKG)_PKGDIR)/mxr-bass-octave-deluxe.lv2/
	printf '%s' "$$MXR_BASS_OCTAVE_DELUXE_MANIFEST_TTL" > $($(PKG)_PKGDIR)/mxr-bass-octave-deluxe.lv2/manifest.ttl
	printf '%s' "$$MXR_BASS_OCTAVE_DELUXE_PLUGIN_TTL" > $($(PKG)_PKGDIR)/mxr-bass-octave-deluxe.lv2/mxr-bass-octave-deluxe.ttl
endef

$(eval $(generic-package))