commit be39f3b287750f187a13cbd1c8b8aca7fda94bf3
parent 4b37577c4a451c41c5784b2d6c2b1ba3368bd94a
Author: Matt Demanett <matt@demanett.net>
Date: Tue, 17 Dec 2019 19:06:44 -0500
EDGE: edge detector, gate-to-trigger, etc. #90
Diffstat:
8 files changed, 441 insertions(+), 1 deletion(-)
diff --git a/plugin.json b/plugin.json
@@ -178,6 +178,16 @@
]
},
{
+ "slug": "Bogaudio-Edge",
+ "name": "EDGE",
+ "description": "Edge detector, gate-to-trigger, comparator",
+ "tags": [
+ "Logic",
+ "Utility",
+ "Polyphonic"
+ ]
+ },
+ {
"slug": "Bogaudio-Noise",
"name": "NOISE",
"description": "Noise source with multiple flavors & absolute value",
diff --git a/res-src/Edge-src.svg b/res-src/Edge-src.svg
@@ -0,0 +1,216 @@
+<svg
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="45"
+ height="380"
+ viewBox="0 0 45 380"
+>
+ <style>
+ text {
+ fill: #333;
+ font-family: 'Roboto', sans-serif;
+ font-weight: bold;
+ }
+ text.title {
+ font-family: 'Comfortaa', sans-serif;
+ font-weight: normal;
+ }
+ text.brand {
+ font-family: 'Audiowide', sans-serif;
+ font-weight: bold;
+ }
+ </style>
+
+ <defs>
+ <symbol id="knob" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <polyline points="-5,0 5,0" stroke-width="1" stroke="#00f" />
+ <polyline points="0,-5 0,5" stroke-width="1" stroke="#00f" />
+ <circle cx="0" cy="0" r="14" stroke-width="1" stroke="#00f" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="knobguide" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <g transform="rotate(-240) translate(17 0)">
+ <polyline points="0,0 4,0" stroke-width="1.5" stroke="#333" />
+ </g>
+ <g transform="rotate(-225) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-210) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-195) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-180) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-165) translate(17 0)">
+ <polyline points="0,0 4,0" stroke-width="1.5" stroke="#333" />
+ </g>
+ <g transform="rotate(-150) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-135) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-120) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-105) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+
+ <g transform="rotate(-90) translate(17 0)">
+ <!-- <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> -->
+ <g transform="translate(2.4 0) rotate(90)">
+ <text font-size="5pt" transform="translate(-1.9 2)">0</text>
+ </g>
+ </g>
+
+ <g transform="rotate(-75) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-60) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-45) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-30) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(-15) translate(17 0)">
+ <polyline points="0,0 4,0" stroke-width="1.5" stroke="#333" />
+ </g>
+ <g transform="rotate(0) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(15) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(30) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(45) translate(17 0)">
+ <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" />
+ </g>
+ <g transform="rotate(60) translate(17 0)">
+ <polyline points="0,0 4,0" stroke-width="1.5" stroke="#333" />
+ </g>
+
+ <g transform="rotate(-225) translate(22 0)">
+ <!-- <polyline points="-10,0 10,0" stroke-width="0.5" stroke="#0f0" /> -->
+ <g transform="translate(2.5 0) rotate(225)">
+ <text font-size="5pt" transform="translate(-3 7)">-10</text>
+ </g>
+ </g>
+ <g transform="rotate(45) translate(22 0)">
+ <!-- <polyline points="-10,0 10,0" stroke-width="0.5" stroke="#0f0" /> -->
+ <g transform="translate(2.5 0) rotate(-45)">
+ <text font-size="5pt" transform="translate(-7 7)">10</text>
+ </g>
+ </g>
+ </g>
+ </symbol>
+
+ <symbol id="knob-smallest" viewBox="0 0 16px 16px">
+ <g transform="translate(8 8)">
+ <polyline points="-3,0 3,0" stroke-width="1" stroke="#00f" />
+ <polyline points="0,-3 0,3" stroke-width="1" stroke="#00f" />
+ <circle r="7.5" stroke-width="1" stroke="#00f" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="knobguide-hold" viewBox="0 0 40px 40px">
+ <g transform="translate(20 20)">
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-240) translate(10 0)" />
+ <text font-size="5pt" transform="rotate(-240) translate(15 0) rotate(240) translate(-3.5 2)">0</text>
+ <polyline points="0,0 1.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-172.92) translate(10 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-145.13) translate(10 0)" />
+ <text font-size="5.0pt" transform="rotate(-145.13) translate(18 0) rotate(145.13) translate(-2 2.2)">0.1</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-90) translate(10 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-27.87) translate(10 0)" />
+ <text font-size="5.0pt" transform="rotate(-27.87) translate(15 0) rotate(27.87) translate(-5 0)">0.5</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(19.8) translate(10 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(60) translate(10 0)" />
+ <text font-size="5pt" letter-spacing="1" transform="rotate(60) translate(15 0) rotate(-60) translate(-0.5 2)">1s</text>
+ </g>
+ </symbol>
+
+ <symbol id="input" viewBox="0 0 24px 24px">
+ <g transform="translate(12 12)">
+ <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#0f0" fill="#0f0" />
+ <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#0f0" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="output" viewBox="0 0 24px 24px">
+ <g transform="translate(12 12)">
+ <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#f00" fill="#f00" />
+ <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#f00" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="light" viewBox="0 0 6.4px 6.4px">
+ <rect width="6.4" height="6.4" fill="#0f0" />
+ </symbol>
+ </defs>
+
+ <rect width="100%" height="100%" fill="#ddd" />
+ <polyline points="1,1 44,1 44,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 44.5,0.5 44.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 45,0 45,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <g transform="rotate(-90) translate(-376 13)">
+ <text class="title" font-size="7pt" letter-spacing="2.5px">EDGE</text>
+ <g transform="translate(0 12)">
+ <text class="brand" font-size="7pt" letter-spacing="2px">BGA</text>
+ <rect width="3.0" height="3" fill="#ddd" transform="translate(11.5 -5)" />
+ </g>
+ </g>
+
+ <g transform="translate(0 25)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(11 0)">RISE</text>
+ <use id="RISE_PARAM" xlink:href="#knob" transform="translate(0 5)" />
+ <use xlink:href="#knobguide" transform="scale(1) translate(0 5)" />
+ <!-- <rect width="45" height="6" fill="#0f0" transform="translate(0 52)" /> -->
+ </g>
+
+ <g transform="translate(0 89)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10.5 0)">FALL</text>
+ <use id="FALL_PARAM" xlink:href="#knob" transform="translate(0 5)" />
+ <use xlink:href="#knobguide" transform="scale(1) translate(0 5)" />
+ <!-- <rect width="45" height="6" fill="#0f0" transform="translate(0 52)" /> -->
+ </g>
+
+ <g transform="translate(0 153)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10.5 0)">HOLD</text>
+ <use id="HOLD_PARAM" xlink:href="#knob-smallest" transform="translate(14.5 9)" />
+ <use xlink:href="#knobguide-hold" transform="scale(1) translate(2.5 -3)" />
+ <!-- <rect width="45" height="6" fill="#0f0" transform="translate(0 32)" /> -->
+ </g>
+
+ <g transform="translate(0 191)">
+ <g transform="translate(5.5 0)">
+ <rect width="34" height="38" rx="5" fill="#fafafa" />
+ <rect width="34" height="10" fill="#fafafa" transform="translate(0 28)" />
+ <use id="IN_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(12.5 35)">IN</text>
+ </g>
+ <g transform="translate(5.5 38)">
+ <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" />
+ <rect width="34" height="109" rx="5" fill="#bbb" />
+ <use id="HIGH_OUTPUT" xlink:href="#output" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="1.2px" transform="translate(11 35)">HIGH</text>
+ <use id="HIGH_LIGHT" xlink:href="#light" transform="translate(2 29.3)" />
+ <use id="RISE_OUTPUT" xlink:href="#output" transform="translate(5 38)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(6.9 70)">RISE</text>
+ <use id="FALL_OUTPUT" xlink:href="#output" transform="translate(5 73)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(6.6 105)">FALL</text>
+ </g>
+ </g>
+</svg>
diff --git a/res/Edge.svg b/res/Edge.svg
Binary files differ.
diff --git a/src/Edge.cpp b/src/Edge.cpp
@@ -0,0 +1,140 @@
+
+#include "Edge.hpp"
+
+void Edge::reset() {
+ for (int c = 0; c < _channels; ++c) {
+ _state[c] = LOW_STATE;
+ _timer[c].reset();
+ _riseOutputPulseGen[c].process(10.0f);
+ _fallOutputPulseGen[c].process(10.0f);
+ }
+}
+
+int Edge::channels() {
+ return inputs[IN_INPUT].getChannels();
+}
+
+void Edge::addChannel(int c) {
+ _state[c] = LOW_STATE;
+}
+
+void Edge::modulate() {
+ _riseThreshold = params[RISE_PARAM].getValue() * 10.0f;
+ _fallThreshold = params[FALL_PARAM].getValue() * 10.0f;
+ _holdSeconds = params[HOLD_PARAM].getValue();
+ _holdSeconds *= _holdSeconds;
+}
+
+void Edge::processAll(const ProcessArgs& args) {
+ _highLightSum = 0;
+
+ outputs[HIGH_OUTPUT].setChannels(_channels);
+ outputs[RISE_OUTPUT].setChannels(_channels);
+ outputs[FALL_OUTPUT].setChannels(_channels);
+}
+
+void Edge::processChannel(const ProcessArgs& args, int c) {
+ static int i = 0;
+ ++i;
+ float in = inputs[IN_INPUT].getPolyVoltage(c);
+
+ switch (_state[c]) {
+ case LOW_STATE: {
+ if (in >= _riseThreshold) {
+ _state[c] = HIGH_STATE;
+ _timer[c].reset();
+ _timer[c].setParams(APP->engine->getSampleRate(), _holdSeconds);
+ _riseOutputPulseGen[c].trigger(0.001f);
+ }
+ break;
+ }
+
+ case HIGH_STATE: {
+ bool expired = !_timer[c].next();
+ ++_highLightSum;
+
+ if (_fallThreshold > _riseThreshold && in > _fallThreshold) {
+ _state[c] = HIGH2_STATE;
+ }
+ else if (in < std::min(_riseThreshold, _fallThreshold) && expired) {
+ _state[c] = _riseThreshold > _fallThreshold ? LOW_STATE : LOW2_STATE;
+ _fallOutputPulseGen[c].trigger(0.001f);
+ }
+ break;
+ }
+
+ case HIGH2_STATE: {
+ bool expired = !_timer[c].next();
+ ++_highLightSum;
+
+ if (in < std::max(_riseThreshold, _fallThreshold) && expired) {
+ _state[c] = _riseThreshold > _fallThreshold ? LOW_STATE : LOW2_STATE;
+ _fallOutputPulseGen[c].trigger(0.001f);
+ }
+ break;
+ }
+
+ case LOW2_STATE: {
+ if (in < std::min(_riseThreshold, _fallThreshold)) {
+ _state[c] = LOW_STATE;
+ }
+ break;
+ }
+ }
+
+ outputs[HIGH_OUTPUT].setVoltage((_state[c] == HIGH_STATE || _state[c] == HIGH2_STATE) * 5.0f, c);
+ float st = APP->engine->getSampleTime();
+ outputs[RISE_OUTPUT].setVoltage(_riseOutputPulseGen[c].process(st) * 5.0f, c);
+ outputs[FALL_OUTPUT].setVoltage(_fallOutputPulseGen[c].process(st) * 5.0f, c);
+}
+
+void Edge::postProcess(const ProcessArgs& args) {
+ lights[HIGH_LIGHT].value = _highLightSum / (float)_channels;
+}
+
+struct EdgeWidget : ModuleWidget {
+ static constexpr int hp = 3;
+
+ EdgeWidget(Edge* module) {
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
+
+ {
+ SvgPanel *panel = new SvgPanel();
+ panel->box.size = box.size;
+ panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Edge.svg")));
+ addChild(panel);
+ }
+
+ addChild(createWidget<ScrewSilver>(Vec(0, 0)));
+ addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto riseParamPosition = Vec(8.0, 38.0);
+ auto fallParamPosition = Vec(8.0, 102.0);
+ auto holdParamPosition = Vec(14.5, 162.0);
+
+ auto inInputPosition = Vec(10.5, 194.0);
+
+ auto highOutputPosition = Vec(10.5, 232.0);
+ auto riseOutputPosition = Vec(10.5, 267.0);
+ auto fallOutputPosition = Vec(10.5, 302.0);
+
+ auto highLightPosition = Vec(7.5, 258.3);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob29>(riseParamPosition, module, Edge::RISE_PARAM));
+ addParam(createParam<Knob29>(fallParamPosition, module, Edge::FALL_PARAM));
+ addParam(createParam<Knob16>(holdParamPosition, module, Edge::HOLD_PARAM));
+
+ addInput(createInput<Port24>(inInputPosition, module, Edge::IN_INPUT));
+
+ addOutput(createOutput<Port24>(highOutputPosition, module, Edge::HIGH_OUTPUT));
+ addOutput(createOutput<Port24>(riseOutputPosition, module, Edge::RISE_OUTPUT));
+ addOutput(createOutput<Port24>(fallOutputPosition, module, Edge::FALL_OUTPUT));
+
+ addChild(createLight<SmallLight<GreenLight>>(highLightPosition, module, Edge::HIGH_LIGHT));
+ }
+};
+
+Model* modelEdge = createModel<Edge, EdgeWidget>("Bogaudio-Edge", "EDGE", "Edge detector, gate-to-trigger, comparator", "Logic", "Utility", "Polyphonic");
diff --git a/src/Edge.hpp b/src/Edge.hpp
@@ -0,0 +1,70 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "signal.hpp"
+
+using namespace bogaudio::dsp;
+
+extern Model* modelEdge;
+
+namespace bogaudio {
+
+struct Edge : BGModule {
+ enum ParamsIds {
+ RISE_PARAM,
+ FALL_PARAM,
+ HOLD_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ IN_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ HIGH_OUTPUT,
+ RISE_OUTPUT,
+ FALL_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ HIGH_LIGHT,
+ NUM_LIGHTS
+ };
+
+ enum State {
+ LOW_STATE,
+ LOW2_STATE,
+ HIGH_STATE,
+ HIGH2_STATE
+ };
+
+ float _riseThreshold = 0.0f;
+ float _fallThreshold = 0.0f;
+ float _holdSeconds = 0.0f;
+ State _state[maxChannels] {};
+ Timer _timer[maxChannels];
+ rack::dsp::PulseGenerator _riseOutputPulseGen[maxChannels];
+ rack::dsp::PulseGenerator _fallOutputPulseGen[maxChannels];
+ int _highLightSum = 0;
+
+ Edge() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configParam(RISE_PARAM, -1.0f, 1.0f, 0.1f, "Rising threshold", " V", 0.0f, 10.0f);
+ configParam(FALL_PARAM, -1.0f, 1.0f, 0.01f, "Falling threshold", " V", 0.0f, 10.0f);
+ configParam<ScaledSquaringParamQuantity<1000>>(HOLD_PARAM, 0.0f, 1.0f, 0.031623f, "Hold/reset time", " ms");
+ reset();
+ }
+
+ void reset() override;
+ int channels() override;
+ void addChannel(int c) override;
+ void modulate() override;
+ void processAll(const ProcessArgs& args) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+ void postProcess(const ProcessArgs& args) override;
+};
+
+} // namespace bogaudio
diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp
@@ -20,6 +20,7 @@
#include "DADSRHPlus.hpp"
#include "DGate.hpp"
#include "Detune.hpp"
+#include "Edge.hpp"
#include "EightFO.hpp"
#include "EightOne.hpp"
#include "FMOp.hpp"
@@ -101,6 +102,7 @@ void init(rack::Plugin *p) {
p->addModel(modelASR);
p->addModel(modelADSR);
p->addModel(modelFollow);
+ p->addModel(modelEdge);
p->addModel(modelNoise);
p->addModel(modelSampleHold);
diff --git a/src/dsp/signal.cpp b/src/dsp/signal.cpp
@@ -499,7 +499,7 @@ float NoiseGate::compressionDb(float detectorDb, float thresholdDb, float ratio,
void Timer::setParams(float sampleRate, float time) {
assert(sampleRate > 0.0f);
- assert(time > 0.0f);
+ assert(time >= 0.0f);
// FIXME: if the timer is running, should set the duration to reflect the time elapsed so far, adjusting it for the delta samplerate.
_durationSteps = time * sampleRate;
}
diff --git a/src/param_quantities.hpp b/src/param_quantities.hpp
@@ -40,6 +40,8 @@ struct ScaledSquaringParamQuantity : ParamQuantity {
}
};
+typedef ScaledSquaringParamQuantity<1> OneXSquaringParamQuantity;
+
typedef ScaledSquaringParamQuantity<10> TenXSquaringParamQuantity;
typedef TenXSquaringParamQuantity EnvelopeSegmentParamQuantity;