# Eventide H90 Rotary - Rotating Speaker (Leslie) Simulation, Standard and Giant
# Incorporating full H90 manual specifications & enumerated dropdown controls.
# Upload at https://builder.mod.audio/buildroot

H90_ROTARY_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
H90_ROTARY_SITE = https://github.com/DISTRHO/DPF.git
H90_ROTARY_SITE_METHOD = git
H90_ROTARY_BUNDLES = h90-rotary.lv2

# ---------------------------------------------------------------------------
# DistrhoPluginInfo.h
# ---------------------------------------------------------------------------
define H90_ROTARY_PLUGIN_INFO_H
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_BRAND        "Eventide"
#define DISTRHO_PLUGIN_NAME         "H90 Rotary"
#define DISTRHO_PLUGIN_URI          "urn:mod-cookbook:h90-rotary"
#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 {
    kMix = 0,
    kType,
    kRotorSpd,
    kHornSpd,
    kRtrHrnMix,
    kDrive,
    kSpeedMod,
    kModRate,
    kModSource,
    kHighCut,
    kSpeedBrake,
    kFastSlow,
    kParameterCount
};

#endif
endef

# ---------------------------------------------------------------------------
# H90RotaryPlugin.cpp
# ---------------------------------------------------------------------------
define H90_ROTARY_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>

START_NAMESPACE_DISTRHO

static const float kPi    = 3.14159265359f;
static const float kTwoPi = 6.28318530718f;

// LFO with 8 standard mod-matrix shapes, independent L/R random state
struct LFO {
    float phase, sah, sahPrev;
    float sahR, sahPrevR;

    LFO() : phase(0.f), sah(0.f), sahPrev(0.f), sahR(0.f), sahPrevR(0.f) {}

    void reset() {
        phase = 0.f;
        sah = 0.f; sahPrev = 0.f;
        sahR = 0.f; sahPrevR = 0.f;
    }

    static float shapeAt(float p, int shape, float sahVal, float sahPrevVal) {
        switch (shape) {
        case 0: return std::sin(p * kTwoPi);
        case 1: return (p < 0.5f) ? (4.f * p - 1.f) : (3.f - 4.f * p);
        case 2: return (p < 0.5f) ? 1.f : -1.f;
        case 3: return 2.f * p - 1.f;
        case 4: return 1.f - 2.f * p;
        case 5: return sahVal;
        case 6: return sahPrevVal + (sahVal - sahPrevVal) * p;
        case 7: { float c = std::cos(p * kTwoPi); return (c > 0.f) ? (c*c*c) : (c*0.25f); }
        default: return std::sin(p * kTwoPi);
        }
    }

    float tick(float rate, float sr, int shape, float phaseOffsetR, float& outR) {
        float outL = shapeAt(phase, shape, sah, sahPrev);

        float pR = phase + phaseOffsetR;
        if (pR >= 1.f) pR -= 1.f;
        if (pR < 0.f)  pR += 1.f;
        outR = shapeAt(pR, shape, sahR, sahPrevR);

        float prevPhase = phase;
        phase += rate / sr;
        if (phase >= 1.f) {
            phase -= 1.f;
            sahPrev = sah;
            sah = ((float)std::rand() / (float)RAND_MAX) * 2.f - 1.f;
        }

        float prevPR = prevPhase + phaseOffsetR;
        if (prevPR >= 1.f) prevPR -= 1.f;
        bool wrappedR = (pR < prevPR);
        if (wrappedR) {
            sahPrevR = sahR;
            sahR = ((float)std::rand() / (float)RAND_MAX) * 2.f - 1.f;
        }

        return outL;
    }
};

// Heap delay line with linear interpolation (for rotor/horn Doppler modulation)
struct DelayLine {
    float* buf;
    int mask;
    int wpos;

    DelayLine() : buf(nullptr), mask(0), wpos(0) {}
    ~DelayLine() { delete[] buf; }

