From c73fb6895c78e452620ba7101da091d3b6a7a494 Mon Sep 17 00:00:00 2001 From: Ewan <915048+hemmer@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:45:51 +0100 Subject: [PATCH 1/2] v2.6.0 Octaves (#47) Better defaults for ADSR, Burst Fix Voltio label bug --- CHANGELOG.md | 8 + plugin.json | 13 +- res/panels/Octaves.svg | 2383 ++++++++++++++++++++++++++++++++++++++++ src/ADSR.cpp | 8 +- src/Burst.cpp | 2 +- src/Octaves.cpp | 383 +++++++ src/Voltio.cpp | 2 +- src/plugin.cpp | 1 + src/plugin.hpp | 11 +- 9 files changed, 2799 insertions(+), 12 deletions(-) create mode 100644 res/panels/Octaves.svg create mode 100644 src/Octaves.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a20bc04..58da59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log + +## v2.6.0 + * Octaves + * Initial release + * Misc + * Better default values for ADSR and Burst + + ## v2.5.0 * Burst * Initial release diff --git a/plugin.json b/plugin.json index bcfe2df..3befb93 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "slug": "Befaco", - "version": "2.5.0", + "version": "2.6.0", "license": "GPL-3.0-or-later", "name": "Befaco", "brand": "Befaco", @@ -307,6 +307,17 @@ "Polyphonic", "Utility" ] + }, + { + "slug": "Octaves", + "name": "Octaves", + "description": "A harsh and funky take of an additive Oscillator.", + "manualUrl": "https://www.befaco.org/octaves-vco/", + "modularGridUrl": "https://www.modulargrid.net/e/befaco-octaves-vco", + "tags": [ + "Hardware clone", + "VCO" + ] } ] } \ No newline at end of file diff --git a/res/panels/Octaves.svg b/res/panels/Octaves.svg new file mode 100644 index 0000000..2f99793 --- /dev/null +++ b/res/panels/Octaves.svg @@ -0,0 +1,2383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + +    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ADSR.cpp b/src/ADSR.cpp index b65b756..5a12f5d 100644 --- a/src/ADSR.cpp +++ b/src/ADSR.cpp @@ -231,10 +231,10 @@ struct ADSR : Module { configButton(MANUAL_TRIGGER_PARAM, "Trigger envelope"); configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Envelope shape"); - configParam(ATTACK_PARAM, 0.f, 1.f, 0.f, "Attack time", "s", maxStageTime / minStageTime, minStageTime); - configParam(DECAY_PARAM, 0.f, 1.f, 0.f, "Decay time", "s", maxStageTime / minStageTime, minStageTime); - configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.f, "Sustain level", "%", 0.f, 100.f); - configParam(RELEASE_PARAM, 0.f, 1.f, 0.f, "Release time", "s", maxStageTime / minStageTime, minStageTime); + configParam(ATTACK_PARAM, 0.f, 1.f, 0.4f, "Attack time", "s", maxStageTime / minStageTime, minStageTime); + configParam(DECAY_PARAM, 0.f, 1.f, 0.4f, "Decay time", "s", maxStageTime / minStageTime, minStageTime); + configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.5f, "Sustain level", "%", 0.f, 100.f); + configParam(RELEASE_PARAM, 0.f, 1.f, 0.4f, "Release time", "s", maxStageTime / minStageTime, minStageTime); configInput(TRIGGER_INPUT, "Trigger"); configInput(CV_ATTACK_INPUT, "Attack CV"); diff --git a/src/Burst.cpp b/src/Burst.cpp index f8108ab..912609c 100644 --- a/src/Burst.cpp +++ b/src/Burst.cpp @@ -183,7 +183,7 @@ struct Burst : Module { Burst() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configSwitch(Burst::CYCLE_PARAM, 0.0, 1.0, 0.0, "Mode", {"One-shot", "Cycle"}); - auto quantityParam = configParam(Burst::QUANTITY_PARAM, 1, MAX_REPETITIONS, 0, "Number of bursts"); + auto quantityParam = configParam(Burst::QUANTITY_PARAM, 1, MAX_REPETITIONS, 4, "Number of bursts"); quantityParam->snapEnabled = true; configButton(Burst::TRIGGER_PARAM, "Manual Trigger"); configParam(Burst::QUANTITY_CV_PARAM, 0.0, 1.0, 1.0, "Quantity CV"); diff --git a/src/Octaves.cpp b/src/Octaves.cpp new file mode 100644 index 0000000..e0723d3 --- /dev/null +++ b/src/Octaves.cpp @@ -0,0 +1,383 @@ +#include "plugin.hpp" +#include "ChowDSP.hpp" + +using namespace simd; + +float aliasSuppressedSaw(const float* phases, float pw) { + float sawBuffer[3]; + for (int i = 0; i < 3; ++i) { + float p = 2 * phases[i] - 1.0; // range -1 to +1 + float pwp = p + 2 * pw; // phase after pw (pw in [0, 1]) + pwp += simd::ifelse(pwp > 1, -2, simd::ifelse(pwp < -1, +2, 0)); // modulo on [-1, +1] + sawBuffer[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11 + } + + return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]); +} + +float aliasSuppressedOffsetSaw(const float* phases, float pw) { + float sawOffsetBuff[3]; + + for (int i = 0; i < 3; ++i) { + float pwp = 2 * phases[i] - 2 * pw; // range -1 to +1 + + pwp += simd::ifelse(pwp > 1, -2, 0); // modulo on [-1, +1] + sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11 + } + return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); +} + +template +class HardClipperADAA { +public: + + T process(T x) { + T y = simd::ifelse(simd::abs(x - xPrev) < 1e-5, + f(0.5 * (xPrev + x)), + (F(x) - F(xPrev)) / (x - xPrev)); + + xPrev = x; + return y; + } + + + static T f(T x) { + return simd::ifelse(simd::abs(x) < 1, x, simd::sgn(x)); + } + + static T F(T x) { + return simd::ifelse(simd::abs(x) < 1, 0.5 * x * x, x * simd::sgn(x) - 0.5); + } + + void reset() { + xPrev = 0.f; + } + +private: + T xPrev = 0.f; +}; + +struct Octaves : Module { + enum ParamId { + PWM_CV_PARAM, + OCTAVE_PARAM, + TUNE_PARAM, + PWM_PARAM, + RANGE_PARAM, + GAIN_01F_PARAM, + GAIN_02F_PARAM, + GAIN_04F_PARAM, + GAIN_08F_PARAM, + GAIN_16F_PARAM, + GAIN_32F_PARAM, + PARAMS_LEN + }; + enum InputId { + VOCT1_INPUT, + VOCT2_INPUT, + SYNC_INPUT, + PWM_INPUT, + GAIN_01F_INPUT, + GAIN_02F_INPUT, + GAIN_04F_INPUT, + GAIN_08F_INPUT, + GAIN_16F_INPUT, + GAIN_32F_INPUT, + INPUTS_LEN + }; + enum OutputId { + OUT_01F_OUTPUT, + OUT_02F_OUTPUT, + OUT_04F_OUTPUT, + OUT_08F_OUTPUT, + OUT_16F_OUTPUT, + OUT_32F_OUTPUT, + OUTPUTS_LEN + }; + enum LightId { + LIGHTS_LEN + }; + + bool limitPW = true; + bool removePulseDC = false; + bool useTriangleCore = false; + static const int NUM_OUTPUTS = 6; + const float ranges[3] = {4.f, 1.f, 1.f / 12.f}; // full, octave, semitone + + float_4 phase[4] = {}; // phase for core waveform, in [0, 1] + chowdsp::VariableOversampling<6, float_4> oversampler[NUM_OUTPUTS][4]; // uses a 2*6=12th order Butterworth filter + int oversamplingIndex = 1; // default is 2^oversamplingIndex == x2 oversampling + + DCBlockerT<2, float_4> blockDCFilter[NUM_OUTPUTS][4]; // optionally block DC with RC filter @ ~22 Hz + dsp::TSchmittTrigger syncTrigger[4]; // for hard sync + + Octaves() { + config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); + configParam(PWM_CV_PARAM, 0.f, 1.f, 1.f, "PWM CV attenuater"); + + auto octParam = configSwitch(OCTAVE_PARAM, 0.f, 6.f, 1.f, "Octave", {"C1", "C2", "C3", "C4", "C5", "C6", "C7"}); + octParam->snapEnabled = true; + + configParam(TUNE_PARAM, -1.f, 1.f, 0.f, "Tune"); + configParam(PWM_PARAM, 0.5f, 0.f, 0.5f, "PWM"); + auto rangeParam = configSwitch(RANGE_PARAM, 0.f, 2.f, 1.f, "Range", {"VCO: Full", "VCO: Octave", "VCO: Semitone"}); + rangeParam->snapEnabled = true; + + configParam(GAIN_01F_PARAM, 0.f, 1.f, 1.00f, "Gain Fundamental"); + configParam(GAIN_02F_PARAM, 0.f, 1.f, 0.75f, "Gain x2 Fundamental"); + configParam(GAIN_04F_PARAM, 0.f, 1.f, 0.50f, "Gain x4 Fundamental"); + configParam(GAIN_08F_PARAM, 0.f, 1.f, 0.25f, "Gain x8 Fundamental"); + configParam(GAIN_16F_PARAM, 0.f, 1.f, 0.f, "Gain x16 Fundamental"); + configParam(GAIN_32F_PARAM, 0.f, 1.f, 0.f, "Gain x32 Fundamental"); + + configInput(VOCT1_INPUT, "V/Octave 1"); + configInput(VOCT2_INPUT, "V/Octave 2"); + configInput(SYNC_INPUT, "Sync"); + configInput(PWM_INPUT, "PWM"); + configInput(GAIN_01F_INPUT, "Gain x1F CV"); + configInput(GAIN_02F_INPUT, "Gain x1F CV"); + configInput(GAIN_04F_INPUT, "Gain x1F CV"); + configInput(GAIN_08F_INPUT, "Gain x1F CV"); + configInput(GAIN_16F_INPUT, "Gain x1F CV"); + configInput(GAIN_32F_INPUT, "Gain x1F CV"); + + configOutput(OUT_01F_OUTPUT, "x1F"); + configOutput(OUT_02F_OUTPUT, "x2F"); + configOutput(OUT_04F_OUTPUT, "x4F"); + configOutput(OUT_08F_OUTPUT, "x8F"); + configOutput(OUT_16F_OUTPUT, "x16F"); + configOutput(OUT_32F_OUTPUT, "x32F"); + + // calculate up/downsampling rates + onSampleRateChange(); + } + + void onSampleRateChange() override { + float sampleRate = APP->engine->getSampleRate(); + for (int c = 0; c < NUM_OUTPUTS; c++) { + for (int i = 0; i < 4; i++) { + oversampler[c][i].setOversamplingIndex(oversamplingIndex); + oversampler[c][i].reset(sampleRate); + blockDCFilter[c][i].setFrequency(22.05 / sampleRate); + } + } + } + + + void process(const ProcessArgs& args) override { + + const int numActivePolyphonyEngines = getNumActivePolyphonyEngines(); + + // work out active outputs + const std::vector connectedOutputs = getConnectedOutputs(); + if (connectedOutputs.size() == 0) { + return; + } + // only process up to highest active channel + const int highestOutput = *std::max_element(connectedOutputs.begin(), connectedOutputs.end()); + + for (int c = 0; c < numActivePolyphonyEngines; c += 4) { + + const int rangeIndex = params[RANGE_PARAM].getValue(); + float_4 pitch = ranges[rangeIndex] * params[TUNE_PARAM].getValue() + inputs[VOCT1_INPUT].getPolyVoltageSimd(c) + inputs[VOCT2_INPUT].getPolyVoltageSimd(c); + pitch += params[OCTAVE_PARAM].getValue() - 3; + const float_4 freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch); + // -1 to +1 + const float_4 pwmCV = params[PWM_CV_PARAM].getValue() * clamp(inputs[PWM_INPUT].getPolyVoltageSimd(c) / 10.f, -1.f, 1.f); + const float_4 pulseWidthLimit = limitPW ? 0.05f : 0.0f; + + // pwm in [-0.25 : +0.25] + const float_4 pwm = 2 * clamp(0.5 - params[PWM_PARAM].getValue() + 0.5 * pwmCV, -0.5f + pulseWidthLimit, 0.5f - pulseWidthLimit); + + const int oversamplingRatio = oversampler[0][0].getOversamplingRatio(); + + const float_4 deltaPhase = freq * args.sampleTime / oversamplingRatio; + + // process sync + float_4 sync = syncTrigger[c / 4].process(inputs[SYNC_INPUT].getPolyVoltageSimd(c)); + phase[c / 4] = simd::ifelse(sync, 0.5f, phase[c / 4]); + + + for (int i = 0; i < oversamplingRatio; i++) { + + phase[c / 4] += deltaPhase; + phase[c / 4] -= simd::floor(phase[c / 4]); + + float_4 sum = {}; + for (int oct = 0; oct <= highestOutput; oct++) { + // derive phases for higher octaves from base phase (this keeps things in sync!) + const float_4 n = (float)(1 << oct); + // this is on [0, 1] + const float_4 effectivePhase = n * simd::fmod(phase[c / 4], 1 / n); + const float_4 gainCV = simd::clamp(inputs[GAIN_01F_INPUT + oct].getNormalPolyVoltageSimd(10.f, c) / 10.f, 0.f, 1.0f); + const float_4 gain = params[GAIN_01F_PARAM + oct].getValue() * gainCV; + + const float_4 waveTri = 1.0 - 2.0 * simd::abs(2.f * effectivePhase - 1.0); + // build square from triangle + comparator + const float_4 waveSquare = simd::ifelse(waveTri > pwm, +1.f, -1.f); + + sum += (useTriangleCore ? waveTri : waveSquare) * gain; + sum = clamp(sum, -1.f, 1.f); + + if (outputs[OUT_01F_OUTPUT + oct].isConnected()) { + oversampler[oct][c/4].getOSBuffer()[i] = sum; + sum = 0.f; + + // DEBUG("here %f %f %f %f %f", phase[c/4][0], waveTri[0], sum[0], gain[0], gainCV[0]); + } + + + + } + + } // end of oversampling loop + + // only downsample required channels + for (int oct = 0; oct <= highestOutput; oct++) { + if (outputs[OUT_01F_OUTPUT + oct].isConnected()) { + + // downsample (if required) + float_4 out = (oversamplingRatio > 1) ? oversampler[oct][c/4].downsample() : oversampler[oct][c/4].getOSBuffer()[0]; + if (removePulseDC) { + out = blockDCFilter[oct][c/4].process(out); + } + + outputs[OUT_01F_OUTPUT + oct].setVoltageSimd(5.f * out, c); + } + } + } // end of polyphony loop + + for (int connectedOutput : connectedOutputs) { + outputs[OUT_01F_OUTPUT + connectedOutput].setChannels(numActivePolyphonyEngines); + } + } + + // polyphony is defined by the largest number of active channels on voct, pwm or gain inputs + int getNumActivePolyphonyEngines() { + int activePolyphonyEngines = 1; + for (int c = 0; c < NUM_OUTPUTS; c++) { + if (inputs[GAIN_01F_INPUT + c].isConnected()) { + activePolyphonyEngines = std::max(activePolyphonyEngines, inputs[GAIN_01F_INPUT + c].getChannels()); + } + } + activePolyphonyEngines = std::max({activePolyphonyEngines, inputs[VOCT1_INPUT].getChannels(), inputs[VOCT2_INPUT].getChannels()}); + activePolyphonyEngines = std::max(activePolyphonyEngines, inputs[PWM_INPUT].getChannels()); + + return activePolyphonyEngines; + } + + std::vector getConnectedOutputs() { + std::vector connectedOutputs; + for (int c = 0; c < NUM_OUTPUTS; c++) { + if (outputs[OUT_01F_OUTPUT + c].isConnected()) { + connectedOutputs.push_back(c); + } + } + return connectedOutputs; + } + + json_t* dataToJson() override { + json_t* rootJ = json_object(); + json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC)); + json_object_set_new(rootJ, "limitPW", json_boolean(limitPW)); + json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0][0].getOversamplingIndex())); + json_object_set_new(rootJ, "useTriangleCore", json_boolean(useTriangleCore)); + + return rootJ; + } + + void dataFromJson(json_t* rootJ) override { + + json_t* removePulseDCJ = json_object_get(rootJ, "removePulseDC"); + if (removePulseDCJ) { + removePulseDC = json_boolean_value(removePulseDCJ); + } + + json_t* limitPWJ = json_object_get(rootJ, "limitPW"); + if (limitPWJ) { + limitPW = json_boolean_value(limitPWJ); + } + + json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex"); + if (oversamplingIndexJ) { + oversamplingIndex = json_integer_value(oversamplingIndexJ); + onSampleRateChange(); + } + + json_t* useTriangleCoreJ = json_object_get(rootJ, "useTriangleCore"); + if (useTriangleCoreJ) { + useTriangleCore = json_boolean_value(useTriangleCoreJ); + } + } +}; + +struct OctavesWidget : ModuleWidget { + OctavesWidget(Octaves* module) { + setModule(module); + setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/Octaves.svg"))); + + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(createParamCentered(mm2px(Vec(52.138, 15.037)), module, Octaves::PWM_CV_PARAM)); + addParam(createParam(mm2px(Vec(22.171, 30.214)), module, Octaves::OCTAVE_PARAM)); + addParam(createParamCentered(mm2px(Vec(10.264, 33.007)), module, Octaves::TUNE_PARAM)); + addParam(createParamCentered(mm2px(Vec(45.384, 40.528)), module, Octaves::PWM_PARAM)); + addParam(createParam(mm2px(Vec(6.023, 48.937)), module, Octaves::RANGE_PARAM)); + addParam(createParam(mm2px(Vec(2.9830, 60.342)), module, Octaves::GAIN_01F_PARAM)); + addParam(createParam(mm2px(Vec(12.967, 60.342)), module, Octaves::GAIN_02F_PARAM)); + addParam(createParam(mm2px(Vec(22.951, 60.342)), module, Octaves::GAIN_04F_PARAM)); + addParam(createParam(mm2px(Vec(32.936, 60.342)), module, Octaves::GAIN_08F_PARAM)); + addParam(createParam(mm2px(Vec(42.920, 60.342)), module, Octaves::GAIN_16F_PARAM)); + addParam(createParam(mm2px(Vec(52.905, 60.342)), module, Octaves::GAIN_32F_PARAM)); + + addInput(createInputCentered(mm2px(Vec(5.247, 15.181)), module, Octaves::VOCT1_INPUT)); + addInput(createInputCentered(mm2px(Vec(15.282, 15.181)), module, Octaves::VOCT2_INPUT)); + addInput(createInputCentered(mm2px(Vec(25.316, 15.181)), module, Octaves::SYNC_INPUT)); + addInput(createInputCentered(mm2px(Vec(37.092, 15.135)), module, Octaves::PWM_INPUT)); + addInput(createInputCentered(mm2px(Vec(5.247, 100.492)), module, Octaves::GAIN_01F_INPUT)); + addInput(createInputCentered(mm2px(Vec(15.282, 100.492)), module, Octaves::GAIN_02F_INPUT)); + addInput(createInputCentered(mm2px(Vec(25.316, 100.492)), module, Octaves::GAIN_04F_INPUT)); + addInput(createInputCentered(mm2px(Vec(35.35, 100.492)), module, Octaves::GAIN_08F_INPUT)); + addInput(createInputCentered(mm2px(Vec(45.384, 100.492)), module, Octaves::GAIN_16F_INPUT)); + addInput(createInputCentered(mm2px(Vec(55.418, 100.492)), module, Octaves::GAIN_32F_INPUT)); + + addOutput(createOutputCentered(mm2px(Vec(5.247, 113.508)), module, Octaves::OUT_01F_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(15.282, 113.508)), module, Octaves::OUT_02F_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(25.316, 113.508)), module, Octaves::OUT_04F_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(35.35, 113.508)), module, Octaves::OUT_08F_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(45.384, 113.508)), module, Octaves::OUT_16F_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(55.418, 113.508)), module, Octaves::OUT_32F_OUTPUT)); + + } + + void appendContextMenu(Menu* menu) override { + Octaves* module = dynamic_cast(this->module); + assert(module); + + menu->addChild(new MenuSeparator()); + menu->addChild(createSubmenuItem("Hardware compatibility", "", + [ = ](Menu * menu) { + menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW)); + menu->addChild(createBoolPtrMenuItem("Remove pulse DC", "", &module->removePulseDC)); + menu->addChild(createBoolPtrMenuItem("Use triangle core", "", &module->useTriangleCore)); + } + )); + + menu->addChild(createIndexSubmenuItem("Oversampling", + {"Off", "x2", "x4", "x8"}, + [ = ]() { + return module->oversamplingIndex; + }, + [ = ](int mode) { + module->oversamplingIndex = mode; + module->onSampleRateChange(); + } + )); + + } +}; + +Model* modelOctaves = createModel("Octaves"); \ No newline at end of file diff --git a/src/Voltio.cpp b/src/Voltio.cpp index b25df3b..550431c 100644 --- a/src/Voltio.cpp +++ b/src/Voltio.cpp @@ -40,7 +40,7 @@ struct Voltio : Module { auto octParam = configParam(OCT_PARAM, 0.f, 10.f, 0.f, "Octave"); octParam->snapEnabled = true; - configSwitch(RANGE_PARAM, 0.f, 1.f, 0.f, "Range", {"-5 to +5", "0 to 10"}); + configSwitch(RANGE_PARAM, 0.f, 1.f, 0.f, "Range", {"0 to 10", "-5 to +5"}); auto semitonesParam = configParam(SEMITONES_PARAM, 0.f, 11.f, 0.f, "Semitones"); semitonesParam->snapEnabled = true; diff --git a/src/plugin.cpp b/src/plugin.cpp index 475f31e..704debd 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -29,4 +29,5 @@ void init(rack::Plugin *p) { p->addModel(modelMotionMTR); p->addModel(modelBurst); p->addModel(modelVoltio); + p->addModel(modelOctaves); } diff --git a/src/plugin.hpp b/src/plugin.hpp index f9d86e8..17addd5 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -30,6 +30,7 @@ extern Model* modelPonyVCO; extern Model* modelMotionMTR; extern Model* modelBurst; extern Model* modelVoltio; +extern Model* modelOctaves; struct Knurlie : SvgScrew { Knurlie() { @@ -312,7 +313,7 @@ struct ADEnvelope { }; // Creates a Butterworth 2*Nth order highpass filter for blocking DC -template +template struct DCBlockerT { DCBlockerT() { @@ -325,7 +326,7 @@ struct DCBlockerT { recalculateCoefficients(); } - float process(float x) { + T process(T x) { for (int idx = 0; idx < N; idx++) { x = blockDCFilter[idx].process(x); } @@ -342,17 +343,17 @@ struct DCBlockerT { for (int idx = 0; idx < N; idx++) { float Q = 1.0f / (2.0f * std::cos(firstAngle + idx * poleInc)); - blockDCFilter[idx].setParameters(dsp::BiquadFilter::HIGHPASS, fc_, Q, 1.0f); + blockDCFilter[idx].setParameters(dsp::TBiquadFilter::HIGHPASS, fc_, Q, 1.0f); } } float fc_; static const int order = 2 * N; - dsp::BiquadFilter blockDCFilter[N]; + dsp::TBiquadFilter blockDCFilter[N]; }; -typedef DCBlockerT<2> DCBlocker; +typedef DCBlockerT<2, float> DCBlocker; /** When triggered, holds a high value for a specified time before going low again */ struct PulseGenerator_4 { From aa72c130d0e2db44b98cbc65bf65d4e74438fda9 Mon Sep 17 00:00:00 2001 From: Ewan <915048+hemmer@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:50:57 +0100 Subject: [PATCH 2/2] Update plugin.json --- plugin.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin.json b/plugin.json index 3befb93..80f7c48 100644 --- a/plugin.json +++ b/plugin.json @@ -316,8 +316,8 @@ "modularGridUrl": "https://www.modulargrid.net/e/befaco-octaves-vco", "tags": [ "Hardware clone", - "VCO" + "Oscillator" ] } ] -} \ No newline at end of file +}