diff --git a/plugin.json b/plugin.json
index d22bc90..001182b 100644
--- a/plugin.json
+++ b/plugin.json
@@ -75,8 +75,8 @@
},
{
"slug": "LFO2",
- "name": "LFO-2",
- "description": "Low-frequency oscillator",
+ "name": "Wavetable LFO",
+ "description": "Low-frequency wavetable oscillator",
"manualUrl": "https://vcvrack.com/Fundamental#LFO",
"tags": [
"LFO",
diff --git a/res/ADSR.svg b/res/ADSR.svg
index 5f9feba..795dd28 100644
--- a/res/ADSR.svg
+++ b/res/ADSR.svg
@@ -1,297 +1,825 @@
-
-
diff --git a/res/LFO-1.svg b/res/LFO-1.svg
deleted file mode 100644
index 7509fc5..0000000
--- a/res/LFO-1.svg
+++ /dev/null
@@ -1,482 +0,0 @@
-
-
-
-
diff --git a/res/LFO-2.svg b/res/LFO-2.svg
deleted file mode 100644
index 7885fda..0000000
--- a/res/LFO-2.svg
+++ /dev/null
@@ -1,328 +0,0 @@
-
-
-
-
diff --git a/res/LFO.svg b/res/LFO.svg
new file mode 100644
index 0000000..95cbf36
--- /dev/null
+++ b/res/LFO.svg
@@ -0,0 +1,655 @@
+
+
diff --git a/res/VCF.svg b/res/VCF.svg
index eca2e87..fb69c93 100644
--- a/res/VCF.svg
+++ b/res/VCF.svg
@@ -1,6 +1,4 @@
-
-
diff --git a/res/VCO-1.svg b/res/VCO-1.svg
deleted file mode 100644
index fbcbe6c..0000000
--- a/res/VCO-1.svg
+++ /dev/null
@@ -1,467 +0,0 @@
-
-
-
-
diff --git a/res/VCO-2.svg b/res/VCO-2.svg
deleted file mode 100644
index c3e2536..0000000
--- a/res/VCO-2.svg
+++ /dev/null
@@ -1,327 +0,0 @@
-
-
-
-
diff --git a/res/VCO.svg b/res/VCO.svg
new file mode 100644
index 0000000..b55bf70
--- /dev/null
+++ b/res/VCO.svg
@@ -0,0 +1,631 @@
+
+
diff --git a/res/WTLFO.svg b/res/WTLFO.svg
new file mode 100644
index 0000000..c321881
--- /dev/null
+++ b/res/WTLFO.svg
@@ -0,0 +1,505 @@
+
+
diff --git a/res/WTVCO.svg b/res/WTVCO.svg
new file mode 100644
index 0000000..369a2e6
--- /dev/null
+++ b/res/WTVCO.svg
@@ -0,0 +1,535 @@
+
+
diff --git a/src/ADSR.cpp b/src/ADSR.cpp
index 99d9ce8..724bb6a 100644
--- a/src/ADSR.cpp
+++ b/src/ADSR.cpp
@@ -160,29 +160,32 @@ struct ADSRWidget : ModuleWidget {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/ADSR.svg")));
- addChild(createWidget(Vec(15, 0)));
- addChild(createWidget(Vec(box.size.x - 30, 0)));
- addChild(createWidget(Vec(15, 365)));
- addChild(createWidget(Vec(box.size.x - 30, 365)));
-
- addParam(createParam(Vec(62, 57), module, ADSR::ATTACK_PARAM));
- addParam(createParam(Vec(62, 124), module, ADSR::DECAY_PARAM));
- addParam(createParam(Vec(62, 191), module, ADSR::SUSTAIN_PARAM));
- addParam(createParam(Vec(62, 257), module, ADSR::RELEASE_PARAM));
-
- addInput(createInput(Vec(9, 63), module, ADSR::ATTACK_INPUT));
- addInput(createInput(Vec(9, 129), module, ADSR::DECAY_INPUT));
- addInput(createInput(Vec(9, 196), module, ADSR::SUSTAIN_INPUT));
- addInput(createInput(Vec(9, 263), module, ADSR::RELEASE_INPUT));
-
- addInput(createInput(Vec(9, 320), module, ADSR::GATE_INPUT));
- addInput(createInput(Vec(48, 320), module, ADSR::TRIG_INPUT));
- addOutput(createOutput(Vec(87, 320), module, ADSR::ENVELOPE_OUTPUT));
-
- addChild(createLight>(Vec(94, 41), module, ADSR::ATTACK_LIGHT));
- addChild(createLight>(Vec(94, 109), module, ADSR::DECAY_LIGHT));
- addChild(createLight>(Vec(94, 175), module, ADSR::SUSTAIN_LIGHT));
- addChild(createLight>(Vec(94, 242), module, ADSR::RELEASE_LIGHT));
+ 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(6.604, 55.454)), module, ADSR::ATTACK_PARAM));
+ addParam(createParamCentered(mm2px(Vec(17.441, 55.454)), module, ADSR::DECAY_PARAM));
+ addParam(createParamCentered(mm2px(Vec(28.279, 55.454)), module, ADSR::SUSTAIN_PARAM));
+ addParam(createParamCentered(mm2px(Vec(39.116, 55.454)), module, ADSR::RELEASE_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(6.604, 80.603)), module, ADSR::ATTCV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(17.441, 80.63)), module, ADSR::DECCV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(28.279, 80.603)), module, ADSR::SUSCV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(39.119, 80.603)), module, ADSR::RELCV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(6.604, 113.115)), module, ADSR::PUSH_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(6.604, 96.882)), module, ADSR::ATTACK_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.441, 96.859)), module, ADSR::DECAY_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.279, 96.886)), module, ADSR::SUSTAIN_INPUT));
+ addInput(createInputCentered(mm2px(Vec(39.119, 96.89)), module, ADSR::RELEASE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.441, 113.115)), module, ADSR::GATE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.279, 113.115)), module, ADSR::TRIG_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(39.119, 113.115)), module, ADSR::ENVELOPE_OUTPUT));
+
+ // mm2px(Vec(45.72, 21.219))
+ // addChild(createWidget(mm2px(Vec(0.0, 13.039))));
}
};
diff --git a/src/LFO.cpp b/src/LFO.cpp
index fe49273..b591485 100644
--- a/src/LFO.cpp
+++ b/src/LFO.cpp
@@ -88,14 +88,14 @@ struct LFO : Module {
INVERT_PARAM,
FREQ_PARAM,
FM1_PARAM,
- FM2_PARAM,
+ FM2_PARAM, // removed
PW_PARAM,
PWM_PARAM,
NUM_PARAMS
};
enum InputIds {
FM1_INPUT,
- FM2_INPUT,
+ FM2_INPUT, // removed
RESET_INPUT,
PW_INPUT,
NUM_INPUTS
@@ -200,37 +200,34 @@ struct LFO : Module {
};
-
struct LFOWidget : ModuleWidget {
LFOWidget(LFO* module) {
setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/LFO-1.svg")));
-
- addChild(createWidget(Vec(15, 0)));
- addChild(createWidget(Vec(box.size.x - 30, 0)));
- addChild(createWidget(Vec(15, 365)));
- addChild(createWidget(Vec(box.size.x - 30, 365)));
-
- addParam(createParam(Vec(15, 77), module, LFO::OFFSET_PARAM));
- addParam(createParam(Vec(119, 77), module, LFO::INVERT_PARAM));
-
- addParam(createParam(Vec(47, 61), module, LFO::FREQ_PARAM));
- addParam(createParam(Vec(23, 143), module, LFO::FM1_PARAM));
- addParam(createParam(Vec(91, 143), module, LFO::PW_PARAM));
- addParam(createParam(Vec(23, 208), module, LFO::FM2_PARAM));
- addParam(createParam(Vec(91, 208), module, LFO::PWM_PARAM));
-
- addInput(createInput(Vec(11, 276), module, LFO::FM1_INPUT));
- addInput(createInput(Vec(45, 276), module, LFO::FM2_INPUT));
- addInput(createInput(Vec(80, 276), module, LFO::RESET_INPUT));
- addInput(createInput(Vec(114, 276), module, LFO::PW_INPUT));
-
- addOutput(createOutput(Vec(11, 320), module, LFO::SIN_OUTPUT));
- addOutput(createOutput(Vec(45, 320), module, LFO::TRI_OUTPUT));
- addOutput(createOutput(Vec(80, 320), module, LFO::SAW_OUTPUT));
- addOutput(createOutput(Vec(114, 320), module, LFO::SQR_OUTPUT));
-
- addChild(createLight>(Vec(99, 42.5f), module, LFO::PHASE_LIGHT));
+ setPanel(createPanel(asset::plugin(pluginInstance, "res/LFO.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(22.902, 29.803)), module, LFO::FREQ_PARAM));
+ addParam(createParamCentered(mm2px(Vec(22.861, 56.388)), module, LFO::PW_PARAM));
+ addParam(createParamCentered(mm2px(Vec(6.604, 80.603)), module, LFO::FM1_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(17.441, 80.603)), module, LFO::INV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(28.279, 80.603)), module, LFO::OFST_PARAM));
+ addParam(createParamCentered(mm2px(Vec(39.116, 80.603)), module, LFO::PWM_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(6.604, 96.859)), module, LFO::FM1_INPUT));
+ // addInput(createInputCentered(mm2px(Vec(17.441, 96.859)), module, LFO::CLK_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.279, 96.819)), module, LFO::RESET_INPUT));
+ addInput(createInputCentered(mm2px(Vec(39.116, 96.819)), module, LFO::PW_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(6.604, 113.115)), module, LFO::SIN_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(17.441, 113.115)), module, LFO::TRI_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(28.279, 113.115)), module, LFO::SAW_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(39.116, 113.115)), module, LFO::SQR_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(31.085, 16.428)), module, LFO::PHASE_LIGHT));
}
};
@@ -238,6 +235,7 @@ struct LFOWidget : ModuleWidget {
Model* modelLFO = createModel("LFO");
+#if 0
struct LFO2 : Module {
enum ParamIds {
OFFSET_PARAM,
@@ -335,7 +333,7 @@ struct LFO2 : Module {
struct LFO2Widget : ModuleWidget {
LFO2Widget(LFO2* module) {
setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/LFO-2.svg")));
+ setPanel(createPanel(asset::plugin(pluginInstance, "res/WTLFO.svg")));
addChild(createWidget(Vec(15, 0)));
addChild(createWidget(Vec(box.size.x - 30, 0)));
@@ -361,3 +359,4 @@ struct LFO2Widget : ModuleWidget {
Model* modelLFO2 = createModel("LFO2");
+#endif
\ No newline at end of file
diff --git a/src/VCF.cpp b/src/VCF.cpp
index 40f546e..de1a0a9 100644
--- a/src/VCF.cpp
+++ b/src/VCF.cpp
@@ -68,7 +68,7 @@ static const int UPSAMPLE = 2;
struct VCF : Module {
enum ParamIds {
FREQ_PARAM,
- FINE_PARAM,
+ FINE_PARAM, // removed
RES_PARAM,
FREQ_CV_PARAM,
DRIVE_PARAM,
@@ -199,24 +199,25 @@ struct VCFWidget : ModuleWidget {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/VCF.svg")));
- addChild(createWidget(Vec(15, 0)));
- addChild(createWidget(Vec(box.size.x - 30, 0)));
- addChild(createWidget(Vec(15, 365)));
- addChild(createWidget(Vec(box.size.x - 30, 365)));
-
- addParam(createParam(Vec(33, 61), module, VCF::FREQ_PARAM));
- addParam(createParam(Vec(12, 143), module, VCF::FINE_PARAM));
- addParam(createParam(Vec(71, 143), module, VCF::RES_PARAM));
- addParam(createParam(Vec(12, 208), module, VCF::FREQ_CV_PARAM));
- addParam(createParam(Vec(71, 208), module, VCF::DRIVE_PARAM));
-
- addInput(createInput(Vec(10, 276), module, VCF::FREQ_INPUT));
- addInput(createInput(Vec(48, 276), module, VCF::RES_INPUT));
- addInput(createInput(Vec(85, 276), module, VCF::DRIVE_INPUT));
- addInput(createInput(Vec(10, 320), module, VCF::IN_INPUT));
-
- addOutput(createOutput(Vec(48, 320), module, VCF::LPF_OUTPUT));
- addOutput(createOutput(Vec(85, 320), module, VCF::HPF_OUTPUT));
+ 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(17.587, 29.808)), module, VCF::FREQ_PARAM));
+ addParam(createParamCentered(mm2px(Vec(8.895, 56.388)), module, VCF::RES_PARAM));
+ addParam(createParamCentered(mm2px(Vec(26.665, 56.388)), module, VCF::DRIVE_PARAM));
+ addParam(createParamCentered(mm2px(Vec(6.996, 80.603)), module, VCF::FREQ_CV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(17.833, 80.603)), module, VCF::RESCV_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(28.67, 80.603)), module, VCF::DRIVE_CV_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(6.996, 96.813)), module, VCF::FREQ_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.833, 96.813)), module, VCF::RES_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.67, 96.813)), module, VCF::DRIVE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(6.996, 113.115)), module, VCF::IN_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(17.833, 113.115)), module, VCF::LPF_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(28.67, 113.115)), module, VCF::HPF_OUTPUT));
}
};
diff --git a/src/VCO.cpp b/src/VCO.cpp
index b1ddc9f..b075933 100644
--- a/src/VCO.cpp
+++ b/src/VCO.cpp
@@ -250,10 +250,11 @@ struct VCO : Module {
MODE_PARAM,
SYNC_PARAM,
FREQ_PARAM,
- FINE_PARAM,
+ FINE_PARAM, // removed
FM_PARAM,
PW_PARAM,
PWM_PARAM,
+ LINEAR_PARAM,
NUM_PARAMS
};
enum InputIds {
@@ -360,40 +361,38 @@ struct VCO : Module {
struct VCOWidget : ModuleWidget {
VCOWidget(VCO* module) {
setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO-1.svg")));
-
- addChild(createWidget(Vec(15, 0)));
- addChild(createWidget(Vec(box.size.x - 30, 0)));
- addChild(createWidget(Vec(15, 365)));
- addChild(createWidget(Vec(box.size.x - 30, 365)));
-
- addParam(createParam(Vec(15, 77), module, VCO::MODE_PARAM));
- addParam(createParam(Vec(119, 77), module, VCO::SYNC_PARAM));
-
- addParam(createParam(Vec(47, 61), module, VCO::FREQ_PARAM));
- addParam(createParam(Vec(23, 143), module, VCO::FINE_PARAM));
- addParam(createParam(Vec(91, 143), module, VCO::PW_PARAM));
- addParam(createParam(Vec(23, 208), module, VCO::FM_PARAM));
- addParam(createParam(Vec(91, 208), module, VCO::PWM_PARAM));
-
- addInput(createInput(Vec(11, 276), module, VCO::PITCH_INPUT));
- addInput(createInput(Vec(45, 276), module, VCO::FM_INPUT));
- addInput(createInput(Vec(80, 276), module, VCO::SYNC_INPUT));
- addInput(createInput(Vec(114, 276), module, VCO::PW_INPUT));
-
- addOutput(createOutput(Vec(11, 320), module, VCO::SIN_OUTPUT));
- addOutput(createOutput(Vec(45, 320), module, VCO::TRI_OUTPUT));
- addOutput(createOutput(Vec(80, 320), module, VCO::SAW_OUTPUT));
- addOutput(createOutput(Vec(114, 320), module, VCO::SQR_OUTPUT));
-
- addChild(createLight>(Vec(99, 42.5f), module, VCO::PHASE_LIGHT));
+ setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO.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(22.905, 29.808)), module, VCO::FREQ_PARAM));
+ addParam(createParamCentered(mm2px(Vec(22.862, 56.388)), module, VCO::PW_PARAM));
+ addParam(createParamCentered(mm2px(Vec(6.607, 80.603)), module, VCO::FM_PARAM));
+ addParam(createParamCentered(mm2px(Vec(17.444, 80.603)), module, VCO::LINEAR_PARAM));
+ addParam(createParamCentered(mm2px(Vec(28.282, 80.603)), module, VCO::SYNC_PARAM));
+ addParam(createParamCentered(mm2px(Vec(39.118, 80.603)), module, VCO::PWM_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(6.607, 96.859)), module, VCO::FM_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.444, 96.859)), module, VCO::PITCH_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.282, 96.859)), module, VCO::SYNC_INPUT));
+ addInput(createInputCentered(mm2px(Vec(39.15, 96.859)), module, VCO::PW_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(6.607, 113.115)), module, VCO::SIN_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(17.444, 113.115)), module, VCO::TRI_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(28.282, 113.115)), module, VCO::SAW_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(39.119, 113.115)), module, VCO::SQR_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(31.089, 16.428)), module, VCO::PHASE_LIGHT));
}
};
Model* modelVCO = createModel("VCO");
-
+#if 0
struct VCO2 : Module {
enum ParamIds {
MODE_PARAM,
@@ -493,7 +492,7 @@ struct VCO2 : Module {
struct VCO2Widget : ModuleWidget {
VCO2Widget(VCO2* module) {
setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO-2.svg")));
+ setPanel(createPanel(asset::plugin(pluginInstance, "res/WTVCO.svg")));
addChild(createWidget(Vec(15, 0)));
addChild(createWidget(Vec(box.size.x - 30, 0)));
@@ -519,3 +518,4 @@ struct VCO2Widget : ModuleWidget {
Model* modelVCO2 = createModel("VCO2");
+#endif
\ No newline at end of file
diff --git a/src/WTLFO.cpp b/src/WTLFO.cpp
new file mode 100644
index 0000000..0779b69
--- /dev/null
+++ b/src/WTLFO.cpp
@@ -0,0 +1,235 @@
+#include "plugin.hpp"
+
+
+using simd::float_4;
+
+
+struct WTLFO : Module {
+ enum ParamId {
+ OFFSET_PARAM,
+ INVERT_PARAM,
+ FREQ_PARAM,
+ POS_PARAM,
+ FM_PARAM,
+ // added in 2.0
+ POS_CV_PARAM,
+ NUM_PARAMS
+ };
+ enum InputId {
+ FM_INPUT,
+ RESET_INPUT,
+ POS_INPUT,
+ // added in 2.0
+ CLOCK_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputId {
+ WAVE_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightId {
+ ENUMS(PHASE_LIGHT, 3),
+ OFFSET_LIGHT,
+ INVERT_LIGHT,
+ NUM_LIGHTS
+ };
+
+ dsp::ClockDivider lightDivider;
+ // All waves concatenated
+ std::vector wavetable;
+ // Number of points in each wave
+ size_t waveLen = 0;
+ bool offset = false;
+ bool invert = false;
+
+ float_4 phases[4] = {};
+ dsp::BooleanTrigger offsetTrigger;
+ dsp::BooleanTrigger invertTrigger;
+
+ WTLFO() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ // TODO Change to momentary with backward compatibility in fromJson()
+ configButton(OFFSET_PARAM, "Offset 0-10V");
+ configButton(INVERT_PARAM, "Invert wave");
+ configParam(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1);
+ configParam(POS_PARAM, 0.f, 1.f, 0.f, "Wavetable position", "%", 0.f, 100.f);
+ configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
+ configParam(POS_CV_PARAM, -1.f, 1.f, 0.f, "Wavetable position CV", "%", 0.f, 100.f);
+ configInput(FM_INPUT, "Frequency modulation");
+ configInput(RESET_INPUT, "Reset");
+ configInput(POS_INPUT, "Wavetable position");
+ configInput(CLOCK_INPUT, "Clock");
+ configOutput(WAVE_OUTPUT, "Wavetable");
+ configLight(PHASE_LIGHT, "Phase");
+
+ lightDivider.setDivision(16);
+ onReset(ResetEvent());
+ }
+
+ void onReset(const ResetEvent& e) override {
+ Module::onReset(e);
+
+ // Build geometric waveforms
+ wavetable.clear();
+ waveLen = 1024;
+ wavetable.resize(waveLen * 4);
+
+ for (size_t i = 0; i < waveLen; i++) {
+ float p = float(i) / waveLen;
+ float sin = std::sin(2 * float(M_PI) * p);
+ wavetable[i + 0 * waveLen] = sin;
+ float tri = (p < 0.25f) ? 4*p : (p < 0.75f) ? 2 - 4*p : 4*p - 4;
+ wavetable[i + 1 * waveLen] = tri;
+ float saw = (p < 0.5f) ? 2*p : 2*p - 2;
+ wavetable[i + 2 * waveLen] = saw;
+ float sqr = (p < 0.5f) ? 1 : -1;
+ wavetable[i + 3 * waveLen] = sqr;
+ }
+
+ // Reset state
+ for (int c = 0; c < 16; c += 4) {
+ phases[c / 4] = 0.f;
+ }
+ }
+
+ void clearOutput() {
+ outputs[WAVE_OUTPUT].setVoltage(0.f);
+ outputs[WAVE_OUTPUT].setChannels(1);
+ lights[PHASE_LIGHT + 0].setBrightness(0.f);
+ lights[PHASE_LIGHT + 1].setBrightness(0.f);
+ lights[PHASE_LIGHT + 2].setBrightness(0.f);
+ }
+
+ void process(const ProcessArgs& args) override {
+ float freqParam = params[FREQ_PARAM].getValue();
+ float fmParam = params[FM_PARAM].getValue();
+ float posParam = params[POS_PARAM].getValue();
+ float posCvParam = params[POS_CV_PARAM].getValue();
+
+ if (offsetTrigger.process(params[OFFSET_PARAM].getValue() > 0.f))
+ offset ^= true;
+ if (invertTrigger.process(params[INVERT_PARAM].getValue() > 0.f))
+ invert ^= true;
+
+ int channels = std::max(1, inputs[FM_INPUT].getChannels());
+
+ // Check valid wave and wavetable size
+ if (waveLen < 2) {
+ clearOutput();
+ return;
+ }
+ int wavetableNum = wavetable.size() / waveLen;
+ if (wavetableNum < 1) {
+ clearOutput();
+ return;
+ }
+
+ for (int c = 0; c < channels; c += 4) {
+ // Calculate frequency in Hz
+ float_4 pitch = freqParam + inputs[FM_INPUT].getVoltageSimd(c) * fmParam;
+ float_4 freq = simd::pow(2.f, pitch);
+
+ // Accumulate phase
+ float_4 phase = phases[c / 4];
+ phase += freq * args.sampleTime;
+ // Wrap phase
+ phase -= simd::trunc(phase);
+ phases[c / 4] = phase;
+ // Scale phase from 0 to waveLen
+ phase *= waveLen;
+
+ // Get wavetable position, scaled from 0 to (wavetableNum - 1)
+ float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd(c) * posCvParam / 10.f;
+ pos = simd::clamp(pos);
+ pos *= (wavetableNum - 1);
+
+ // Get wavetable points
+ float_4 out = 0.f;
+ for (int i = 0; i < 4 && c + i < channels; i++) {
+ // Get wave indexes
+ float phaseF = phase[i] - std::trunc(phase[i]);
+ size_t i0 = std::trunc(phase[i]);
+ size_t i1 = (i0 + 1) % waveLen;
+ // Get pos indexes
+ float posF = pos[0] - std::trunc(pos[0]);
+ size_t pos0 = std::trunc(pos[0]);
+ size_t pos1 = pos0 + 1;
+ // Get waves
+ float out0 = crossfade(wavetable[i0 + pos0 * waveLen], wavetable[i1 + pos0 * waveLen], phaseF);
+ if (posF > 0.f) {
+ float out1 = crossfade(wavetable[i0 + pos1 * waveLen], wavetable[i1 + pos1 * waveLen], phaseF);
+ out[i] = crossfade(out0, out1, posF);
+ }
+ else {
+ out[i] = out0;
+ }
+ }
+
+ // Invert and offset
+ if (invert)
+ out *= -1.f;
+ if (offset)
+ out += 1.f;
+
+ outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);
+ }
+
+ outputs[WAVE_OUTPUT].setChannels(channels);
+
+ // Light
+ if (lightDivider.process()) {
+ if (channels == 1) {
+ float b = 1.f - phases[0][0];
+ lights[PHASE_LIGHT + 0].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision());
+ lights[PHASE_LIGHT + 1].setBrightness(0.f);
+ lights[PHASE_LIGHT + 2].setBrightness(0.f);
+ }
+ else {
+ lights[PHASE_LIGHT + 0].setBrightness(0.f);
+ lights[PHASE_LIGHT + 1].setBrightness(0.f);
+ lights[PHASE_LIGHT + 2].setBrightness(1.f);
+ }
+ }
+ }
+};
+
+
+struct WTLFODisplay : LedDisplay {
+ WTLFODisplay() {
+ box.size = mm2px(Vec(35.56, 29.224));
+ }
+};
+
+
+struct WTLFOWidget : ModuleWidget {
+ WTLFOWidget(WTLFO* module) {
+ setModule(module);
+ setPanel(createPanel(asset::plugin(pluginInstance, "res/WTLFO.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(8.913, 56.388)), module, WTLFO::FREQ_PARAM));
+ addParam(createParamCentered(mm2px(Vec(26.647, 56.388)), module, WTLFO::POS_PARAM));
+ addParam(createParamCentered(mm2px(Vec(6.987, 80.603)), module, WTLFO::FM_PARAM));
+ addParam(createLightParamCentered>(mm2px(Vec(17.824, 80.517)), module, WTLFO::INVERT_PARAM, WTLFO::INVERT_LIGHT));
+ addParam(createParamCentered(mm2px(Vec(28.662, 80.536)), module, WTLFO::POS_CV_PARAM));
+ addParam(createLightParamCentered>(mm2px(Vec(17.824, 96.859)), module, WTLFO::OFFSET_PARAM, WTLFO::OFFSET_LIGHT));
+
+ addInput(createInputCentered(mm2px(Vec(6.987, 96.859)), module, WTLFO::FM_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.662, 96.859)), module, WTLFO::POS_INPUT));
+ addInput(createInputCentered(mm2px(Vec(6.987, 113.115)), module, WTLFO::CLOCK_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.824, 113.115)), module, WTLFO::RESET_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(28.662, 113.115)), module, WTLFO::WAVE_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(17.731, 49.409)), module, WTLFO::PHASE_LIGHT));
+
+ addChild(createWidget(mm2px(Vec(0.004, 13.04))));
+ }
+};
+
+
+Model* modelLFO2 = createModel("LFO2");
\ No newline at end of file
diff --git a/src/WTVCO.cpp b/src/WTVCO.cpp
new file mode 100644
index 0000000..4a5b75a
--- /dev/null
+++ b/src/WTVCO.cpp
@@ -0,0 +1,136 @@
+#include "plugin.hpp"
+
+
+struct WTVCO : Module {
+ enum ParamIds {
+ MODE_PARAM, // removed
+ SYNC_PARAM,
+ FREQ_PARAM,
+ WAVE_PARAM,
+ FM_PARAM,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ FM_INPUT,
+ SYNC_INPUT,
+ WAVE_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ ENUMS(PHASE_LIGHT, 3),
+ NUM_LIGHTS
+ };
+
+ // VoltageControlledOscillator<8, 8, float_4> oscillators[4];
+ dsp::ClockDivider lightDivider;
+
+ WTVCO() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configSwitch(MODE_PARAM, 0.f, 1.f, 1.f, "Engine mode", {"Digital", "Analog"});
+ configSwitch(SYNC_PARAM, 0.f, 1.f, 1.f, "Sync mode", {"Soft", "Hard"});
+ configParam(FREQ_PARAM, -54.f, 54.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
+ configParam(WAVE_PARAM, 0.f, 3.f, 1.5f, "Wave");
+ configParam(FM_PARAM, 0.f, 1.f, 1.f, "Frequency modulation", "%", 0.f, 100.f);
+ configInput(FM_INPUT, "Frequency modulation");
+ configInput(SYNC_INPUT, "Sync");
+ configInput(WAVE_INPUT, "Wave type");
+ configOutput(OUT_OUTPUT, "Audio");
+
+ lightDivider.setDivision(16);
+ }
+
+ void process(const ProcessArgs& args) override {
+ // float freqParam = params[FREQ_PARAM].getValue() / 12.f;
+ // float fmParam = dsp::quadraticBipolar(params[FM_PARAM].getValue());
+ // float waveParam = params[WAVE_PARAM].getValue();
+
+ // int channels = std::max(inputs[FM_INPUT].getChannels(), 1);
+
+ // for (int c = 0; c < channels; c += 4) {
+ // auto* oscillator = &oscillators[c / 4];
+ // oscillator->channels = std::min(channels - c, 4);
+ // oscillator->analog = (params[MODE_PARAM].getValue() > 0.f);
+ // oscillator->soft = (params[SYNC_PARAM].getValue() <= 0.f);
+
+ // float_4 pitch = freqParam;
+ // pitch += fmParam * inputs[FM_INPUT].getVoltageSimd(c);
+ // oscillator->setPitch(pitch);
+
+ // oscillator->syncEnabled = inputs[SYNC_INPUT].isConnected();
+ // oscillator->process(args.sampleTime, inputs[SYNC_INPUT].getPolyVoltageSimd(c));
+
+ // // Outputs
+ // if (outputs[OUT_OUTPUT].isConnected()) {
+ // float_4 wave = simd::clamp(waveParam + inputs[WAVE_INPUT].getPolyVoltageSimd(c) / 10.f * 3.f, 0.f, 3.f);
+ // float_4 v = 0.f;
+ // v += oscillator->sin() * simd::fmax(0.f, 1.f - simd::fabs(wave - 0.f));
+ // v += oscillator->tri() * simd::fmax(0.f, 1.f - simd::fabs(wave - 1.f));
+ // v += oscillator->saw() * simd::fmax(0.f, 1.f - simd::fabs(wave - 2.f));
+ // v += oscillator->sqr() * simd::fmax(0.f, 1.f - simd::fabs(wave - 3.f));
+ // outputs[OUT_OUTPUT].setVoltageSimd(5.f * v, c);
+ // }
+ // }
+
+ // outputs[OUT_OUTPUT].setChannels(channels);
+
+ // // Light
+ // if (lightDivider.process()) {
+ // if (channels == 1) {
+ // float lightValue = oscillators[0].light()[0];
+ // lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
+ // lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
+ // lights[PHASE_LIGHT + 2].setBrightness(0.f);
+ // }
+ // else {
+ // lights[PHASE_LIGHT + 0].setBrightness(0.f);
+ // lights[PHASE_LIGHT + 1].setBrightness(0.f);
+ // lights[PHASE_LIGHT + 2].setBrightness(1.f);
+ // }
+ // }
+ }
+};
+
+
+struct WTVCODisplay : LedDisplay {
+ WTVCODisplay() {
+ box.size = mm2px(Vec(35.56, 29.021));
+ }
+};
+
+
+struct WTVCOWidget : ModuleWidget {
+ WTVCOWidget(WTVCO* module) {
+ setModule(module);
+ setPanel(createPanel(asset::plugin(pluginInstance, "res/WTVCO.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(8.915, 56.388)), module, WTVCO::FREQ_PARAM));
+ addParam(createParamCentered(mm2px(Vec(26.645, 56.388)), module, WTVCO::WAVE_PARAM));
+ addParam(createParamCentered(mm2px(Vec(6.897, 80.603)), module, WTVCO::FM_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(17.734, 80.603)), module, WTVCO::LFM_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(28.571, 80.603)), module, WTVCO::POSCV_PARAM));
+ addParam(createParamCentered(mm2px(Vec(17.734, 96.859)), module, WTVCO::SYNC_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(6.897, 96.813)), module, WTVCO::FM_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.571, 96.859)), module, WTVCO::WAVE_INPUT));
+ // addInput(createInputCentered(mm2px(Vec(6.897, 113.115)), module, WTVCO::PITCH_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.734, 113.115)), module, WTVCO::SYNC_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(28.571, 113.115)), module, WTVCO::OUT_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(17.733, 49.409)), module, WTVCO::PHASE_LIGHT));
+
+ addChild(createWidget(mm2px(Vec(0.0, 13.039))));
+ }
+};
+
+
+Model* modelVCO2 = createModel("VCO2");
\ No newline at end of file