# Copyright (c) 2026 MOD Audio Limited
# SPDX-License-Identifier: MIT
#
# Microcosm: granular delay / glitch / reverb processor inspired by the
# Hologram Microcosm. Mono input, stereo output.
#
# Features:
#   - Granular engine: up to 8 overlapping grains with size, pitch, scatter,
#     and density controls
#   - Glitch: buffer freeze / stutter with rate control
#   - Reverb tail: Schroeder network (4 comb + 2 allpass) fed by grain output
#   - Feedback loop: granular output feeds back into the grain buffer
#   - Spray: randomises grain start position within the buffer
#   - Reverse: grains play backwards
#   - Mix: dry/wet
#
# Upload at https://builder.mod.audio/buildroot with MOD unit over USB.

MICROCOSM_VERSION = 61d38eb638449647fb8395a35c5b8dab7e981ba7
MICROCOSM_SITE = https://github.com/DISTRHO/DPF.git
MICROCOSM_SITE_METHOD = git
MICROCOSM_BUNDLES = microcosm.lv2

define MICROCOSM_PLUGIN_CPP
#include "DistrhoPlugin.hpp"
#include <cmath>
#include <cstring>
#include <cstdlib>

START_NAMESPACE_DISTRHO

// ─── Constants ───────────────────────────────────────────────────────────────

static const uint32_t GRAIN_BUF_SIZE  = 131072; // ~3 seconds at 44.1kHz
static const uint32_t GRAIN_BUF_MASK  = GRAIN_BUF_SIZE - 1;
static const uint32_t MAX_GRAINS      = 8;

// Schroeder reverb sizes (prime-ish, in samples at 44.1kHz — scaled at runtime)
static const float COMB_DELAYS_MS[4]  = { 29.7f, 37.1f, 41.1f, 43.7f };
static const float AP_DELAYS_MS[2]    = {  5.0f,  1.7f };
static const float COMB_FEEDBACK      = 0.84f;
static const float AP_FEEDBACK        = 0.7f;

static const uint32_t MAX_DELAY_SAMP  = 8192; // for comb/AP lines

// ─── Utilities ───────────────────────────────────────────────────────────────

static inline float randf() // 0..1
{
    return (float)(rand() & 0x7FFF) / (float)0x7FFF;
}

static inline float lerpBuf(const float* buf, float pos, uint32_t mask)
{
    uint32_t i0 = (uint32_t)pos & mask;
    uint32_t i1 = (i0 + 1) & mask;
    float    fr = pos - (float)(uint32_t)pos;
    return buf[i0] + fr * (buf[i1] - buf[i0]);
}

// Hanning window
static inline float hanning(float phase) // phase 0..1
{
    return 0.5f * (1.0f - cosf(phase * 6.283185f));
}

// ─── Grain ───────────────────────────────────────────────────────────────────

struct Grain
{
    bool     active;
    float    readPos;    // current read position in grain buffer
    float    startPos;   // start position in grain buffer
    uint32_t length;     // grain length in samples
    uint32_t age;        // samples elapsed
    float    pitch;      // playback rate (1.0 = normal)
    bool     reverse;    // play backwards
    float    panL, panR; // stereo pan

    Grain() : active(false), readPos(0), startPos(0), length(0),
              age(0), pitch(1.0f), reverse(false), panL(1.0f), panR(1.0f) {}

    void trigger(float startP, uint32_t len, float pitchRate, bool rev, float pan)
    {
        startPos = startP;
        readPos  = rev ? (startP + (float)len) : startP;
        length   = len;
        age      = 0;
        pitch    = pitchRate;
        reverse  = rev;
        active   = true;
        // Equal-power pan
        float angle = pan * 1.5707963f;
        panL = cosf(angle);
        panR = sinf(angle);
    }