    void init(int n) {
        int sz = 1;
        while (sz < n) sz <<= 1;
        delete[] buf;
        buf = new float[sz]();
        mask = sz - 1;
        wpos = 0;
    }

    void write(float v) {
        buf[wpos & mask] = v;
        ++wpos;
    }

    float read(float d) const {
        if (d < 2.0f) d = 2.0f;
        float rd = (float)wpos - d - 1.f;
        int ri = (int)std::floor(rd);
        float frac = rd - (float)ri;
        float y0 = buf[(ri - 1) & mask];
        float y1 = buf[ri & mask];
        float y2 = buf[(ri + 1) & mask];
        float y3 = buf[(ri + 2) & mask];
        float a0 = y3 - y2 - y0 + y1;
        float a1 = y0 - y1 - a0;
        float a2 = y2 - y0;
        float a3 = y1;
        return ((a0 * frac + a1) * frac + a2) * frac + a3;
    }
};

// One-pole low-pass filter
struct OnePoleLP {
    float z1;
    OnePoleLP() : z1(0.f) {}
    void reset() { z1 = 0.f; }
    float process(float in, float c) {
        z1 = (1.f - c) * in + c * z1;
        return z1;
    }
};

// Crossover: one-pole LP + complementary HP for rotor/horn band split
struct Crossover {
    float z1;
    Crossover() : z1(0.f) {}
    void reset() { z1 = 0.f; }

    void process(float in, float freq, float sr, float& outLow, float& outHigh) {
        float c = std::exp(-kTwoPi * freq / sr);
        float lp = (1.f - c) * in + c * z1;
        z1 = lp;
        outLow  = lp;
        outHigh = in - lp;
    }
};

