BogaudioModules

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

commit 18552e82e450ddd6699214de3468a8f7537c8927
parent 6f04039e200f0c6f752541bc6040e2f4900066e2
Author: Matt Demanett <matt@demanett.net>
Date:   Tue, 30 Jun 2020 00:30:24 -0400

Crude proof-of-concepts for PEQ14 vocoder and resynthesizer expanders; also changed how expanders check they're next to the correct modules.

Diffstat:
Mplugin.json | 27++++++++++++++++++++++++++-
Ares-src/PEQ14XR-src.svg | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares-src/PEQ14XV-src.svg | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/PEQ14XR.svg | 0
Ares/PEQ14XV.svg | 0
Msrc/Mix4.hpp | 2+-
Msrc/Mix4x.hpp | 2+-
Msrc/Mix8.hpp | 2+-
Msrc/Mix8x.hpp | 2+-
Msrc/PEQ.cpp | 3+--
Msrc/PEQ14.cpp | 37++++++++++++++++++++++++++++---------
Msrc/PEQ14.hpp | 3++-
Msrc/PEQ14XO.cpp | 10++++++++++
Msrc/PEQ14XO.hpp | 5+++--
Asrc/PEQ14XR.cpp | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PEQ14XR.hpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PEQ14XV.cpp | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PEQ14XV.hpp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PEQ14_shared.hpp | 34+++++++++++++++++++++++++++++++---
Msrc/PEQ6.cpp | 8++++----
Msrc/PEQ6.hpp | 2+-
Msrc/PEQ6XO.hpp | 2+-
Msrc/Pgmr.hpp | 2+-
Msrc/PgmrX.hpp | 5++---
Msrc/TestExpander.hpp | 4++--
Msrc/bogaudio.cpp | 4++++
Msrc/expanders.hpp | 21++++++++-------------
Msrc/parametric_eq.cpp | 32+++++++++++++++++---------------
Msrc/parametric_eq.hpp | 12+++++++++++-
29 files changed, 605 insertions(+), 63 deletions(-)