    // Returns true while still alive
    bool process(const float* buf, uint32_t bufMask, float& outL, float& outR)
    {
        if (!active) { outL = outR = 0.0f; return false; }

        float window = hanning((float)age / (float)length);
        float sample = lerpBuf(buf, readPos, bufMask) * window;

        outL = sample * panL;
        outR = sample * panR;

        if (reverse)
            readPos -= pitch;
        else
            readPos += pitch;

        // Wrap read position
        if (readPos < 0.0f)             readPos += (float)GRAIN_BUF_SIZE;
        if (readPos >= (float)GRAIN_BUF_SIZE) readPos -= (float)GRAIN_BUF_SIZE;

        ++age;
        if (age >= length)
            active = false;

        return active;
    }
};

// ─── Simple delay line (for reverb) ──────────────────────────────────────────

struct DelayLine
{
    float    buf[MAX_DELAY_SAMP];
    uint32_t writePos;
    uint32_t length;

    DelayLine() : writePos(0), length(1) { memset(buf, 0, sizeof(buf)); }

    void setLength(uint32_t len)
    {
        length = (len < MAX_DELAY_SAMP) ? len : MAX_DELAY_SAMP - 1;
    }

    float readBack() const
    {
        uint32_t readPos = (writePos + MAX_DELAY_SAMP - length) % MAX_DELAY_SAMP;
        return buf[readPos];
    }

    void write(float v) { buf[writePos] = v; writePos = (writePos + 1) % MAX_DELAY_SAMP; }
};

// ─── Schroeder Reverb ─────────────────────────────────────────────────────────

struct SchroederReverb
{
    DelayLine comb[4];
    DelayLine ap[2];

    void init(float sampleRate)
    {
        for (int i = 0; i < 4; ++i)
            comb[i].setLength((uint32_t)(COMB_DELAYS_MS[i] * 0.001f * sampleRate));
        for (int i = 0; i < 2; ++i)
            ap[i].setLength((uint32_t)(AP_DELAYS_MS[i] * 0.001f * sampleRate));
    }

    // Returns wet reverb sample
    float process(float in, float roomSize)
    {
        float fb = COMB_FEEDBACK * (0.5f + roomSize * 0.5f);

        // 4 parallel comb filters
        float combOut = 0.0f;
        for (int i = 0; i < 4; ++i)
        {
            float delayed = comb[i].readBack();
            comb[i].write(in + delayed * fb);
            combOut += delayed;
        }
        combOut *= 0.25f;

        // 2 series allpass filters
        float sig = combOut;
        for (int i = 0; i < 2; ++i)
        {
            float delayed = ap[i].readBack();
            float v = sig + delayed * (-AP_FEEDBACK);
            ap[i].write(sig + delayed * AP_FEEDBACK);
            sig = delayed + v * AP_FEEDBACK;
        }
        return sig;
    }

    void clear()
    {
        for (int i = 0; i < 4; ++i) memset(comb[i].buf, 0, sizeof(comb[i].buf));
        for (int i = 0; i < 2; ++i) memset(ap[i].buf,  0, sizeof(ap[i].buf));
    }
};

// ─── Main Plugin ─────────────────────────────────────────────────────────────

