diff --git a/plugins/community/repos/bsp/README.md b/plugins/community/repos/bsp/README.md
index 5f037b9c..b37bb61d 100644
--- a/plugins/community/repos/bsp/README.md
+++ b/plugins/community/repos/bsp/README.md
@@ -32,6 +32,21 @@ NOTE: here's an example video: https://vimeo.com/288968750
+# Legato
+
+Meant for legato-slides, this module applies a slew filter to the incoming (V/oct) signal.
+
+Two parameter sets are used to configure the slide speed
+1) when a new note is triggered ("min")
+2) when the next note is played while the previous note key is still held down (i.e. no new trigger) ("max")
+
+- Connect the original V/oct signal to the "I" input
+- Connect the trigger (gate) to the "T" input.
+- The "R" knob (rate) determines the interpolation speed between the min/max parameter sets. The speed can be modulated via the "M" input. Whe a new note is triggered, the interpolation is reset to 0.
+- The "min" and "max" knobs are used to adjust the rise and fall rates
+
+
+
# Obxd_VCF
An adaption of Filatov Vadim's excellent Ob-Xd filter. Distributed under terms of the GNU General Public License V3.
diff --git a/plugins/community/repos/bsp/make.objects b/plugins/community/repos/bsp/make.objects
index 9cae6605..e641b3e8 100644
--- a/plugins/community/repos/bsp/make.objects
+++ b/plugins/community/repos/bsp/make.objects
@@ -2,6 +2,7 @@ ALL_OBJ= \
src/AttenuMixer.o \
src/DownSampler.o \
src/bsp.o \
+ src/Legato.o \
src/Obxd_VCF.o \
src/RMS.o \
src/Scanner.o \
diff --git a/plugins/community/repos/bsp/res/Legato.svg b/plugins/community/repos/bsp/res/Legato.svg
new file mode 100644
index 00000000..fafa4f3e
--- /dev/null
+++ b/plugins/community/repos/bsp/res/Legato.svg
@@ -0,0 +1,555 @@
+
+
+
+
diff --git a/plugins/community/repos/bsp/src/Legato.cpp b/plugins/community/repos/bsp/src/Legato.cpp
new file mode 100644
index 00000000..ea703bfd
--- /dev/null
+++ b/plugins/community/repos/bsp/src/Legato.cpp
@@ -0,0 +1,197 @@
+/*
+Copyright (c) 2018 bsp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include
+
+#include "bsp.hpp"
+#include "dsp/digital.hpp"
+
+namespace rack_plugin_bsp {
+
+struct Legato : Module {
+ enum ParamIds {
+ DECAY_RATE_PARAM,
+ SMOOTH_MIN_RISE_PARAM,
+ SMOOTH_MIN_FALL_PARAM,
+ SMOOTH_MAX_RISE_PARAM,
+ SMOOTH_MAX_FALL_PARAM,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ CTL_INPUT,
+ TRIG_INPUT,
+ RATE_MOD_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ CTL_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ double smoothed_sign;
+ double last_smoothed_val;
+ double smoothed_val;
+ double decay_t;
+
+ SchmittTrigger trigger;
+
+ double rcp_sample_rate;
+
+ Legato() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
+ smoothed_sign = 0.0;
+ last_smoothed_val = 0.0;
+ smoothed_val = 0.0;
+ decay_t = 0.0;
+ handleSampleRateChanged();
+ }
+
+ void handleSampleRateChanged(void) {
+ rcp_sample_rate = 1.0 / double(engineGetSampleRate());
+ }
+
+ void onSampleRateChange() override {
+ Module::onSampleRateChange();
+
+ handleSampleRateChanged();
+ }
+
+ void step() override;
+};
+
+
+void Legato::step() {
+
+ // Read ctl input
+ float inVal = inputs[CTL_INPUT].value;
+
+ if(trigger.process(inputs[TRIG_INPUT].value))
+ {
+ decay_t = 0.0;
+ smoothed_sign = 0.0f;
+ }
+ else
+ {
+ double dcyR = params[DECAY_RATE_PARAM].value;
+
+ if(inputs[RATE_MOD_INPUT].active)
+ {
+ dcyR += inputs[RATE_MOD_INPUT].value * (1.0 / 5);
+ if(dcyR < 0.0)
+ dcyR = 0.0;
+ else if(dcyR > 1.0)
+ dcyR = 1.0;
+ }
+
+ dcyR *= dcyR;
+ dcyR *= dcyR;
+ dcyR *= 4000.0f;
+ dcyR += 1.0f;
+ dcyR *= rcp_sample_rate; // divide by sample rate
+ decay_t += dcyR;
+ if(decay_t >= 1.0)
+ decay_t = 1.0;
+ }
+
+ double smoothAmt;
+
+ if(smoothed_sign >= 0.0f)
+ {
+ smoothAmt = params[SMOOTH_MIN_RISE_PARAM].value + (params[SMOOTH_MAX_RISE_PARAM].value - params[SMOOTH_MIN_RISE_PARAM].value) * decay_t;
+ }
+ else
+ {
+ smoothAmt = params[SMOOTH_MIN_FALL_PARAM].value + (params[SMOOTH_MAX_FALL_PARAM].value - params[SMOOTH_MIN_FALL_PARAM].value) * decay_t;
+ }
+
+ smoothAmt = (1.0 - smoothAmt);
+ smoothAmt *= smoothAmt;
+ smoothAmt *= smoothAmt;
+ smoothAmt *= smoothAmt;
+
+ smoothed_val = smoothed_val + (inVal - smoothed_val) * smoothAmt;
+
+ smoothed_sign = (smoothed_val - last_smoothed_val);
+ last_smoothed_val = smoothed_val;
+
+ outputs[CTL_OUTPUT].value = float(smoothed_val);
+
+#if 0
+ static int xxx = 0;
+ if(0 == (++xxx & 32767))
+ {
+ printf("xxx smoothAmt=%g decay_t=%g\n", smoothAmt, decay_t);
+ }
+#endif
+}
+
+
+struct LegatoWidget : ModuleWidget {
+ LegatoWidget(Legato *module);
+};
+
+LegatoWidget::LegatoWidget(Legato *module) : ModuleWidget(module) {
+ setPanel(SVG::load(assetPlugin(plugin, "res/Legato.svg")));
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+
+ float cx;
+ float cy;
+
+#define STY 30.0f
+ cx = 12.0f;
+ cy = 50.0f;
+ addInput(Port::create(Vec(11.0f, cy), Port::INPUT, module, Legato::CTL_INPUT));
+ cy += STY;
+ addInput(Port::create(Vec(11.0f, cy), Port::INPUT, module, Legato::TRIG_INPUT));
+ cy += STY;
+ addParam(ParamWidget::create(Vec(cx, cy), module, Legato::DECAY_RATE_PARAM, 0.0f, 1.0f, 0.2f));
+ cy += STY;
+ addInput(Port::create(Vec(11.0f, cy), Port::INPUT, module, Legato::RATE_MOD_INPUT));
+#undef STY
+
+#define STY 30.0f
+ cx = 12.0f;
+ cy = 185.0f;
+ addParam(ParamWidget::create(Vec(cx, cy), module, Legato::SMOOTH_MIN_RISE_PARAM, 0.0f, 1.0f, 0.0f));
+ cy += STY;
+ addParam(ParamWidget::create(Vec(cx, cy), module, Legato::SMOOTH_MIN_FALL_PARAM, 0.0f, 1.0f, 0.0f));
+
+ cy += 10.0f;
+ cy += STY;
+ addParam(ParamWidget::create(Vec(cx, cy), module, Legato::SMOOTH_MAX_RISE_PARAM, 0.0f, 1.0f, 0.6f));
+ cy += STY;
+ addParam(ParamWidget::create(Vec(cx, cy), module, Legato::SMOOTH_MAX_FALL_PARAM, 0.0f, 1.0f, 0.6f));
+#undef STX
+#undef STY
+
+ addOutput(Port::create(Vec(11, 325), Port::OUTPUT, module, Legato::CTL_OUTPUT));
+}
+
+} // namespace rack_plugin_bsp
+
+using namespace rack_plugin_bsp;
+
+RACK_PLUGIN_MODEL_INIT(bsp, Legato) {
+ Model *modelLegato = Model::create("bsp", "Legato", "Legato", SLEW_LIMITER_TAG, UTILITY_TAG);
+ return modelLegato;
+}
diff --git a/plugins/community/repos/bsp/src/bsp.cpp b/plugins/community/repos/bsp/src/bsp.cpp
index 9d3e7889..5e225c80 100644
--- a/plugins/community/repos/bsp/src/bsp.cpp
+++ b/plugins/community/repos/bsp/src/bsp.cpp
@@ -2,6 +2,7 @@
RACK_PLUGIN_MODEL_DECLARE(bsp, AttenuMixer);
RACK_PLUGIN_MODEL_DECLARE(bsp, DownSampler);
+RACK_PLUGIN_MODEL_DECLARE(bsp, Legato);
RACK_PLUGIN_MODEL_DECLARE(bsp, Obxd_VCF);
RACK_PLUGIN_MODEL_DECLARE(bsp, RMS);
RACK_PLUGIN_MODEL_DECLARE(bsp, Scanner);
@@ -15,6 +16,7 @@ RACK_PLUGIN_INIT(bsp) {
RACK_PLUGIN_MODEL_ADD(bsp, AttenuMixer);
RACK_PLUGIN_MODEL_ADD(bsp, DownSampler);
+ RACK_PLUGIN_MODEL_ADD(bsp, Legato);
RACK_PLUGIN_MODEL_ADD(bsp, Obxd_VCF);
RACK_PLUGIN_MODEL_ADD(bsp, RMS);
RACK_PLUGIN_MODEL_ADD(bsp, Scanner);
diff --git a/plugins/community/repos/bsp/src/curve_test.tks b/plugins/community/repos/bsp/src/curve_test.tks
new file mode 100644
index 00000000..5a4d06a9
--- /dev/null
+++ b/plugins/community/repos/bsp/src/curve_test.tks
@@ -0,0 +1,132 @@
+
+use tksdl;
+use tkopengl;
+
+int numframesrendered=0;
+
+float shape = 0.0f;
+
+function onDraw() {
+
+ float dt=FPS.precision;
+ glClearColor(0,0,0.2,1);
+ // glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDisable(GL_DEPTH_TEST);
+
+ if( !(++numframesrendered&127) )
+ trace "FPS.real="+FPS.real;
+
+ zglInitOrtho(1, 1);
+ glColor3f(0.9,0.9,0.9);
+
+ float cx = -1.0f;
+ float stepCx = (2.0f / Viewport.width);
+
+ float x = 0.0f;
+ float stepX = (1.0f / Viewport.width);
+
+ float lx = 0;
+ float ly = 0;
+
+ glBegin(GL_LINES);
+ loop(Viewport.width)
+ {
+ float cy = 0.0f;
+
+ float triY = x*2;
+ if(triY > 1.0f)
+ triY = (2.0f - triY);
+
+ float expY = sin(x*PI);
+ expY = mathPowerf(expY, mathPowerf(1.0f - shape, 9.0f)*64 + 1.0f);
+
+ float rectY = triY;
+ // rectY = (triY < 0.5) ? 0.0f : 1.0f;
+ rectY = mathPowerf(triY * 2.0f, mathPowerf((shape - 0.75f)*4, 20) * 400.0f + 1.0f);
+ if(rectY > 1.0f)
+ rectY = 1.0f;
+
+ float t;
+
+ if(shape < 0.5f)
+ {
+ cy = expY;
+ }
+ else if( (shape >= 0.5f) && (shape < 0.75f))
+ {
+ t = (shape -0.5f) * 4.0f;
+ cy = expY + (triY - expY) * t;
+ }
+ else if(shape >= 0.75f)
+ {
+ t = (shape - 0.75f) * 4.0f;
+ cy = triY + (rectY - triY) * t;
+ }
+
+ // cy = triY;
+ // cy = expY;
+
+ glVertex2f(lx, ly);
+ glVertex2f(cx, cy);
+ lx = cx;
+ ly = cy;
+ cx += stepCx;
+ x += stepX;
+ }
+
+ glVertex2f(0.0f, 0.0f);
+ glVertex2f(1.0f, 0.0f);
+
+ glEnd();
+}
+
+function onMouse(int _x, int _y, int _cbs, int _nbs) {
+ print "x="+_x+" y="+_y+" cbs="+_cbs+" nbs="+_nbs;
+
+ shape = (_y / float(Viewport.height));
+}
+
+function onKeyboard(Key _k) {
+ switch(_k.pressed)
+ {
+ case VKEY_ESCAPE:
+ SDL.exitEventLoop();
+ break;
+ }
+}
+
+int tim_count = 0;
+function onTimer() {
+ // trace "xxx onTimer";
+ // if(++tim_count > 100)
+ // SDL.exitEventLoop();
+}
+
+function main() {
+ use callbacks;
+
+ FPS.tickInterval=1000.0/60;
+ //FPS.limit=30;
+ FPS.limit=60;
+
+ SDL.eventPolling = false; // tksdl default is "true" (do not block while waiting for an SDL event)
+ SDL.timerInterval = 20;
+
+ // SDL.dpiAwareness = true; // no OS scaling
+ // SDL.dpiAwareness = false; // assume 96dpi and let OS scale window to actual DPI
+
+ // SDL.touchInput = true; // enable WM_POINTER* messages
+
+ Viewport.multisampleSamples = 4;
+ Viewport.multisampleBuffers = 1;
+ // Viewport.stencilBits = 8;
+ Viewport.openWindow(640, 480);
+ //Viewport.openScreen(640, 480, 32);
+ //Viewport.swapInterval(1);
+
+ trace "xxx Viewport.dpi="+Viewport.dpi;
+ trace "xxx entering eventloop";
+
+ SDL.eventLoop();
+}
diff --git a/vst2_bin/plugins/bsp/README.md b/vst2_bin/plugins/bsp/README.md
index 5f037b9c..b37bb61d 100644
--- a/vst2_bin/plugins/bsp/README.md
+++ b/vst2_bin/plugins/bsp/README.md
@@ -32,6 +32,21 @@ NOTE: here's an example video: https://vimeo.com/288968750
+# Legato
+
+Meant for legato-slides, this module applies a slew filter to the incoming (V/oct) signal.
+
+Two parameter sets are used to configure the slide speed
+1) when a new note is triggered ("min")
+2) when the next note is played while the previous note key is still held down (i.e. no new trigger) ("max")
+
+- Connect the original V/oct signal to the "I" input
+- Connect the trigger (gate) to the "T" input.
+- The "R" knob (rate) determines the interpolation speed between the min/max parameter sets. The speed can be modulated via the "M" input. Whe a new note is triggered, the interpolation is reset to 0.
+- The "min" and "max" knobs are used to adjust the rise and fall rates
+
+
+
# Obxd_VCF
An adaption of Filatov Vadim's excellent Ob-Xd filter. Distributed under terms of the GNU General Public License V3.
diff --git a/vst2_bin/plugins/bsp/res/Legato.svg b/vst2_bin/plugins/bsp/res/Legato.svg
new file mode 100644
index 00000000..fafa4f3e
--- /dev/null
+++ b/vst2_bin/plugins/bsp/res/Legato.svg
@@ -0,0 +1,555 @@
+
+
+
+