commit c52c16fc1478e4cbd69f73619c24ff35f716572c
parent e82a6928862f79feb2553473fcdfa889a8ac09e6
Author: Matt Demanett <matt@demanett.net>
Date: Thu, 15 Mar 2018 00:14:01 -0400
Work-in-progress FM operator oacillator (yes it is actually PM).
Diffstat:
7 files changed, 469 insertions(+), 1 deletion(-)
diff --git a/res-src/FMOp-src.svg b/res-src/FMOp-src.svg
@@ -0,0 +1,206 @@
+<svg
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="300"
+ height="380"
+ viewBox="0 0 300 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 38px 38px">
+ <g transform="translate(19 19)">
+ <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="18" stroke-width="1" stroke="#00f" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="knob-medium" viewBox="0 0 26px 26px">
+ <g transform="translate(13 13)">
+ <polyline points="-3,0 3,0" stroke-width="1" stroke="#00f" />
+ <polyline points="0,-3 0,3" stroke-width="1" stroke="#00f" />
+ <circle cx="0" cy="0" r="12" stroke-width="1" stroke="#00f" fill="none" />
+ </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="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" viewBox="0 0 1.1px 1.1px">
+ <rect width="3.2" height="3.2" fill="#0f0" />
+ </symbol>
+
+ <symbol id="light-red" viewBox="0 0 1.1px 1.1px">
+ <rect width="3.2" height="3.2" fill="#f00" />
+ </symbol>
+ </defs>
+
+ <rect width="100%" height="100%" fill="#ddd" />
+ <polyline points="1,1 299,1 299,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 299.5,0.5 299.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 300,0 300,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <!-- <rect width="115" height="20" fill="#0f0" transform="translate(0 0)" /> -->
+ <!-- <rect width="115" height="20" fill="#0f0" transform="translate(185 0)" /> -->
+
+ <text class="title" x="114" y="19" font-size="12pt" letter-spacing="4px">FM-OP</text>
+ <g transform="translate(110 374)">
+ <text class="brand" font-size="8pt" letter-spacing="2px">BOGAUDIO</text>
+ <rect width="3.0" height="3" fill="#ddd" transform="translate(24 -5)" />
+ </g>
+
+ <g transform="translate(10 50)">
+ <g transform="translate(10 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 40) rotate(270)">RATIO</text>
+ <use id="RATIO_PARAM" xlink:href="#knob" transform="translate(20 0)" />
+ </g>
+ <g transform="translate(90 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 35) rotate(270)">FINE</text>
+ <use id="FINE_PARAM" xlink:href="#knob-smallest" transform="translate(20 12)" />
+ </g>
+ </g>
+
+ <g transform="translate(10 130)">
+ <g transform="translate(10 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 40) rotate(270)">ATTACK</text>
+ <use id="ATTACK_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ <g transform="translate(65 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 35) rotate(270)">DECAY</text>
+ <use id="DECAY_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ <g transform="translate(120 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 40) rotate(270)">SUSTAIN</text>
+ <use id="SUSTAIN_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ <g transform="translate(175 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 40) rotate(270)">RELEASE</text>
+ <use id="RELEASE_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ </g>
+
+ <g transform="translate(20 160)">
+ <g transform="translate(0 17.5)">
+ <use id="BYPASS_LIGHT" xlink:href="#light" transform="translate(0 0)" />
+ <text font-size="7pt" letter-spacing="2px" transform="translate(7 5)">BYPASS</text>
+ </g>
+ <use id="BYPASS_PARAM" xlink:href="#button-small" transform="translate(58 14.5)" />
+ </g>
+
+ <g transform="translate(10 215)">
+ <g transform="translate(10 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 35) rotate(270)">DEPTH</text>
+ <use id="DEPTH_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ <g transform="translate(65 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 45) rotate(270)">FEEDBACK</text>
+ <use id="FEEDBACK_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ <g transform="translate(120 0)">
+ <text font-size="8pt" letter-spacing="2px" transform="translate(8 35) rotate(270)">LEVEL</text>
+ <use id="LEVEL_PARAM" xlink:href="#knob-medium" transform="translate(20 0)" />
+ </g>
+ </g>
+
+ <g transform="translate(0 270)">
+ <g transform="translate(10 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="SUSTAIN_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(11.5 36)">S</text>
+ </g>
+ <g transform="translate(50 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="DEPTH_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(11.5 36)">D</text>
+ </g>
+ <g transform="translate(90 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="FEEDBACK_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(9 36)">FB</text>
+ </g>
+ <g transform="translate(130 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="LEVEL_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(11.5 36)">L</text>
+ </g>
+ <g transform="translate(255 17.5)">
+ <use id="NEGATIVE_LIGHT" xlink:href="#light-red" transform="translate(0 0)" />
+ <text font-size="7pt" letter-spacing="2px" transform="translate(7 5)">NEG</text>
+ </g>
+ </g>
+
+ <g transform="translate(0 320)">
+ <g transform="translate(10 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="PITCH_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(1 36)">V/OCT</text>
+ </g>
+ <g transform="translate(50 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="GATE_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(3 36)">GATE</text>
+ </g>
+ <g transform="translate(90 0)">
+ <rect width="30" height="40" rx="5" fill="#fafafa" />
+ <use id="FM_INPUT" xlink:href="#input" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(8.5 36)">FM</text>
+ </g>
+
+ <g transform="translate(180 0)">
+ <rect width="30" height="40" rx="5" fill="#bbb" />
+ <use id="PITCH_OUTPUT" xlink:href="#output" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(1 36)">V/OCT</text>
+ </g>
+ <g transform="translate(220 0)">
+ <rect width="30" height="40" rx="5" fill="#bbb" />
+ <use id="GATE_OUTPUT" xlink:href="#output" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(3 36)">GATE</text>
+ </g>
+ <g transform="translate(260 0)">
+ <rect width="30" height="40" rx="5" fill="#bbb" />
+ <use id="AUDIO_OUTPUT" xlink:href="#output" transform="translate(3 3)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(6 36)">OUT</text>
+ </g>
+ </g>
+</svg>
diff --git a/res/FMOp.svg b/res/FMOp.svg
Binary files differ.
diff --git a/src/FMOp.cpp b/src/FMOp.cpp
@@ -0,0 +1,176 @@
+
+#include "FMOp.hpp"
+#include "dsp/pitch.hpp"
+
+void FMOp::onReset() {
+ _steps = modulationSteps;
+ _envelope.reset();
+ _gateTrigger.reset();
+}
+
+void FMOp::onSampleRateChange() {
+ _steps = modulationSteps;
+ _envelope.setSampleRate(engineGetSampleRate());
+ _phasor.setSampleRate(engineGetSampleRate());
+ _sineTable.setSampleRate(engineGetSampleRate());
+}
+
+void FMOp::step() {
+ lights[BYPASS_LIGHT].value = !_envelopeOn;
+ lights[NEGATIVE_LIGHT].value = 0.0f;
+ if (!(outputs[PITCH_OUTPUT].active || outputs[GATE_OUTPUT].active || outputs[AUDIO_OUTPUT].active)) {
+ return;
+ }
+
+ float pitchIn = 0.0f;
+ if (inputs[PITCH_INPUT].active) {
+ pitchIn = inputs[PITCH_INPUT].value;
+ }
+ float gateIn = 0.0f;
+ if (inputs[GATE_INPUT].active) {
+ gateIn = inputs[GATE_INPUT].value;
+ }
+
+ ++_steps;
+ if (_steps >= modulationSteps) {
+ _steps = 0;
+
+ float ratio = params[RATIO_PARAM].value;
+ if (ratio < 0.0f) {
+ ratio = std::max(1.0f + ratio, 0.01f);
+ }
+ else {
+ ratio *= 9.0f;
+ ratio += 1.0f;
+ }
+ _baseHZ = pitchIn;
+ _baseHZ += params[FINE_PARAM].value;
+ _baseHZ = cvToFrequency(_baseHZ);
+ _baseHZ *= ratio;
+
+ bool envelopeOn = params[BYPASS_PARAM].value < 0.5f;
+ if (_envelopeOn != envelopeOn) {
+ if (_envelopeOn) {
+ _envelope.reset();
+ }
+ _envelopeOn = envelopeOn;
+ }
+ if (_envelopeOn) {
+ float sustain = params[SUSTAIN_PARAM].value;
+ if (inputs[SUSTAIN_INPUT].active) {
+ sustain *= clamp(inputs[SUSTAIN_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+ _envelope.setAttack(params[ATTACK_PARAM].value);
+ _envelope.setDecay(params[DECAY_PARAM].value);
+ _envelope.setSustain(sustain);
+ _envelope.setRelease(params[RELEASE_PARAM].value);
+ }
+
+ _feedback = params[FEEDBACK_PARAM].value;
+ if (inputs[FEEDBACK_INPUT].active) {
+ _feedback *= clamp(inputs[FEEDBACK_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+
+ _depth = params[DEPTH_PARAM].value;
+ if (inputs[DEPTH_INPUT].active) {
+ _depth *= clamp(inputs[DEPTH_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+
+ _level = params[LEVEL_PARAM].value;
+ if (inputs[LEVEL_INPUT].active) {
+ _level *= clamp(inputs[LEVEL_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+ }
+
+ _phasor.setFrequency(_baseHZ);
+ float offset = _feedback * _phasor.next();
+ if (inputs[FM_INPUT].active) {
+ offset += clamp(inputs[FM_INPUT].value / 5.0f, -1.0f, 1.0f) * _depth * 10.0f;
+ }
+ float out = _sineTable.nextFromPhasor(_phasor, Phasor::radiansToPhase(offset));
+ out *= _level;
+ if (_envelopeOn) {
+ _gateTrigger.process(gateIn);
+ _envelope.setGate(_gateTrigger.isHigh());
+ out *= _envelope.next();
+ }
+ lights[NEGATIVE_LIGHT].value = _phasor._delta + offset < 0.0f;
+
+ outputs[PITCH_OUTPUT].value = pitchIn;
+ outputs[GATE_OUTPUT].value = gateIn;
+ outputs[AUDIO_OUTPUT].value = out * 5.0f;
+}
+
+struct FMOpWidget : ModuleWidget {
+ FMOpWidget(FMOp* module) : ModuleWidget(module) {
+ box.size = Vec(RACK_GRID_WIDTH * 20, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/FMOp.svg")));
+ addChild(panel);
+ }
+
+ addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
+ addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0)));
+ addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
+ addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365)));
+
+ // generated by svg_widgets.rb
+ auto ratioParamPosition = Vec(40.5, 50.5);
+ auto fineParamPosition = Vec(120.0, 62.0);
+ auto attackParamPosition = Vec(40.5, 130.5);
+ auto decayParamPosition = Vec(95.5, 130.5);
+ auto sustainParamPosition = Vec(150.5, 130.5);
+ auto releaseParamPosition = Vec(205.5, 130.5);
+ auto bypassParamPosition = Vec(78.0, 174.5);
+ auto depthParamPosition = Vec(40.5, 215.5);
+ auto feedbackParamPosition = Vec(95.5, 215.5);
+ auto levelParamPosition = Vec(150.5, 215.5);
+
+ auto sustainInputPosition = Vec(13.0, 273.0);
+ auto depthInputPosition = Vec(53.0, 273.0);
+ auto feedbackInputPosition = Vec(93.0, 273.0);
+ auto levelInputPosition = Vec(133.0, 273.0);
+ auto pitchInputPosition = Vec(13.0, 323.0);
+ auto gateInputPosition = Vec(53.0, 323.0);
+ auto fmInputPosition = Vec(93.0, 323.0);
+
+ auto pitchOutputPosition = Vec(183.0, 323.0);
+ auto gateOutputPosition = Vec(223.0, 323.0);
+ auto audioOutputPosition = Vec(263.0, 323.0);
+
+ auto bypassLightPosition = Vec(20.0, 177.5);
+ auto negativeLightPosition = Vec(255.0, 287.5);
+ // end generated by svg_widgets.rb
+
+ addParam(ParamWidget::create<Knob38>(ratioParamPosition, module, FMOp::RATIO_PARAM, -1.0, 1.0, 0.0));
+ addParam(ParamWidget::create<Knob16>(fineParamPosition, module, FMOp::FINE_PARAM, -1.0, 1.0, 0.0));
+ addParam(ParamWidget::create<Knob26>(attackParamPosition, module, FMOp::ATTACK_PARAM, 0.0, 1.0, 0.1));
+ addParam(ParamWidget::create<Knob26>(decayParamPosition, module, FMOp::DECAY_PARAM, 0.0, 1.0, 0.1));
+ addParam(ParamWidget::create<Knob26>(sustainParamPosition, module, FMOp::SUSTAIN_PARAM, 0.0, 1.0, 1.0));
+ addParam(ParamWidget::create<Knob26>(releaseParamPosition, module, FMOp::RELEASE_PARAM, 0.0, 1.0, 0.3));
+ addParam(ParamWidget::create<StatefulButton9>(bypassParamPosition, module, FMOp::BYPASS_PARAM, 0.0, 1.0, 1.0));
+ addParam(ParamWidget::create<Knob26>(depthParamPosition, module, FMOp::DEPTH_PARAM, 0.0, 1.0, 0.5));
+ addParam(ParamWidget::create<Knob26>(feedbackParamPosition, module, FMOp::FEEDBACK_PARAM, 0.0, 1.0, 0.0));
+ addParam(ParamWidget::create<Knob26>(levelParamPosition, module, FMOp::LEVEL_PARAM, 0.0, 1.0, 1.0));
+
+ addInput(Port::create<Port24>(sustainInputPosition, Port::INPUT, module, FMOp::SUSTAIN_INPUT));
+ addInput(Port::create<Port24>(depthInputPosition, Port::INPUT, module, FMOp::DEPTH_INPUT));
+ addInput(Port::create<Port24>(feedbackInputPosition, Port::INPUT, module, FMOp::FEEDBACK_INPUT));
+ addInput(Port::create<Port24>(levelInputPosition, Port::INPUT, module, FMOp::LEVEL_INPUT));
+ addInput(Port::create<Port24>(pitchInputPosition, Port::INPUT, module, FMOp::PITCH_INPUT));
+ addInput(Port::create<Port24>(gateInputPosition, Port::INPUT, module, FMOp::GATE_INPUT));
+ addInput(Port::create<Port24>(fmInputPosition, Port::INPUT, module, FMOp::FM_INPUT));
+
+ addOutput(Port::create<Port24>(pitchOutputPosition, Port::OUTPUT, module, FMOp::PITCH_OUTPUT));
+ addOutput(Port::create<Port24>(gateOutputPosition, Port::OUTPUT, module, FMOp::GATE_OUTPUT));
+ addOutput(Port::create<Port24>(audioOutputPosition, Port::OUTPUT, module, FMOp::AUDIO_OUTPUT));
+
+ addChild(ModuleLightWidget::create<TinyLight<GreenLight>>(bypassLightPosition, module, FMOp::BYPASS_LIGHT));
+ addChild(ModuleLightWidget::create<TinyLight<RedLight>>(negativeLightPosition, module, FMOp::NEGATIVE_LIGHT));
+ }
+};
+
+Model* modelFMOp = Model::create<FMOp, FMOpWidget>("Bogaudio", "Bogaudio-FMOp", "FM-OP");
diff --git a/src/FMOp.hpp b/src/FMOp.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "dsp/envelope.hpp"
+#include "dsp/oscillator.hpp"
+
+using namespace bogaudio::dsp;
+
+extern Model* modelFMOp;
+
+namespace bogaudio {
+
+struct FMOp : Module {
+ enum ParamsIds {
+ RATIO_PARAM,
+ FINE_PARAM,
+ ATTACK_PARAM,
+ DECAY_PARAM,
+ SUSTAIN_PARAM,
+ RELEASE_PARAM,
+ BYPASS_PARAM,
+ DEPTH_PARAM,
+ FEEDBACK_PARAM,
+ LEVEL_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ SUSTAIN_INPUT,
+ DEPTH_INPUT,
+ FEEDBACK_INPUT,
+ LEVEL_INPUT,
+ PITCH_INPUT,
+ GATE_INPUT,
+ FM_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ PITCH_OUTPUT,
+ GATE_OUTPUT,
+ AUDIO_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ BYPASS_LIGHT,
+ NEGATIVE_LIGHT,
+ NUM_LIGHTS
+ };
+
+ const int modulationSteps = 100;
+ int _steps = 0;
+ float _baseHZ = 0.0f;
+ float _feedback = 0.0f;
+ float _depth = 0.0f;
+ float _level = 0.0f;
+ bool _envelopeOn = false;
+ ADSR _envelope;
+ Phasor _phasor;
+ SineTableOscillator _sineTable;
+ SchmittTrigger _gateTrigger;
+
+ FMOp()
+ : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS)
+ , _envelope(engineGetSampleRate())
+ , _phasor(engineGetSampleRate(), 0.0f)
+ , _sineTable(engineGetSampleRate(), 0.0f)
+ {
+ onReset();
+ }
+
+ virtual void onReset() override;
+ virtual void onSampleRateChange() override;
+ virtual void step() override;
+};
+
+} // namespace bogaudio
diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp
@@ -3,6 +3,7 @@
#include "Additator.hpp"
#include "EightFO.hpp"
+#include "FMOp.hpp"
#include "Shaper.hpp"
#include "ShaperPlus.hpp"
@@ -39,6 +40,7 @@ void init(rack::Plugin *p) {
#ifdef EXPERIMENTAL
p->addModel(modelAdditator);
p->addModel(modelEightFO);
+ p->addModel(modelFMOp);
#endif
p->addModel(modelShaper);
diff --git a/src/dsp/envelope.cpp b/src/dsp/envelope.cpp
@@ -14,6 +14,11 @@ void EnvelopeGenerator::setSampleRate(float sampleRate) {
}
}
+void ADSR::reset() {
+ _stage = STOPPED_STAGE;
+ _gated = false;
+ _envelope = 0.0f;
+}
void ADSR::setGate(bool high) {
_gated = high;
@@ -127,5 +132,5 @@ float ADSR::_next() {
}
}
- return 10.0f * _envelope;
+ return _envelope;
}
diff --git a/src/dsp/envelope.hpp b/src/dsp/envelope.hpp
@@ -45,6 +45,7 @@ struct ADSR : EnvelopeGenerator {
{
}
+ void reset();
void setGate(bool high);
void setAttack(float seconds);
void setDecay(float seconds);