diff --git a/src/Scope.cpp b/src/Scope.cpp index ca00040..f34e860 100644 --- a/src/Scope.cpp +++ b/src/Scope.cpp @@ -34,8 +34,10 @@ struct Scope : Module { NUM_LIGHTS }; - float bufferX[BUFFER_SIZE] = {}; - float bufferY[BUFFER_SIZE] = {}; + float bufferX[16][BUFFER_SIZE] = {}; + float bufferY[16][BUFFER_SIZE] = {}; + int channelsX = 0; + int channelsY = 0; int bufferIndex = 0; float frameIndex = 0; @@ -60,6 +62,8 @@ struct Scope : Module { void onReset() override { lissajous = false; external = false; + std::memset(bufferX, 0, sizeof(bufferX)); + std::memset(bufferY, 0, sizeof(bufferY)); } void process(const ProcessArgs &args) override { @@ -67,25 +71,42 @@ struct Scope : Module { if (sumTrigger.process(params[LISSAJOUS_PARAM].getValue() > 0.f)) { lissajous = !lissajous; } - lights[PLOT_LIGHT].value = lissajous ? 0.f : 1.f; - lights[LISSAJOUS_LIGHT].value = lissajous ? 1.f : 0.f; + lights[PLOT_LIGHT].setBrightness(!lissajous); + lights[LISSAJOUS_LIGHT].setBrightness(lissajous); if (extTrigger.process(params[EXTERNAL_PARAM].getValue() > 0.f)) { external = !external; } - lights[INTERNAL_LIGHT].value = external ? 0.f : 1.f; - lights[EXTERNAL_LIGHT].value = external ? 1.f : 0.f; + lights[INTERNAL_LIGHT].setBrightness(!external); + lights[EXTERNAL_LIGHT].setBrightness(external); // Compute time float deltaTime = std::pow(2.f, -params[TIME_PARAM].getValue()); int frameCount = (int) std::ceil(deltaTime * args.sampleRate); + // Set channels + int channelsX = inputs[X_INPUT].isConnected() ? inputs[X_INPUT].getChannels() : 0; + if (channelsX != this->channelsX) { + std::memset(bufferX, 0, sizeof(bufferX)); + this->channelsX = channelsX; + } + + int channelsY = inputs[Y_INPUT].isConnected() ? inputs[Y_INPUT].getChannels() : 0; + if (channelsY != this->channelsY) { + std::memset(bufferY, 0, sizeof(bufferY)); + this->channelsY = channelsY; + } + // Add frame to buffer if (bufferIndex < BUFFER_SIZE) { if (++frameIndex > frameCount) { frameIndex = 0; - bufferX[bufferIndex] = inputs[X_INPUT].getVoltage(); - bufferY[bufferIndex] = inputs[Y_INPUT].getVoltage(); + 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); + } bufferIndex++; } } @@ -105,18 +126,16 @@ struct Scope : Module { } frameIndex++; - // Must go below 0.1fV to trigger + // Must go below 0.1V to trigger float gate = external ? inputs[TRIG_INPUT].getVoltage() : inputs[X_INPUT].getVoltage(); // Reset if triggered float holdTime = 0.1f; - if (resetTrigger.process(rescale(gate, params[TRIG_PARAM].getValue() - 0.1f, params[TRIG_PARAM].getValue(), 0.f, 1.f)) || (frameIndex >= args.sampleRate * holdTime)) { - bufferIndex = 0; frameIndex = 0; return; - } - - // Reset if we've waited too long - if (frameIndex >= args.sampleRate * holdTime) { - bufferIndex = 0; frameIndex = 0; return; + float trigValue = params[TRIG_PARAM].getValue(); + if (resetTrigger.process(rescale(gate, trigValue - 0.1f, trigValue, 0.f, 1.f)) || (frameIndex >= args.sampleRate * holdTime)) { + bufferIndex = 0; + frameIndex = 0; + return; } } } @@ -142,55 +161,52 @@ struct Scope : Module { struct ScopeDisplay : TransparentWidget { Scope *module; - int frame = 0; + int statsFrame = 0; std::shared_ptr font; struct Stats { - float vrms = 0.f; + // float vrms = 0.f; float vpp = 0.f; float vmin = 0.f; float vmax = 0.f; - void calculate(float *values) { - vrms = 0.f; + + void calculate(float *buffer, int channels) { + // vrms = 0.f; vmax = -INFINITY; vmin = INFINITY; - for (int i = 0; i < BUFFER_SIZE; i++) { - float v = values[i]; - vrms += v*v; + for (int i = 0; i < BUFFER_SIZE * channels; i++) { + float v = buffer[i]; + // vrms += v*v; vmax = std::fmax(vmax, v); vmin = std::fmin(vmin, v); } - vrms = std::sqrt(vrms / BUFFER_SIZE); + // vrms = std::sqrt(vrms / BUFFER_SIZE); vpp = vmax - vmin; } }; + Stats statsX, statsY; ScopeDisplay() { font = APP->window->loadFont(asset::plugin(pluginInstance, "res/sudo/Sudo.ttf")); } - void drawWaveform(const DrawArgs &args, float *valuesX, float *valuesY) { - if (!valuesX) - return; + void drawWaveform(const DrawArgs &args, float *bufferX, float offsetX, float gainX, float *bufferY, float offsetY, float gainY) { + assert(bufferY); 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); nvgBeginPath(args.vg); - // Draw maximum display left to right for (int i = 0; i < BUFFER_SIZE; i++) { - float x, y; - if (valuesY) { - x = valuesX[i] / 2.f + 0.5f; - y = valuesY[i] / 2.f + 0.5f; - } - else { - x = (float)i / (BUFFER_SIZE - 1); - y = valuesX[i] / 2.f + 0.5f; - } + Vec v; + if (bufferX) + v.x = (bufferX[i] + offsetX) * gainX / 2.f + 0.5f; + else + v.x = (float) i / (BUFFER_SIZE - 1); + v.y = (bufferY[i] + offsetY) * gainY / 2.f + 0.5f; Vec p; - p.x = b.pos.x + b.size.x * x; - p.y = b.pos.y + b.size.y * (1.f - y); + 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); if (i == 0) nvgMoveTo(args.vg, p.x, p.y); else @@ -251,61 +267,61 @@ struct ScopeDisplay : TransparentWidget { nvgText(args.vg, pos.x + 6, pos.y + 11, title, NULL); nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x80)); - char text[128]; - snprintf(text, sizeof(text), "pp % 06.2f max % 06.2f min % 06.2f", stats->vpp, stats->vmax, stats->vmin); - nvgText(args.vg, pos.x + 22, pos.y + 11, text, NULL); + pos = pos.plus(Vec(22, 11)); + + std::string text; + text = "pp "; + text += isNear(stats->vpp, 0.f, 100.f) ? string::f("% 6.2f", stats->vpp) : " ---"; + 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) : " ---"; + 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) : " ---"; + nvgText(args.vg, pos.x + 58*2, pos.y, text.c_str(), NULL); } void draw(const DrawArgs &args) override { if (!module) return; - float gainX = std::pow(2.f, std::round(module->params[Scope::X_SCALE_PARAM].getValue())); - float gainY = std::pow(2.f, std::round(module->params[Scope::Y_SCALE_PARAM].getValue())); + float gainX = std::pow(2.f, std::round(module->params[Scope::X_SCALE_PARAM].getValue())) / 10.f; + float gainY = std::pow(2.f, std::round(module->params[Scope::Y_SCALE_PARAM].getValue())) / 10.f; float offsetX = module->params[Scope::X_POS_PARAM].getValue(); float offsetY = module->params[Scope::Y_POS_PARAM].getValue(); - 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.f; - valuesY[i] = (module->bufferY[j] + offsetY) * gainY / 10.f; - } - // Draw waveforms if (module->lissajous) { // X x Y - if (module->inputs[Scope::X_INPUT].isConnected() || module->inputs[Scope::Y_INPUT].isConnected()) { + int lissajousChannels = std::max(module->channelsX, module->channelsY); + for (int c = 0; c < lissajousChannels; c++) { nvgStrokeColor(args.vg, nvgRGBA(0x9f, 0xe4, 0x36, 0xc0)); - drawWaveform(args, valuesX, valuesY); + drawWaveform(args, module->bufferX[c], offsetX, gainX, module->bufferY[c], offsetY, gainY); } } else { // Y - if (module->inputs[Scope::Y_INPUT].isConnected()) { + for (int c = 0; c < module->channelsY; c++) { nvgStrokeColor(args.vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0)); - drawWaveform(args, valuesY, NULL); + drawWaveform(args, NULL, 0, 0, module->bufferY[c], offsetY, gainY); } // X - if (module->inputs[Scope::X_INPUT].isConnected()) { + for (int c = 0; c < module->channelsX; c++) { nvgStrokeColor(args.vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0)); - drawWaveform(args, valuesX, NULL); + drawWaveform(args, NULL, 0, 0, module->bufferX[c], offsetX, gainX); } - float valueTrig = (module->params[Scope::TRIG_PARAM].getValue() + offsetX) * gainX / 10.f; - drawTrig(args, valueTrig); + float trigValue = module->params[Scope::TRIG_PARAM].getValue(); + trigValue = (trigValue + offsetX) * gainX; + drawTrig(args, trigValue); } // Calculate and draw stats - if (++frame >= 4) { - frame = 0; - statsX.calculate(module->bufferX); - statsY.calculate(module->bufferY); + if (++statsFrame >= 4) { + statsFrame = 0; + statsX.calculate(module->bufferX[0], module->channelsX); + statsY.calculate(module->bufferY[0], module->channelsY); } drawStats(args, Vec(0, 0), "X", &statsX); drawStats(args, Vec(0, box.size.y - 15), "Y", &statsY);