class H90RotaryPlugin : public Plugin
{
public:
    H90RotaryPlugin()
        : Plugin(kParameterCount, 0, 0),
          fMix(100.f), fType(0.f), fRotorSpd(0.8f), fHornSpd(6.0f),
          fRtrHrnMix(50.f), fDrive(0.f), fSpeedMod(0.f),
          fModRate(0.3f), fModSource(0.f), fHighCut(0.f),
          fSpeedBrake(0.f), fFastSlow(0.f),
          fLastSpeedBrake(0.f), fBrakeFactor(1.f), fSpeedBrakeTimer(0.f),
          fIsFastToggle(true), fSr(48000.f)
    {
        fRotorDelayL.init(4096);
        fRotorDelayR.init(4096);
        fHornDelayL.init(4096);
        fHornDelayR.init(4096);
    }

protected:
    const char* getLabel()       const override { return "H90 Rotary"; }
    const char* getDescription() const override { return "Rotating speaker (Leslie) simulation with independent rotor and horn. Eventide H90 Rotary emulation."; }
    const char* getMaker()       const override { return "Eventide"; }
    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('H','9','R','t'); }

    void initParameter(uint32_t index, Parameter& p) override {
        p.hints = kParameterIsAutomatable;
        switch (index) {
        case kMix:
            p.name = "Mix"; p.symbol = "mix"; p.unit = "%";
            p.ranges.def = 100.f; p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kType:
            p.name = "Type"; p.symbol = "type";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 1.f;
            p.hints |= kParameterIsInteger; break;
        case kRotorSpd:
            p.name = "Rotor Spd"; p.symbol = "rotor_spd"; p.unit = "Hz";
            p.ranges.def = 0.8f; p.ranges.min = 0.1f; p.ranges.max = 20.f; break;
        case kHornSpd:
            p.name = "Horn Spd"; p.symbol = "horn_spd"; p.unit = "Hz";
            p.ranges.def = 6.0f; p.ranges.min = 0.1f; p.ranges.max = 20.f; break;
        case kRtrHrnMix:
            p.name = "Rtr/Hrn Mix"; p.symbol = "rtr_hrn_mix"; p.unit = "%";
            p.ranges.def = 50.f; p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kDrive:
            p.name = "Drive"; p.symbol = "drive"; p.unit = "%";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kSpeedMod:
            p.name = "Speed Mod"; p.symbol = "speed_mod"; p.unit = "%";
            p.ranges.def = 0.f; p.ranges.min = -100.f; p.ranges.max = 100.f; break;
        case kModRate:
            p.name = "Mod Rate"; p.symbol = "mod_rate"; p.unit = "Hz";
            p.ranges.def = 0.3f; p.ranges.min = 0.01f; p.ranges.max = 10.f; break;
        case kModSource:
            p.name = "Mod Source"; p.symbol = "mod_source";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 7.f;
            p.hints |= kParameterIsInteger; break;
        case kHighCut:
            p.name = "High Cut"; p.symbol = "high_cut"; p.unit = "%";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 100.f; break;
        case kSpeedBrake:
            p.name = "Speed / Brake Switch"; p.symbol = "speed_brake";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 1.f;
            p.hints |= kParameterIsBoolean; break;
        case kFastSlow:
            p.name = "Fast / Slow Toggle"; p.symbol = "fast_slow";
            p.ranges.def = 0.f; p.ranges.min = 0.f; p.ranges.max = 1.f;
            p.hints |= kParameterIsBoolean; break;
        }
    }

    float getParameterValue(uint32_t index) const override {
        switch (index) {
        case kMix:       return fMix;
        case kType:      return fType;
        case kRotorSpd:  return fRotorSpd;
        case kHornSpd:   return fHornSpd;
        case kRtrHrnMix: return fRtrHrnMix;
        case kDrive:     return fDrive;
        case kSpeedMod:  return fSpeedMod;
        case kModRate:   return fModRate;
        case kModSource: return fModSource;
        case kHighCut:   return fHighCut;
        case kSpeedBrake:return fSpeedBrake;
        case kFastSlow:  return fFastSlow;
        }
        return 0.f;
    }

    void setParameterValue(uint32_t index, float value) override {
        switch (index) {
        case kMix:       fMix = value; break;
        case kType:      fType = value; break;
        case kRotorSpd:  fRotorSpd = value; break;
        case kHornSpd:   fHornSpd = value; break;
        case kRtrHrnMix: fRtrHrnMix = value; break;
        case kDrive:     fDrive = value; break;
        case kSpeedMod:  fSpeedMod = value; break;
        case kModRate:   fModRate = value; break;
        case kModSource: fModSource = value; break;
        case kHighCut:   fHighCut = value; break;
        case kSpeedBrake:fSpeedBrake = value; break;
        case kFastSlow:  fFastSlow = value; break;
        }
    }

    void sampleRateChanged(double newSampleRate) override {
        fSr = (float)newSampleRate;
        fRotorLFO.reset();
        fHornLFO.reset();
        fModLFO.reset();
        fCrossoverL.reset();
        fCrossoverR.reset();
        fHighCutL.reset();
        fHighCutR.reset();
    }

    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];

        // Read real-time switch parameter registers
        float speedBrakeIn = getParameterValue(kSpeedBrake);
        float fastSlowIn   = getParameterValue(kFastSlow);

        // Speed/Brake Switch Evaluator Layout
        bool speedBrakePressed = (speedBrakeIn > 0.5f);
        if (speedBrakePressed) {
            fSpeedBrakeTimer += (float)frames / fSr;
            if (fSpeedBrakeTimer >= 0.45f) {
                // Smooth deceleration loop window while button is active
                fBrakeFactor = std::max(0.001f, fBrakeFactor - (4.f * (float)frames / fSr));
            }
        } else {
            if (fLastSpeedBrake > 0.5f && fSpeedBrakeTimer < 0.45f) {
                fIsFastToggle = !fIsFastToggle;
            }
            fSpeedBrakeTimer = 0.f;
            // Linear release ramp up velocity recovery
            fBrakeFactor = std::min(1.f, fBrakeFactor + (3.f * (float)frames / fSr));
        }
        fLastSpeedBrake = speedBrakeIn;

        // Global Speed Modulation Scales Downscale Strategy
        float globalSpeedFactor = (fIsFastToggle && (fastSlowIn <= 0.5f)) ? 1.0f : 0.25f;

        float mix = fMix * 0.01f;
        bool giant = fType > 0.5f;
        float rtrHrnMix = fRtrHrnMix * 0.01f;
        float driveAmt = fDrive * 0.01f;
        float speedModAmt = fSpeedMod * 0.01f;
        int modShape = (int)fModSource;

        float highCutFreq = 18000.f - fHighCut * 0.01f * 14000.f;
        float highCutC = std::exp(-kTwoPi * highCutFreq / fSr);

        float modLfoDummy;
        float modVal = fModLFO.tick(fModRate * globalSpeedFactor * fBrakeFactor, fSr / (float)frames, modShape, 0.f, modLfoDummy);

        float rotorSpd = fRotorSpd * (1.f + modVal * speedModAmt);
        float hornSpd  = fHornSpd  * (1.f + modVal * speedModAmt);
        
        // Scale total dynamic calculations by the evaluated global constraints
        rotorSpd = std::max(0.05f, std::min(rotorSpd, 40.f)) * globalSpeedFactor * fBrakeFactor;
        hornSpd  = std::max(0.05f, std::min(hornSpd,  40.f)) * globalSpeedFactor * fBrakeFactor;

        float xoverFreq = giant ? 500.f : 800.f;

        float rotorSwing = (giant ? 0.0030f : 0.0018f) * fSr;
        float hornSwing  = (giant ? 0.0018f : 0.0012f) * fSr;
        float rotorSpeedFactor = 0.5f + 0.5f * std::min(rotorSpd / 6.0f, 1.0f);
        float hornSpeedFactor  = 0.5f + 0.5f * std::min(hornSpd  / 6.0f, 1.0f);
        rotorSwing *= rotorSpeedFactor;
        hornSwing  *= hornSpeedFactor;
        float rotorCenter = rotorSwing + 2.f;
        float hornCenter  = hornSwing  + 2.f;

        float rotorAMDepth = 0.30f;
        float hornAMDepth  = 0.22f;

        for (uint32_t i = 0; i < frames; ++i) {
            float l = inL[i];
            float r = inR[i];

            if (driveAmt > 0.001f) {
                float d = 1.f + driveAmt * 8.f;
                l = std::tanh(l * d) / std::tanh(d);
                r = std::tanh(r * d) / std::tanh(d);
            }

            float rotorInL, hornInL, rotorInR, hornInR;
            fCrossoverL.process(l, xoverFreq, fSr, rotorInL, hornInL);
            fCrossoverR.process(r, xoverFreq, fSr, rotorInR, hornInR);

            float rotorLFOR;
            float rotorLFOL = fRotorLFO.tick(rotorSpd, fSr, 0, 0.25f, rotorLFOR);

            float rotorDelL = rotorCenter + rotorSwing * rotorLFOL;
            float rotorDelR = rotorCenter + rotorSwing * rotorLFOR;
            if (rotorDelL < 0.5f) rotorDelL = 0.5f;
            if (rotorDelR < 0.5f) rotorDelR = 0.5f;

            float rotorAML = 1.f - rotorAMDepth * (rotorLFOL * 0.5f + 0.5f);
            float rotorAMR = 1.f - rotorAMDepth * (rotorLFOR * 0.5f + 0.5f);

            fRotorDelayL.write(rotorInL);
            fRotorDelayR.write(rotorInR);
            float rotorOutL = fRotorDelayL.read(rotorDelL) * rotorAML;
            float rotorOutR = fRotorDelayR.read(rotorDelR) * rotorAMR;

            float hornLFOR;
            float hornLFOL = fHornLFO.tick(hornSpd, fSr, 0, 0.25f, hornLFOR);

            float hornDelL = hornCenter + hornSwing * hornLFOL;
            float hornDelR = hornCenter + hornSwing * hornLFOR;
            if (hornDelL < 0.5f) hornDelL = 0.5f;
            if (hornDelR < 0.5f) hornDelR = 0.5f;

            float hornAML = 1.f - hornAMDepth * (hornLFOL * 0.5f + 0.5f);
            float hornAMR = 1.f - hornAMDepth * (hornLFOR * 0.5f + 0.5f);

            fHornDelayL.write(hornInL);
            fHornDelayR.write(hornInR);
            float hornOutL = fHornDelayL.read(hornDelL) * hornAML;
            float hornOutR = fHornDelayR.read(hornDelR) * hornAMR;

            float wetL = rotorOutL * (1.f - rtrHrnMix) + hornOutL * rtrHrnMix;
            float wetR = rotorOutR * (1.f - rtrHrnMix) + hornOutR * rtrHrnMix;

            if (fHighCut > 0.5f) {
                wetL = fHighCutL.process(wetL, highCutC);
                wetR = fHighCutR.process(wetR, highCutC);
            }

            outL[i] = l * (1.f - mix) + wetL * mix;
            outR[i] = r * (1.f - mix) + wetR * mix;
        }
    }

