diff --git a/res/Random.svg b/res/Random.svg
index b26a2f8..a2a5518 100644
--- a/res/Random.svg
+++ b/res/Random.svg
@@ -1,6 +1,4 @@
-
-
diff --git a/res/Viz.svg b/res/Viz.svg
index 97ecf4f..34c452e 100644
--- a/res/Viz.svg
+++ b/res/Viz.svg
@@ -1,6 +1,4 @@
-
-
diff --git a/src/LFO.cpp b/src/LFO.cpp
index f05358b..eec68a7 100644
--- a/src/LFO.cpp
+++ b/src/LFO.cpp
@@ -48,14 +48,12 @@ struct LFO : Module {
float clockFreq = 1.f;
dsp::Timer clockTimer;
- dsp::BooleanTrigger offsetTrigger;
- dsp::BooleanTrigger invertTrigger;
dsp::ClockDivider lightDivider;
LFO() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
- configButton(OFFSET_PARAM, "Offset 0-10V");
- configButton(INVERT_PARAM, "Invert");
+ configSwitch(OFFSET_PARAM, 0.f, 1.f, 0.f, "Offset", {"Bipolar", "Unipolar"});
+ configSwitch(INVERT_PARAM, 0.f, 1.f, 0.f, "Invert");
struct FrequencyQuantity : ParamQuantity {
float getDisplayValue() override {
@@ -95,8 +93,6 @@ struct LFO : Module {
}
void onReset() override {
- offset = false;
- invert = false;
for (int c = 0; c < 16; c += 4) {
phases[c / 4] = 0.f;
}
@@ -109,14 +105,8 @@ struct LFO : Module {
float fmParam = params[FM_PARAM].getValue();
float pwParam = params[PW_PARAM].getValue();
float pwmParam = params[PWM_PARAM].getValue();
-
- // Buttons
- if (offsetTrigger.process(params[OFFSET_PARAM].getValue() > 0.f)) {
- offset ^= true;
- }
- if (invertTrigger.process(params[INVERT_PARAM].getValue() > 0.f)) {
- invert ^= true;
- }
+ bool offset = params[OFFSET_PARAM].getValue() > 0.f;
+ bool invert = params[INVERT_PARAM].getValue() > 0.f;
// Clock
if (inputs[CLOCK_INPUT].isConnected()) {
@@ -227,39 +217,6 @@ struct LFO : Module {
lights[INVERT_LIGHT].setBrightness(invert);
}
}
-
- json_t* dataToJson() override {
- json_t* rootJ = json_object();
- // offset
- json_object_set_new(rootJ, "offset", json_boolean(offset));
- // invert
- json_object_set_new(rootJ, "invert", json_boolean(invert));
- return rootJ;
- }
-
- void dataFromJson(json_t* rootJ) override {
- // offset
- json_t* offsetJ = json_object_get(rootJ, "offset");
- if (offsetJ)
- offset = json_boolean_value(offsetJ);
- // invert
- json_t* invertJ = json_object_get(rootJ, "invert");
- if (invertJ)
- invert = json_boolean_value(invertJ);
- }
-
- void paramsFromJson(json_t* rootJ) override {
- Module::paramsFromJson(rootJ);
- // In <2.0, OFFSET_PARAM and INVERT_PARAM were toggle switches instead of momentary buttons, so if params are on after deserializing, set boolean states instead.
- if (params[OFFSET_PARAM].getValue() > 0.f) {
- offset = true;
- params[OFFSET_PARAM].setValue(0.f);
- }
- if (params[INVERT_PARAM].getValue() > 0.f) {
- invert = true;
- params[INVERT_PARAM].setValue(0.f);
- }
- }
};
@@ -276,8 +233,8 @@ struct LFOWidget : ModuleWidget {
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::FM_PARAM));
- addParam(createLightParamCentered>>(mm2px(Vec(17.441, 80.603)), module, LFO::INVERT_PARAM, LFO::INVERT_LIGHT));
- addParam(createLightParamCentered>>(mm2px(Vec(28.279, 80.603)), module, LFO::OFFSET_PARAM, LFO::OFFSET_LIGHT));
+ addParam(createLightParamCentered>>(mm2px(Vec(17.441, 80.603)), module, LFO::INVERT_PARAM, LFO::INVERT_LIGHT));
+ addParam(createLightParamCentered>>(mm2px(Vec(28.279, 80.603)), module, LFO::OFFSET_PARAM, LFO::OFFSET_LIGHT));
addParam(createParamCentered(mm2px(Vec(39.116, 80.603)), module, LFO::PWM_PARAM));
addInput(createInputCentered(mm2px(Vec(6.604, 96.859)), module, LFO::FM_INPUT));
diff --git a/src/Random.cpp b/src/Random.cpp
index 27267d2..1b0a430 100644
--- a/src/Random.cpp
+++ b/src/Random.cpp
@@ -7,6 +7,13 @@ struct Random : Module {
SHAPE_PARAM,
OFFSET_PARAM,
MODE_PARAM,
+ // new in 2.0
+ PROB_PARAM, // TODO
+ RANDOM_PARAM, // TODO
+ RATE_CV_PARAM,
+ SHAPE_CV_PARAM,
+ PROB_CV_PARAM,
+ RANDOM_CV_PARAM,
NUM_PARAMS
};
enum InputIds {
@@ -14,6 +21,9 @@ struct Random : Module {
SHAPE_INPUT,
TRIGGER_INPUT,
EXTERNAL_INPUT,
+ // new in 2.0
+ PROB_INPUT,
+ RANDOM_INPUT,
NUM_INPUTS
};
enum OutputIds {
@@ -21,35 +31,64 @@ struct Random : Module {
LINEAR_OUTPUT,
SMOOTH_OUTPUT,
EXPONENTIAL_OUTPUT,
+ // new in 2.0
+ GATE_OUTPUT, // TODO
NUM_OUTPUTS
};
enum LightIds {
RATE_LIGHT,
SHAPE_LIGHT,
+ PROB_LIGHT,
+ RANDOM_LIGHT,
+ OFFSET_LIGHT,
NUM_LIGHTS
};
dsp::SchmittTrigger trigTrigger;
+ dsp::BooleanTrigger offsetTrigger;
float lastValue = 0.f;
float value = 0.f;
float clockPhase = 0.f;
int trigFrame = 0;
int lastTrigFrames = INT_MAX;
+ bool offset = false;
Random() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam(RATE_PARAM, std::log2(0.002f), std::log2(2000.f), std::log2(2.f), "Rate", " Hz", 2);
- configParam(SHAPE_PARAM, 0.f, 1.f, 0.5f, "Shape", "%", 0, 100);
- configSwitch(OFFSET_PARAM, 0.f, 1.f, 1.f, "Offset", {"Bipolar", "Unipolar"});
- configSwitch(MODE_PARAM, 0.f, 1.f, 1.f, "Randomness mode", {"Relative", "Absolute"});
+ configParam(SHAPE_PARAM, 0.f, 1.f, 1.f, "Shape", "%", 0, 100);
+ configParam(PROB_PARAM, 0.f, 1.f, 1.f, "Probability", "%", 0, 100);
+ configParam(RANDOM_PARAM, 0.f, 1.f, 1.f, "Randomness", "%", 0, 100);
+
+ configParam(RATE_CV_PARAM, -1.f, 1.f, 0.f, "Rate CV", "%", 0, 100);
+ getParamQuantity(RATE_CV_PARAM)->randomizeEnabled = false;
+ configParam(SHAPE_CV_PARAM, -1.f, 1.f, 0.f, "Shape CV", "%", 0, 100);
+ getParamQuantity(SHAPE_CV_PARAM)->randomizeEnabled = false;
+ configParam(PROB_CV_PARAM, -1.f, 1.f, 0.f, "Probability CV", "%", 0, 100);
+ getParamQuantity(PROB_CV_PARAM)->randomizeEnabled = false;
+ configParam(RANDOM_CV_PARAM, -1.f, 1.f, 0.f, "Randomness CV", "%", 0, 100);
+ getParamQuantity(RANDOM_CV_PARAM)->randomizeEnabled = false;
+
+ configButton(OFFSET_PARAM, "Offset 0-10V");
+
configInput(RATE_INPUT, "Rate");
configInput(SHAPE_INPUT, "Shape");
+ configInput(PROB_INPUT, "Probability");
+ configInput(RANDOM_INPUT, "Randomness");
configInput(TRIGGER_INPUT, "Trigger");
configInput(EXTERNAL_INPUT, "External");
+
configOutput(STEPPED_OUTPUT, "Stepped");
configOutput(LINEAR_OUTPUT, "Linear");
configOutput(SMOOTH_OUTPUT, "Smooth");
configOutput(EXPONENTIAL_OUTPUT, "Exponential");
+ configOutput(GATE_OUTPUT, "Gate");
+
+ onReset();
+ }
+
+ void onReset() override {
+ offset = false;
}
void trigger() {
@@ -87,6 +126,10 @@ struct Random : Module {
}
void process(const ProcessArgs& args) override {
+ if (offsetTrigger.process(params[OFFSET_PARAM].getValue() > 0.f)) {
+ offset ^= true;
+ }
+
if (inputs[TRIGGER_INPUT].isConnected()) {
// Advance clock phase based on tempo estimate
trigFrame++;
@@ -104,7 +147,7 @@ struct Random : Module {
else {
// Advance clock phase by rate
float rate = params[RATE_PARAM].getValue();
- rate += inputs[RATE_PARAM].getVoltage();
+ rate += inputs[RATE_PARAM].getVoltage() * params[RATE_CV_PARAM].getValue();
float clockFreq = std::pow(2.f, rate);
float deltaPhase = std::fmin(clockFreq * args.sampleTime, 0.5f);
clockPhase += deltaPhase;
@@ -115,10 +158,9 @@ struct Random : Module {
}
}
-
// Shape
float shape = params[SHAPE_PARAM].getValue();
- shape += inputs[SHAPE_INPUT].getVoltage() / 10.f;
+ shape += inputs[SHAPE_INPUT].getVoltage() / 10.f * params[SHAPE_CV_PARAM].getValue();
shape = clamp(shape, 0.f, 1.f);
// Stepped
@@ -178,6 +220,35 @@ struct Random : Module {
// Lights
lights[RATE_LIGHT].setSmoothBrightness(0.f, args.sampleTime);
lights[SHAPE_LIGHT].setBrightness(shape);
+ lights[OFFSET_LIGHT].setBrightness(offset);
+ }
+
+ void paramsFromJson(json_t* rootJ) override {
+ // In <2.0, there were no attenuverters, so set them to 1.0 in case they are not overwritten.
+ params[RATE_CV_PARAM].setValue(1.f);
+ params[SHAPE_CV_PARAM].setValue(1.f);
+ params[PROB_CV_PARAM].setValue(1.f);
+ params[RANDOM_CV_PARAM].setValue(1.f);
+ Module::paramsFromJson(rootJ);
+ // In <2.0, OFFSET_PARAM was a toggle switch instead of a momentary button, so if param is on after deserializing, set boolean states instead.
+ if (params[OFFSET_PARAM].getValue() > 0.f) {
+ offset = true;
+ params[OFFSET_PARAM].setValue(0.f);
+ }
+ }
+
+ json_t* dataToJson() override {
+ json_t* rootJ = json_object();
+ // offset
+ json_object_set_new(rootJ, "offset", json_boolean(offset));
+ return rootJ;
+ }
+
+ void dataFromJson(json_t* rootJ) override {
+ // offset
+ json_t* offsetJ = json_object_get(rootJ, "offset");
+ if (offsetJ)
+ offset = json_boolean_value(offsetJ);
}
};
@@ -192,20 +263,28 @@ struct RandomWidget : ModuleWidget {
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(createLightParamCentered>(mm2px(Vec(7.215, 30.858)), module, Random::RATE_PARAM, Random::RATE_LIGHT));
- addParam(createLightParamCentered>(mm2px(Vec(18.214, 30.858)), module, Random::SHAPE_PARAM, Random::SHAPE_LIGHT));
- addParam(createParamCentered(mm2px(Vec(7.214, 78.259)), module, Random::OFFSET_PARAM));
- addParam(createParamCentered(mm2px(Vec(18.214, 78.259)), module, Random::MODE_PARAM));
+ addParam(createLightParamCentered>(mm2px(Vec(6.479, 33.605)), module, Random::RATE_PARAM, Random::RATE_LIGHT));
+ addParam(createLightParamCentered>(mm2px(Vec(17.315, 33.605)), module, Random::PROB_PARAM, Random::PROB_LIGHT));
+ addParam(createLightParamCentered>(mm2px(Vec(28.152, 33.605)), module, Random::RANDOM_PARAM, Random::RANDOM_LIGHT));
+ addParam(createLightParamCentered>(mm2px(Vec(38.98, 33.605)), module, Random::SHAPE_PARAM, Random::SHAPE_LIGHT));
+ addParam(createParamCentered(mm2px(Vec(6.479, 64.347)), module, Random::RATE_CV_PARAM));
+ addParam(createParamCentered(mm2px(Vec(17.317, 64.347)), module, Random::PROB_CV_PARAM));
+ addParam(createParamCentered(mm2px(Vec(28.154, 64.347)), module, Random::RANDOM_CV_PARAM));
+ addParam(createParamCentered(mm2px(Vec(38.991, 64.347)), module, Random::SHAPE_CV_PARAM));
+ addParam(createLightParamCentered>>(mm2px(Vec(28.154, 96.859)), module, Random::OFFSET_PARAM, Random::OFFSET_LIGHT));
- addInput(createInputCentered(mm2px(Vec(7.214, 50.726)), module, Random::RATE_INPUT));
- addInput(createInputCentered(mm2px(Vec(18.214, 50.726)), module, Random::SHAPE_INPUT));
- addInput(createInputCentered(mm2px(Vec(7.214, 64.513)), module, Random::TRIGGER_INPUT));
- addInput(createInputCentered(mm2px(Vec(18.214, 64.513)), module, Random::EXTERNAL_INPUT));
+ addInput(createInputCentered(mm2px(Vec(6.479, 80.549)), module, Random::RATE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.317, 80.549)), module, Random::PROB_INPUT));
+ addInput(createInputCentered(mm2px(Vec(28.154, 80.553)), module, Random::RANDOM_INPUT));
+ addInput(createInputCentered(mm2px(Vec(38.991, 80.557)), module, Random::SHAPE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(6.479, 96.859)), module, Random::TRIGGER_INPUT));
+ addInput(createInputCentered(mm2px(Vec(17.317, 96.859)), module, Random::EXTERNAL_INPUT));
- addOutput(createOutputCentered(mm2px(Vec(7.214, 96.727)), module, Random::STEPPED_OUTPUT));
- addOutput(createOutputCentered(mm2px(Vec(18.214, 96.727)), module, Random::LINEAR_OUTPUT));
- addOutput(createOutputCentered(mm2px(Vec(7.214, 112.182)), module, Random::SMOOTH_OUTPUT));
- addOutput(createOutputCentered(mm2px(Vec(18.214, 112.182)), module, Random::EXPONENTIAL_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(38.991, 96.859)), module, Random::GATE_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(6.479, 113.115)), module, Random::STEPPED_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(17.317, 113.115)), module, Random::LINEAR_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(28.154, 113.115)), module, Random::EXPONENTIAL_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(38.991, 113.115)), module, Random::SMOOTH_OUTPUT));
}
};
diff --git a/src/SEQ3.cpp b/src/SEQ3.cpp
index 60138e1..6dc2a4b 100644
--- a/src/SEQ3.cpp
+++ b/src/SEQ3.cpp
@@ -50,11 +50,9 @@ struct SEQ3 : Module {
dsp::BooleanTrigger gateTriggers[8];
dsp::SchmittTrigger clockTrigger;
- dsp::SchmittTrigger runTrigger;
dsp::SchmittTrigger resetTrigger;
dsp::PulseGenerator clockPulse;
- dsp::PulseGenerator runPulse;
dsp::PulseGenerator resetPulse;
/** Phase of internal LFO */
@@ -147,9 +145,12 @@ struct SEQ3 : Module {
void process(const ProcessArgs& args) override {
// Run
// Use bitwise OR "|" to always evaluate both expressions
- if (runButtonTrigger.process(params[RUN_PARAM].getValue()) | runTrigger.process(inputs[RUN_INPUT].getVoltage(), 0.1f, 2.f)) {
+ if (runButtonTrigger.process(params[RUN_PARAM].getValue())) {
running ^= true;
- runPulse.trigger(1e-3f);
+ }
+ // Run input overrides button
+ if (inputs[RUN_INPUT].isConnected()) {
+ running = (inputs[RUN_INPUT].getVoltage() >= 2.f);
}
// Reset
@@ -218,7 +219,7 @@ struct SEQ3 : Module {
outputs[STEPS_OUTPUT].setVoltage((numSteps - 1) * 1.f);
outputs[CLOCK_OUTPUT].setVoltage(clockGate ? 10.f : 0.f);
- outputs[RUN_OUTPUT].setVoltage(runPulse.process(args.sampleTime) ? 10.f : 0.f);
+ outputs[RUN_OUTPUT].setVoltage(running ? 10.f : 0.f);
outputs[RESET_OUTPUT].setVoltage(resetGate ? 10.f : 0.f);
lights[CLOCK_LIGHT].setSmoothBrightness(clockGate, args.sampleTime);
diff --git a/src/Scope.cpp b/src/Scope.cpp
index 8366085..85db1fb 100644
--- a/src/Scope.cpp
+++ b/src/Scope.cpp
@@ -24,6 +24,9 @@ struct Scope : Module {
NUM_INPUTS
};
enum OutputIds {
+ // new in 2.0
+ X_OUTPUT,
+ Y_OUTPUT,
NUM_OUTPUTS
};
enum LightIds {
@@ -353,36 +356,31 @@ struct ScopeWidget : ModuleWidget {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/Scope.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)));
-
- {
- ScopeDisplay* display = new ScopeDisplay();
- display->module = module;
- display->box.pos = Vec(0, 44);
- display->box.size = Vec(box.size.x, 140);
- addChild(display);
- }
-
- addParam(createParam(Vec(15, 209), module, Scope::X_SCALE_PARAM));
- addParam(createParam(Vec(15, 263), module, Scope::X_POS_PARAM));
- addParam(createParam(Vec(61, 209), module, Scope::Y_SCALE_PARAM));
- addParam(createParam(Vec(61, 263), module, Scope::Y_POS_PARAM));
- addParam(createParam(Vec(107, 209), module, Scope::TIME_PARAM));
- addParam(createParam(Vec(106, 262), module, Scope::LISSAJOUS_PARAM));
- addParam(createParam(Vec(153, 209), module, Scope::TRIG_PARAM));
- addParam(createParam(Vec(152, 262), module, Scope::EXTERNAL_PARAM));
-
- addInput(createInput(Vec(17, 319), module, Scope::X_INPUT));
- addInput(createInput(Vec(63, 319), module, Scope::Y_INPUT));
- addInput(createInput(Vec(154, 319), module, Scope::TRIG_INPUT));
-
- addChild(createLight>(Vec(104, 251), module, Scope::PLOT_LIGHT));
- addChild(createLight>(Vec(104, 296), module, Scope::LISSAJOUS_LIGHT));
- addChild(createLight>(Vec(150, 251), module, Scope::INTERNAL_LIGHT));
- addChild(createLight>(Vec(150, 296), module, Scope::EXTERNAL_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(8.643, 80.603)), module, Scope::_1X2_PARAM));
+ addParam(createParamCentered(mm2px(Vec(24.897, 80.551)), module, Scope::X_SCALE_PARAM));
+ addParam(createParamCentered(mm2px(Vec(41.147, 80.551)), module, Scope::Y_SCALE_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(57.397, 80.521)), module, Scope::TRIG_PARAM));
+ addParam(createParamCentered(mm2px(Vec(8.643, 96.819)), module, Scope::TIME_PARAM));
+ addParam(createParamCentered(mm2px(Vec(24.897, 96.789)), module, Scope::X_POS_PARAM));
+ addParam(createParamCentered(mm2px(Vec(41.147, 96.815)), module, Scope::Y_POS_PARAM));
+ // addParam(createParamCentered(mm2px(Vec(57.397, 96.815)), module, Scope::THERS_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(8.643, 113.115)), module, Scope::X_INPUT));
+ addInput(createInputCentered(mm2px(Vec(33.023, 113.115)), module, Scope::Y_INPUT));
+ addInput(createInputCentered(mm2px(Vec(57.397, 113.115)), module, Scope::TRIG_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(20.833, 113.115)), module, Scope::X_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(45.212, 113.115)), module, Scope::Y_OUTPUT));
+
+ ScopeDisplay* display = createWidget(Vec(0, 44));
+ display->box.size = Vec(box.size.x, 140);
+ display->module = module;
+ addChild(display);
}
};
diff --git a/src/Viz.cpp b/src/Viz.cpp
index a0d1efc..449621a 100644
--- a/src/Viz.cpp
+++ b/src/Viz.cpp
@@ -44,28 +44,30 @@ struct Viz : Module {
struct VizDisplay : Widget {
Viz* module;
- std::shared_ptr font;
VizDisplay() {
box.size = mm2px(Vec(15.24, 88.126));
- font = APP->window->loadFont(asset::plugin(pluginInstance, "res/nunito/Nunito-Bold.ttf"));
}
- void draw(const DrawArgs& args) override {
- for (int c = 0; c < 16; c++) {
- Vec p = Vec(15, 16 + (float) c / 16 * (box.size.y - 10));
- std::string text = string::f("%d", c + 1);
-
- nvgFontFaceId(args.vg, font->handle);
- nvgFontSize(args.vg, 11);
- nvgTextLetterSpacing(args.vg, 0.0);
- nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
- if (module && c < module->lastChannel)
- nvgFillColor(args.vg, nvgRGB(255, 255, 255));
- else
- nvgFillColor(args.vg, nvgRGB(99, 99, 99));
- nvgText(args.vg, p.x, p.y, text.c_str(), NULL);
+ void drawLayer(const DrawArgs& args, int layer) override {
+ if (layer == 1) {
+ for (int c = 0; c < 16; c++) {
+ Vec p = Vec(15, 16 + (float) c / 16 * (box.size.y - 10));
+ std::string text = string::f("%d", c + 1);
+ std::shared_ptr font = APP->window->loadFont(asset::plugin(pluginInstance, "res/nunito/Nunito-Bold.ttf"));
+
+ nvgFontFaceId(args.vg, font->handle);
+ nvgFontSize(args.vg, 11);
+ nvgTextLetterSpacing(args.vg, 0.0);
+ nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
+ if (module && c < module->lastChannel)
+ nvgFillColor(args.vg, nvgRGB(255, 255, 255));
+ else
+ nvgFillColor(args.vg, nvgRGB(99, 99, 99));
+ nvgText(args.vg, p.x, p.y, text.c_str(), NULL);
+ }
}
+ Widget::drawLayer(args, layer);
}
};
@@ -80,28 +82,29 @@ struct VizWidget : ModuleWidget {
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)));
- addInput(createInputCentered(mm2px(Vec(7.619, 21.346)), module, Viz::POLY_INPUT));
-
- addChild(createLightCentered>(mm2px(Vec(10.854, 33.626)), module, Viz::VU_LIGHTS + 0 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 38.916)), module, Viz::VU_LIGHTS + 1 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 44.205)), module, Viz::VU_LIGHTS + 2 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 49.496)), module, Viz::VU_LIGHTS + 3 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 54.785)), module, Viz::VU_LIGHTS + 4 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 60.075)), module, Viz::VU_LIGHTS + 5 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 65.364)), module, Viz::VU_LIGHTS + 6 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 70.654)), module, Viz::VU_LIGHTS + 7 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 75.943)), module, Viz::VU_LIGHTS + 8 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 81.233)), module, Viz::VU_LIGHTS + 9 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 86.522)), module, Viz::VU_LIGHTS + 10 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 91.812)), module, Viz::VU_LIGHTS + 11 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 97.101)), module, Viz::VU_LIGHTS + 12 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 102.392)), module, Viz::VU_LIGHTS + 13 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 107.681)), module, Viz::VU_LIGHTS + 14 * 2));
- addChild(createLightCentered>(mm2px(Vec(10.854, 112.971)), module, Viz::VU_LIGHTS + 15 * 2));
-
- VizDisplay* vizDisplay = createWidget(mm2px(Vec(0.0, 29.235)));
- vizDisplay->module = module;
- addChild(vizDisplay);
+ addInput(createInputCentered(mm2px(Vec(2.58, 7.229)), module, Viz::POLY_INPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(3.676, 11.388)), module, Viz::VU_LIGHTS + 0 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 13.18)), module, Viz::VU_LIGHTS + 1 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 14.971)), module, Viz::VU_LIGHTS + 2 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 16.763)), module, Viz::VU_LIGHTS + 3 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 18.554)), module, Viz::VU_LIGHTS + 4 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 20.345)), module, Viz::VU_LIGHTS + 5 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 22.137)), module, Viz::VU_LIGHTS + 6 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 23.928)), module, Viz::VU_LIGHTS + 7 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 25.719)), module, Viz::VU_LIGHTS + 8 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 27.511)), module, Viz::VU_LIGHTS + 9 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 29.302)), module, Viz::VU_LIGHTS + 10 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 31.094)), module, Viz::VU_LIGHTS + 11 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 32.885)), module, Viz::VU_LIGHTS + 12 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 34.677)), module, Viz::VU_LIGHTS + 13 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 36.468)), module, Viz::VU_LIGHTS + 14 * 2));
+ addChild(createLightCentered>(mm2px(Vec(3.676, 38.259)), module, Viz::VU_LIGHTS + 15 * 2));
+
+ VizDisplay* display = createWidget(mm2px(Vec(0.0, 9.901)));
+ display->box.size = mm2px(Vec(5.161, 29.845));
+ display->module = module;
+ addChild(display);
}
};
diff --git a/src/WTLFO.cpp b/src/WTLFO.cpp
index d5ca482..bf2085f 100644
--- a/src/WTLFO.cpp
+++ b/src/WTLFO.cpp
@@ -36,8 +36,6 @@ struct WTLFO : Module {
};
Wavetable wavetable;
- bool offset = false;
- bool invert = false;
float_4 phases[4] = {};
float lastPos = 0.f;
@@ -52,9 +50,8 @@ struct WTLFO : Module {
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");
+ configSwitch(OFFSET_PARAM, 0.f, 1.f, 0.f, "Offset", {"Bipolar", "Unipolar"});
+ configSwitch(INVERT_PARAM, 0.f, 1.f, 0.f, "Invert");
struct FrequencyQuantity : ParamQuantity {
float getDisplayValue() override {
@@ -88,8 +85,6 @@ struct WTLFO : Module {
}
void onReset() override {
- offset = false;
- invert = false;
wavetable.reset();
// Reset state
@@ -100,12 +95,6 @@ struct WTLFO : Module {
clockTimer.reset();
}
- void onRandomize(const RandomizeEvent& e) override {
- Module::onRandomize(e);
- offset = random::get();
- invert = random::get();
- }
-
void onAdd(const AddEvent& e) override {
std::string path = system::join(getPatchStorageDirectory(), "wavetable.wav");
// Silently fails
@@ -119,19 +108,9 @@ struct WTLFO : Module {
}
}
- 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 {
- if (offsetTrigger.process(params[OFFSET_PARAM].getValue() > 0.f))
- offset ^= true;
- if (invertTrigger.process(params[INVERT_PARAM].getValue() > 0.f))
- invert ^= true;
+ bool offset = (params[OFFSET_PARAM].getValue() > 0.f);
+ bool invert = (params[INVERT_PARAM].getValue() > 0.f);
int channels = std::max(1, inputs[FM_INPUT].getChannels());
@@ -158,72 +137,71 @@ struct WTLFO : Module {
float posCvParam = params[POS_CV_PARAM].getValue();
// Check valid wave and wavetable size
- if (wavetable.waveLen < 2) {
- clearOutput();
- return;
- }
int waveCount = wavetable.getWaveCount();
- if (waveCount < 1) {
- clearOutput();
- return;
- }
-
- // Iterate channels
- 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 = clockFreq / 2.f * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
- freq = simd::fmin(freq, 1024.f);
-
- // Accumulate phase
- float_4 phase = phases[c / 4];
- phase += freq * args.sampleTime;
- // Wrap phase
- phase -= simd::trunc(phase);
- // Reset phase
- float_4 reset = resetTriggers[c / 4].process(simd::rescale(inputs[RESET_INPUT].getPolyVoltageSimd(c), 0.1f, 2.f, 0.f, 1.f));
- phase = simd::ifelse(reset, 0.f, phase);
- phases[c / 4] = phase;
- // Scale phase from 0 to waveLen
- phase *= wavetable.waveLen;
-
- // Get wavetable position, scaled from 0 to (waveCount - 1)
- float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd(c) * posCvParam / 10.f;
- pos = simd::clamp(pos);
- pos *= (waveCount - 1);
-
- if (c == 0)
- lastPos = pos[0];
-
- // Get wavetable points
- float_4 out = 0.f;
- for (int cc = 0; cc < 4 && c + cc < channels; cc++) {
- // Get wave indexes
- float phaseF = phase[cc] - std::trunc(phase[cc]);
- size_t i0 = std::trunc(phase[cc]);
- size_t i1 = (i0 + 1) % wavetable.waveLen;
- // Get pos indexes
- float posF = pos[cc] - std::trunc(pos[cc]);
- size_t pos0 = std::trunc(pos[cc]);
- size_t pos1 = pos0 + 1;
- // Get waves
- float out0 = crossfade(wavetable.at(pos0, i0), wavetable.at(pos0, i1), phaseF);
- if (posF > 0.f) {
- float out1 = crossfade(wavetable.at(pos1, i0), wavetable.at(pos1, i1), phaseF);
- out[cc] = crossfade(out0, out1, posF);
+ if (wavetable.waveLen >= 2 && waveCount >= 1) {
+ // Iterate channels
+ 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 = clockFreq / 2.f * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
+ freq = simd::fmin(freq, 1024.f);
+
+ // Accumulate phase
+ float_4 phase = phases[c / 4];
+ phase += freq * args.sampleTime;
+ // Wrap phase
+ phase -= simd::trunc(phase);
+ // Reset phase
+ float_4 reset = resetTriggers[c / 4].process(simd::rescale(inputs[RESET_INPUT].getPolyVoltageSimd(c), 0.1f, 2.f, 0.f, 1.f));
+ phase = simd::ifelse(reset, 0.f, phase);
+ phases[c / 4] = phase;
+ // Scale phase from 0 to waveLen
+ phase *= wavetable.waveLen;
+
+ // Get wavetable position, scaled from 0 to (waveCount - 1)
+ float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd(c) * posCvParam / 10.f;
+ pos = simd::clamp(pos);
+ pos *= (waveCount - 1);
+
+ if (c == 0)
+ lastPos = pos[0];
+
+ // Get wavetable points
+ float_4 out = 0.f;
+ for (int cc = 0; cc < 4 && c + cc < channels; cc++) {
+ // Get wave indexes
+ float phaseF = phase[cc] - std::trunc(phase[cc]);
+ size_t i0 = std::trunc(phase[cc]);
+ size_t i1 = (i0 + 1) % wavetable.waveLen;
+ // Get pos indexes
+ float posF = pos[cc] - std::trunc(pos[cc]);
+ size_t pos0 = std::trunc(pos[cc]);
+ size_t pos1 = pos0 + 1;
+ // Get waves
+ float out0 = crossfade(wavetable.at(pos0, i0), wavetable.at(pos0, i1), phaseF);
+ if (posF > 0.f) {
+ float out1 = crossfade(wavetable.at(pos1, i0), wavetable.at(pos1, i1), phaseF);
+ out[cc] = crossfade(out0, out1, posF);
+ }
+ else {
+ out[cc] = out0;
+ }
}
- else {
- out[cc] = out0;
- }
- }
- // Invert and offset
- if (invert)
- out *= -1.f;
- if (offset)
- out += 1.f;
+ // Invert and offset
+ if (invert)
+ out *= -1.f;
+ if (offset)
+ out += 1.f;
- outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);
+ outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);
+ }
+ }
+ else {
+ // Wavetable is invalid, so set 0V
+ for (int c = 0; c < channels; c += 4) {
+ outputs[WAVE_OUTPUT].setVoltageSimd(float_4(0.f), c);
+ }
}
outputs[WAVE_OUTPUT].setChannels(channels);
@@ -248,10 +226,6 @@ struct WTLFO : Module {
json_t* dataToJson() override {
json_t* rootJ = json_object();
- // offset
- json_object_set_new(rootJ, "offset", json_boolean(offset));
- // invert
- json_object_set_new(rootJ, "invert", json_boolean(invert));
// Merge wavetable
json_t* wavetableJ = wavetable.toJson();
json_object_update(rootJ, wavetableJ);
@@ -260,14 +234,6 @@ struct WTLFO : Module {
}
void dataFromJson(json_t* rootJ) override {
- // offset
- json_t* offsetJ = json_object_get(rootJ, "offset");
- if (offsetJ)
- offset = json_boolean_value(offsetJ);
- // invert
- json_t* invertJ = json_object_get(rootJ, "invert");
- if (invertJ)
- invert = json_boolean_value(invertJ);
// wavetable
wavetable.fromJson(rootJ);
}
@@ -287,9 +253,9 @@ struct WTLFOWidget : ModuleWidget {
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(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));
+ 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));
diff --git a/src/Wavetable.hpp b/src/Wavetable.hpp
index 080afaa..0160ed8 100644
--- a/src/Wavetable.hpp
+++ b/src/Wavetable.hpp
@@ -84,6 +84,8 @@ struct Wavetable {
/** Returns the number of waves in the wavetable. */
size_t getWaveCount() const {
+ if (waveLen == 0)
+ return 0;
return samples.size() / waveLen;
}
@@ -145,8 +147,6 @@ struct Wavetable {
}
void load(std::string path) {
- samples.clear();
-
std::string ext = string::lowercase(system::getExtension(path));
if (ext == ".wav") {
// Load WAV
@@ -158,6 +158,7 @@ struct Wavetable {
#endif
return;
+ samples.clear();
samples.resize(wav.totalPCMFrameCount * wav.channels);
drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, samples.data());
@@ -166,6 +167,7 @@ struct Wavetable {
else {
// Load bytes from file
std::vector data = system::readFile(path);
+ samples.clear();
if (ext == ".f32") {
size_t len = data.size() / sizeof(float);