BogaudioModules

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

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:
Ares-src/FMOp-src.svg | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/FMOp.svg | 0
Asrc/FMOp.cpp | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/FMOp.hpp | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/bogaudio.cpp | 2++
Msrc/dsp/envelope.cpp | 7++++++-
Msrc/dsp/envelope.hpp | 1+
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);