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 @@
+
+
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
+}