private:
    float fMix, fType, fRotorSpd, fHornSpd, fRtrHrnMix;
    float fDrive, fSpeedMod, fModRate, fModSource, fHighCut;
    
    float fSpeedBrake, fFastSlow;
    float fLastSpeedBrake, fBrakeFactor, fSpeedBrakeTimer;
    bool fIsFastToggle;
    
    float fSr;

    LFO fRotorLFO;
    LFO fHornLFO;
    LFO fModLFO;

    DelayLine fRotorDelayL, fRotorDelayR;
    DelayLine fHornDelayL,  fHornDelayR;
    Crossover fCrossoverL,  fCrossoverR;
    OnePoleLP fHighCutL,    fHighCutR;

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(H90RotaryPlugin)
};

Plugin* createPlugin() { return new H90RotaryPlugin(); }

END_NAMESPACE_DISTRHO
endef

# ---------------------------------------------------------------------------
# Makefile
# ---------------------------------------------------------------------------
define H90_ROTARY_PLUGIN_MAKEFILE
#!/usr/bin/make -f
NAME      = h90-rotary
FILES_DSP = H90RotaryPlugin.cpp
include ../../Makefile.plugins.mk
TARGETS = lv2_dsp
all: $(TARGETS)
endef

# ---------------------------------------------------------------------------
# LV2 TTL Manifest
# ---------------------------------------------------------------------------
define H90_ROTARY_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<urn:mod-cookbook:h90-rotary>
    a lv2:Plugin ;
    lv2:binary <h90-rotary_dsp.so> ;
    rdfs:seeAlso <h90-rotary.ttl> .
