#include #include "JWModules.hpp" #include "dsp/digital.hpp" namespace rack_plugin_JW_Modules { #define BUFFER_SIZE 512 struct MinMax : Module { enum ParamIds { TIME_PARAM, TRIG_PARAM, NUM_PARAMS }; enum InputIds { X_INPUT, Y_INPUT, TRIG_INPUT, NUM_INPUTS }; enum OutputIds { NUM_OUTPUTS }; float bufferX[BUFFER_SIZE] = {}; float bufferY[BUFFER_SIZE] = {}; int bufferIndex = 0; float frameIndex = 0; SchmittTrigger sumTrigger; SchmittTrigger extTrigger; bool lissajous = false; SchmittTrigger resetTrigger; MinMax() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} void step() override; json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "lissajous", json_integer((int) lissajous)); return rootJ; } void fromJson(json_t *rootJ) override { json_t *sumJ = json_object_get(rootJ, "lissajous"); if (sumJ) lissajous = json_integer_value(sumJ); } void reset() override { lissajous = false; } }; void MinMax::step() { // Compute time float deltaTime = powf(2.0, params[TIME_PARAM].value); int frameCount = (int)ceilf(deltaTime * engineGetSampleRate()); // Add frame to buffer if (bufferIndex < BUFFER_SIZE) { if (++frameIndex > frameCount) { frameIndex = 0; bufferX[bufferIndex] = inputs[X_INPUT].value; bufferY[bufferIndex] = inputs[Y_INPUT].value; bufferIndex++; } } // Are we waiting on the next trigger? if (bufferIndex >= BUFFER_SIZE) { // Trigger immediately if external but nothing plugged in, or in Lissajous mode if (lissajous) { bufferIndex = 0; frameIndex = 0; return; } // Reset the Schmitt trigger so we don't trigger immediately if the input is high if (frameIndex == 0) { resetTrigger.reset(); } frameIndex++; // Must go below 0.1V to trigger // resetTrigger.setThresholds(params[TRIG_PARAM].value - 0.1, params[TRIG_PARAM].value); float gate = inputs[X_INPUT].value; // Reset if triggered float holdTime = 0.1; if (resetTrigger.process(gate) || (frameIndex >= engineGetSampleRate() * holdTime)) { bufferIndex = 0; frameIndex = 0; return; } // Reset if we've waited too long if (frameIndex >= engineGetSampleRate() * holdTime) { bufferIndex = 0; frameIndex = 0; return; } } } struct MinMaxDisplay : TransparentWidget { MinMax *module; int frame = 0; std::shared_ptr font; struct Stats { float vrms, vpp, vmin, vmax; void calculate(float *values) { vrms = 0.0; vmax = -INFINITY; vmin = INFINITY; for (int i = 0; i < BUFFER_SIZE; i++) { float v = values[i]; vrms += v*v; vmax = fmaxf(vmax, v); vmin = fminf(vmin, v); } vrms = sqrtf(vrms / BUFFER_SIZE); vpp = vmax - vmin; } }; Stats statsX, statsY; MinMaxDisplay() { font = Font::load(assetPlugin(plugin, "res/DejaVuSansMono.ttf")); } void drawStats(NVGcontext *vg, Vec pos, const char *title, Stats *stats) { nvgFontSize(vg, 24); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, -2); nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x80)); char text[128]; snprintf(text, sizeof(text), "%5.2f", stats->vmin); nvgText(vg, pos.x + 10, pos.y + 28, text, NULL); snprintf(text, sizeof(text), "%5.2f", stats->vmax); nvgText(vg, pos.x + 10, pos.y + 78, text, NULL); } void draw(NVGcontext *vg) { float gainX = 1; float gainY = 1; float offsetX = 0; float offsetY = 0; float valuesX[BUFFER_SIZE]; float valuesY[BUFFER_SIZE]; for (int i = 0; i < BUFFER_SIZE; i++) { int j = i; // Lock display to buffer if buffer update deltaTime <= 2^-11 if (module->lissajous) j = (i + module->bufferIndex) % BUFFER_SIZE; valuesX[i] = (module->bufferX[j] + offsetX) * gainX / 10.0; valuesY[i] = (module->bufferY[j] + offsetY) * gainY / 10.0; } // Calculate and draw stats if (++frame >= 4) { frame = 0; statsX.calculate(module->bufferX); statsY.calculate(module->bufferY); } drawStats(vg, Vec(0, 20), "X", &statsX); } }; struct MinMaxWidget : ModuleWidget { MinMaxWidget(MinMax *module); }; MinMaxWidget::MinMaxWidget(MinMax *module) : ModuleWidget(module) { box.size = Vec(RACK_GRID_WIDTH*6, RACK_GRID_HEIGHT); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/MinMax.svg"))); addChild(panel); } addChild(Widget::create(Vec(16, 1))); addChild(Widget::create(Vec(16, 365))); addChild(Widget::create(Vec(box.size.x-29, 1))); addChild(Widget::create(Vec(box.size.x-29, 365))); CenteredLabel* const titleLabel = new CenteredLabel(16); titleLabel->box.pos = Vec(22, 15); titleLabel->text = "MinMax"; addChild(titleLabel); { MinMaxDisplay *display = new MinMaxDisplay(); display->module = module; display->box.pos = Vec(0, 44); display->box.size = Vec(box.size.x, 140); addChild(display); } CenteredLabel* const minLabel = new CenteredLabel(12); minLabel->box.pos = Vec(22, 35); minLabel->text = "Min"; addChild(minLabel); CenteredLabel* const maxLabel = new CenteredLabel(12); maxLabel->box.pos = Vec(22, 60); maxLabel->text = "Max"; addChild(maxLabel); CenteredLabel* const timeLabel = new CenteredLabel(12); timeLabel->box.pos = Vec(22, 101); timeLabel->text = "Time"; addChild(timeLabel); CenteredLabel* const inLabel = new CenteredLabel(12); inLabel->box.pos = Vec(23, 132); inLabel->text = "Input"; addChild(inLabel); addParam(ParamWidget::create(Vec(32, 209), module, MinMax::TIME_PARAM, -6.0, -16.0, -14.0)); addInput(Port::create(Vec(33, 275), Port::INPUT, module, MinMax::X_INPUT)); } } // namespace rack_plugin_JW_Modules using namespace rack_plugin_JW_Modules; RACK_PLUGIN_MODEL_INIT(JW_Modules, MinMax) { Model *modelMinMax = Model::create("JW-Modules", "MinMax", "Min Max", UTILITY_TAG); return modelMinMax; }