<Cabbage>
{
    "pluginId": "adl3",
    "widgets": [
        {
            "type": "form",
            "caption": "Audio transient delay",
            "size": {
                "width": 420,
                "height": 358
            },
            "guiMode": "queue",
            "style": {
                "fill": "#1e2328"
            }
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 5,
                "width": 200,
                "height": 14
            },
            "label": {
                "text": "Transient detection"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "comboBox",
            "bounds": {
                "left": 330,
                "top": 2,
                "width": 88,
                "height": 18
            },
            "items": [
                "pvs buffer",
                "audio buffer"
            ],
            "max": 1,
            "channels": [
                {
                    "id": "buf_mode",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 5,
                "top": 25,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "noisefloor",
                    "event": "valueChanged",
                    "range": {
                        "min": -90,
                        "max": 0,
                        "defaultValue": -40,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 50,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "Noise floor"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 75,
                "top": 25,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "trans_thresh",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 30,
                        "defaultValue": 2.5,
                        "skew": 1,
                        "increment": 0.001
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 75,
                "top": 50,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "T.thresh"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 145,
                "top": 25,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "retrig_thresh",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 30,
                        "defaultValue": 2,
                        "skew": 1,
                        "increment": 0.001
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 145,
                "top": 50,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "T.retrig"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 215,
                "top": 25,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "low_trans",
                    "event": "valueChanged",
                    "range": {
                        "min": -50,
                        "max": -5,
                        "defaultValue": -30,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 215,
                "top": 50,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "T.lowlimit"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 285,
                "top": 25,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "double_limit",
                    "event": "valueChanged",
                    "range": {
                        "min": 0.01,
                        "max": 0.5,
                        "defaultValue": 0.05,
                        "skew": 0.35,
                        "increment": 0.001
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 285,
                "top": 50,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "T.dbl.lim"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 5,
                "top": 75,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "shape",
                    "event": "valueChanged",
                    "range": {
                        "min": 0.3,
                        "max": 3,
                        "defaultValue": 1,
                        "skew": 0.35,
                        "increment": 0.001
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 100,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "Shape"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 75,
                "top": 75,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "amp_trans",
                    "event": "valueChanged",
                    "range": {
                        "min": 0.0,
                        "max": 1,
                        "defaultValue": 1,
                        "skew": 0.35,
                        "increment": 0.001
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 75,
                "top": 100,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "Amp_amt"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 145,
                "top": 75,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "shuf_prob",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 145,
                "top": 100,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "Shuf.prob"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 215,
                "top": 75,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "shuf_span",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0.5,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 215,
                "top": 100,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "Shuf.span"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "checkBox",
            "bounds": {
                "left": 350,
                "top": 25,
                "width": 60,
                "height": 25
            },
            "active": false,
            "channels": [
                {
                    "id": "trans_led",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ],
            "label": {
                "text": "trig"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 350,
                "top": 75,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "trig_prob",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0.5,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 350,
                "top": 100,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "Dly.prob"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 130,
                "width": 200,
                "height": 14
            },
            "label": {
                "text": "Delay (8th notes at set tempo)"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "button",
            "bounds": {
                "left": 350,
                "top": 128,
                "width": 60,
                "height": 20
            },
            "channels": [
                {
                    "id": "hold",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ],
            "label": {
                "text": {
                    "off": "hold",
                    "on": "HOLD"
                }
            },
            "style": {
                "off": {
                    "backgroundColor": "#c87800"
                },
                "on": {
                    "backgroundColor": "#3c3c3c"
                }
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 290,
                "top": 128,
                "width": 50,
                "height": 20
            },
            "channels": [
                {
                    "id": "loop_len",
                    "event": "valueChanged",
                    "range": {
                        "min": 1,
                        "max": 64,
                        "defaultValue": 8,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 215,
                "top": 131,
                "width": 70,
                "height": 14
            },
            "label": {
                "text": "Loop length"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 5,
                "top": 150,
                "width": 60,
                "height": 25
            },
            "channels": [
                {
                    "id": "bpm",
                    "event": "valueChanged",
                    "range": {
                        "min": 10,
                        "max": 999,
                        "defaultValue": 120,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 175,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "bpm"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "button",
            "bounds": {
                "left": 5,
                "top": 189,
                "width": 60,
                "height": 15
            },
            "channels": [
                {
                    "id": "tap",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ],
            "label": {
                "text": {
                    "off": "tap",
                    "on": "tap"
                }
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 75,
                "top": 150,
                "width": 40,
                "height": 25
            },
            "channels": [
                {
                    "id": "dly1_min",
                    "event": "valueChanged",
                    "range": {
                        "min": 1,
                        "max": 32,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 75,
                "top": 175,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t1 min"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 120,
                "top": 150,
                "width": 40,
                "height": 25
            },
            "channels": [
                {
                    "id": "dly1_max",
                    "event": "valueChanged",
                    "range": {
                        "min": 1,
                        "max": 32,
                        "defaultValue": 4,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 120,
                "top": 175,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t1 max"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 165,
                "top": 150,
                "width": 40,
                "height": 25
            },
            "channels": [
                {
                    "id": "transpose1",
                    "event": "valueChanged",
                    "range": {
                        "min": -24,
                        "max": 24,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 165,
                "top": 175,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t1 trsp"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 210,
                "top": 150,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "level1",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0.7,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 210,
                "top": 175,
                "width": 50,
                "height": 14
            },
            "label": {
                "text": "t1 level"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 270,
                "top": 150,
                "width": 40,
                "height": 25
            },
            "active": false,
            "channels": [
                {
                    "id": "dly1_disp",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 64,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 0.5
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 270,
                "top": 175,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t1 now"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "checkBox",
            "bounds": {
                "left": 320,
                "top": 150,
                "width": 90,
                "height": 25
            },
            "channels": [
                {
                    "id": "tap1_enable",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ],
            "label": {
                "text": "tap1"
            }
        },
        {
            "type": "horizontalSlider",
            "bounds": {
                "left": 315,
                "top": 172,
                "width": 100,
                "height": 20
            },
            "channels": [
                {
                    "id": "pan1",
                    "event": "valueChanged",
                    "range": {
                        "min": -1,
                        "max": 1,
                        "defaultValue": -1,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 315,
                "top": 190,
                "width": 100,
                "height": 12
            },
            "label": {
                "text": "t1 pan"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 75,
                "top": 205,
                "width": 40,
                "height": 25
            },
            "channels": [
                {
                    "id": "dly2_min",
                    "event": "valueChanged",
                    "range": {
                        "min": 1,
                        "max": 32,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 75,
                "top": 230,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t2 min"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 120,
                "top": 205,
                "width": 40,
                "height": 25
            },
            "channels": [
                {
                    "id": "dly2_max",
                    "event": "valueChanged",
                    "range": {
                        "min": 1,
                        "max": 32,
                        "defaultValue": 4,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 120,
                "top": 230,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t2 max"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 165,
                "top": 205,
                "width": 40,
                "height": 25
            },
            "channels": [
                {
                    "id": "transpose2",
                    "event": "valueChanged",
                    "range": {
                        "min": -24,
                        "max": 24,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 165,
                "top": 230,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t2 trsp"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 210,
                "top": 205,
                "width": 50,
                "height": 25
            },
            "channels": [
                {
                    "id": "level2",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 0.7,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 210,
                "top": 230,
                "width": 50,
                "height": 14
            },
            "label": {
                "text": "t2 level"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 270,
                "top": 205,
                "width": 40,
                "height": 25
            },
            "active": false,
            "channels": [
                {
                    "id": "dly2_disp",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 64,
                        "defaultValue": 0,
                        "skew": 1,
                        "increment": 0.5
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 270,
                "top": 230,
                "width": 40,
                "height": 14
            },
            "label": {
                "text": "t2 now"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "checkBox",
            "bounds": {
                "left": 320,
                "top": 205,
                "width": 90,
                "height": 25
            },
            "channels": [
                {
                    "id": "tap2_enable",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ],
            "label": {
                "text": "tap2"
            }
        },
        {
            "type": "horizontalSlider",
            "bounds": {
                "left": 315,
                "top": 227,
                "width": 100,
                "height": 20
            },
            "channels": [
                {
                    "id": "pan2",
                    "event": "valueChanged",
                    "range": {
                        "min": -1,
                        "max": 1,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 315,
                "top": 245,
                "width": 100,
                "height": 12
            },
            "label": {
                "text": "t2 pan"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "numberSlider",
            "bounds": {
                "left": 5,
                "top": 205,
                "width": 60,
                "height": 25
            },
            "channels": [
                {
                    "id": "dry",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 0.01
                    }
                }
            ]
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 230,
                "width": 60,
                "height": 14
            },
            "label": {
                "text": "dry"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "checkBox",
            "bounds": {
                "left": 5,
                "top": 248,
                "width": 110,
                "height": 20
            },
            "channels": [
                {
                    "id": "auto_delay",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 1,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ],
            "label": {
                "text": "auto delay"
            }
        },
        {
            "type": "label",
            "bounds": {
                "left": 120,
                "top": 252,
                "width": 90,
                "height": 14
            },
            "label": {
                "text": "Keep envelope"
            },
            "style": {
                "fontSize": 11,
                "fontColor": "#dddddd"
            }
        },
        {
            "type": "comboBox",
            "bounds": {
                "left": 210,
                "top": 248,
                "width": 90,
                "height": 20
            },
            "items": [
                "off",
                "lifter",
                "true env"
            ],
            "max": 2,
            "channels": [
                {
                    "id": "formant",
                    "event": "valueChanged",
                    "range": {
                        "min": 0,
                        "max": 2,
                        "defaultValue": 1,
                        "skew": 1,
                        "increment": 1
                    }
                }
            ]
        },
        {
            "type": "image",
            "bounds": {
                "left": 5,
                "top": 272,
                "width": 410,
                "height": 70
            },
            "id": "bufDisplay",
            "style": {
                "backgroundColor": "rgba(0,0,0,0)"
            }
        },
        {
            "type": "label",
            "bounds": {
                "left": 5,
                "top": 343,
                "width": 410,
                "height": 12
            },
            "label": {
                "text": "buffer: red = record, green = tap 1 play, blue = tap 2 play, orange = hold loop"
            },
            "style": {
                "fontSize": 10,
                "textAlign": "left",
                "fontColor": "#dddddd"
            }
        }
    ]
}
</Cabbage>

<CsoundSynthesizer>
<CsOptions>
-m0 -d
</CsOptions>
<CsInstruments>

; Audio transient delay
; Each detected transient in the input picks new random delay times, quantized to 8th notes
; at the set tempo. The audio is held in a pvsbuffer and read back at the delayed position,
; so changing the delay time causes no doppler/pitch glide. Reader switches are crossfaded.
; A random mask (Dly.prob) lets only some transients start new delays; masked segments are silent in the delay output.
; Tap 2 is delayed by tap 1 delay plus its own random delay.

ksmps = 32
nchnls = 2
0dbfs = 1

giBufLen = int(12*sr/ksmps)*ksmps/sr	; buffer length in seconds, longest possible delay, a whole number of blocks
giABufN = giBufLen*sr			; audio ring length in samples
giABuf ftgen 0, 0, 1048576, 2, 0	; audio ring buffer with a guard copy of the first block after the end
gkBufMode init 0			; 0 = pvs buffer, 1 = audio buffer
giMinDelay = 0.06		; shortest delay (must stay clear of the buffer write position)

;***************************************************
; Transient detection udo (from MountainsLakes3.csd)

opcode TransientDetect, kk,kikkkk
  kin, iresponse, ktthresh, klowThresh, kdecThresh, kdoubleLimit xin 
  kinDel	delayk	kin, iresponse/1000		; delay with response time for comparision of levels
  ktrig		= ((kin > kinDel + ktthresh) ? 1 : 0) 	; if current rms plus threshold is larger than previous rms, set trig signal to current rms
  klowGate	= (kin < klowThresh? 0 : 1)		; gate to remove transient of low level signals
  ktrig		= ktrig * klowGate			; activate gate on trig signal
  ktransLev	init 0
  ktransLev	samphold kin, 1-ktrig			; read amplitude at transient
  
  kreGate	init 1					; retrigger gate, to limit transient double trig before signal has decayed (decThresh) from its local max
  ktrig		= ktrig*kreGate				; activate gate
  kmaxAmp	init -99999
  kmaxAmp	max kmaxAmp, kin			; find local max amp
  kdiff		= kmaxAmp-kin				; how much the signal has decayed since its local max value
  kreGate	limit kreGate-ktrig, 0, 1		; mute when trig detected
  kreGate	= (kdiff > kdecThresh ? 1 : kreGate)	; re-enable gate when signal has decayed sufficiently
  kmaxAmp	= (kreGate == 1 ? -99999 : kmaxAmp)	; reset max amp gauge

  ; avoid closely spaced transient triggers (first trig priority)
  kdouble	init 1
  ktrig		= ktrig*kdouble
  if ktrig > 0 then
    reinit double
  endif
  double:
  idoubleLimit  = i(kdoubleLimit)	
  idoubleLimit  limit idoubleLimit, 1/kr, 5
  kdouble	linseg	0, idoubleLimit, 0, 0, 1, 1, 1
  rireturn

  xout ktrig, kdiff
endop

;***************************************************
; Random mask: on each incoming trigger, passes it with probability kprob (1 = pass, 0 = masked)

opcode RandMask, k, kk
  ktrig, kprob xin
  kpass = 0
  if ktrig > 0 then
    krnd random 0, 1
    kpass = (krnd < kprob ? 1 : 0)
  endif
  xout kpass
endop

;***************************************************
; Polyphonic delay: every transient spawns one instr 2 voice per tap. A voice reads the pvsbuffer
; at a fixed delay and only plays the input segment between its own transient and the next one,
; so each piece of audio keeps the delay time that was active when it came in.
gibuf init 0			; pvsbuffer handle
gktime init 0			; buffer write position
gktnow init 0			; running time in seconds
gkEpoch init 0			; segment counter, incremented on each spawn
giEpT ftgen 0, 0, 4096, 2, 0	; segment start times by epoch
giPre = 0.02			; segment starts this much before the detection to keep the attack
gaL init 0
gaR init 0
gkLoopLen init 0		; hold loop length in seconds
gkLoopEnd init 0		; buffer position where the hold started
gkHoldMix init 0		; 0 = live reading, 1 = looping
gkHoldOn init 0
giBnd ftgen 0, 0, 128, 2, 0	; recorded transient positions in the buffer (ring)
giBndT ftgen 0, 0, 128, 2, 0	; written time at each recorded transient, to detect overwritten ones
gkNB init 0			; number of boundaries recorded
gkWrNow init 0			; total time written into the buffer
gkRecPos init 0			; display: record head position in the buffer
gkPos1 init -1			; display: tap 1 playback position, -1 when no voice is audible
gkPos2 init -1
gkMute1 init 0			; display: 1 when the tap 1 voice is masked by Dly.prob
gkMute2 init 0
gkDly1 init 1			; display: current tap delays, to place heads where masked segments have no voice
gkDly2 init 1
giDW = 410			; display size in pixels
giDH = 70
giEnvN = 256			; amplitude envelope bins covering the buffer
giEnv ftgen 0, 0, 256, 2, 0

; read the shared pvsbuffer at kpos, transposed by kratio
opcode PosRead, a, kkk
  kpos, kratio, kform xin
  fs pvsbufread kpos, gibuf
  if kratio == 1 then
    aout pvsynth fs
  else
    fs2 pvscale fs, kratio, kform
    aout pvsynth fs2
  endif
  xout aout
endop

; read the audio ring buffer at kpos (aramp = sample offsets within the block), transposed by kratio
opcode PosReadA, a, kkka
  kpos, kratio, kform, aramp xin
  ; the pvs transposer delays the signal by about one fft frame, so read slightly ahead
  kposa = kpos + (kratio == 1 ? 0 : 1024/sr)
  kposa = (kposa >= giBufLen ? kposa - giBufLen : kposa)
  asig tablei kposa*sr + aramp, giABuf, 0, 0, 0
  if kratio == 1 then
    aout = asig
  else
    fs pvsanal asig, 1024, 256, 1024, 0
    fs2 pvscale fs, kratio, kform
    aout pvsynth fs2
  endif
  xout aout
endop

opcode PosRead2, a, kkka
  kpos, kratio, kform, aramp xin
  if gkBufMode == 0 then
    aout PosRead kpos, kratio, kform
  else
    aout PosReadA kpos, kratio, kform, aramp
  endif
  xout aout
endop

instr 1
  a1 inch 1

  knoiseFloor_dB chnget "noisefloor"
  iresponse = 10 ; transient detect response time in milliseconds
  ktthresh chnget "trans_thresh"
  klowThresh chnget "low_trans"
  kdoubleLimit chnget "double_limit"
  kdecThresh chnget "retrig_thresh"
  kshape chnget "shape"

  krms rms a1
  krms_dB = dbfsamp(krms)
  kgate = (krms_dB < knoiseFloor_dB ? 0 : 1)
  aenv follow2 a1, 0.01, 0.3

  ; ***************
  ; spectral analysis L2, low fft size, smoothing, custom window
  ifftsize = 1024
  ioverlap = 8
  iwin ftgen 0, 0, ifftsize, 20, 7, 1, 1.5 ;  KAISER
  fsin pvsanal a1, ifftsize, ifftsize/ioverlap, ifftsize, -iwin
  ismoothing = 0.002
  fsmooth pvsmooth fsin, ismoothing, ismoothing

  iarrsize = ifftsize/2 + 1
  kAmps[] init iarrsize
  kFreqs[] init iarrsize
  kAmpsmooth[] init iarrsize
  kFreqsmooth[] init iarrsize
  kmaxAmp init 0
  kfluxL2 init 0
  kflag pvs2array kAmps, kFreqs, fsin
  kflag pvs2array kAmpsmooth, kFreqsmooth, fsmooth
  if changed(kflag) > 0 then
    kmaxAmp maxarray kAmps
    kFluxL2[] = limit(kAmps^2-kAmpsmooth^2, 0, 9999) ; L2 distance (limit, includes only positive changes)
    kfluxL2 = sumarray(kFluxL2) ; sum of all distances
  endif
  kfluxL2_norm divz kfluxL2, kmaxAmp^2, 0 ; normalized flux, independent of amplitude
  kfluxL2_norm *= 0.15
  aflux_env2 follow2 butterlp(limit(butterhp(a(kfluxL2_norm),2),0,1),25), 0.01, 0.3

  ktrans_in = (k(aflux_env2)^kshape)
  kamp_trans chnget "amp_trans"
  ktrans_in = ktrans_in*kamp_trans*k(aenv) + ktrans_in*(1-kamp_trans) ; mix in amp envelope
  kttrans,ktdiff TransientDetect dbfsamp(ktrans_in), iresponse, ktthresh, klowThresh, kdecThresh, kdoubleLimit
  kttrans *= kgate

  ; ***************
  ; new delay times on each transient, quantized to 8th notes
  kbpm chnget "bpm"

  ; tap tempo: time between button presses sets the bpm (intervals longer than 6 s start a new tap series)
  ktap chnget "tap"
  ktaptrig changed ktap
  ktaptime init -1
  kclock times
  if ktaptrig > 0 then
    kdt = kclock - ktaptime
    if ktaptime >= 0 && kdt <= 6 && kdt > 0.0601 then
      kbpm = limit(60/kdt, 10, 999)
      cabbageSetValue "bpm", kbpm, 1
    endif
    ktaptime = kclock
  endif
  keighth = 60/(max(kbpm, 1)*2)
  kmin1 chnget "dly1_min"
  kmax1 chnget "dly1_max"
  kmin2 chnget "dly2_min"
  kmax2 chnget "dly2_max"
  kn1 init 2
  kn2 init 2
  kauto chnget "auto_delay"
  kprob chnget "trig_prob"
  ; masked transients still close the previous segment, but start no delay voices, so that audio is not delayed
  khold chnget "hold"
  ; while holding, spawned segments always pass; the mask is re-rolled per loop segment in the voices instead
  kpass RandMask kttrans*kauto, (khold == 1 ? 1 : kprob)
  if kpass > 0 then
    kr1 random min(kmin1, kmax1), max(kmin1, kmax1)+0.999
    kr2 random min(kmin2, kmax2), max(kmin2, kmax2)+0.999
    kn1 = int(kr1)
    kn2 = int(kr2)
  endif
  kdly1 = limit(kn1*keighth, giMinDelay, giBufLen-0.1)
  kdly2 = limit((kn1+kn2)*keighth, giMinDelay, giBufLen-0.1)
  gkDly1 = kdly1
  gkDly2 = kdly2

  ; if the last segment was masked when hold starts there is no voice to re-roll the mask, so start one per tap
  khrise trigger khold, 0.5, 0
  kmaskedlast init 0
  if kttrans*kauto > 0 then
    kmaskedlast = (kpass > 0 ? 0 : 1)
  endif
  khsp = khrise*kmaskedlast

  ; spawn delay voices for the new segment
  gktnow times
  kfirst init 1
  if kfirst == 1 || (kttrans > 0 && kauto > 0) || khsp > 0 then
    kseg = max(gktnow - giPre, 0)
    kepoch = gkEpoch + 1
    tabw kseg, kepoch % 4096, giEpT
    gkEpoch = kepoch
    if kfirst == 1 || kpass > 0 || khsp > 0 then
      kst1 = (khsp > 0 ? 0 : max(kdly1 - (gktnow - kseg), 0))
      kst2 = (khsp > 0 ? 0 : max(kdly2 - (gktnow - kseg), 0))
      event "i", 2, kst1, 86400, 1, kepoch, kdly1
      event "i", 2, kst2, 86400, 2, kepoch, kdly2
    endif
    kfirst = 0
  endif
  kmaskedlast = (khsp > 0 ? 0 : kmaskedlast)

  ; display (guiMode queue: cabbageSetValue instead of chnset)
  kdispchg changed kn1, kn2
  kd1 = kn1
  kd2 = kn1+kn2
  cabbageSetValue "dly1_disp", kd1, kdispchg
  cabbageSetValue "dly2_disp", kd2, kdispchg

  kledtime init 0
  kledon = 0
  if kttrans > 0 then
    kledon = 1
    kledtime = 0.1
  endif
  if kledtime > 0 then
    kledtime -= 1/kr
  endif
  kledoff = (kledtime <= 0 ? 1 : 0)
  kledoff *= changed(kledtime > 0 ? 1 : 0)
  cabbageSetValue "trans_led", 1, kledon
  cabbageSetValue "trans_led", 0, kledoff

  ; ***************
  ; pvsbuffer delay
  ifftsize_buf = 512
  ioverlap_buf = 8
  fbuf pvsanal a1, ifftsize_buf, ifftsize_buf/ioverlap_buf, ifftsize_buf, 0
  ; hold: skip the write so the buffer keeps its content, and advance the read reference so the pattern loops
  ktime init 0
  kprevbin init -1
  kwpos init 0
  gkBufMode = limit(chnget:k("buf_mode"), 0, 1)
  arampw phasor sr/ksmps
  arampw *= ksmps
  if khold == 0 then
    ibuf, ktime pvsbuffer fbuf, giBufLen
    ; the audio ring is written alongside the pvsbuffer so the mode can be switched live
    awidx = kwpos + arampw
    tablew a1, awidx, giABuf
    if kwpos == 0 then
      tablew a1, awidx + giABufN, giABuf
    endif
    kwpos += ksmps
    if kwpos >= giABufN then
      kwpos = 0
    endif
    if gkBufMode == 1 then
      ktime = kwpos/sr
    endif
    gktime = ktime
    gkWrNow += 1/kr
    ; peak envelope of what is written, per buffer position, for the display
    kbin = int(ktime/giBufLen*giEnvN) % giEnvN
    if kbin != kprevbin then
      tabw 0, kbin, giEnv
      kprevbin = kbin
    endif
    tabw max(tab:k(kbin, giEnv), k(aenv)), kbin, giEnv
    if kttrans > 0 then
      kbp = ktime - giPre
      kbp = (kbp < 0 ? kbp + giBufLen : kbp)
      kbi = gkNB % 128
      tabw kbp, kbi, giBnd
      tabw gkWrNow, kbi, giBndT
      gkNB += 1
    endif
  else
    gktime += 1/kr
    gkLoopEnd = ktime
    gkLoopLen = limit(chnget:k("loop_len")*keighth, 0.1, giBufLen)
  endif
  ; 50 ms ramp between live and loop reading, voices crossfade their two read pointers
  gkHoldMix = (khold == 0 ? 0 : min(gkHoldMix + 1/(0.05*kr), 1))
  gkHoldOn = khold
  gkRecPos = ktime
  gibuf = ibuf

  kdry chnget "dry"
  outs a1*kdry, a1*kdry
endin

; delay voice: p4 tap, p5 segment epoch, p6 delay in seconds
instr 2
  itap = p4
  iep = p5
  idel = p6
  iidx = (iep + 1) % 4096
  kdel init idel
  if itap == 1 then
    Strans = "transpose1"
    Slevel = "level1"
    Sen = "tap1_enable"
    Span = "pan1"
  else
    Strans = "transpose2"
    Slevel = "level2"
    Sen = "tap2_enable"
    Span = "pan2"
  endif

  ; the segment ends when the next one started, as seen at this voice's delayed input time
  kt1 = 1000000
  if gkEpoch > iep then
    kt1 tab iidx, giEpT
  endif
  kin = gktnow - kdel
  kon = (kin < kt1 ? 1 : 0)
  kenv init 0
  kenv += (kon - kenv)*0.1
  if kon == 0 && kenv < 0.001 then
    turnoff
  endif

  kratio = pow(2, chnget:k(Strans)/12)
  kform = limit(chnget:k("formant"), 0, 2)
  kmix = gkHoldMix
  aramps phasor sr/ksmps
  aramps *= ksmps
  a1 = 0
  aloop = 0
  kppH init -1
  kgate init 1
  kgsm init 1
  kposH init 0
  kposS init 0
  koff init 0
  kbc init 0
  kholdprev init i(gkHoldOn)
  kposL = gktime - kdel
  kposL = (kposL < 0 ? kposL + giBufLen : kposL)
  kposL = (kposL >= giBufLen ? kposL - giBufLen : kposL)
  ; on release the head keeps its lag behind the record head, so it continues without a jump until a new transient sets a new delay
  if kholdprev == 1 && gkHoldOn == 0 then
    kdel = gktime - kposS
    kdel = (kdel < 0 ? kdel + giBufLen : kdel)
    kposL = kposS
  endif
  kholdprev = gkHoldOn
  if kmix < 1 then
    a1 PosRead2 kposL, kratio, kform, aramps
  endif
  if kmix > 0 then
    kls = gkLoopEnd - gkLoopLen
    kposH = kls + ((gktime - kdel - kls) % gkLoopLen + gkLoopLen) % gkLoopLen
    kposH = (kposH < 0 ? kposH + giBufLen : kposH)
    kposH = (kposH >= giBufLen ? kposH - giBufLen : kposH)
    ; re-roll the mask whenever the read position passes a recorded transient or wraps the loop
    kdp = kposH - kppH
    kcross = (kppH < 0 || kdp < 0 ? 1 : 0)
    kbc = kposH
    if kcross == 0 then
      ki = 0
      kn = min(gkNB, 128)
      while ki < kn do
        kd = tab:k(ki, giBnd) - kppH
        kd = (kd < 0 ? kd + giBufLen : kd)
        if kd > 0 && kd <= kdp && gkWrNow - tab:k(ki, giBndT) < giBufLen - 0.2 then
          kcross = 1
          kbc = tab:k(ki, giBnd)
        endif
        ki += 1
      od
    endif
    kppH = kposH
    if kcross == 1 then
      krnd random 0, 1
      kgate = (krnd < chnget:k("trig_prob") ? 1 : 0)
      ; reshuffle: jump to the start of another recorded segment inside the loop, within +/- span/2 of the loop length (circular in the loop)
      koff = 0
      kspan = chnget:k("shuf_span")
      krs random 0, 1
      if krs < chnget:k("shuf_prob") && kspan > 0 then
        khalf = kspan*gkLoopLen*0.5
        kcrel = kbc - kls
        kcrel = (kcrel < 0 ? kcrel + giBufLen : (kcrel >= giBufLen ? kcrel - giBufLen : kcrel))
        kncand = 0
        kj = 0
        kn = min(gkNB, 128)
        while kj < kn do
          kbrel = tab:k(kj, giBnd) - kls
          kbrel = (kbrel < 0 ? kbrel + giBufLen : (kbrel >= giBufLen ? kbrel - giBufLen : kbrel))
          kdist = abs(kbrel - kcrel)
          kdist = min(kdist, gkLoopLen - kdist)
          if kbrel < gkLoopLen && kdist <= khalf && gkWrNow - tab:k(kj, giBndT) < giBufLen - 0.2 then
            kncand += 1
          endif
          kj += 1
        od
        if kncand > 0 then
          krp random 0, 1
          kpick = min(int(krp*kncand), kncand - 1)
          kcnt = 0
          kj = 0
          while kj < kn do
            kbrel = tab:k(kj, giBnd) - kls
            kbrel = (kbrel < 0 ? kbrel + giBufLen : (kbrel >= giBufLen ? kbrel - giBufLen : kbrel))
            kdist = abs(kbrel - kcrel)
            kdist = min(kdist, gkLoopLen - kdist)
            if kbrel < gkLoopLen && kdist <= khalf && gkWrNow - tab:k(kj, giBndT) < giBufLen - 0.2 then
              if kcnt == kpick then
                koff = kbrel - kcrel
              endif
              kcnt += 1
            endif
            kj += 1
          od
        endif
      endif
    endif
    kgsm += (kgate - kgsm)*0.1
    ; the shuffled position wraps inside the loop
    krelH = kposH - kls
    krelH = (krelH < 0 ? krelH + giBufLen : (krelH >= giBufLen ? krelH - giBufLen : krelH))
    kposS = kls + ((krelH + koff) % gkLoopLen + gkLoopLen) % gkLoopLen
    kposS = (kposS < 0 ? kposS + giBufLen : kposS)
    kposS = (kposS >= giBufLen ? kposS - giBufLen : kposS)
    aloop PosRead2 kposS, kratio, kform, aramps
    aloop *= interp(kgsm)
  else
    kppH = -1
    koff = 0
  endif
  a1 = a1*interp(1-kmix) + aloop*interp(kmix)

  kshow = (kmix > 0.5 ? kposS : kposL)
  if kenv > 0.05 then
    kmute = (kmix > 0.5 && kgate == 0 ? 1 : 0)
    if itap == 1 then
      gkPos1 = kshow
      gkMute1 = kmute
    else
      gkPos2 = kshow
      gkMute2 = kmute
    endif
  endif

  klev = chnget:k(Sen)*chnget:k(Slevel)
  kpan = limit((chnget:k(Span)+1)*0.5, 0, 1)
  a1 *= interp(kenv*klev)
  ; equal power panning
  gaL += a1*cos(kpan*1.5707963)
  gaR += a1*sin(kpan*1.5707963)
endin

; output of the summed delay voices
instr 3
  outs gaL, gaR
  gaL = 0
  gaR = 0
endin

; buffer display, runs after the voices so it sees their positions
instr 4
  kupd metro 20
  if kupd == 1 then
    SPath sprintfk "<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" fill=\"rgb(15,18,22)\"/>", giDW, giDH
    SPath strcatk SPath, sprintfk("<path d=\"M0 %d", giDH)
    ki = 0
    while ki < giEnvN do
      kdb = dbfsamp(tab:k(ki, giEnv) + 0.000001)
      ky = giDH*(1 - limit((kdb + 60)/60, 0, 1))
      SPath strcatk SPath, sprintfk(" L%d %d", ki*giDW/giEnvN, ky)
      ki += 1
    od
    SPath strcatk SPath, sprintfk(" L%d %d Z\" fill=\"rgb(110,120,150)\"/>", giDW, giDH)

    if gkHoldMix > 0 then
      kls = gkLoopEnd - gkLoopLen
      kop = gkHoldMix*0.3
      kxe = gkLoopEnd/giBufLen*giDW
      if kls >= 0 then
        kxs = kls/giBufLen*giDW
        SPath strcatk SPath, sprintfk("<rect x=\"%d\" y=\"0\" width=\"%d\" height=\"%d\" fill=\"orange\" fill-opacity=\"%f\"/>", kxs, kxe-kxs, giDH, kop)
      else
        kxs = (kls + giBufLen)/giBufLen*giDW
        SPath strcatk SPath, sprintfk("<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" fill=\"orange\" fill-opacity=\"%f\"/>", kxe, giDH, kop)
        SPath strcatk SPath, sprintfk("<rect x=\"%d\" y=\"0\" width=\"%d\" height=\"%d\" fill=\"orange\" fill-opacity=\"%f\"/>", kxs, giDW-kxs, giDH, kop)
      endif
    endif

    kxr = gkRecPos/giBufLen*giDW
    SPath strcatk SPath, sprintfk("<line x1=\"%d\" y1=\"0\" x2=\"%d\" y2=\"%d\" style=\"stroke:red;stroke-width:2;stroke-opacity:%f\"/>", kxr, kxr, giDH, 1 - 0.6*gkHoldMix)
    ; a full-height line is an audible head, a short mark at the bottom is a head silenced by Dly.prob
    kp1 = gkPos1
    km1 = gkMute1
    if kp1 < 0 then
      kp1 = gktime - gkDly1
      kp1 = (kp1 < 0 ? kp1 + giBufLen : kp1)
      km1 = 1
    endif
    kp2 = gkPos2
    km2 = gkMute2
    if kp2 < 0 then
      kp2 = gktime - gkDly2
      kp2 = (kp2 < 0 ? kp2 + giBufLen : kp2)
      km2 = 1
    endif
    kx1 = kp1/giBufLen*giDW
    kx2 = kp2/giBufLen*giDW
    SPath strcatk SPath, sprintfk("<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" style=\"stroke:lime;stroke-width:3\"/>", kx1, km1*(giDH-10), kx1, giDH)
    SPath strcatk SPath, sprintfk("<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" style=\"stroke:dodgerblue;stroke-width:3\"/>", kx2, km2*(giDH-10), kx2, giDH)
    cabbageSet 1, "bufDisplay", "svg.markup", SPath
  endif
  gkPos1 = -1
  gkPos2 = -1
  gkMute1 = 0
  gkMute2 = 0
endin

</CsInstruments>
<CsScore>
i1 0 86400
i3 0 86400
i4 0 86400
</CsScore>
</CsoundSynthesizer>