diff --git a/plugin.json b/plugin.json @@ -197,10 +197,11 @@ { "slug": "Bogaudio-PEQ14", "name": "PEQ14", - "description": "6-channel parametric equalizer / filter bank", + "description": "14-channel parametric equalizer / filter bank", "manualUrl": "https://github.com/bogaudio/BogaudioModules/blob/master/README.md#peq14", "tags": [ "Filter", + "Vocoder", "Polyphonic" ] }, @@ -216,6 +217,30 @@ ] }, { + "slug": "Bogaudio-PEQ14XV", + "name": "PEQ14XV", + "description": "PEQ14 vocoder expander", + "manualUrl": "https://github.com/bogaudio/BogaudioModules/blob/master/README.md#peq14xv", + "tags": [ + "Filter", + "Vocoder", + "Expander", + "Polyphonic" + ] + }, + { + "slug": "Bogaudio-PEQ14XR", + "name": "PEQ14XR", + "description": "PEQ14 resynthesizer expander", + "manualUrl": "https://github.com/bogaudio/BogaudioModules/blob/master/README.md#peq14xr", + "tags": [ + "Filter", + "Vocoder", + "Expander", + "Polyphonic" + ] + }, + { "slug": "Bogaudio-DADSRH", "name": "DADSR(H)", "description": "Advanced envelope generator", diff --git a/res-src/PEQ14XR-src.svg b/res-src/PEQ14XR-src.svg @@ -0,0 +1,58 @@ +<svg + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="75" + height="380" + viewBox="0 0 75 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="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> + </defs> + + <rect width="100%" height="100%" fill="#ddd" /> + <polyline points="1,1 74,1 74,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" /> + <polyline points="0.5,0.5 74.5,0.5 74.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" /> + <polyline points="0,0 75,0 75,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" /> + + <text class="title" x="20" y="14" font-size="7pt" letter-spacing="1px">PEQ14XR</text> + <!-- <text class="title" x="15" y="17" font-size="9pt" letter-spacing="3px" transform="translate(75 -8) rotate(90)">PEQ14-XO</text> --> + <g transform="translate(25 374)"> + <text class="brand" font-size="6.5pt" letter-spacing="2px">BGA</text> + <rect width="2" height="2" fill="#ddd" transform="translate(11.5 -4)" /> + </g> + + <g transform="translate(20.5 318)"> + <rect width="34" height="42" rx="5" fill="#bbb" /> + <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(8.5 37)">OUT</text> + </g> +</svg> diff --git a/res-src/PEQ14XV-src.svg b/res-src/PEQ14XV-src.svg @@ -0,0 +1,62 @@ +<svg + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="75" + height="380" + viewBox="0 0 75 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="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> + </defs> + + <rect width="100%" height="100%" fill="#ddd" /> + <polyline points="1,1 74,1 74,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" /> + <polyline points="0.5,0.5 74.5,0.5 74.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" /> + <polyline points="0,0 75,0 75,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" /> + + <text class="title" x="20" y="14" font-size="7pt" letter-spacing="1px">PEQ14XV</text> + <!-- <text class="title" x="15" y="17" font-size="9pt" letter-spacing="3px" transform="translate(75 -8) rotate(90)">PEQ14-XO</text> --> + <g transform="translate(25 374)"> + <text class="brand" font-size="6.5pt" letter-spacing="2px">BGA</text> + <rect width="2" height="2" fill="#ddd" transform="translate(11.5 -4)" /> + </g> + + <g transform="translate(5.5 318)"> + <rect width="64" height="40" rx="5" fill="#fafafa" /> + <rect width="32" height="40" rx="5" fill="#bbb" transform="translate(32)" /> + <rect width="10" height="40" fill="#bbb" transform="translate(32)" /> + <use id="IN_INPUT" xlink:href="#input" transform="translate(3.5 4)" /> + <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(35.5 4)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(11 36)">IN</text> + <text font-size="6pt" letter-spacing="2px" transform="translate(37 36)">OUT</text> + </g> +</svg> diff --git a/res/PEQ14XR.svg b/res/PEQ14XR.svg Binary files differ. diff --git a/res/PEQ14XV.svg b/res/PEQ14XV.svg Binary files differ. diff --git a/src/Mix4.hpp b/src/Mix4.hpp @@ -86,7 +86,7 @@ struct Mix4 : ExpandableModule<Mix4ExpanderMessage, BGModule> { sampleRateChange(); _rms.setSensitivity(0.05f); - setExpanderModel(modelMix4x); + setExpanderModelPredicate([](Model* m) { return m == modelMix4x; }); } virtual ~Mix4() { for (int i = 0; i < 4; ++i) { diff --git a/src/Mix4x.hpp b/src/Mix4x.hpp @@ -108,7 +108,7 @@ struct Mix4x : ExpanderModule<Mix4ExpanderMessage, BGModule> { _channels[2] = new MixerExpanderChannel(params[LOW3_PARAM], params[MID3_PARAM], params[HIGH3_PARAM], params[A3_PARAM], params[B3_PARAM], params[PRE_A3_PARAM], params[PRE_B3_PARAM], inputs[A3_INPUT], inputs[B3_INPUT]); _channels[3] = new MixerExpanderChannel(params[LOW4_PARAM], params[MID4_PARAM], params[HIGH4_PARAM], params[A4_PARAM], params[B4_PARAM], params[PRE_A4_PARAM], params[PRE_B4_PARAM], inputs[A4_INPUT], inputs[B4_INPUT]); - setBaseModel(modelMix4); + setBaseModelPredicate([](Model* m) { return m == modelMix4; }); } virtual ~Mix4x() { for (int i = 0; i < 4; ++i) { diff --git a/src/Mix8.hpp b/src/Mix8.hpp @@ -126,7 +126,7 @@ struct Mix8 : ExpandableModule<Mix8ExpanderMessage, BGModule> { sampleRateChange(); _rms.setSensitivity(0.05f); - setExpanderModel(modelMix8x); + setExpanderModelPredicate([](Model* m) { return m == modelMix8x; }); } virtual ~Mix8() { for (int i = 0; i < 8; ++i) { diff --git a/src/Mix8x.hpp b/src/Mix8x.hpp @@ -175,7 +175,7 @@ struct Mix8x : ExpanderModule<Mix8ExpanderMessage, BGModule> { _channels[6] = new MixerExpanderChannel(params[LOW7_PARAM], params[MID7_PARAM], params[HIGH7_PARAM], params[A7_PARAM], params[B7_PARAM], params[PRE_A7_PARAM], params[PRE_B7_PARAM], inputs[A7_INPUT], inputs[B7_INPUT]); _channels[7] = new MixerExpanderChannel(params[LOW8_PARAM], params[MID8_PARAM], params[HIGH8_PARAM], params[A8_PARAM], params[B8_PARAM], params[PRE_A8_PARAM], params[PRE_B8_PARAM], inputs[A8_INPUT], inputs[B8_INPUT]); - setBaseModel(modelMix8); + setBaseModelPredicate([](Model* m) { return m == modelMix8; }); } virtual ~Mix8x() { for (int i = 0; i < 8; ++i) { diff --git a/src/PEQ.cpp b/src/PEQ.cpp @@ -59,8 +59,7 @@ void PEQ::processAlways(const ProcessArgs& args) { } void PEQ::processChannel(const ProcessArgs& args, int c) { - float outs[6]; - outputs[OUT_OUTPUT].setVoltage(_engines[c]->next(inputs[IN_INPUT].getVoltage(c), outs, _rmsSums), c); + outputs[OUT_OUTPUT].setVoltage(_engines[c]->next(inputs[IN_INPUT].getVoltage(c), _rmsSums), c); } void PEQ::postProcessAlways(const ProcessArgs& args) { diff --git a/src/PEQ14.cpp b/src/PEQ14.cpp @@ -87,22 +87,41 @@ void PEQ14::processAlways(const ProcessArgs& args) { } void PEQ14::processChannel(const ProcessArgs& args, int c) { - float outs[14] {}; - float out = _engines[c]->next(inputs[IN_INPUT].getVoltage(c), outs, _rmsSums); + PEQEngine& e = *_engines[c]; + float out = e.next(inputs[IN_INPUT].getVoltage(c), _rmsSums); outputs[OUT_OUTPUT].setVoltage(out, c); - if (expanderConnected()) { - std::copy(outs, outs + 14, toExpander()->outs[c]); - } + float levels[14]; float oddOut = 0.0f; float evenOut = 0.0f; for (int i = 0; i < 14; ++i) { - oddOut += outs[i] * (float)(i % 2 == 0 || (i == 13 && _highMode == MultimodeFilter::HIGHPASS_MODE)); - evenOut += outs[i] * (float)(i % 2 == 1 || (i == 0 && _lowMode == MultimodeFilter::LOWPASS_MODE)); - outputs[EF1_OUTPUT + i].setVoltage(2.0f * _efs[c][i].next(outs[i]), c); + oddOut += e.outs[i] * (float)(i % 2 == 0 || (i == 13 && _highMode == MultimodeFilter::HIGHPASS_MODE)); + evenOut += e.outs[i] * (float)(i % 2 == 1 || (i == 0 && _lowMode == MultimodeFilter::LOWPASS_MODE)); + levels[i] = scaleEF(_efs[c][i].next(e.outs[i]), e.frequencies[i]); + outputs[EF1_OUTPUT + i].setVoltage(levels[i], c); } outputs[ODDS_OUTPUT].setVoltage(oddOut, c); outputs[EVENS_OUTPUT].setVoltage(evenOut, c); + + if (expanderConnected()) { + auto m = toExpander(); + m->valid = true; + std::copy(e.outs, e.outs + 14, m->outs[c]); + std::copy(e.frequencies, e.frequencies + 14, m->frequencies[c]); + std::copy(levels, levels + 14, m->levels[c]); + m->bandwidths[c] = e.bandwidth; + m->lowLP = _lowMode == MultimodeFilter::LOWPASS_MODE; + m->highHP = _highMode == MultimodeFilter::HIGHPASS_MODE; + } +} + +float PEQ14::scaleEF(float ef, float frequency) { + float bandwidth = 2.0 * params[BANDWIDTH_PARAM].getValue(); + float minf = std::max(0.0f, powf(2.0f, -bandwidth) * frequency); + float maxf = std::min(PEQChannel::maxFrequency, powf(2.0f, bandwidth) * frequency); + float scale = (maxf - minf) / PEQChannel::maxFrequency; + scale = 1.0f / scale; + return scale * ef; } void PEQ14::postProcessAlways(const ProcessArgs& args) { @@ -351,4 +370,4 @@ struct PEQ14Widget : ModuleWidget { } }; -Model* modelPEQ14 = createModel<PEQ14, PEQ14Widget>("Bogaudio-PEQ14", "PEQ14", "6-channel parametric equalizer / filter bank", "Filter", "Polyphonic"); +Model* modelPEQ14 = createModel<PEQ14, PEQ14Widget>("Bogaudio-PEQ14", "PEQ14", "14-channel parametric equalizer / filter bank", "Filter", "Vocoder", "Polyphonic"); diff --git a/src/PEQ14.hpp b/src/PEQ14.hpp @@ -181,7 +181,7 @@ struct PEQ14 : ExpandableModule<PEQ14ExpanderMessage, BGModule> { configParam<ScaledSquaringParamQuantity<(int)PEQChannel::maxFrequency>>(FREQUENCY14_PARAM, 0.0f, 1.0f, 0.5873670f, "Channel 6 frequency", " HZ"); configParam(FREQUENCY_CV14_PARAM, -1.0f, 1.0f, 1.0f, "Channel 6 frequency CV attenuation", "%", 0.0f, 100.0f); - setExpanderModel(modelPEQ14XO); + setExpanderModelPredicate([](Model* m) { return m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); } void sampleRateChange() override; @@ -192,6 +192,7 @@ struct PEQ14 : ExpandableModule<PEQ14ExpanderMessage, BGModule> { void modulate() override; void processAlways(const ProcessArgs& args) override; void processChannel(const ProcessArgs& args, int c) override; + float scaleEF(float ef, float frequency); void postProcessAlways(const ProcessArgs& args) override; }; diff --git a/src/PEQ14XO.cpp b/src/PEQ14XO.cpp @@ -5,6 +5,16 @@ void PEQ14XO::processAll(const ProcessArgs& args) { for (int i = 0; i < 14; ++i) { outputs[BAND1_OUTPUT + i].setChannels(_channels); } + + if (expanderConnected()) { + if (baseConnected()) { + // *toExpander() = *fromBase(); + fromBase()->copyTo(toExpander()); + } + else { + toExpander()->reset(); + } + } } void PEQ14XO::processChannel(const ProcessArgs& args, int c) { diff --git a/src/PEQ14XO.hpp b/src/PEQ14XO.hpp @@ -4,7 +4,7 @@ namespace bogaudio { -struct PEQ14XO : ExpanderModule<PEQ14ExpanderMessage, BGModule> { +struct PEQ14XO : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14ExpanderMessage, BGModule>> { enum ParamsIds { NUM_PARAMS }; @@ -33,7 +33,8 @@ struct PEQ14XO : ExpanderModule<PEQ14ExpanderMessage, BGModule> { PEQ14XO() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - setBaseModel(modelPEQ14); + setBaseModelPredicate([](Model* m) { return m == modelPEQ14 || m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); + setExpanderModelPredicate([](Model* m) { return m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); } void processAll(const ProcessArgs& args) override; diff --git a/src/PEQ14XR.cpp b/src/PEQ14XR.cpp @@ -0,0 +1,94 @@ + +#include "PEQ14XR.hpp" + +void PEQ14XR::Engine::setSampleRate(float sr) { + for (int i = 0; i < 14; ++i) { + oscillators[i].setSampleRate(sr); + } +} + +void PEQ14XR::sampleRateChange() { + float sr = APP->engine->getSampleRate(); + for (int c = 0; c < _channels; ++c) { + _engines[c]->setSampleRate(sr); + } +} + +void PEQ14XR::addChannel(int c) { + _engines[c] = new Engine(); + _engines[c]->setSampleRate(APP->engine->getSampleRate()); +} + +void PEQ14XR::removeChannel(int c) { + delete _engines[c]; + _engines[c] = NULL; +} + +void PEQ14XR::processAlways(const ProcessArgs& args) { + outputs[OUT_OUTPUT].setChannels(_channels); + + _baseMessage = NULL; + if (baseConnected()) { + _baseMessage = fromBase(); + } + + if (expanderConnected()) { + if (_baseMessage) { + // *toExpander() = *_baseMessage; + _baseMessage->copyTo(toExpander()); + } + else { + toExpander()->reset(); + } + } +} + +void PEQ14XR::processChannel(const ProcessArgs& args, int c) { + if (_baseMessage && _baseMessage->valid) { + Engine& e = *_engines[c]; + float out = 0.0f; + for (int i = 0; i < 14; ++i) { + e.oscillators[i].setFrequency(_baseMessage->frequencies[c][i]); + + float db = _baseMessage->levels[c][i]; + db *= 0.2f; + db = std::max(0.0f, std::min(1.0f, db)); + db = 1.0f - db; + db *= Amplifier::minDecibels; + e.amplifiers[i].setLevel(db); + + out += e.amplifiers[i].next(e.oscillators[i].next()); + } + outputs[OUT_OUTPUT].setVoltage(_saturator.next(out), c); + } + else { + outputs[OUT_OUTPUT].setVoltage(0.0f, c); + } +} + +struct PEQ14XRWidget : ModuleWidget { + static constexpr int hp = 5; + + PEQ14XRWidget(PEQ14XR* 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/PEQ14XR.svg"))); + addChild(panel); + } + + addChild(createWidget<ScrewSilver>(Vec(0, 0))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365))); + + // generated by svg_widgets.rb + auto outOutputPosition = Vec(25.5, 322.0); + // end generated by svg_widgets.rb + + addOutput(createOutput<Port24>(outOutputPosition, module, PEQ14XR::OUT_OUTPUT)); + } +}; + +Model* modelPEQ14XR = createModel<PEQ14XR, PEQ14XRWidget>("Bogaudio-PEQ14XR", "PEQ14XR", "PEQ14 resynthesizer expander", "Filter", "Vocoder", "Expander", "Polyphonic"); diff --git a/src/PEQ14XR.hpp b/src/PEQ14XR.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "PEQ14_shared.hpp" +#include "dsp/oscillator.hpp" + +namespace bogaudio { + +struct PEQ14XR : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14ExpanderMessage, BGModule>> { + enum ParamsIds { + NUM_PARAMS + }; + + enum InputsIds { + NUM_INPUTS + }; + + enum OutputsIds { + OUT_OUTPUT, + NUM_OUTPUTS + }; + + struct Engine { + struct BandOscillator { + Phasor _phasor; + TriangleOscillator _oscillator; + // SineTableOscillator _oscillator; + // BandLimitedSawOscillator _oscillator; + + inline void setSampleRate(float sr) { _phasor.setSampleRate(sr); } + inline void setFrequency(float f) { _phasor.setFrequency(f); } + inline float next() { + _phasor.advancePhase(); + return _oscillator.nextFromPhasor(_phasor); + } + }; + + BandOscillator oscillators[14]; + Amplifier amplifiers[14]; + + void setSampleRate(float sr); + }; + + Engine* _engines[maxChannels] {}; + Saturator _saturator; + PEQ14ExpanderMessage* _baseMessage = NULL; + + PEQ14XR() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); + + setBaseModelPredicate([](Model* m) { return m == modelPEQ14 || m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); + setExpanderModelPredicate([](Model* m) { return m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); + } + + void sampleRateChange() override; + void addChannel(int c) override; + void removeChannel(int c) override; + void processAlways(const ProcessArgs& args) override; + void processChannel(const ProcessArgs& args, int c) override; +}; + +} // namespace bogaudio diff --git a/src/PEQ14XV.cpp b/src/PEQ14XV.cpp @@ -0,0 +1,124 @@ + +#include "PEQ14XV.hpp" + +PEQ14XV::Engine::Engine() { + filters[0] = new MultimodeFilter8(); + for (int i = 1; i < 13; ++i) { + filters[i] = new MultimodeFilter4(); + } + filters[13] = new MultimodeFilter8(); +} + +PEQ14XV::Engine::~Engine() { + for (int i = 0; i < 14; ++i) { + delete filters[i]; + } +} + +void PEQ14XV::sampleRateChange() { + _sampleRate = APP->engine->getSampleRate(); +} + +void PEQ14XV::addChannel(int c) { + _engines[c] = new Engine(); +} + +void PEQ14XV::removeChannel(int c) { + delete _engines[c]; + _engines[c] = NULL; +} + +void PEQ14XV::processAlways(const ProcessArgs& args) { + outputs[OUT_OUTPUT].setChannels(_channels); + + _baseMessage = NULL; + if (baseConnected()) { + _baseMessage = fromBase(); + } + + if (expanderConnected()) { + if (_baseMessage) { + // *toExpander() = *_baseMessage; + _baseMessage->copyTo(toExpander()); + } + else { + toExpander()->reset(); + } + } +} + +void PEQ14XV::processChannel(const ProcessArgs& args, int c) { + if (_baseMessage && _baseMessage->valid) { + Engine& e = *_engines[c]; + float in = inputs[IN_INPUT].getPolyVoltage(c); + float out = 0.0f; + for (int i = 0; i < 14; ++i) { + auto mode = MultimodeFilter::BANDPASS_MODE; + int poles = 4; + float bandwidth = _baseMessage->bandwidths[c]; + if (i == 0 && _baseMessage->lowLP) { + mode = MultimodeFilter::LOWPASS_MODE; + poles = 12; + bandwidth = MultimodeFilter::minQbw; + } + if (i == 13 && _baseMessage->highHP) { + mode = MultimodeFilter::HIGHPASS_MODE; + poles = 12; + bandwidth = MultimodeFilter::minQbw; + } + e.filters[i]->setParams( + _sampleRate, + MultimodeFilter::BUTTERWORTH_TYPE, + poles, + mode, + _baseMessage->frequencies[c][i], + bandwidth, + MultimodeFilter::PITCH_BANDWIDTH_MODE + ); + + float db = _baseMessage->levels[c][i]; + db *= 0.2f; + db = std::max(0.0f, std::min(1.0f, db)); + db = 1.0f - db; + db *= Amplifier::minDecibels; + e.amplifiers[i].setLevel(db); + + out += e.amplifiers[i].next(e.filters[i]->next(in)); + } + outputs[OUT_OUTPUT].setVoltage(_saturator.next(out), c); + } + else { + outputs[OUT_OUTPUT].setVoltage(0.0f, c); + } +} + +struct PEQ14XVWidget : ModuleWidget { + static constexpr int hp = 5; + + PEQ14XVWidget(PEQ14XV* 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/PEQ14XV.svg"))); + addChild(panel); + } + + addChild(createWidget<ScrewSilver>(Vec(0, 0))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365))); + + // generated by svg_widgets.rb + auto inInputPosition = Vec(9.0, 322.0); + + auto outOutputPosition = Vec(41.0, 322.0); + // end generated by svg_widgets.rb + + addInput(createInput<Port24>(inInputPosition, module, PEQ14XV::IN_INPUT)); + + addOutput(createOutput<Port24>(outOutputPosition, module, PEQ14XV::OUT_OUTPUT)); + } +}; + +Model* modelPEQ14XV = createModel<PEQ14XV, PEQ14XVWidget>("Bogaudio-PEQ14XV", "PEQ14XV", "PEQ14 vocoder expander", "Filter", "Vocoder", "Expander", "Polyphonic"); diff --git a/src/PEQ14XV.hpp b/src/PEQ14XV.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "PEQ14_shared.hpp" +#include "dsp/filters/multimode.hpp" + +namespace bogaudio { + +struct PEQ14XV : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14ExpanderMessage, BGModule>> { + enum ParamsIds { + NUM_PARAMS + }; + + enum InputsIds { + IN_INPUT, + NUM_INPUTS + }; + + enum OutputsIds { + OUT_OUTPUT, + NUM_OUTPUTS + }; + + struct Engine { + MultimodeFilter* filters[14] {}; + Amplifier amplifiers[14]; + + Engine(); + ~Engine(); + }; + + float _sampleRate = 1000.0f; + Engine* _engines[maxChannels] {}; + Saturator _saturator; + PEQ14ExpanderMessage* _baseMessage = NULL; + + PEQ14XV() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); + + setBaseModelPredicate([](Model* m) { return m == modelPEQ14 || m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); + setExpanderModelPredicate([](Model* m) { return m == modelPEQ14XO || m == modelPEQ14XR || m == modelPEQ14XV; }); + } + + void sampleRateChange() override; + void addChannel(int c) override; + void removeChannel(int c) override; + void processAlways(const ProcessArgs& args) override; + void processChannel(const ProcessArgs& args, int c) override; +}; + +} // namespace bogaudio diff --git a/src/PEQ14_shared.hpp b/src/PEQ14_shared.hpp @@ -6,16 +6,44 @@ extern Model* modelPEQ14; extern Model* modelPEQ14XO; +extern Model* modelPEQ14XR; +extern Model* modelPEQ14XV; namespace bogaudio { struct PEQ14ExpanderMessage : ExpanderMessage { + bool valid = false; float outs[BGModule::maxChannels][14]; + float frequencies[BGModule::maxChannels][14]; + float levels[BGModule::maxChannels][14]; + float bandwidths[BGModule::maxChannels]; + bool lowLP = false; + bool highHP = false; PEQ14ExpanderMessage() { - for (int c = 0; c < BGModule::maxChannels; ++c) { - std::fill(outs[c], outs[c] + 14, 0.0f); - } + reset(); + } + + void reset() { + valid = false; + const int n = BGModule::maxChannels * 14; + std::fill((float*)outs, (float*)outs + n, 0.0f); + std::fill((float*)frequencies, (float*)frequencies + n, 0.0f); + std::fill((float*)levels, (float*)levels + n, 0.0f); + std::fill((float*)bandwidths, (float*)bandwidths + BGModule::maxChannels, 0.0f); + lowLP = false; + highHP = false; + } + + void copyTo(PEQ14ExpanderMessage* o) { + o->valid = valid; + const int n = BGModule::maxChannels * 14; + std::copy((float*)outs, (float*)outs + n, (float*)o->outs); + std::copy((float*)frequencies, (float*)frequencies + n, (float*)o->frequencies); + std::copy((float*)levels, (float*)levels + n, (float*)o->levels); + std::copy((float*)bandwidths, (float*)bandwidths + BGModule::maxChannels, (float*)o->bandwidths); + o->lowLP = lowLP; + o->highHP = highHP; } }; diff --git a/src/PEQ6.cpp b/src/PEQ6.cpp @@ -81,15 +81,15 @@ void PEQ6::processAlways(const ProcessArgs& args) { } void PEQ6::processChannel(const ProcessArgs& args, int c) { - float outs[6] {}; - float out = _engines[c]->next(inputs[IN_INPUT].getVoltage(c), outs, _rmsSums); + PEQEngine& e = *_engines[c]; + float out = e.next(inputs[IN_INPUT].getVoltage(c), _rmsSums); outputs[OUT_OUTPUT].setVoltage(out, c); if (expanderConnected()) { - std::copy(outs, outs + 6, toExpander()->outs[c]); + std::copy(e.outs, e.outs + 6, toExpander()->outs[c]); } for (int i = 0; i < 6; ++i) { - outputs[EF1_OUTPUT + i].setVoltage(2.0f * _efs[c][i].next(outs[i]), c); + outputs[EF1_OUTPUT + i].setVoltage(2.0f * _efs[c][i].next(e.outs[i]), c); } } diff --git a/src/PEQ6.hpp b/src/PEQ6.hpp @@ -104,7 +104,7 @@ struct PEQ6 : ExpandableModule<PEQ6ExpanderMessage, BGModule> { configParam<ScaledSquaringParamQuantity<(int)PEQChannel::maxFrequency>>(FREQUENCY6_PARAM, 0.0f, 1.0f, 0.3535534f, "Channel 6 frequency", " HZ"); configParam(FREQUENCY_CV6_PARAM, -1.0f, 1.0f, 1.0f, "Channel 6 frequency CV attenuation", "%", 0.0f, 100.0f); - setExpanderModel(modelPEQ6XO); + setExpanderModelPredicate([](Model* m) { return m == modelPEQ6XO; }); } void sampleRateChange() override; diff --git a/src/PEQ6XO.hpp b/src/PEQ6XO.hpp @@ -25,7 +25,7 @@ struct PEQ6XO : ExpanderModule<PEQ6ExpanderMessage, BGModule> { PEQ6XO() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - setBaseModel(modelPEQ6); + setBaseModelPredicate([](Model* m) { return m == modelPEQ6; }); } void processAll(const ProcessArgs& args) override; diff --git a/src/Pgmr.hpp b/src/Pgmr.hpp @@ -101,7 +101,7 @@ struct Pgmr : ExpandableModule<PgmrExpanderMessage, OutputRangeAddressableSequen _localSteps[2] = new PgmrStep(params[CVA3_PARAM], params[CVB3_PARAM], params[CVC3_PARAM], params[CVD3_PARAM], lights[SELECT3_LIGHT], params[SELECT3_PARAM], inputs[SELECT3_INPUT], outputs[SELECT3_OUTPUT]); _localSteps[3] = new PgmrStep(params[CVA4_PARAM], params[CVB4_PARAM], params[CVC4_PARAM], params[CVD4_PARAM], lights[SELECT4_LIGHT], params[SELECT4_PARAM], inputs[SELECT4_INPUT], outputs[SELECT4_OUTPUT]); - setExpanderModel(modelPgmrX); + setExpanderModelPredicate([](Model* m) { return m == modelPgmrX; }); _id = PgmrRegistry::registry().registerBase(*this); } virtual ~Pgmr() { diff --git a/src/PgmrX.hpp b/src/PgmrX.hpp @@ -85,9 +85,8 @@ struct PgmrX : ExpanderModule<PgmrExpanderMessage, ExpandableModule<PgmrExpander _localSteps[2] = new PgmrStep(params[CVA3_PARAM], params[CVB3_PARAM], params[CVC3_PARAM], params[CVD3_PARAM], lights[SELECT3_LIGHT], params[SELECT3_PARAM], inputs[SELECT3_INPUT], outputs[SELECT3_OUTPUT]); _localSteps[3] = new PgmrStep(params[CVA4_PARAM], params[CVB4_PARAM], params[CVC4_PARAM], params[CVD4_PARAM], lights[SELECT4_LIGHT], params[SELECT4_PARAM], inputs[SELECT4_INPUT], outputs[SELECT4_OUTPUT]); - setBaseModel(modelPgmr); - setChainableModel(modelPgmrX); - setExpanderModel(modelPgmrX); + setBaseModelPredicate([](Model* m) { return m == modelPgmr || m == modelPgmrX; }); + setExpanderModelPredicate([](Model* m) { return m == modelPgmrX; }); } virtual ~PgmrX() { PgmrRegistry::registry().deregisterExpander(_baseID, _position); diff --git a/src/TestExpander.hpp b/src/TestExpander.hpp @@ -36,7 +36,7 @@ struct TestExpanderBase : ExpandableModule<TestExpanderMessage, BGModule> { TestExpanderBase() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - setExpanderModel(modelTestExpanderExtension); + setExpanderModelPredicate([](Model* m) { return m == modelTestExpanderExtension; }); } int channels() override; @@ -65,7 +65,7 @@ struct TestExpanderExtension : ExpanderModule<TestExpanderMessage, BGModule> { TestExpanderExtension() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - setBaseModel(modelTestExpanderBase); + setBaseModelPredicate([](Model* m) { return m == modelTestExpanderBase; }); } void processAll(const ProcessArgs& args) override; diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp @@ -61,6 +61,8 @@ #include "PEQ6XO.hpp" #include "PEQ14.hpp" #include "PEQ14XO.hpp" +#include "PEQ14XR.hpp" +#include "PEQ14XV.hpp" #include "Pgmr.hpp" #include "PgmrX.hpp" #include "PolyCon8.hpp" @@ -130,6 +132,8 @@ void init(rack::Plugin *p) { p->addModel(modelPEQ6XO); p->addModel(modelPEQ14); p->addModel(modelPEQ14XO); + p->addModel(modelPEQ14XV); + p->addModel(modelPEQ14XR); p->addModel(modelDADSRH); p->addModel(modelDADSRHPlus); diff --git a/src/expanders.hpp b/src/expanders.hpp @@ -17,7 +17,7 @@ struct ExpanderMessage { template<class MSG, class BASE> struct ExpandableModule : BASE { - Model* _expanderModel = NULL; + std::function<bool(Model*)> _expanderModel; MSG _messages[2] {}; bool _wasConnected = false; @@ -29,12 +29,12 @@ struct ExpandableModule : BASE { BGModule::rightExpander.consumerMessage = &_messages[1]; } - void setExpanderModel(Model* m) { - _expanderModel = m; + void setExpanderModelPredicate(std::function<bool(Model*)> p) { + _expanderModel = p; } bool expanderConnected() { - bool connected = BGModule::rightExpander.module && _expanderModel && BGModule::rightExpander.module->model == _expanderModel; + bool connected = BGModule::rightExpander.module && _expanderModel && _expanderModel(BGModule::rightExpander.module->model); if (!connected && _wasConnected) { _messages[1] = _messages[0] = MSG(); } @@ -60,8 +60,7 @@ struct ExpandableModule : BASE { // An expander must be to the right of the expanded module to work. template<class MSG, class BASE> struct ExpanderModule : BASE { - Model* _baseModel = NULL; - Model* _chainableModel = NULL; + std::function<bool(Model*)> _baseModel; MSG _messages[2] {}; bool _wasConnected = false; @@ -73,16 +72,12 @@ struct ExpanderModule : BASE { BGModule::leftExpander.consumerMessage = &_messages[1]; } - void setBaseModel(Model* m) { - _baseModel = m; - } - - void setChainableModel(Model* m) { - _chainableModel = m; + void setBaseModelPredicate(std::function<bool(Model*)> p) { + _baseModel = p; } bool baseConnected() { - bool connected = BGModule::leftExpander.module && ((_baseModel && BGModule::leftExpander.module->model == _baseModel) || (_chainableModel && BGModule::leftExpander.module->model == _chainableModel)); + bool connected = BGModule::leftExpander.module && _baseModel && _baseModel(BGModule::leftExpander.module->model); if (!connected && _wasConnected) { _messages[1] = _messages[0] = MSG(); } diff --git a/src/parametric_eq.cpp b/src/parametric_eq.cpp @@ -50,30 +50,30 @@ void PEQChannel::modulate() { fcv *= 12.0f; } - float f = _frequencyParam.getValue(); - f *= f; - f *= maxFrequency; - f = clamp(f, minFrequency, maxFrequency); - f = frequencyToSemitone(f); - f += fcv; - f = clamp(f, minFrequencySemitone, maxFrequencySemitone); - f = semitoneToFrequency(_frequencySL.next(f)); + frequency = _frequencyParam.getValue(); + frequency *= frequency; + frequency *= maxFrequency; + frequency = clamp(frequency, minFrequency, maxFrequency); + frequency = frequencyToSemitone(frequency); + frequency += fcv; + frequency = clamp(frequency, minFrequencySemitone, maxFrequencySemitone); + frequency = semitoneToFrequency(_frequencySL.next(frequency)); - float bw = MultimodeFilter::minQbw; + bandwidth = MultimodeFilter::minQbw; if (_mode == MultimodeFilter::BANDPASS_MODE) { - bw = clamp(_bandwidthParam.getValue(), 0.0f, 1.0f); + bandwidth = clamp(_bandwidthParam.getValue(), 0.0f, 1.0f); if (_bandwidthInput && _bandwidthInput->isConnected()) { - bw *= clamp(_bandwidthInput->getPolyVoltage(_c) / 10.0f, 0.0f, 1.0f); + bandwidth *= clamp(_bandwidthInput->getPolyVoltage(_c) / 10.0f, 0.0f, 1.0f); } - bw = MultimodeFilter::minQbw + bw * (MultimodeFilter::maxQbw - MultimodeFilter::minQbw); + bandwidth = MultimodeFilter::minQbw + bandwidth * (MultimodeFilter::maxQbw - MultimodeFilter::minQbw); } _filter->setParams( _sampleRate, MultimodeFilter::BUTTERWORTH_TYPE, _poles, _mode, - f, - bw, + frequency, + bandwidth, MultimodeFilter::PITCH_BANDWIDTH_MODE ); } @@ -102,12 +102,14 @@ void PEQEngine::modulate() { } } -float PEQEngine::next(float sample, float* outs, float* rmsSums) { +float PEQEngine::next(float sample, float* rmsSums) { + bandwidth = _channels[1]->bandwidth; // take from any bandpass-only channel. float out = 0.0f; for (int i = 0; i < _n; ++i) { PEQChannel& c = *_channels[i]; c.next(sample); out += outs[i] = c.out; + frequencies[i] = c.frequency; rmsSums[i] += c.rms; } return _saturator.next(out); diff --git a/src/parametric_eq.hpp b/src/parametric_eq.hpp @@ -40,6 +40,8 @@ struct PEQChannel { float out = 0.0f; float rms = 0.0f; + float frequency = 0.0f; + float bandwidth = 0.0f; PEQChannel( MultimodeFilter* filter, @@ -87,14 +89,22 @@ struct PEQEngine { PEQChannel** _channels; Saturator _saturator; + float* outs = NULL; + float* frequencies = NULL; + float bandwidth = 0.0f; + PEQEngine(int channels) : _n(channels) { _channels = new PEQChannel*[_n] {}; + outs = new float[_n] {}; + frequencies = new float[_n] {}; } ~PEQEngine() { for (int i = 0; i < _n; ++i) { delete _channels[i]; } delete[] _channels; + delete[] outs; + delete[] frequencies; } inline void configChannel( @@ -129,7 +139,7 @@ struct PEQEngine { void setFrequencyMode(bool full); void setSampleRate(float sr); void modulate(); - float next(float sample, float* outs, float* rmsSums); + float next(float sample, float* rmsSums); }; } // namespace bogaudio