endef

# ---------------------------------------------------------------------------
# LV2 TTL Plugin Configuration with Custom Enumerations Dropdowns
# ---------------------------------------------------------------------------
define H90_ROTARY_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#> .
@prefix mod:   <http://moddevices.com/ns/mod#> .

<urn:mod-cookbook:h90-rotary>
    a lv2:Plugin , lv2:ModulatorPlugin ;
    doap:name "H90 Rotary" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [ foaf:name "Eventide" ] ;
    rdfs:comment "Rotating speaker (Leslie) simulation with independent rotor and horn. Eventide H90 Rotary emulation." ;

    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 "mix" ; lv2:name "Mix" ;
        lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ; lv2:symbol "type" ; lv2:name "Type" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Standard" ; rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Giant" ;    rdf:value 1 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ; lv2:symbol "rotor_spd" ; lv2:name "Rotor Spd" ;
        lv2:default 0.8 ; lv2:minimum 0.1 ; lv2:maximum 20.0 ;
        units:unit units:hz
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ; lv2:symbol "horn_spd" ; lv2:name "Horn Spd" ;
        lv2:default 6.0 ; lv2:minimum 0.1 ; lv2:maximum 20.0 ;
        units:unit units:hz
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ; lv2:symbol "rtr_hrn_mix" ; lv2:name "Rtr/Hrn Mix" ;
        lv2:default 50.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ; lv2:symbol "drive" ; lv2:name "Drive" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 10 ; lv2:symbol "speed_mod" ; lv2:name "Speed Mod" ;
        lv2:default 0.0 ; lv2:minimum -100.0 ; lv2:maximum 100.0 ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 11 ; lv2:symbol "mod_rate" ; lv2:name "Mod Rate" ;
        lv2:default 0.3 ; lv2:minimum 0.01 ; lv2:maximum 10.0 ;
        units:unit units:hz
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 12 ; lv2:symbol "mod_source" ; lv2:name "Mod Source" ;
        lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 7 ;
        lv2:portProperty lv2:integer , lv2:enumeration ;
        lv2:scalePoint [ rdfs:label "Sine" ;          rdf:value 0 ] ;
        lv2:scalePoint [ rdfs:label "Triangle" ;      rdf:value 1 ] ;
        lv2:scalePoint [ rdfs:label "Square" ;        rdf:value 2 ] ;
        lv2:scalePoint [ rdfs:label "Ramp Up" ;       rdf:value 3 ] ;
        lv2:scalePoint [ rdfs:label "Ramp Down" ;     rdf:value 4 ] ;
        lv2:scalePoint [ rdfs:label "S&H" ;           rdf:value 5 ] ;
        lv2:scalePoint [ rdfs:label "Smooth Random" ; rdf:value 6 ] ;
        lv2:scalePoint [ rdfs:label "Peak" ;          rdf:value 7 ] ;
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 13 ; lv2:symbol "high_cut" ; lv2:name "High Cut" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ;
        units:unit units:pc
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 14 ; lv2:symbol "speed_brake" ; lv2:name "Speed / Brake Multi" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ;
        lv2:portProperty lv2:toggled ;
        mod:templates mod:Footswitch ;
     ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 15 ; lv2:symbol "fast_slow" ; lv2:name "Fast / Slow Factor" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ;
        lv2:portProperty lv2:toggled ;
        mod:templates mod:Footswitch ;
    ] .