class MicrocosmPlugin : public Plugin
{
public:
    MicrocosmPlugin()
        : Plugin(kParameterCount, 0, 0),
          fSampleRate(44100.0f),
          fMix(0.8f),
          fGrainSize(120.0f),
          fDensity(0.5f),
          fSpray(0.3f),
          fPitch(1.0f),
          fReverse(0.0f),
          fFeedback(0.4f),
          fRoomSize(0.6f),
          fReverbMix(0.4f),
          fGlitch(0.0f),
          fGlitchRate(4.0f),
          fWritePos(0),
          fFreezePos(0),
          fFrozen(false),
          fGlitchPhase(0.0f),
          fGlitchActive(false),
          fGlitchStart(0),
          fGlitchLen(0),
          fGrainTimer(0.0f),
          fNextGrainInterval(0.0f)
    {
        memset(fGrainBuf, 0, sizeof(fGrainBuf));
        srand(12345);
        computeNextGrainInterval();
    }

protected:
    const char* getLabel()       const override { return "Microcosm"; }
    const char* getDescription() const override { return "Granular delay / glitch / reverb processor inspired by Hologram Microcosm."; }
    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, 0, 0); }
    int64_t     getUniqueId()    const override { return d_cconst('M', 'C', 'S', 'M'); }

    void initParameter(uint32_t index, Parameter& parameter) override
    {
        switch (index)
        {
        case kMix:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Dry/Wet"; parameter.symbol = "mix";
            parameter.ranges.def = 0.8f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kGrainSize:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Grain Size"; parameter.symbol = "grain_size"; parameter.unit = "ms";
            parameter.ranges.def = 120.0f; parameter.ranges.min = 10.0f; parameter.ranges.max = 500.0f;
            break;
        case kDensity:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Density"; parameter.symbol = "density";
            parameter.ranges.def = 0.5f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kSpray:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Spray"; parameter.symbol = "spray";
            parameter.ranges.def = 0.3f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kPitch:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Pitch"; parameter.symbol = "pitch"; parameter.unit = "x";
            parameter.ranges.def = 1.0f; parameter.ranges.min = 0.25f; parameter.ranges.max = 2.0f;
            break;
        case kReverse:
            parameter.hints = kParameterIsAutomatable | kParameterIsBoolean;
            parameter.name = "Reverse"; parameter.symbol = "reverse";
            parameter.ranges.def = 0.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kFeedback:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Feedback"; parameter.symbol = "feedback";
            parameter.ranges.def = 0.4f; parameter.ranges.min = 0.0f; parameter.ranges.max = 0.95f;
            break;
        case kRoomSize:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Room Size"; parameter.symbol = "room_size";
            parameter.ranges.def = 0.6f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kReverbMix:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Reverb Mix"; parameter.symbol = "reverb_mix";
            parameter.ranges.def = 0.4f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kGlitch:
            parameter.hints = kParameterIsAutomatable | kParameterIsBoolean;
            parameter.name = "Glitch"; parameter.symbol = "glitch";
            parameter.ranges.def = 0.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        case kGlitchRate:
            parameter.hints = kParameterIsAutomatable;
            parameter.name = "Glitch Rate"; parameter.symbol = "glitch_rate"; parameter.unit = "Hz";
            parameter.ranges.def = 4.0f; parameter.ranges.min = 0.5f; parameter.ranges.max = 16.0f;
            break;
        case kFreeze:
            parameter.hints = kParameterIsAutomatable | kParameterIsBoolean;
            parameter.name = "Freeze"; parameter.symbol = "freeze";
            parameter.ranges.def = 0.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f;
            break;
        default: break;
        }
    }

    float getParameterValue(uint32_t index) const override
    {
        switch (index)
        {
        case kMix:        return fMix;
        case kGrainSize:  return fGrainSize;
        case kDensity:    return fDensity;
        case kSpray:      return fSpray;
        case kPitch:      return fPitch;
        case kReverse:    return fReverse;
        case kFeedback:   return fFeedback;
        case kRoomSize:   return fRoomSize;
        case kReverbMix:  return fReverbMix;
        case kGlitch:     return fGlitch;
        case kGlitchRate: return fGlitchRate;
        case kFreeze:     return fFrozen ? 1.0f : 0.0f;
        default:          return 0.0f;
        }
    }

    void setParameterValue(uint32_t index, float value) override
    {
        switch (index)
        {
        case kMix:        fMix        = value; break;
        case kGrainSize:  fGrainSize  = value; break;
        case kDensity:    fDensity    = value; break;
        case kSpray:      fSpray      = value; break;
        case kPitch:      fPitch      = value; break;
        case kReverse:    fReverse    = value; break;
        case kFeedback:   fFeedback   = value; break;
        case kRoomSize:   fRoomSize   = value; break;
        case kReverbMix:  fReverbMix  = value; break;
        case kGlitch:     fGlitch     = value; break;
        case kGlitchRate: fGlitchRate = value; break;
        case kFreeze:
            if (value >= 0.5f && !fFrozen)
            {
                fFreezePos = fWritePos; // latch current buffer position
                fFrozen = true;
            }
            else if (value < 0.5f)
            {
                fFrozen = false;
            }
            break;
        default: break;
        }
    }

    void activate() override
    {
        fSampleRate = (float)getSampleRate();
        fReverb.init(fSampleRate);
        fReverb.clear();
        memset(fGrainBuf, 0, sizeof(fGrainBuf));
        fWritePos   = 0;
        fFreezePos  = 0;
        fFrozen     = false;
        fGlitchPhase = 0.0f;
        fGlitchActive = false;
        fGrainTimer = 0.0f;
        computeNextGrainInterval();
        for (int g = 0; g < MAX_GRAINS; ++g)
            fGrains[g].active = false;
    }

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

        const float wet = fMix;
        const float dry = 1.0f - fMix;
        const bool  rev = (fReverse >= 0.5f);

        // Grain length in samples
        uint32_t grainLen = (uint32_t)(fGrainSize * 0.001f * fSampleRate);
        if (grainLen < 64)    grainLen = 64;
        if (grainLen > GRAIN_BUF_SIZE / 2) grainLen = GRAIN_BUF_SIZE / 2;

        // Spray range in samples
        uint32_t sprayRange = (uint32_t)(fSpray * (float)GRAIN_BUF_SIZE * 0.25f);

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

            // ── Glitch stutter ─────────────────────────────────────────────
            if (fGlitch >= 0.5f)
            {
                fGlitchPhase += fGlitchRate / fSampleRate;
                if (fGlitchPhase >= 1.0f)
                {
                    fGlitchPhase -= 1.0f;
                    // New stutter: random length 1/32..1/4 of grain size
                    fGlitchLen    = grainLen / 4 + (uint32_t)(randf() * grainLen * 0.75f);
                    fGlitchStart  = fWritePos;
                    fGlitchActive = true;
                }
                if (fGlitchActive)
                {
                    // Replay from stutter start instead of live input
                    uint32_t stutterPos = (fGlitchStart + (fWritePos - fGlitchStart) % fGlitchLen) & GRAIN_BUF_MASK;
                    inSample = fGrainBuf[stutterPos];
                    if ((fWritePos - fGlitchStart) >= fGlitchLen)
                        fGlitchActive = false;
                }
            }

            // ── Write to grain buffer (unless frozen) ─────────────────────
            if (!fFrozen)
            {
                fGrainBuf[fWritePos] = inSample + fGrainBuf[fWritePos] * fFeedback * 0.5f;
                fWritePos = (fWritePos + 1) & GRAIN_BUF_MASK;
            }

            // ── Grain spawning ────────────────────────────────────────────
            fGrainTimer += 1.0f;
            if (fGrainTimer >= fNextGrainInterval)
            {
                fGrainTimer = 0.0f;
                computeNextGrainInterval();
                spawnGrain(grainLen, sprayRange, rev);
            }

            // ── Process active grains ─────────────────────────────────────
            float grainL = 0.0f, grainR = 0.0f;
            int   activeCount = 0;

            for (int g = 0; g < MAX_GRAINS; ++g)
            {
                float gL, gR;
                if (fGrains[g].process(fGrainBuf, GRAIN_BUF_MASK, gL, gR))
                {
                    grainL += gL;
                    grainR += gR;
                    ++activeCount;
                }
            }

            // Normalise by active grain count to avoid clipping
            if (activeCount > 1)
            {
                float scale = 1.0f / sqrtf((float)activeCount);
                grainL *= scale;
                grainR *= scale;
            }

            // Feedback: granular output feeds back into buffer
            if (!fFrozen)
            {
                uint32_t fbPos = (fWritePos + GRAIN_BUF_SIZE - 1) & GRAIN_BUF_MASK;
                fGrainBuf[fbPos] += (grainL + grainR) * 0.5f * fFeedback;
                // Soft clip to prevent runaway
                if (fGrainBuf[fbPos] >  1.0f) fGrainBuf[fbPos] =  1.0f;
                if (fGrainBuf[fbPos] < -1.0f) fGrainBuf[fbPos] = -1.0f;
            }

            // ── Reverb ────────────────────────────────────────────────────
            float reverbIn = (grainL + grainR) * 0.5f;
            float reverbOut = fReverb.process(reverbIn, fRoomSize);

            float wetL = grainL * (1.0f - fReverbMix) + reverbOut * fReverbMix;
            float wetR = grainR * (1.0f - fReverbMix) + reverbOut * fReverbMix;

            outL[i] = dry * inSample + wet * wetL;
            outR[i] = dry * inSample + wet * wetR;
        }
    }

