#include "Features.hpp" #if USE_NEW_SCOPE #include #include "trowaSoft.hpp" #include "trowaSoftComponents.hpp" #include "trowaSoftUtilities.hpp" #include "dsp/digital.hpp" #include "Module_multiScope.hpp" #include "TSScopeBase.hpp" #include "Widget_multiScope.hpp" //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // multiScope() // Multi scope. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- multiScope::multiScope() : Module(multiScope::NUM_PARAMS, multiScope::NUM_INPUTS, multiScope::NUM_OUTPUTS, multiScope::NUM_LEDS) { initialized = false; firstLoad = true; plotBackgroundColor = COLOR_BLACK; float initColorKnobs[4] = { -10, -3.33, 3, 7.2 }; for (int wIx = 0; wIx < TROWA_SCOPE_NUM_WAVEFORMS; wIx++) { waveForms[wIx] = new TSWaveform(); waveForms[wIx]->setHueFromKnob(initColorKnobs[wIx]); waveForms[wIx]->setFillHueFromKnob(initColorKnobs[wIx]); } return; } // end multiScope() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // ~multiScope() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- multiScope::~multiScope() { // Clean our stuff for (int wIx = 0; wIx < TROWA_SCOPE_NUM_WAVEFORMS; wIx++) { delete waveForms[wIx]; } return; } // end multiScope() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // step(void) //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void multiScope::step() { if (!initialized) return; #if ENABLE_BG_COLOR_PICKER if (plotBackgroundDisplayOnTrigger.process(params[multiScope::BGCOLOR_DISPLAY_PARAM].value)) { showColorPicker = !showColorPicker; if (showColorPicker) { // Load Background color: editColorPointer = &(this->plotBackgroundColor); } } lights[multiScope::BGCOLOR_DISPLAY_LED].value = showColorPicker; #endif //TSWaveform* waveForm = NULL; for (int wIx = 0; wIx < TROWA_SCOPE_NUM_WAVEFORMS; wIx++) { //waveForm = waveForms[wIx]; // tmp pointer // Effect: waveForms[wIx]->gEffectIx = (int)clamp(static_cast(roundf(params[multiScope::EFFECT_PARAM+wIx].value)), 0, TROWA_SCOPE_NUM_EFFECTS - 1); // Lissajous: if (waveForms[wIx]->lissajousTrigger.process(params[multiScope::LISSAJOUS_PARAM + wIx].value)) { waveForms[wIx]->lissajous = !waveForms[wIx]->lissajous; } lights[multiScope::LISSAJOUS_LED + wIx].value = waveForms[wIx]->lissajous; // Compute Color: float hue = 0; if(inputs[multiScope::COLOR_INPUT+wIx].active){ hue = clamp(rescale(inputs[multiScope::COLOR_INPUT+wIx].value, TROWA_SCOPE_HUE_INPUT_MIN_V, TROWA_SCOPE_HUE_INPUT_MAX_V, 0.0, 1.0), 0.0, 1.0); } else { hue = rescale(params[multiScope::COLOR_PARAM+wIx].value, TROWA_SCOPE_HUE_KNOB_MIN, TROWA_SCOPE_HUE_KNOB_MAX, 0.0, 1.0); } waveForms[wIx]->colorChanged = hue != waveForms[wIx]->waveHue || firstLoad; if (waveForms[wIx]->colorChanged) { waveForms[wIx]->waveHue = hue; if (hue > 0.99) { // Inject white waveForms[wIx]->waveColor = COLOR_WHITE; } else { waveForms[wIx]->waveColor = HueToColor(waveForms[wIx]->waveHue); // Base Color (opacity full) } #if TROWA_SCOPE_USE_COLOR_LIGHTS // Change the light color: waveForms[wIx]->waveLight->setColor(waveForms[wIx]->waveColor); #endif } // end if color changed // Opacity: if (inputs[multiScope::OPACITY_INPUT + wIx].active) { waveForms[wIx]->waveOpacity = clamp(rescale(inputs[multiScope::OPACITY_INPUT + wIx].value, TROWA_SCOPE_OPACITY_INPUT_MIN, TROWA_SCOPE_OPACITY_INPUT_MAX, TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY), TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY); } else { waveForms[wIx]->waveOpacity = params[multiScope::OPACITY_PARAM + wIx].value; } // Line Thickness if (inputs[multiScope::THICKNESS_INPUT + wIx].active) { waveForms[wIx]->lineThickness = clamp(rescale(inputs[multiScope::THICKNESS_INPUT + wIx].value, TROWA_SCOPE_THICKNESS_INPUT_MIN, TROWA_SCOPE_THICKNESS_INPUT_MAX, TROWA_SCOPE_THICKNESS_MIN, TROWA_SCOPE_THICKNESS_MAX), TROWA_SCOPE_THICKNESS_MIN, TROWA_SCOPE_THICKNESS_MAX); } else { waveForms[wIx]->lineThickness = params[multiScope::THICKNESS_PARAM + wIx].value; } // Compute Fill ::::::::::::::::::::::::::::::::::::::::: if (waveForms[wIx]->fillOnTrigger.process(params[multiScope::FILL_ON_PARAM + wIx].value)) { waveForms[wIx]->doFill = !waveForms[wIx]->doFill; //debug("Waveform %d: Fill On Clicked : %d (ParamId: %d).", wIx, waveForms[wIx]->doFill, multiScope::FILL_ON_PARAM + wIx); } lights[multiScope::FILL_ON_LED + wIx].value = waveForms[wIx]->doFill; hue = 0; if (inputs[multiScope::FILL_COLOR_INPUT + wIx].active) { hue = clamp(rescale(inputs[multiScope::FILL_COLOR_INPUT + wIx].value, TROWA_SCOPE_HUE_INPUT_MIN_V, TROWA_SCOPE_HUE_INPUT_MAX_V, 0.0, 1.0), 0.0, 1.0); } else { hue = rescale(params[multiScope::FILL_COLOR_PARAM + wIx].value, TROWA_SCOPE_HUE_KNOB_MIN, TROWA_SCOPE_HUE_KNOB_MAX, 0.0, 1.0); } if (hue != waveForms[wIx]->fillHue || firstLoad) { waveForms[wIx]->fillHue = hue; if (hue > 0.99) { // Inject White waveForms[wIx]->fillColor = COLOR_WHITE; } else { waveForms[wIx]->fillColor = HueToColor(waveForms[wIx]->fillHue); // Base Color (opacity full) } } // end if color changed // Opacity: if (inputs[multiScope::FILL_OPACITY_INPUT + wIx].active) { waveForms[wIx]->fillOpacity = clamp(rescale(inputs[multiScope::FILL_OPACITY_INPUT + wIx].value, TROWA_SCOPE_OPACITY_INPUT_MIN, TROWA_SCOPE_OPACITY_INPUT_MAX, TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY), TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY); } else { waveForms[wIx]->fillOpacity = params[multiScope::FILL_OPACITY_PARAM + wIx].value; } // Compute rotation: waveForms[wIx]->rotKnobValue = params[multiScope::ROTATION_PARAM+wIx].value; if (waveForms[wIx]->rotModeTrigger.process(params[multiScope::ROTATION_MODE_PARAM+wIx].value)) { waveForms[wIx]->rotMode = !waveForms[wIx]->rotMode; //debug("Waveform %d: Rotation Mode On Clicked : %d.", wIx, waveForms[wIx]->rotMode); } lights[multiScope::ROT_LED+wIx].value = waveForms[wIx]->rotMode; float rot = 0; float rotRate = 0; if (waveForms[wIx]->rotMode) { // Absolute position: rot = rescale(params[multiScope::ROTATION_PARAM+wIx].value + inputs[multiScope::ROTATION_INPUT+wIx].value, 0, 10, 0, NVG_PI); } else { // Differential rotation rotRate = rescale(params[multiScope::ROTATION_PARAM+wIx].value + inputs[multiScope::ROTATION_INPUT+wIx].value, 0, 10, 0, 0.5); } waveForms[wIx]->rotAbsValue = rot; waveForms[wIx]->rotDiffValue = rotRate; // Compute time: float deltaTime = powf(2.0, params[TIME_PARAM+wIx].value + inputs[TIME_INPUT+wIx].value); int frameCount = (int)ceilf(deltaTime * engineGetSampleRate()); // Add frame to buffer if (waveForms[wIx]->bufferIndex < BUFFER_SIZE) { if (++(waveForms[wIx]->frameIndex) > frameCount) { waveForms[wIx]->frameIndex = 0; waveForms[wIx]->bufferX[waveForms[wIx]->bufferIndex] = inputs[X_INPUT+wIx].value; waveForms[wIx]->bufferY[waveForms[wIx]->bufferIndex] = inputs[Y_INPUT+wIx].value; waveForms[wIx]->bufferPenOn[waveForms[wIx]->bufferIndex] = (!inputs[PEN_ON_INPUT + wIx].active || inputs[PEN_ON_INPUT + wIx].value > 0.1); // Allow some noise? waveForms[wIx]->bufferIndex++; } } else { if (waveForms[wIx]->lissajous) { // Reset waveForms[wIx]->bufferIndex = 0; waveForms[wIx]->frameIndex = 0; } else { // Just show stuff (no trigger inputs) waveForms[wIx]->frameIndex++; float holdTime = 0.1; if (waveForms[wIx]->frameIndex >= engineGetSampleRate() * holdTime) { waveForms[wIx]->bufferIndex = 0; waveForms[wIx]->frameIndex = 0; } } } } // end loop through waveforms firstLoad = false; return; } // end step() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // drawWaveform() // @vg : (IN) NVGcontext // @valX: (IN) Pointer to x values. // @valY: (IN) Pointer to y values. // @rotRate: (IN) Rotation rate in radians // @lineThickness: (IN) Line thickness // @compositeOp: (IN) Some global effect if any // @flipX: (IN) Flip along x (at x=0) // @flipY: (IN) Flip along y //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void multiScopeDisplay::drawWaveform(NVGcontext *vg, float *valX, float *valY, bool* penOn, float rotRate, float lineThickness, NVGcolor lineColor, bool doFill, NVGcolor fillColor, NVGcompositeOperation compositeOp, bool flipX, bool flipY) { if (!valX) return; nvgSave(vg); Rect b = Rect(Vec(0, 0), box.size); nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y); //nvgTranslate(vg, box.size.x / 2.0, box.size.y / 2.0); //nvgRotate(vg, rot += rotRate); rot += rotRate; if (flipX || flipY) { // Sets the transform to scale matrix. // void nvgTransformScale(float* dst, float sx, float sy); nvgScale(vg, ((flipX) ? -1 : 1), (flipY) ? -1 : 1); // flip } // Draw maximum display left to right nvgBeginPath(vg); float xOffset = 0;// -box.size.x / 2.0; float yOffset = 0;// -box.size.y / 2.0; float minX = b.pos.x + xOffset + lineThickness / 2.0; float maxX = minX + b.size.x - lineThickness; float minY = b.pos.y + yOffset + lineThickness / 2.0; float maxY = minY + box.size.y - lineThickness; bool doTrim = false; switch (compositeOp) { case NVG_DESTINATION_OVER: case NVG_SOURCE_IN: case NVG_SOURCE_OUT: case NVG_DESTINATION_IN: case NVG_DESTINATION_ATOP: case NVG_COPY: doTrim = true; break; case NVG_SOURCE_OVER: case NVG_ATOP: case NVG_DESTINATION_OUT: case NVG_LIGHTER: case NVG_XOR: default: break; } float ox = b.pos.x + b.size.x / 2.0; // Center of box float oy = b.pos.y + b.size.y / 2.0; float s = sin(rot); float c = cos(rot); bool lastPointStarted = false; // If the last point was actually plotted //bool lastPointInBounds = false; // If the last point was in bounds Vec lastPoint; Vec lastPointRaw; uint8_t lastLocCodeRaw = POINT_POS_INSIDE; bool lastPointExists = false; // If the last point was actually calculated (i.e. false if pen is off) uint8_t lastLocCode = POINT_POS_INSIDE; for (int i = 0; i < BUFFER_SIZE; i++) { if (penOn[i]) { float x, y; if (valY) { x = valX[i] / 2.0 + 0.5; y = valY[i] / 2.0 + 0.5; } else { x = (float)i / (BUFFER_SIZE - 1); y = valX[i] / 2.0 + 0.5; } Vec p; //p.x = b.pos.x + xOffset + b.size.x * x; //p.y = b.pos.y + yOffset + b.size.y * (1.0 - y); p.x = b.size.x * x; p.y = b.size.y * (1.0 - y); // Rotate ourselves so we can cull easily. double dx = p.x - ox; double dy = p.y - oy; p.x = ox + dx * c - dy * s; p.y = oy + dx * s + dy * c; bool plotPoint = true; if (doTrim) { Vec origPoint = p; // Do some cropping/clipping if needed uint8_t locCode = GetPointLocationCode(p, minX, maxX, minY, maxY); uint8_t origLocCode = locCode; //bool inBounds = !locCode; bool doSearch = false; if (locCode) { // Outside of bounds if (lastPointExists && !LINE_OUT_OF_BOUNDS(locCode, lastLocCodeRaw)) // If there was a point last time and both prev and this one don't make a line totally outside of bounds. { // Check the last point calculated (it may be out of bounds too) doSearch = true; } else { // Just save this for next time. Do not plot plotPoint = false; } } // end if this point is out of bounds else if (lastPointExists && lastLocCodeRaw) { // Last point wasn't valid although this one is, so we will have to inject both points. doSearch = true; } if (doSearch) { Vec p1 = lastPointRaw; Vec p2 = p; uint8_t outcode0 = lastLocCodeRaw; uint8_t outcode1 = locCode; while (doSearch) { if (LINE_IS_IN_BOUNDS(outcode0, outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop plotPoint = true; doSearch = false; } else if (LINE_OUT_OF_BOUNDS(outcode0, outcode1)) { // Bitwise AND is not 0. (implies both end points are in the same region outside the window). Reject and get out of loop doSearch = false; plotPoint = false; } else { // failed both tests, so calculate the line segment to clip // from an outside point to an intersection with clip edge double x, y; // At least one endpoint is outside the clip rectangle; pick it. uint8_t outcodeOut = outcode0 ? outcode0 : outcode1; // Now find the intersection point; // use formulas: // slope = (y1 - y0) / (x1 - x0) // x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax // y = y0 + slope * (xm - x0), where xm is xmin or xmax if (outcodeOut & POINT_POS_TOP) { // point is above the clip rectangle x = p1.x + (p2.x - p1.x) * (maxY - p1.y) / (p2.y - p1.y); y = maxY; } else if (outcodeOut & POINT_POS_BOTTOM) { // point is below the clip rectangle x = p1.x + (p2.x - p1.x) * (minY - p1.y) / (p2.y - p1.y); y = minY; } else if (outcodeOut & POINT_POS_RIGHT) { // point is to the right of clip rectangle y = p1.y + (p2.y - p1.y) * (maxX - p1.x) / (p2.x - p1.x); x = maxX; } else if (outcodeOut & POINT_POS_LEFT) { // point is to the left of clip rectangle y = p1.y + (p2.y - p1.y) * (minX - p1.x) / (p2.x - p1.x); x = minX; } // Now we move outside point to intersection point to clip // and get ready for next pass. if (outcodeOut == outcode0) { p1.x = x; p1.y = y; outcode0 = GetPointLocationCode(p1, minX, maxX, minY, maxY); } else { p2.x = x; p2.y = y; outcode1 = GetPointLocationCode(p2, minX, maxX, minY, maxY); } } // end else (check bounds) } // end while // See if we should plot the last point (now that it's fixed) if (lastLocCode && !outcode0) { // Last point was out of bounds, but is now not out of bounds if (!lastPointStarted) { nvgMoveTo(vg, p1.x, p1.y); } else { nvgLineTo(vg, p1.x, p1.y); } lastPointStarted = true; } // end if plot prev point locCode = outcode1; p = p2; if (locCode) { // Still not in bounds plotPoint = false; } } lastLocCode = locCode; lastPointRaw = origPoint; lastLocCodeRaw = origLocCode; } // end if do trimming if (plotPoint) { if (!lastPointStarted) { nvgMoveTo(vg, p.x, p.y); } else { nvgLineTo(vg, p.x, p.y); } lastPointStarted = true; } else { lastPointStarted = false; } lastPoint = p; lastPointExists = true; } // end if penOn else { // Pen is off, ignore this point lastPointStarted = false; lastPointExists = false; } // end else (pen off) } // end loop through buffer nvgLineCap(vg, NVG_ROUND); nvgMiterLimit(vg, 2.0); nvgGlobalCompositeOperation(vg, compositeOp); if (doFill) { nvgFillColor(vg, fillColor); nvgFill(vg); } nvgStrokeColor(vg, lineColor); nvgStrokeWidth(vg, lineThickness); nvgStroke(vg); nvgResetScissor(vg); nvgRestore(vg); nvgGlobalCompositeOperation(vg, NVG_SOURCE_OVER); // Restore to normal return; } // end drawWaveform() RACK_PLUGIN_MODEL_INIT(trowaSoft, MultiScope) { Model *modelMultiScope = Model::create(/*manufacturer*/ TROWA_PLUGIN_NAME, /*slug*/ "multiScope", /*name*/ "multiScope", /*Tags*/ VISUAL_TAG, UTILITY_TAG); return modelMultiScope; } #endif // end if use new scope