BogaudioModules

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

commit 1665119747bb497f005433d1b4958aef737f17c9
parent efb795aa10dd281ad3b79c81aa72a43f404a510c
Author: Matt Demanett <matt@demanett.net>
Date:   Tue, 31 May 2022 23:07:34 -0400

LFOs: add offset range option, such that the offset may be +/-10V. #196

Diffstat:
MREADME-prerelease.md | 2++
Msrc/EightFO.cpp | 5+++--
Msrc/EightFO.hpp | 2+-
Msrc/FourFO.cpp | 5+++--
Msrc/FourFO.hpp | 2+-
Msrc/LFO.cpp | 8++++++--
Msrc/LFO.hpp | 2+-
Msrc/LLFO.cpp | 10+++++++---
Msrc/LLFO.hpp | 2+-
Msrc/lfo_base.cpp | 25+++++++++++++++++++++++++
Msrc/lfo_base.hpp | 25+++++++++++++++++++++++++
11 files changed, 75 insertions(+), 13 deletions(-)

diff --git a/README-prerelease.md b/README-prerelease.md @@ -201,6 +201,8 @@ The stepped random output selects a new random value in the range +/-5V once eac The sampling feature is not used with the square and stepped outputs, but applies to the others. +By default OFFSET varies from -5 to +5V. The "Offset range" context-menu item allows this range to be set to +/-10V. Note that the output is clipped to +/-12V. SCALE is applied to the output before the offset is added. + Output smoothing is applied to the signal last, after offset and scale. Smoothing is implemented with a slew limiter (see <a href="#slew">SLEW</a>), where the rise/fall times are a function of both the LFO rate and the smoothing amount. The effect of smoothing varies radically with the amount, the wave selected, and with use of sampling and pulse width. Some examples: - Smoothing will turn the stepped random "wave" into a random walk (similar to, but distinct from, the output of <a href="#walk">WALK</a>). - The triangle output is unaffected by smoothing even at its maximum (unless sampling is turned up). diff --git a/src/EightFO.cpp b/src/EightFO.cpp @@ -118,6 +118,7 @@ void EightFO::modulateChannel(int c) { if (inputs[OFFSET_INPUT].isConnected()) { e.offset *= clamp(inputs[OFFSET_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); } + e.offset *= _offsetScale; e.offset *= 5.0f; e.scale = params[SCALE_PARAM].getValue(); if (inputs[SCALE_INPUT].isConnected()) { @@ -206,7 +207,7 @@ void EightFO::updateOutput(int c, bool useSample, Output& output, Phasor::phase_ } sample = amplitude * _engines[c]->scale * v + _engines[c]->offset; } - output.setVoltage(smoother.next(sample), c); + output.setVoltage(clamp(smoother.next(sample), -12.0f, 12.0f), c); active = true; } else { @@ -214,7 +215,7 @@ void EightFO::updateOutput(int c, bool useSample, Output& output, Phasor::phase_ } } -struct EightFOWidget : BGModuleWidget { +struct EightFOWidget : LFOBaseModuleWidget { static constexpr int hp = 17; EightFOWidget(EightFO* module) { diff --git a/src/EightFO.hpp b/src/EightFO.hpp @@ -134,7 +134,7 @@ struct EightFO : LFOBase { configButton(SLOW_PARAM, "Slow"); configParam(SAMPLE_PWM_PARAM, -1.0, 1.0, 0.0, "Width", "%", 0.0f, 100.0f); configParam(SMOOTH_PARAM, 0.0f, 1.0f, 0.0f, "Smoothing", "%", 0.0f, 100.0f); - configParam(OFFSET_PARAM, -1.0, 1.0, 0.0, "Offset", " V", 0.0f, 5.0f); + configParam<OffsetParamQuantity>(OFFSET_PARAM, -1.0, 1.0, 0.0, "Offset", " V", 0.0f, 5.0f); configParam(SCALE_PARAM, 0.0, 1.0, 1.0, "Scale", "%", 0.0f, 100.0f); configParam(PHASE7_PARAM, -1.0, 1.0, 0.0, "Phase 315", "º", 0.0f, 180.0f); configParam(PHASE6_PARAM, -1.0, 1.0, 0.0, "Phase 270", "º", 0.0f, 180.0f); diff --git a/src/FourFO.cpp b/src/FourFO.cpp @@ -106,6 +106,7 @@ void FourFO::modulateChannel(int c) { if (inputs[OFFSET_INPUT].isConnected()) { e.offset *= clamp(inputs[OFFSET_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); } + e.offset *= _offsetScale; e.offset *= 5.0f; e.scale = params[SCALE_PARAM].getValue(); if (inputs[SCALE_INPUT].isConnected()) { @@ -186,7 +187,7 @@ void FourFO::updateOutput(int c, bool useSample, Output& output, Phasor::phase_d } sample = amplitude * _engines[c]->scale * v + _engines[c]->offset; } - output.setVoltage(smoother.next(sample), c); + output.setVoltage(clamp(smoother.next(sample), -12.0f, 12.0f), c); active = true; } else { @@ -194,7 +195,7 @@ void FourFO::updateOutput(int c, bool useSample, Output& output, Phasor::phase_d } } -struct FourFOWidget : BGModuleWidget { +struct FourFOWidget : LFOBaseModuleWidget { static constexpr int hp = 10; FourFOWidget(FourFO* module) { diff --git a/src/FourFO.hpp b/src/FourFO.hpp @@ -106,7 +106,7 @@ struct FourFO : LFOBase { configButton(SLOW_PARAM, "Slow"); configParam(SAMPLE_PWM_PARAM, -1.0, 1.0, 0.0, "Width", "%", 0.0f, 100.0f); configParam(SMOOTH_PARAM, 0.0f, 1.0f, 0.0f, "Smoothing", "%", 0.0f, 100.0f); - configParam(OFFSET_PARAM, -1.0, 1.0, 0.0, "Offset", " V", 0.0f, 5.0f); + configParam<OffsetParamQuantity>(OFFSET_PARAM, -1.0, 1.0, 0.0, "Offset", " V", 0.0f, 5.0f); configParam(SCALE_PARAM, 0.0, 1.0, 1.0, "Scale", "%", 0.0f, 100.0f); configParam(PHASE3_PARAM, -1.0, 1.0, 0.0, "Phase 270", "º", 0.0f, 180.0f); configParam(PHASE2_PARAM, -1.0, 1.0, 0.0, "Phase 180", "º", 0.0f, 180.0f); diff --git a/src/LFO.cpp b/src/LFO.cpp @@ -26,11 +26,13 @@ void LFO::sampleRateChange() { } json_t* LFO::saveToJson(json_t* root) { + root = LFOBase::saveToJson(root); json_object_set_new(root, OFFSET_CV_TO_SMOOTHING, json_boolean(_useOffsetCvForSmooth)); return root; } void LFO::loadFromJson(json_t* root) { + LFOBase::loadFromJson(root); json_t* ocv = json_object_get(root, OFFSET_CV_TO_SMOOTHING); if (ocv) { _useOffsetCvForSmooth = json_boolean_value(ocv); @@ -107,6 +109,7 @@ void LFO::modulateChannel(int c) { if (!_useOffsetCvForSmooth && inputs[OFFSET_INPUT].isConnected()) { e.offset *= clamp(inputs[OFFSET_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); } + e.offset *= _offsetScale; e.offset *= 5.0f; e.scale = params[SCALE_PARAM].getValue(); @@ -151,7 +154,7 @@ void LFO::updateOutput(int c, Phasor& wave, bool useSample, bool invert, Output& } sample += _engines[c]->offset; } - output.setVoltage(smoother.next(sample), c); + output.setVoltage(clamp(smoother.next(sample), -12.0f, 12.0f), c); active = true; } else { @@ -159,7 +162,7 @@ void LFO::updateOutput(int c, Phasor& wave, bool useSample, bool invert, Output& } } -struct LFOWidget : BGModuleWidget { +struct LFOWidget : LFOBaseModuleWidget { static constexpr int hp = 10; LFOWidget(LFO* module) { @@ -218,6 +221,7 @@ struct LFOWidget : BGModuleWidget { void contextMenu(Menu* menu) override { auto m = dynamic_cast<LFO*>(module); assert(m); + LFOBaseModuleWidget::contextMenu(menu); OptionsMenuItem* uo = new OptionsMenuItem("OFF/SM input routing"); uo->addItem(OptionMenuItem("To offset (OFF)", [m]() { return !m->_useOffsetCvForSmooth; }, [m]() { m->_useOffsetCvForSmooth = false; })); diff --git a/src/LFO.hpp b/src/LFO.hpp @@ -90,7 +90,7 @@ struct LFO : LFOBase { configParam(SAMPLE_PARAM, 0.0f, 1.0f, 0.0f, "Output sampling", "%", 0.0f, 100.0f); configParam(PW_PARAM, -1.0f, 1.0f, 0.0f, "Pulse width", "%", 0.0f, 100.0f*0.5f*(1.0f - 2.0f * SquareOscillator::minPulseWidth), 50.0f); configParam(SMOOTH_PARAM, 0.0f, 1.0f, 0.0f, "Smoothing", "%", 0.0f, 100.0f); - configParam(OFFSET_PARAM, -1.0f, 1.0f, 0.0f, "Offset", " V", 0.0f, 5.0f); + configParam<OffsetParamQuantity>(OFFSET_PARAM, -1.0f, 1.0f, 0.0f, "Offset", " V", 0.0f, 5.0f); configParam(SCALE_PARAM, 0.0f, 1.0f, 1.0f, "Scale", "%", 0.0f, 100.0f); configInput(SAMPLE_INPUT, "Sample CV"); diff --git a/src/LLFO.cpp b/src/LLFO.cpp @@ -19,6 +19,7 @@ void LLFO::sampleRateChange() { } json_t* LLFO::saveToJson(json_t* root) { + root = LFOBase::saveToJson(root); json_object_set_new(root, OUTPUT_SAMPLING, json_real(_sample)); json_object_set_new(root, PULSE_WIDTH, json_real(_pulseWidth)); json_object_set_new(root, SMOOTHING, json_real(_smooth)); @@ -27,6 +28,8 @@ json_t* LLFO::saveToJson(json_t* root) { } void LLFO::loadFromJson(json_t* root) { + LFOBase::loadFromJson(root); + json_t* os = json_object_get(root, OUTPUT_SAMPLING); if (os) { _sample = json_real_value(os); @@ -125,7 +128,7 @@ void LLFO::modulate() { } } - _offset = params[OFFSET_PARAM].getValue() * 5.0f; + _offset = params[OFFSET_PARAM].getValue() * _offsetScale * 5.0f; _scale = params[SCALE_PARAM].getValue(); } @@ -178,7 +181,7 @@ void LLFO::processChannel(const ProcessArgs& args, int c) { } outputs[OUT_OUTPUT].setChannels(_channels); - outputs[OUT_OUTPUT].setVoltage(_smoother[c].next(_currentSample[c]), c); + outputs[OUT_OUTPUT].setVoltage(clamp(_smoother[c].next(_currentSample[c]), -12.0f, 12.0f), c); } struct SampleQuantity : Quantity { @@ -292,7 +295,7 @@ struct LLFOSliderMenuItem : MenuItem { } }; -struct LLFOWidget : BGModuleWidget { +struct LLFOWidget : LFOBaseModuleWidget { static constexpr int hp = 3; LLFOWidget(LLFO* module) { @@ -345,6 +348,7 @@ struct LLFOWidget : BGModuleWidget { void contextMenu(Menu* menu) override { auto m = dynamic_cast<LLFO*>(module); assert(m); + LFOBaseModuleWidget::contextMenu(menu); menu->addChild(new LLFOSliderMenuItem<SampleQuantity>(m, "Output sampling")); menu->addChild(new LLFOSliderMenuItem<PWQuantity>(m, "Pulse width")); diff --git a/src/LLFO.hpp b/src/LLFO.hpp @@ -89,7 +89,7 @@ struct LLFO : LFOBase { configParam<LFOFrequencyParamQuantity>(FREQUENCY_PARAM, -5.0f, 8.0f, 0.0f, "Frequency", " Hz"); configSwitch(WAVE_PARAM, 0.0f, 6.0f, 0.0f, "Waveform", {"Sine", "Triangle", "Ramp up", "Ramp down", "Square", "Pulse", "Stepped"}); configButton(SLOW_PARAM, "Slow mode"); - configParam(OFFSET_PARAM, -1.0f, 1.0f, 0.0f, "Offset", " V", 0.0f, 5.0f); + configParam<OffsetParamQuantity>(OFFSET_PARAM, -1.0f, 1.0f, 0.0f, "Offset", " V", 0.0f, 5.0f); configParam(SCALE_PARAM, 0.0f, 1.0f, 1.0f, "Scale", "%", 0.0f, 100.0f); configInput(PITCH_INPUT, "Pitch (1V/octave)"); diff --git a/src/lfo_base.cpp b/src/lfo_base.cpp @@ -52,3 +52,28 @@ void LFOBase::setFrequency(Param& frequency, Input& pitch, Phasor& phasor, int c } phasor.setFrequency(f); } + +#define OFFSET_SCALE "offset_scale" + +json_t* LFOBase::saveToJson(json_t* root) { + json_object_set_new(root, OFFSET_SCALE, json_real(_offsetScale)); + return root; +} + +void LFOBase::loadFromJson(json_t* root) { + json_t* os = json_object_get(root, OFFSET_SCALE); + if (os) { + _offsetScale = clamp(json_real_value(os), 1.0f, 2.0f); + } +} + + +void LFOBaseModuleWidget::contextMenu(Menu* menu) { + auto m = dynamic_cast<LFOBase*>(module); + assert(m); + + OptionsMenuItem* o = new OptionsMenuItem("Offset range"); + o->addItem(OptionMenuItem("+/-5V", [m]() { return (int)m->_offsetScale == 1; }, [m]() { m->_offsetScale = 1.0f; })); + o->addItem(OptionMenuItem("+/-10V", [m]() { return (int)m->_offsetScale == 2; }, [m]() { m->_offsetScale = 2.0f; })); + OptionsMenuItem::addToMenu(o, menu); +} diff --git a/src/lfo_base.hpp b/src/lfo_base.hpp @@ -14,6 +14,25 @@ struct PitchModeListener { }; struct LFOBase : BGModule { + struct OffsetParamQuantity : ParamQuantity { + float getDisplayValue() override { + float v = getValue(); + if (!module) { + return v; + } + auto m = dynamic_cast<LFOBase*>(module); + return v * m->_offsetScale * 5.0f; + } + + void setDisplayValue(float dv) override { + if (!module) { + return; + } + auto m = dynamic_cast<LFOBase*>(module); + setValue(dv / (m->_offsetScale * 5.0f)); + } + }; + struct Smoother { float _sampleRate = 0.0f; float _frequency = 0.0f; @@ -25,6 +44,7 @@ struct LFOBase : BGModule { }; bool _slowMode = false; + float _offsetScale = 1.0f; PitchModeListener* _pitchModeListener = NULL; struct LFOFrequencyParamQuantity : FrequencyParamQuantity { @@ -37,7 +57,12 @@ struct LFOBase : BGModule { float getPitchOffset(); void setFrequency(Param& frequency, Input& pitch, Phasor& phasor, int c = 0); // FIXME + json_t* saveToJson(json_t* root) override; + void loadFromJson(json_t* root) override; }; +struct LFOBaseModuleWidget : BGModuleWidget { + void contextMenu(Menu* menu) override; +}; } // namespace bogaudio