private:
    // ── Parameters ──────────────────────────────────────────────────────────
    float    fSampleRate;
    float    fMix;
    float    fGrainSize;
    float    fDensity;
    float    fSpray;
    float    fPitch;
    float    fReverse;
    float    fFeedback;
    float    fRoomSize;
    float    fReverbMix;
    float    fGlitch;
    float    fGlitchRate;

    // ── Grain engine ────────────────────────────────────────────────────────
    float    fGrainBuf[GRAIN_BUF_SIZE];
    Grain    fGrains[MAX_GRAINS];
    uint32_t fWritePos;
    uint32_t fFreezePos;
    bool     fFrozen;
    float    fGrainTimer;
    float    fNextGrainInterval;

    // ── Glitch ──────────────────────────────────────────────────────────────
    float    fGlitchPhase;
    bool     fGlitchActive;
    uint32_t fGlitchStart;
    uint32_t fGlitchLen;

    // ── Reverb ──────────────────────────────────────────────────────────────
    SchroederReverb fReverb;

    // ── Helpers ─────────────────────────────────────────────────────────────
    void computeNextGrainInterval()
    {
        // Density 0..1 maps to interval 2*grainSize..0.1*grainSize (overlapping)
        float grainLenSec = fGrainSize * 0.001f;
        float interval    = grainLenSec * (2.0f - fDensity * 1.9f) * fSampleRate;
        // Add small random jitter
        interval += (randf() - 0.5f) * interval * 0.2f;
        if (interval < 64.0f) interval = 64.0f;
        fNextGrainInterval = interval;
    }

    void spawnGrain(uint32_t grainLen, uint32_t sprayRange, bool rev)
    {
        // Find inactive grain slot
        int slot = -1;
        for (int g = 0; g < MAX_GRAINS; ++g)
        {
            if (!fGrains[g].active) { slot = g; break; }
        }
        if (slot < 0) return; // all slots busy

        // Start position: current write head offset back by grain length,
        // plus random spray
        uint32_t basePos  = fFrozen ? fFreezePos : fWritePos;
        int32_t  offset   = -(int32_t)grainLen;
        if (sprayRange > 0)
            offset -= (int32_t)(randf() * (float)sprayRange);

        float startPos = (float)((basePos + GRAIN_BUF_SIZE + offset) & GRAIN_BUF_MASK);

        // Pitch: user pitch +/- tiny random detune for shimmer
        float pitchRate = fPitch + (randf() - 0.5f) * 0.02f;
        if (pitchRate < 0.1f) pitchRate = 0.1f;

        // Random pan for stereo spread
        float pan = 0.2f + randf() * 0.6f; // 0.2..0.8 (keep out of hard pan)

        fGrains[slot].trigger(startPos, grainLen, pitchRate, rev, pan);
    }

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MicrocosmPlugin)
};

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

