| @@ -2,7 +2,7 @@ | |||
| #include "plugin.hpp" | |||
| static const int BUFFER_SIZE = 512; | |||
| static const int BUFFER_SIZE = 256; | |||
| struct Scope : Module { | |||
| @@ -30,200 +30,277 @@ struct Scope : Module { | |||
| NUM_OUTPUTS | |||
| }; | |||
| enum LightIds { | |||
| PLOT_LIGHT, | |||
| LISSAJOUS_LIGHT, | |||
| INTERNAL_LIGHT, | |||
| EXTERNAL_LIGHT, | |||
| NUM_LIGHTS | |||
| }; | |||
| float bufferX[16][BUFFER_SIZE] = {}; | |||
| float bufferY[16][BUFFER_SIZE] = {}; | |||
| struct Point { | |||
| float minX[16] = {}; | |||
| float maxX[16] = {}; | |||
| float minY[16] = {}; | |||
| float maxY[16] = {}; | |||
| Point() { | |||
| for (int c = 0; c < 16; c++) { | |||
| minX[c] = INFINITY; | |||
| maxX[c] = -INFINITY; | |||
| minY[c] = INFINITY; | |||
| maxY[c] = -INFINITY; | |||
| } | |||
| } | |||
| }; | |||
| Point pointBuffer[BUFFER_SIZE]; | |||
| int channelsX = 0; | |||
| int channelsY = 0; | |||
| int bufferIndex = 0; | |||
| int frameIndex = 0; | |||
| Point currentPoint; | |||
| dsp::BooleanTrigger sumTrigger; | |||
| dsp::BooleanTrigger extTrigger; | |||
| bool lissajous = false; | |||
| bool external = false; | |||
| dsp::SchmittTrigger triggers[16]; | |||
| Scope() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(X_SCALE_PARAM, -2.f, 8.f, 0.f, "X scale", " V/div", 1 / 2.f, 5); | |||
| configParam(X_POS_PARAM, -10.f, 10.f, 0.f, "X position", " V"); | |||
| configParam(Y_SCALE_PARAM, -2.f, 8.f, 0.f, "Y scale", " V/div", 1 / 2.f, 5); | |||
| configParam(Y_POS_PARAM, -10.f, 10.f, 0.f, "Y position", " V"); | |||
| const float timeBase = (float) BUFFER_SIZE / 6; | |||
| configParam(TIME_PARAM, 6.f, 16.f, 14.f, "Time", " ms/div", 1 / 2.f, 1000 * timeBase); | |||
| configButton(LISSAJOUS_PARAM, "Separate/Lissajous mode"); | |||
| configParam(TRIG_PARAM, -10.f, 10.f, 0.f, "Trigger position", " V"); | |||
| configButton(EXTERNAL_PARAM, "Internal/external trigger mode"); | |||
| configInput(X_INPUT, "X"); | |||
| configInput(Y_INPUT, "Y"); | |||
| configParam(X_SCALE_PARAM, 0.f, 8.f, 0.f, "Gain 1", " V/screen", 1 / 2.f, 20); | |||
| getParamQuantity(X_SCALE_PARAM)->snapEnabled = true; | |||
| configParam(X_POS_PARAM, -10.f, 10.f, 0.f, "Offset 1", " V"); | |||
| configParam(Y_SCALE_PARAM, 0.f, 8.f, 0.f, "Gain 2", " V/screen", 1 / 2.f, 20); | |||
| getParamQuantity(Y_SCALE_PARAM)->snapEnabled = true; | |||
| configParam(Y_POS_PARAM, -10.f, 10.f, 0.f, "Offset 2", " V"); | |||
| const float maxTime = -std::log2(5e1f); | |||
| const float minTime = -std::log2(5e-3f); | |||
| const float defaultTime = -std::log2(5e-1f); | |||
| configParam(TIME_PARAM, maxTime, minTime, defaultTime, "Time", " ms/screen", 1 / 2.f, 1000); | |||
| configSwitch(LISSAJOUS_PARAM, 0.f, 1.f, 0.f, "Scope mode", {"1 & 2", "1 x 2"}); | |||
| configParam(TRIG_PARAM, -10.f, 10.f, 0.f, "Trigger threshold", " V"); | |||
| configSwitch(EXTERNAL_PARAM, 0.f, 1.f, 1.f, "Trigger mode", {"Internal", "External"}); | |||
| configInput(X_INPUT, "Ch 1"); | |||
| configInput(Y_INPUT, "Ch 2"); | |||
| configInput(TRIG_INPUT, "External trigger"); | |||
| configOutput(X_OUTPUT, "Ch 1"); | |||
| configOutput(Y_OUTPUT, "Ch 2"); | |||
| } | |||
| void onReset() override { | |||
| lissajous = false; | |||
| external = false; | |||
| std::memset(bufferX, 0, sizeof(bufferX)); | |||
| std::memset(bufferY, 0, sizeof(bufferY)); | |||
| for (int i = 0; i < BUFFER_SIZE; i++) { | |||
| pointBuffer[i] = Point(); | |||
| } | |||
| } | |||
| void process(const ProcessArgs& args) override { | |||
| // Modes | |||
| if (sumTrigger.process(params[LISSAJOUS_PARAM].getValue() > 0.f)) { | |||
| lissajous = !lissajous; | |||
| } | |||
| lights[PLOT_LIGHT].setBrightness(!lissajous); | |||
| bool lissajous = params[LISSAJOUS_PARAM].getValue() > 0.f; | |||
| lights[LISSAJOUS_LIGHT].setBrightness(lissajous); | |||
| if (extTrigger.process(params[EXTERNAL_PARAM].getValue() > 0.f)) { | |||
| external = !external; | |||
| } | |||
| lights[INTERNAL_LIGHT].setBrightness(!external); | |||
| bool external = params[EXTERNAL_PARAM].getValue() > 0.f; | |||
| lights[EXTERNAL_LIGHT].setBrightness(external); | |||
| // Compute time | |||
| float deltaTime = std::pow(2.f, -params[TIME_PARAM].getValue()); | |||
| float deltaTime = std::pow(2.f, -params[TIME_PARAM].getValue()) / BUFFER_SIZE; | |||
| int frameCount = (int) std::ceil(deltaTime * args.sampleRate); | |||
| // Set channels | |||
| int channelsX = inputs[X_INPUT].getChannels(); | |||
| if (channelsX != this->channelsX) { | |||
| std::memset(bufferX, 0, sizeof(bufferX)); | |||
| // TODO | |||
| // std::memset(bufferX, 0, sizeof(bufferX)); | |||
| this->channelsX = channelsX; | |||
| } | |||
| int channelsY = inputs[Y_INPUT].getChannels(); | |||
| if (channelsY != this->channelsY) { | |||
| std::memset(bufferY, 0, sizeof(bufferY)); | |||
| // TODO | |||
| // std::memset(bufferY, 0, sizeof(bufferY)); | |||
| this->channelsY = channelsY; | |||
| } | |||
| // Add frame to buffer | |||
| // Copy inputs to outputs | |||
| outputs[X_OUTPUT].setChannels(channelsX); | |||
| outputs[X_OUTPUT].writeVoltages(inputs[X_INPUT].getVoltages()); | |||
| outputs[Y_OUTPUT].setChannels(channelsY); | |||
| outputs[Y_OUTPUT].writeVoltages(inputs[Y_INPUT].getVoltages()); | |||
| // Add point to buffer if recording | |||
| if (bufferIndex < BUFFER_SIZE) { | |||
| if (++frameIndex > frameCount) { | |||
| // Get input | |||
| for (int c = 0; c < channelsX; c++) { | |||
| float x = inputs[X_INPUT].getVoltage(c); | |||
| currentPoint.minX[c] = std::min(currentPoint.minX[c], x); | |||
| currentPoint.maxX[c] = std::max(currentPoint.maxX[c], x); | |||
| } | |||
| for (int c = 0; c < channelsY; c++) { | |||
| float y = inputs[Y_INPUT].getVoltage(c); | |||
| currentPoint.minY[c] = std::min(currentPoint.minY[c], y); | |||
| currentPoint.maxY[c] = std::max(currentPoint.maxY[c], y); | |||
| } | |||
| if (++frameIndex >= frameCount) { | |||
| frameIndex = 0; | |||
| for (int c = 0; c < channelsX; c++) { | |||
| bufferX[c][bufferIndex] = inputs[X_INPUT].getVoltage(c); | |||
| } | |||
| for (int c = 0; c < channelsY; c++) { | |||
| bufferY[c][bufferIndex] = inputs[Y_INPUT].getVoltage(c); | |||
| } | |||
| // Push current point | |||
| pointBuffer[bufferIndex] = currentPoint; | |||
| // Reset current point | |||
| currentPoint = Point(); | |||
| bufferIndex++; | |||
| } | |||
| } | |||
| // Don't wait for trigger if still filling buffer | |||
| if (bufferIndex < BUFFER_SIZE) { | |||
| return; | |||
| } | |||
| // Trigger immediately if external but nothing plugged in, or in Lissajous mode | |||
| if (lissajous || (external && !inputs[TRIG_INPUT].isConnected())) { | |||
| trigger(); | |||
| return; | |||
| } | |||
| frameIndex++; | |||
| // Reset if triggered | |||
| float trigThreshold = params[TRIG_PARAM].getValue(); | |||
| Input& trigInput = external ? inputs[TRIG_INPUT] : inputs[X_INPUT]; | |||
| // Wait for trigger if no longer recording | |||
| if (bufferIndex >= BUFFER_SIZE) { | |||
| auto trigger = [&]() { | |||
| for (int c = 0; c < 16; c++) { | |||
| triggers[c].reset(); | |||
| } | |||
| bufferIndex = 0; | |||
| frameIndex = 0; | |||
| }; | |||
| // This may be 0 | |||
| int trigChannels = trigInput.getChannels(); | |||
| for (int c = 0; c < trigChannels; c++) { | |||
| float trigVoltage = trigInput.getVoltage(c); | |||
| if (triggers[c].process(rescale(trigVoltage, trigThreshold, trigThreshold + 0.001f, 0.f, 1.f))) { | |||
| // Trigger immediately if external but nothing plugged in, or in Lissajous mode | |||
| if (lissajous || (external && !inputs[TRIG_INPUT].isConnected())) { | |||
| trigger(); | |||
| return; | |||
| } | |||
| } | |||
| // Reset if we've been waiting for `holdTime` | |||
| const float holdTime = 0.5f; | |||
| if (frameIndex * args.sampleTime >= holdTime) { | |||
| trigger(); | |||
| return; | |||
| } | |||
| } | |||
| void trigger() { | |||
| for (int c = 0; c < 16; c++) { | |||
| triggers[c].reset(); | |||
| // Reset if triggered | |||
| float trigThreshold = params[TRIG_PARAM].getValue(); | |||
| Input& trigInput = external ? inputs[TRIG_INPUT] : inputs[X_INPUT]; | |||
| // This may be 0 | |||
| int trigChannels = trigInput.getChannels(); | |||
| for (int c = 0; c < trigChannels; c++) { | |||
| float trigVoltage = trigInput.getVoltage(c); | |||
| if (triggers[c].process(rescale(trigVoltage, trigThreshold, trigThreshold + 0.001f, 0.f, 1.f))) { | |||
| trigger(); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| bufferIndex = 0; | |||
| frameIndex = 0; | |||
| } | |||
| json_t* dataToJson() override { | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "lissajous", json_integer((int) lissajous)); | |||
| json_object_set_new(rootJ, "external", json_integer((int) external)); | |||
| return rootJ; | |||
| bool isLissajous() { | |||
| return params[LISSAJOUS_PARAM].getValue() > 0.f; | |||
| } | |||
| void dataFromJson(json_t* rootJ) override { | |||
| json_t* sumJ = json_object_get(rootJ, "lissajous"); | |||
| if (sumJ) | |||
| lissajous = json_integer_value(sumJ); | |||
| // In <2.0, lissajous and external were class variables | |||
| json_t* lissajousJ = json_object_get(rootJ, "lissajous"); | |||
| if (lissajousJ) { | |||
| if (json_integer_value(lissajousJ)) | |||
| params[LISSAJOUS_PARAM].setValue(1.f); | |||
| } | |||
| json_t* extJ = json_object_get(rootJ, "external"); | |||
| if (extJ) | |||
| external = json_integer_value(extJ); | |||
| json_t* externalJ = json_object_get(rootJ, "external"); | |||
| if (externalJ) { | |||
| if (json_integer_value(externalJ)) | |||
| params[EXTERNAL_PARAM].setValue(1.f); | |||
| } | |||
| } | |||
| }; | |||
| struct ScopeDisplay : LedDisplay { | |||
| Scope* module; | |||
| ModuleWidget* moduleWidget; | |||
| int statsFrame = 0; | |||
| std::string fontPath; | |||
| struct Stats { | |||
| float vpp = 0.f; | |||
| float vmin = 0.f; | |||
| float vmax = 0.f; | |||
| void calculate(float* buffer, int channels) { | |||
| vmax = -INFINITY; | |||
| vmin = INFINITY; | |||
| for (int i = 0; i < BUFFER_SIZE * channels; i++) { | |||
| float v = buffer[i]; | |||
| vmax = std::fmax(vmax, v); | |||
| vmin = std::fmin(vmin, v); | |||
| } | |||
| vpp = vmax - vmin; | |||
| } | |||
| float min = INFINITY; | |||
| float max = -INFINITY; | |||
| }; | |||
| Stats statsX, statsY; | |||
| Stats statsX; | |||
| Stats statsY; | |||
| ScopeDisplay() { | |||
| fontPath = asset::system("res/fonts/ShareTechMono-Regular.ttf"); | |||
| } | |||
| void drawWaveform(const DrawArgs& args, float* bufferX, float offsetX, float gainX, float* bufferY, float offsetY, float gainY) { | |||
| assert(bufferY); | |||
| void calculateStats(Stats& stats, int wave, int channels) { | |||
| if (!module) | |||
| return; | |||
| stats = Stats(); | |||
| for (int i = 0; i < BUFFER_SIZE; i++) { | |||
| const Scope::Point& point = module->pointBuffer[i]; | |||
| for (int c = 0; c < channels; c++) { | |||
| float max = (wave == 0) ? point.maxX[c] : point.maxY[c]; | |||
| float min = (wave == 0) ? point.minX[c] : point.minY[c]; | |||
| stats.max = std::fmax(stats.max, max); | |||
| stats.min = std::fmin(stats.min, min); | |||
| } | |||
| } | |||
| } | |||
| void drawWave(const DrawArgs& args, int wave, int channel, float offset, float gain) { | |||
| if (!module) | |||
| return; | |||
| nvgSave(args.vg); | |||
| Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15 * 2))); | |||
| nvgScissor(args.vg, b.pos.x, b.pos.y, b.size.x, b.size.y); | |||
| Rect b = box.zeroPos().shrink(Vec(0, 15)); | |||
| nvgScissor(args.vg, RECT_ARGS(b)); | |||
| nvgBeginPath(args.vg); | |||
| // Draw max points on top | |||
| for (int i = 0; i < BUFFER_SIZE; i++) { | |||
| Vec v; | |||
| if (bufferX) | |||
| v.x = (bufferX[i] + offsetX) * gainX / 2.f + 0.5f; | |||
| const Scope::Point& point = module->pointBuffer[i]; | |||
| float max = (wave == 0) ? point.maxX[channel] : point.maxY[channel]; | |||
| if (!std::isfinite(max)) | |||
| continue; | |||
| Vec p; | |||
| p.x = (float) i / (BUFFER_SIZE - 1); | |||
| p.y = (max + offset) * gain * -0.5f + 0.5f; | |||
| p = b.interpolate(p); | |||
| p.y += 1.0; | |||
| if (i == 0) | |||
| nvgMoveTo(args.vg, p.x, p.y); | |||
| else | |||
| v.x = (float) i / (BUFFER_SIZE - 1); | |||
| v.y = (bufferY[i] + offsetY) * gainY / 2.f + 0.5f; | |||
| nvgLineTo(args.vg, p.x, p.y); | |||
| } | |||
| // Draw min points on bottom | |||
| for (int i = BUFFER_SIZE - 1; i >= 0; i--) { | |||
| const Scope::Point& point = module->pointBuffer[i]; | |||
| float min = (wave == 0) ? point.minX[channel] : point.minY[channel]; | |||
| if (!std::isfinite(min)) | |||
| continue; | |||
| Vec p; | |||
| p.x = (float) i / (BUFFER_SIZE - 1); | |||
| p.y = (min + offset) * gain * -0.5f + 0.5f; | |||
| p = b.interpolate(p); | |||
| p.y -= 1.0; | |||
| nvgLineTo(args.vg, p.x, p.y); | |||
| } | |||
| nvgClosePath(args.vg); | |||
| // nvgLineCap(args.vg, NVG_ROUND); | |||
| // nvgMiterLimit(args.vg, 2.f); | |||
| nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER); | |||
| nvgFill(args.vg); | |||
| nvgResetScissor(args.vg); | |||
| nvgRestore(args.vg); | |||
| } | |||
| void drawLissajous(const DrawArgs& args, int channel, float offsetX, float gainX, float offsetY, float gainY) { | |||
| if (!module) | |||
| return; | |||
| nvgSave(args.vg); | |||
| Rect b = box.zeroPos().shrink(Vec(0, 15)); | |||
| nvgScissor(args.vg, RECT_ARGS(b)); | |||
| nvgBeginPath(args.vg); | |||
| int bufferIndex = module->bufferIndex; | |||
| for (int i = 0; i < BUFFER_SIZE; i++) { | |||
| // Get average point | |||
| const Scope::Point& point = module->pointBuffer[(i + bufferIndex) % BUFFER_SIZE]; | |||
| float avgX = (point.minX[channel] + point.maxX[channel]) / 2; | |||
| float avgY = (point.minY[channel] + point.maxY[channel]) / 2; | |||
| if (!std::isfinite(avgX) || !std::isfinite(avgY)) | |||
| continue; | |||
| Vec p; | |||
| p.x = rescale(v.x, 0.f, 1.f, b.pos.x, b.pos.x + b.size.x); | |||
| p.y = rescale(v.y, 0.f, 1.f, b.pos.y + b.size.y, b.pos.y); | |||
| p.x = (avgX + offsetX) * gainX * -0.5f + 0.5f; | |||
| p.y = (avgY + offsetY) * gainY * -0.5f + 0.5f; | |||
| p = b.interpolate(p); | |||
| if (i == 0) | |||
| nvgMoveTo(args.vg, p.x, p.y); | |||
| else | |||
| @@ -251,7 +328,6 @@ struct ScopeDisplay : LedDisplay { | |||
| nvgBeginPath(args.vg); | |||
| nvgMoveTo(args.vg, p.x - 13, p.y); | |||
| nvgLineTo(args.vg, 0, p.y); | |||
| nvgClosePath(args.vg); | |||
| } | |||
| nvgStroke(args.vg); | |||
| @@ -278,7 +354,7 @@ struct ScopeDisplay : LedDisplay { | |||
| nvgResetScissor(args.vg); | |||
| } | |||
| void drawStats(const DrawArgs& args, Vec pos, const char* title, Stats* stats) { | |||
| void drawStats(const DrawArgs& args, Vec pos, const char* title, const Stats& stats) { | |||
| std::shared_ptr<Font> font = APP->window->loadFont(fontPath); | |||
| if (!font) | |||
| return; | |||
| @@ -294,16 +370,35 @@ struct ScopeDisplay : LedDisplay { | |||
| std::string text; | |||
| text = "pp "; | |||
| text += isNear(stats->vpp, 0.f, 100.f) ? string::f("% 6.2f", stats->vpp) : " ---"; | |||
| float pp = stats.max - stats.min; | |||
| text += isNear(pp, 0.f, 100.f) ? string::f("% 6.2f", pp) : " ---"; | |||
| nvgText(args.vg, pos.x, pos.y, text.c_str(), NULL); | |||
| text = "max "; | |||
| text += isNear(stats->vmax, 0.f, 100.f) ? string::f("% 6.2f", stats->vmax) : " ---"; | |||
| text += isNear(stats.max, 0.f, 100.f) ? string::f("% 6.2f", stats.max) : " ---"; | |||
| nvgText(args.vg, pos.x + 58 * 1, pos.y, text.c_str(), NULL); | |||
| text = "min "; | |||
| text += isNear(stats->vmin, 0.f, 100.f) ? string::f("% 6.2f", stats->vmin) : " ---"; | |||
| text += isNear(stats.min, 0.f, 100.f) ? string::f("% 6.2f", stats.min) : " ---"; | |||
| nvgText(args.vg, pos.x + 58 * 2, pos.y, text.c_str(), NULL); | |||
| } | |||
| void drawBackground(const DrawArgs& args) { | |||
| Rect b = box.zeroPos().shrink(Vec(0, 15)); | |||
| nvgStrokeColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x10)); | |||
| for (int i = 0; i < 5; i++) { | |||
| nvgBeginPath(args.vg); | |||
| Vec p; | |||
| p.x = 0.0; | |||
| p.y = float(i) / (5 - 1); | |||
| nvgMoveTo(args.vg, VEC_ARGS(b.interpolate(p))); | |||
| p.x = 1.0; | |||
| nvgLineTo(args.vg, VEC_ARGS(b.interpolate(p))); | |||
| nvgStroke(args.vg); | |||
| } | |||
| } | |||
| void drawLayer(const DrawArgs& args, int layer) override { | |||
| if (layer != 1) | |||
| return; | |||
| @@ -316,41 +411,53 @@ struct ScopeDisplay : LedDisplay { | |||
| float offsetX = module->params[Scope::X_POS_PARAM].getValue(); | |||
| float offsetY = module->params[Scope::Y_POS_PARAM].getValue(); | |||
| // Get input colors | |||
| PortWidget* inputX = moduleWidget->getInput(Scope::X_INPUT); | |||
| PortWidget* inputY = moduleWidget->getInput(Scope::Y_INPUT); | |||
| CableWidget* inputXCable = APP->scene->rack->getTopCable(inputX); | |||
| CableWidget* inputYCable = APP->scene->rack->getTopCable(inputY); | |||
| NVGcolor inputXColor = inputXCable ? inputXCable->color : color::WHITE; | |||
| NVGcolor inputYColor = inputYCable ? inputYCable->color : color::WHITE; | |||
| // Draw waveforms | |||
| if (module->lissajous) { | |||
| if (module->isLissajous()) { | |||
| // X x Y | |||
| int lissajousChannels = std::max(module->channelsX, module->channelsY); | |||
| int lissajousChannels = std::min(module->channelsX, module->channelsY); | |||
| for (int c = 0; c < lissajousChannels; c++) { | |||
| nvgStrokeColor(args.vg, nvgRGBA(0x9f, 0xe4, 0x36, 0xc0)); | |||
| drawWaveform(args, module->bufferX[c], offsetX, gainX, module->bufferY[c], offsetY, gainY); | |||
| nvgStrokeColor(args.vg, SCHEME_YELLOW); | |||
| drawLissajous(args, c, offsetX, gainX, offsetY, gainY); | |||
| } | |||
| } | |||
| else { | |||
| // Y | |||
| for (int c = 0; c < module->channelsY; c++) { | |||
| nvgStrokeColor(args.vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0)); | |||
| drawWaveform(args, NULL, 0, 0, module->bufferY[c], offsetY, gainY); | |||
| nvgFillColor(args.vg, inputYColor); | |||
| drawWave(args, 1, c, offsetY, gainY); | |||
| } | |||
| // X | |||
| for (int c = 0; c < module->channelsX; c++) { | |||
| nvgStrokeColor(args.vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0)); | |||
| drawWaveform(args, NULL, 0, 0, module->bufferX[c], offsetX, gainX); | |||
| nvgFillColor(args.vg, inputXColor); | |||
| drawWave(args, 0, c, offsetX, gainX); | |||
| } | |||
| // Trigger | |||
| float trigThreshold = module->params[Scope::TRIG_PARAM].getValue(); | |||
| trigThreshold = (trigThreshold + offsetX) * gainX; | |||
| drawTrig(args, trigThreshold); | |||
| // Background lines | |||
| drawBackground(args); | |||
| } | |||
| // Calculate and draw stats | |||
| if (++statsFrame >= 4) { | |||
| statsFrame = 0; | |||
| statsX.calculate(module->bufferX[0], module->channelsX); | |||
| statsY.calculate(module->bufferY[0], module->channelsY); | |||
| calculateStats(statsX, 0, module->channelsX); | |||
| calculateStats(statsY, 1, module->channelsY); | |||
| } | |||
| drawStats(args, Vec(0, 0), "X", &statsX); | |||
| drawStats(args, Vec(0, box.size.y - 15), "Y", &statsY); | |||
| drawStats(args, Vec(0, 0 + 1), "X", statsX); | |||
| drawStats(args, Vec(0, box.size.y - 15 - 1), "Y", statsY); | |||
| } | |||
| }; | |||
| @@ -365,14 +472,14 @@ struct ScopeWidget : ModuleWidget { | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| // addParam(createParamCentered<VCVButton>(mm2px(Vec(8.643, 80.603)), module, Scope::_1X2_PARAM)); | |||
| addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(8.643, 80.603)), module, Scope::LISSAJOUS_PARAM, Scope::LISSAJOUS_LIGHT)); | |||
| addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(24.897, 80.551)), module, Scope::X_SCALE_PARAM)); | |||
| addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(41.147, 80.551)), module, Scope::Y_SCALE_PARAM)); | |||
| // addParam(createParamCentered<VCVButton>(mm2px(Vec(57.397, 80.521)), module, Scope::TRIG_PARAM)); | |||
| addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(57.397, 80.521)), module, Scope::EXTERNAL_PARAM, Scope::EXTERNAL_LIGHT)); | |||
| addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(8.643, 96.819)), module, Scope::TIME_PARAM)); | |||
| addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(24.897, 96.789)), module, Scope::X_POS_PARAM)); | |||
| addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(41.147, 96.815)), module, Scope::Y_POS_PARAM)); | |||
| // addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(57.397, 96.815)), module, Scope::THERS_PARAM)); | |||
| addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(57.397, 96.815)), module, Scope::TRIG_PARAM)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.643, 113.115)), module, Scope::X_INPUT)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(33.023, 113.115)), module, Scope::Y_INPUT)); | |||
| @@ -381,9 +488,10 @@ struct ScopeWidget : ModuleWidget { | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(20.833, 113.115)), module, Scope::X_OUTPUT)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(45.212, 113.115)), module, Scope::Y_OUTPUT)); | |||
| ScopeDisplay* display = createWidget<ScopeDisplay>(Vec(0, 44)); | |||
| display->box.size = Vec(box.size.x, 140); | |||
| ScopeDisplay* display = createWidget<ScopeDisplay>(mm2px(Vec(0.0, 13.039))); | |||
| display->box.size = mm2px(Vec(66.04, 55.88)); | |||
| display->module = module; | |||
| display->moduleWidget = this; | |||
| addChild(display); | |||
| } | |||
| }; | |||