endef

export H90_ROTARY_PLUGIN_CPP H90_ROTARY_PLUGIN_INFO_H H90_ROTARY_PLUGIN_MAKEFILE
export H90_ROTARY_MANIFEST_TTL H90_ROTARY_PLUGIN_TTL

# ---------------------------------------------------------------------------
# Buildroot Platform Compilation Commands
# ---------------------------------------------------------------------------
define H90_ROTARY_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/h90-rotary
	printf '%s' "$$H90_ROTARY_PLUGIN_CPP"      > $(@D)/examples/h90-rotary/H90RotaryPlugin.cpp
	printf '%s' "$$H90_ROTARY_PLUGIN_INFO_H"   > $(@D)/examples/h90-rotary/DistrhoPluginInfo.h
	printf '%s' "$$H90_ROTARY_PLUGIN_MAKEFILE" > $(@D)/examples/h90-rotary/Makefile
endef

define H90_ROTARY_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) NOOPT=true -C $(@D)/examples/h90-rotary lv2_dsp
endef

define H90_ROTARY_INSTALL_TARGET_CMDS
	mkdir -p $(TARGET_DIR)/usr/lib/lv2/h90-rotary.lv2
	cp $(@D)/bin/h90-rotary.lv2/h90-rotary_dsp.so $(TARGET_DIR)/usr/lib/lv2/h90-rotary.lv2/
	printf '%s' "$$H90_ROTARY_MANIFEST_TTL" > $(TARGET_DIR)/usr/lib/lv2/h90-rotary.lv2/manifest.ttl
	printf '%s' "$$H90_ROTARY_PLUGIN_TTL" > $(TARGET_DIR)/usr/lib/lv2/h90-rotary.lv2/h90-rotary.ttl
endef

$(eval $(generic-package))