END_NAMESPACE_DISTRHO
endef

define MICROCOSM_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        "Microcosm"
#define DISTRHO_PLUGIN_URI         "urn:mod-cookbook:microcosm"

#define DISTRHO_PLUGIN_HAS_UI       0
#define DISTRHO_PLUGIN_IS_RT_SAFE   1
#define DISTRHO_PLUGIN_NUM_INPUTS   1
#define DISTRHO_PLUGIN_NUM_OUTPUTS  2

enum Parameters {
    kMix = 0,
    kGrainSize,
    kDensity,
    kSpray,
    kPitch,
    kReverse,
    kFeedback,
    kRoomSize,
    kReverbMix,
    kGlitch,
    kGlitchRate,
    kFreeze,
    kParameterCount
};

#endif
endef

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

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

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

define MICROCOSM_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:microcosm>
    a lv2:Plugin , lv2:DelayPlugin ;
    doap:name "Microcosm" ;
    doap:license <http://opensource.org/licenses/MIT> ;
    doap:maintainer [
        foaf:name "MOD Cookbook" ;
        foaf:homepage <https://mod.audio>
    ] ;
    rdfs:comment "Granular delay / glitch / reverb inspired by the Hologram Microcosm. Mono in, stereo out." ;
    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_l" ; lv2:name "Audio Out L"
    ] , [
        a lv2:OutputPort , lv2:AudioPort ;
        lv2:index 2 ; lv2:symbol "out_r" ; lv2:name "Audio Out R"
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 3 ; lv2:symbol "mix" ; lv2:name "Dry/Wet" ;
        lv2:default 0.8 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 4 ; lv2:symbol "grain_size" ; lv2:name "Grain Size" ;
        lv2:default 120.0 ; lv2:minimum 10.0 ; lv2:maximum 500.0 ; units:unit units:ms
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 5 ; lv2:symbol "density" ; lv2:name "Density" ;
        lv2:default 0.5 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 6 ; lv2:symbol "spray" ; lv2:name "Spray" ;
        lv2:default 0.3 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 7 ; lv2:symbol "pitch" ; lv2:name "Pitch" ;
        lv2:default 1.0 ; lv2:minimum 0.25 ; lv2:maximum 2.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 8 ; lv2:symbol "reverse" ; lv2:name "Reverse" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 9 ; lv2:symbol "feedback" ; lv2:name "Feedback" ;
        lv2:default 0.4 ; lv2:minimum 0.0 ; lv2:maximum 0.95
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 10 ; lv2:symbol "room_size" ; lv2:name "Room Size" ;
        lv2:default 0.6 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 11 ; lv2:symbol "reverb_mix" ; lv2:name "Reverb Mix" ;
        lv2:default 0.4 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 12 ; lv2:symbol "glitch" ; lv2:name "Glitch" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 13 ; lv2:symbol "glitch_rate" ; lv2:name "Glitch Rate" ;
        lv2:default 4.0 ; lv2:minimum 0.5 ; lv2:maximum 16.0 ; units:unit units:hz
    ] , [
        a lv2:InputPort , lv2:ControlPort ;
        lv2:index 14 ; lv2:symbol "freeze" ; lv2:name "Freeze" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0
    ] .
