BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

commit bf456ba2e682af7c6ea0951817cd3d6e2aae845d
parent 9caa041b640f70bee8bf2cf95bef82ffa812cf9c
Author: Matt Demanett <matt@demanett.net>
Date:   Wed, 25 Apr 2018 00:19:47 -0400

Utility ADSR module; generalized shape params on dsp ADSR class.

Diffstat:
Ares-src/ADSR-src.svg | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/ADSR.svg | 0
Asrc/ADSR.cpp | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ADSR.hpp | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/FMOp.hpp | 2+-
Msrc/Test2.cpp | 26++++++++++++++++++++++----
Msrc/bogaudio.cpp | 4++++
Msrc/dsp/envelope.cpp | 41++++++++++++++++++++---------------------
Msrc/dsp/envelope.hpp | 19++++++++-----------
9 files changed, 380 insertions(+), 37 deletions(-)

diff --git a/res-src/ADSR-src.svg b/res-src/ADSR-src.svg @@ -0,0 +1,178 @@ +<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.5" 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)"> + <g transform="translate(3 0) rotate(240)"> + <text font-size="6.5pt" transform="translate(-2.8 3.3)">0</text> + </g> + </g> + <g transform="rotate(-172.92) translate(17 0)"> + <polyline points="0,0 1.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-145.13) translate(17 0)"> + <g transform="translate(3 0) rotate(145.13)"> + <text font-size="6.5pt" transform="translate(-2.8 3.3)">1</text> + </g> + </g> + <g transform="rotate(-105.84) translate(17 0)"> + <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-75.68) translate(17 0)"> + <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-50.26) translate(17 0)"> + <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-27.87) translate(17 0)"> + <g transform="translate(3 0) rotate(27.87)"> + <text font-size="6.5pt" transform="translate(-2.8 3.3)">5</text> + </g> + </g> + <g transform="rotate(-7.62) translate(17 0)"> + <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(11) translate(17 0)"> + <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(28.33) translate(17 0)"> + <polyline points="0,0 3,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(44.6) 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> + </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="button-small" viewBox="0 0 9px 9px"> + <g transform="translate(4.5 4.5)"> + <circle r="4" stroke-width="1" stroke="#00f" fill="#f00" /> + </g> + </symbol> + + <symbol id="light-small" viewBox="0 0 6.4px 6.4px"> + <rect width="6.4" height="6.4" fill="#0f0" /> + </symbol> + + <symbol id="light-tiny" viewBox="0 0 1.1px 1.1px"> + <rect width="3.2" height="3.2" 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" /> + + <!-- <polyline points="22.5,0 22.5,380" stroke-width="0.5" stroke="#0f0" /> --> + <!-- <rect width="45" height="10" fill="#0f0" transform="translate(0 68)" /> --> + <!-- <rect width="45" height="10" fill="#0f0" transform="translate(0 127)" /> --> + <!-- <rect width="45" height="10" fill="#0f0" transform="translate(0 177)" /> --> + + <g transform="rotate(-90) translate(-376 13)"> + <text class="title" font-size="7pt" letter-spacing="2.5px">ADSR</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(13 0)">ATT</text> + <use id="ATTACK_PARAM" xlink:href="#knob" transform="translate(0 0)" /> + <use xlink:href="#knobguide" transform="translate(0 0)" /> + <use id="ATTACK_LIGHT" xlink:href="#light-tiny" transform="translate(20.8 40)" /> + </g> + + <g transform="translate(0 82)"> + <text font-size="6pt" letter-spacing="2px" transform="translate(13 0)">DEC</text> + <use id="DECAY_PARAM" xlink:href="#knob" transform="translate(0 0)" /> + <use xlink:href="#knobguide" transform="translate(0 0)" /> + <use id="DECAY_LIGHT" xlink:href="#light-tiny" transform="translate(20.8 40)" /> + </g> + + <g transform="translate(0 139)"> + <text font-size="6pt" letter-spacing="2px" transform="translate(13 0)">SUS</text> + <use id="SUSTAIN_PARAM" xlink:href="#knob" transform="translate(0 0)" /> + <use xlink:href="#knobguide" transform="translate(0 0)" /> + <use id="SUSTAIN_LIGHT" xlink:href="#light-tiny" transform="translate(20.8 40)" /> + </g> + + <g transform="translate(0 196)"> + <text font-size="6pt" letter-spacing="2px" transform="translate(13 0)">REL</text> + <use id="RELEASE_PARAM" xlink:href="#knob" transform="translate(0 0)" /> + <use xlink:href="#knobguide" transform="translate(0 0)" /> + <use id="RELEASE_LIGHT" xlink:href="#light-tiny" transform="translate(20.8 40)" /> + </g> + + <g transform="translate(4 247)"> + <!-- <polyline points="0,3.2 70,3.2" stroke="#0f0" stroke-width="1" fill="none" /> --> + <use id="LINEAR_PARAM" xlink:href="#button-small" transform="translate(28 -1.3)" /> + <use id="LINEAR_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(9 6.1)">LIN</text> + <!-- <rect width="90" height="10" fill="#0f0" transform="translate(0 7)" /> --> + </g> + + <g transform="translate(0 262)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 28)" /> + <rect width="34" height="35" rx="5" fill="#fafafa" /> + <use id="GATE_INPUT" xlink:href="#input" transform="translate(5 3)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(5.5 35)">GATE</text> + </g> + <g transform="translate(5.5 41)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 -3)" /> + <rect width="34" height="35" rx="5" fill="#bbb" /> + <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(5 0)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(8.3 32)">OUT</text> + </g> + </g> +</svg> diff --git a/res/ADSR.svg b/res/ADSR.svg Binary files differ. diff --git a/src/ADSR.cpp b/src/ADSR.cpp @@ -0,0 +1,92 @@ + +#include "ADSR.hpp" + +void ADSR::onReset() { + _gateTrigger.reset(); + _envelope.reset(); + _modulationStep = modulationSteps; +} + +void ADSR::onSampleRateChange() { + _envelope.setSampleRate(engineGetSampleRate()); + _modulationStep = modulationSteps; +} + +void ADSR::step() { + lights[LINEAR_LIGHT].value = _linearMode = params[LINEAR_PARAM].value > 0.5f; + if (!(outputs[OUT_OUTPUT].active || inputs[GATE_INPUT].active)) { + return; + } + + ++_modulationStep; + if (_modulationStep >= modulationSteps) { + _modulationStep = 0; + + _envelope.setAttack(powf(params[ATTACK_PARAM].value, 2.0f) * 10.f); + _envelope.setDecay(powf(params[DECAY_PARAM].value, 2.0f) * 10.f); + _envelope.setSustain(params[SUSTAIN_PARAM].value); + _envelope.setRelease(powf(params[RELEASE_PARAM].value, 2.0f) * 10.f); + _envelope.setLinearShape(_linearMode); + } + + _gateTrigger.process(inputs[GATE_INPUT].value); + _envelope.setGate(_gateTrigger.isHigh()); + outputs[OUT_OUTPUT].value = _envelope.next() * 10.0f; + + lights[ATTACK_LIGHT].value = _envelope.isStage(bogaudio::dsp::ADSR::ATTACK_STAGE); + lights[DECAY_LIGHT].value = _envelope.isStage(bogaudio::dsp::ADSR::DECAY_STAGE); + lights[SUSTAIN_LIGHT].value = _envelope.isStage(bogaudio::dsp::ADSR::SUSTAIN_STAGE); + lights[RELEASE_LIGHT].value = _envelope.isStage(bogaudio::dsp::ADSR::RELEASE_STAGE); +} + +struct ADSRWidget : ModuleWidget { + ADSRWidget(ADSR* module) : ModuleWidget(module) { + box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT); + + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/ADSR.svg"))); + addChild(panel); + } + + addChild(Widget::create<ScrewSilver>(Vec(0, 0))); + addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15, 365))); + + // generated by svg_widgets.rb + auto attackParamPosition = Vec(7.5, 32.5); + auto decayParamPosition = Vec(7.5, 89.5); + auto sustainParamPosition = Vec(7.5, 146.5); + auto releaseParamPosition = Vec(7.5, 203.5); + auto linearParamPosition = Vec(32.0, 245.7); + + auto gateInputPosition = Vec(10.5, 265.0); + + auto outOutputPosition = Vec(10.5, 303.0); + + auto attackLightPosition = Vec(20.8, 65.0); + auto decayLightPosition = Vec(20.8, 122.0); + auto sustainLightPosition = Vec(20.8, 179.0); + auto releaseLightPosition = Vec(20.8, 236.0); + auto linearLightPosition = Vec(4.0, 247.0); + // end generated by svg_widgets.rb + + addParam(ParamWidget::create<Knob26>(attackParamPosition, module, ADSR::ATTACK_PARAM, 0.0, 1.0, 0.12)); + addParam(ParamWidget::create<Knob26>(decayParamPosition, module, ADSR::DECAY_PARAM, 0.0, 1.0, 0.31623)); + addParam(ParamWidget::create<Knob26>(sustainParamPosition, module, ADSR::SUSTAIN_PARAM, 0.0, 1.0, 1.0)); + addParam(ParamWidget::create<Knob26>(releaseParamPosition, module, ADSR::RELEASE_PARAM, 0.0, 1.0, 0.31623)); + addParam(ParamWidget::create<StatefulButton9>(linearParamPosition, module, ADSR::LINEAR_PARAM, 0.0, 1.0, 0.0)); + + addInput(Port::create<Port24>(gateInputPosition, Port::INPUT, module, ADSR::GATE_INPUT)); + + addOutput(Port::create<Port24>(outOutputPosition, Port::OUTPUT, module, ADSR::OUT_OUTPUT)); + + addChild(ModuleLightWidget::create<TinyLight<GreenLight>>(attackLightPosition, module, ADSR::ATTACK_LIGHT)); + addChild(ModuleLightWidget::create<TinyLight<GreenLight>>(decayLightPosition, module, ADSR::DECAY_LIGHT)); + addChild(ModuleLightWidget::create<TinyLight<GreenLight>>(sustainLightPosition, module, ADSR::SUSTAIN_LIGHT)); + addChild(ModuleLightWidget::create<TinyLight<GreenLight>>(releaseLightPosition, module, ADSR::RELEASE_LIGHT)); + addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(linearLightPosition, module, ADSR::LINEAR_LIGHT)); + } +}; + +Model* modelADSR = Model::create<ADSR, ADSRWidget>("Bogaudio", "Bogaudio-ADSR", "ADSR"); diff --git a/src/ADSR.hpp b/src/ADSR.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "bogaudio.hpp" +#include "dsp/envelope.hpp" + +extern Model* modelADSR; + +namespace bogaudio { + +struct ADSR : Module { + enum ParamsIds { + ATTACK_PARAM, + DECAY_PARAM, + SUSTAIN_PARAM, + RELEASE_PARAM, + LINEAR_PARAM, + NUM_PARAMS + }; + + enum InputsIds { + GATE_INPUT, + NUM_INPUTS + }; + + enum OutputsIds { + OUT_OUTPUT, + NUM_OUTPUTS + }; + + enum LightsIds { + ATTACK_LIGHT, + DECAY_LIGHT, + SUSTAIN_LIGHT, + RELEASE_LIGHT, + LINEAR_LIGHT, + NUM_LIGHTS + }; + + const int modulationSteps = 100; + int _modulationStep = 0; + bool _linearMode = false; + SchmittTrigger _gateTrigger; + bogaudio::dsp::ADSR _envelope; + + ADSR() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + onSampleRateChange(); + } + + virtual void onReset() override; + virtual void onSampleRateChange() override; + virtual void step() override; +}; + +} // namespace bogaudio diff --git a/src/FMOp.hpp b/src/FMOp.hpp @@ -79,7 +79,7 @@ struct FMOp : Module { FMOp() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) - , _envelope(ADSR::LINEAR_SHAPE) + , _envelope(true) { onReset(); onSampleRateChange(); diff --git a/src/Test2.cpp b/src/Test2.cpp @@ -60,11 +60,29 @@ void Test2::step() { _trigger.process(inputs[IN_INPUT].value); } _adsr.setGate(_trigger.isHigh()); - _adsr.setAttack(params[PARAM1A_PARAM].value * 10.0f); - _adsr.setDecay(params[PARAM1B_PARAM].value * 10.0f); + _adsr.setAttack(powf(params[PARAM1A_PARAM].value, 2.0f) * 10.0f); + _adsr.setDecay(powf(params[PARAM1B_PARAM].value, 2.0f) * 10.0f); _adsr.setSustain(params[PARAM2A_PARAM].value); - _adsr.setRelease(params[PARAM2B_PARAM].value * 10.0f); - _adsr.setShape(params[PARAM3A_PARAM].value > 0.5f ? ADSR::LINEAR_SHAPE : ADSR::EXPONENTIAL_SHAPE); + _adsr.setRelease(powf(params[PARAM2B_PARAM].value, 2.0f) * 10.0f); + float attackShape = params[PARAM3A_PARAM].value; + if (attackShape < 0.5f) { + attackShape += 0.5f; + } + else { + attackShape -= 0.5; + attackShape *= 2.0f; + attackShape += 1.0f; + } + float decayShape = params[PARAM3B_PARAM].value; + if (decayShape < 0.5f) { + decayShape += 0.5f; + } + else { + decayShape -= 0.5; + decayShape *= 2.0f; + decayShape += 1.0f; + } + _adsr.setShapes(attackShape, decayShape, decayShape); outputs[OUT_OUTPUT].value = _adsr.next() * 10.0f; } #endif diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp @@ -15,6 +15,7 @@ #include "Analyzer.hpp" +#include "ADSR.hpp" #include "Detune.hpp" #include "DGate.hpp" #include "Manual.hpp" @@ -55,6 +56,9 @@ void init(rack::Plugin *p) { p->addModel(modelAnalyzer); +#ifdef EXPERIMENTAL + p->addModel(modelADSR); +#endif p->addModel(modelDetune); p->addModel(modelDGate); p->addModel(modelManual); diff --git a/src/dsp/envelope.cpp b/src/dsp/envelope.cpp @@ -46,8 +46,22 @@ void ADSR::setRelease(float seconds) { _release = std::max(seconds, 0.001f); } -void ADSR::setShape(Shape shape) { - _shape = shape; +void ADSR::setLinearShape(bool linear) { + if (linear) { + setShapes(1.0f, 1.0f, 1.0f); + } + else { + setShapes(0.5f, 0.5f, 0.5f); + } +} + +void ADSR::setShapes(float attackShape, float decayShape, float releaseShape) { + assert(attackShape >= 0.1f && attackShape <= 10.0f); + assert(decayShape >= 0.0f && decayShape <= 10.0f); + assert(releaseShape >= 0.0f && releaseShape <= 10.0f); + _attackShape = attackShape; + _decayShape = decayShape; + _releaseShape = releaseShape; } float ADSR::_next() { @@ -77,16 +91,7 @@ float ADSR::_next() { } case RELEASE_STAGE: { _stage = ATTACK_STAGE; - switch (_shape) { - case EXPONENTIAL_SHAPE: { - _stageProgress = _attack * powf(_envelope, 2.0f); - break; - } - case LINEAR_SHAPE: { - _stageProgress = _attack * _envelope; - break; - } - } + _stageProgress = _attack * powf(_envelope, 1.0f / _releaseShape); break; } } @@ -121,17 +126,13 @@ float ADSR::_next() { case ATTACK_STAGE: { _stageProgress += _sampleTime; _envelope = _stageProgress / _attack; - if (_shape == EXPONENTIAL_SHAPE) { - _envelope = powf(_envelope, 0.5f); - } + _envelope = powf(_envelope, _attackShape); break; } case DECAY_STAGE: { _stageProgress += _sampleTime; _envelope = _stageProgress / _decay; - if (_shape == EXPONENTIAL_SHAPE) { - _envelope = powf(_envelope, 0.5f); - } + _envelope = powf(_envelope, _decayShape); _envelope *= 1.0f - _sustain; _envelope = 1.0f - _envelope; break; @@ -143,9 +144,7 @@ float ADSR::_next() { case RELEASE_STAGE: { _stageProgress += _sampleTime; _envelope = _stageProgress / _release; - if (_shape == EXPONENTIAL_SHAPE) { - _envelope = powf(_envelope, 0.5f); - } + _envelope = powf(_envelope, _releaseShape); _envelope *= _releaseLevel; _envelope = _releaseLevel - _envelope; break; diff --git a/src/dsp/envelope.hpp b/src/dsp/envelope.hpp @@ -30,26 +30,21 @@ struct ADSR : EnvelopeGenerator { RELEASE_STAGE }; - enum Shape { - LINEAR_SHAPE, - EXPONENTIAL_SHAPE - }; - Stage _stage = STOPPED_STAGE; bool _gated = false; float _attack = 0.0f; float _decay = 0.0f; float _sustain = 1.0f; float _release = 0.0f; - Shape _shape; + float _attackShape; + float _decayShape; + float _releaseShape; float _stageProgress = 0.0f; float _releaseLevel = 0.0f; float _envelope = 0.0f; - ADSR(Shape shape = EXPONENTIAL_SHAPE, float sampleRate = 1000.0f) - : EnvelopeGenerator(sampleRate) - , _shape(shape) - { + ADSR(bool linear = false, float sampleRate = 1000.0f) : EnvelopeGenerator(sampleRate) { + setLinearShape(linear); } void reset(); @@ -58,7 +53,9 @@ struct ADSR : EnvelopeGenerator { void setDecay(float seconds); void setSustain(float level); void setRelease(float seconds); - void setShape(Shape shape); + void setLinearShape(bool linear); + void setShapes(float attackShape, float decayShape, float releaseShape); + bool isStage(Stage stage) { return _stage == stage; } virtual float _next() override; };