endef

export MICROCOSM_PLUGIN_CPP
export MICROCOSM_PLUGIN_INFO_H
export MICROCOSM_PLUGIN_MAKEFILE
export MICROCOSM_MANIFEST_TTL
export MICROCOSM_PLUGIN_TTL

define MICROCOSM_CONFIGURE_CMDS
	mkdir -p $(@D)/examples/microcosm
	printf '%s' "$$MICROCOSM_PLUGIN_CPP"      > $(@D)/examples/microcosm/MicrocosmPlugin.cpp
	printf '%s' "$$MICROCOSM_PLUGIN_INFO_H"   > $(@D)/examples/microcosm/DistrhoPluginInfo.h
	printf '%s' "$$MICROCOSM_PLUGIN_MAKEFILE" > $(@D)/examples/microcosm/Makefile
endef

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

define MICROCOSM_INSTALL_TARGET_CMDS
	mkdir -p $($(PKG)_PKGDIR)/microcosm.lv2
	cp $(@D)/bin/microcosm.lv2/microcosm_dsp.so $($(PKG)_PKGDIR)/microcosm.lv2/
	printf '%s' "$$MICROCOSM_MANIFEST_TTL" > $($(PKG)_PKGDIR)/microcosm.lv2/manifest.ttl
	printf '%s' "$$MICROCOSM_PLUGIN_TTL"   > $($(PKG)_PKGDIR)/microcosm.lv2/microcosm.ttl
endef

$(eval $(generic-package))
