#pragma once #include "rack.hpp" #include "asset.hpp" #include "widgets.hpp" #define LCD_FONT_DIG7 "res/digital-7.ttf" #define LCD_FONTSIZE 11 #define LCD_LETTER_SPACING 0 /* show values of all knobs */ #define DEBUG_VALUES false typedef std::shared_ptr TrueType; using namespace rack; #ifdef USE_VST2 #define plugin "LindenbergResearch" #else extern Plugin *plugin; #endif // USE_VST2 /** * @brief Standard LRT module */ struct LRModule : Module { long cnt = 0; /** * @brief Overtake default constructor for module to be compatible * @param numParams * @param numInputs * @param numOutputs * @param numLights */ LRModule(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module(numParams, numInputs, numOutputs, numLights) {} void step() override { Module::step(); // increment counter cnt++; } }; /** * @brief Standard LRT ModuleWidget definition */ struct LRModuleWidget : ModuleWidget { explicit LRModuleWidget(Module *module); }; /** * @brief Emulation of a LCD monochrome display */ struct LCDWidget : Label { enum LCDType { NUMERIC, TEXT, LIST }; TrueType ttfLCDDig7; float fontsize; LCDType type; NVGcolor fg; NVGcolor bg; bool active = true; float value = 0.0; unsigned char length = 0; std::string format; std::vector items; std::string s1; std::string s2; /** * @brief Constructor */ LCDWidget(NVGcolor fg, unsigned char length, std::string format, LCDType type, float fontsize = LCD_FONTSIZE); /** * @brief Draw LCD display * @param vg */ void draw(NVGcontext *vg) override; inline void addItem(std::string name) { items.push_back(name); } }; /** * @brief Indicator for control voltages on knobs */ struct Indicator { static constexpr float OVERFLOW_THRESHOLD = 0.01f; /** flag to control drawing */ bool active = false; /** color of indicator */ NVGcolor normalColor = nvgRGBA(0x00, 0x00, 0x00, 0xBB); NVGcolor overflowColor = nvgRGBA(0xBB, 0x00, 0x00, 0xBB); /** radius from middle */ float distance; /** normalized control voltage. must between [0..1] */ float cv = 0.f; /** draw angle */ float angle; float angle2; /** middle of parent */ Vec middle; /** * @brief Init indicator * @param distance Radius viewed from the middle * @param angle Angle of active knob area */ Indicator(float distance, float angle) { Indicator::distance = distance; Indicator::angle = angle; /** for optimization */ angle2 = 2 * angle; } /** * @brief Draw routine for cv indicator * @param vg */ void draw(NVGcontext *vg); }; /** * @brief Standard LR Shadow */ struct LRShadow { private: Rect box; float size = 0.65; float strength = 1.f; /** shadow shift */ Vec shadowPos = Vec(3, 5); public: /** * @brief Set the new offset of the shadow gradient * @param x * @param y */ void setShadowPosition(float x, float y) { shadowPos = Vec(x, y); } void setBox(const Rect &box); void setSize(float size); void setStrength(float strength); /** * @brief Draw shadow for circular knobs * @param vg NVGcontext * @param strength Alpha value of outside gradient * @param size Outer size * @param shift XY Offset shift from middle */ void drawShadow(NVGcontext *vg, float strength, float size); void draw(NVGcontext *vg); }; /** * @brief The base of all knobs used in LR panels, includes a indicator */ struct LRKnob : SVGKnob { private: static constexpr float ANGLE = 0.83f; /** setup indicator with default values */ Indicator idc = Indicator(15.f, ANGLE); bool debug = DEBUG_VALUES; TrueType font; /** snap mode */ bool snap = false; /** position to snap */ float snapAt = 0.0f; /** snap sensitivity */ float snapSens = 0.1; protected: /** shader */ LRShadow shadow = LRShadow(); public: /** * @brief Default constructor */ LRKnob() { minAngle = -ANGLE * (float) M_PI; maxAngle = ANGLE * (float) M_PI; font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); } /** * @brief Set the value of the indicator * @param value */ void setIndicatorValue(float value) { idc.cv = value; } /** * @brief Switch indicator on/off * @param active */ void setIndicatorActive(bool active) { idc.active = active; } /** * @brief Get indicator state * @return */ bool isIndicatorActive() { return idc.active; } /** * @brief Setup distance of indicator from middle * @param distance */ void setIndicatorDistance(float distance) { idc.distance = distance; } /** * @brief Hook into setSVG() method to setup box dimensions correct for indicator * @param svg */ void setSVG(std::shared_ptr svg) { SVGKnob::setSVG(svg); /** inherit dimensions after loaded svg */ idc.middle = Vec(box.size.x / 2, box.size.y / 2); shadow.setBox(box); } /** * @brief Creates a new instance of a LRKnob child * @tparam TParamWidget Subclass of LRKnob * @param pos Position * @param module Module pointer * @param paramId Parameter ID * @param minValue Min * @param maxValue Max * @param defaultValue Default * @return Pointer to new subclass of LRKnob */ template static TParamWidget *create(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { auto *param = new TParamWidget(); param->box.pos = pos; param->module = module; param->paramId = paramId; param->setLimits(minValue, maxValue); param->setDefaultValue(defaultValue); return param; } /** * @brief Draw knob * @param vg */ void draw(NVGcontext *vg) override { /** shadow */ shadow.draw(vg); /** component */ FramebufferWidget::draw(vg); /* nvgBeginPath(vg); nvgRect(vg, -30, -30, box.size.x + 60, box.size.y + 60); NVGcolor icol = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.3f); NVGcolor ocol = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.0f);; NVGpaint paint = nvgRadialGradient(vg, box.size.x / 2, box.size.y / 2, 0.f, box.size.x /2.f * 0.9f, icol, ocol); nvgFillPaint(vg, paint); nvgFill(vg);*/ /** indicator */ idc.draw(vg); if (debug) { auto text = stringf("%4.2f", value); nvgFontSize(vg, 15); nvgFontFaceId(vg, font->handle); nvgFillColor(vg, nvgRGBAf(1.f, 1.f, 1.0f, 1.0f)); nvgText(vg, box.size.x - 5, box.size.y + 10, text.c_str(), NULL); } } /** * @brief Setup knob snapping * @param position * @param sensitivity */ void setSnap(float position, float sensitivity) { snap = true; snapSens = sensitivity; snapAt = position; } /** * @brief Remove knob snaping */ void unsetSnap() { snap = false; } /** * @brief Snapping mode for knobs * @param e */ void onChange(EventChange &e) override { // if the value still inside snap-tolerance keep the value zero if (snap && value > -snapSens + snapAt && value < snapSens + snapAt) value = 0; SVGKnob::onChange(e); } }; /** * @brief Quantize position to odd numbers to simulate a toggle switch */ struct LRToggleKnob : LRKnob { LRToggleKnob(float length = 0.5f) { //TODO: parametrize start and end angle minAngle = -0.666666f * (float) M_PI; maxAngle = length * (float) M_PI; setSVG(SVG::load(assetPlugin(plugin, "res/ToggleKnob.svg"))); shadow.setShadowPosition(3, 4); shadow.setStrength(1.2f); shadow.setSize(0.7f); speed = 2.f; } void onChange(EventChange &e) override { value = round(value); SVGKnob::onChange(e); } }; /** * @brief Quantize position to odd numbers to simulate a toggle switch */ struct LRMiddleIncremental : LRKnob { LRMiddleIncremental(float length = 0.5f) { minAngle = -length * (float) M_PI; maxAngle = length * (float) M_PI; setSVG(SVG::load(assetPlugin(plugin, "res/MiddleIncremental.svg"))); shadow.setShadowPosition(3, 4); shadow.setStrength(1.2f); shadow.setSize(0.7f); speed = 3.f; } void onChange(EventChange &e) override { value = lround(value); //value = round(value); SVGKnob::onChange(e); } }; /** * @brief LR Big Knob */ struct LRBigKnob : LRKnob { LRBigKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/BigKnob.svg"))); setIndicatorDistance(15); shadow.setShadowPosition(5, 6); } }; /** * @brief LR Middle Knob */ struct LRMiddleKnob : LRKnob { LRMiddleKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/MiddleKnob.svg"))); setIndicatorDistance(12); shadow.setShadowPosition(4, 4); } }; /** * @brief LR Small Knob */ struct LRSmallKnob : LRKnob { LRSmallKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/SmallKnob.svg"))); shadow.setShadowPosition(3, 3); setSnap(0.0f, 0.03f); speed = 0.9f; } }; /** * @brief LR Alternate Small Knob */ struct LRAlternateSmallKnob : LRKnob { LRAlternateSmallKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/AlternateSmallKnob.svg"))); shadow.setShadowPosition(3, 3); setSnap(0.0f, 0.03f); speed = 0.9f; } }; /** * @brief LR Middle Knob */ struct LRAlternateMiddleKnob : LRKnob { LRAlternateMiddleKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/AlternateMiddleKnob.svg"))); setIndicatorDistance(12); shadow.setShadowPosition(4, 4); } }; /** * @brief LR Big Knob */ struct LRAlternateBigKnob : LRKnob { LRAlternateBigKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/AlternateBigKnob.svg"))); setIndicatorDistance(15); shadow.setShadowPosition(5, 6); } }; /** * @brief Alternative IO Port */ struct LRIOPort : SVGPort { private: LRShadow shadow = LRShadow(); public: LRIOPort() { background->svg = SVG::load(assetPlugin(plugin, "res/IOPortB.svg")); background->wrap(); box.size = background->box.size; /** inherit dimensions */ shadow.setBox(box); shadow.setSize(0.50); shadow.setShadowPosition(2, 1); } /** * @brief Hook into draw method * @param vg */ void draw(NVGcontext *vg) override { shadow.draw(vg); SVGPort::draw(vg); } }; /** * @brief Alternative IO Port */ struct LRIOPortC : SVGPort { private: LRShadow shadow = LRShadow(); public: LRIOPortC() { background->svg = SVG::load(assetPlugin(plugin, "res/IOPortC.svg")); background->wrap(); box.size = background->box.size; /** inherit dimensions */ shadow.setBox(box); shadow.setSize(0.50); shadow.setShadowPosition(2, 1); } /** * @brief Hook into draw method * @param vg */ void draw(NVGcontext *vg) override { shadow.draw(vg); SVGPort::draw(vg); } }; /** * @brief Alternative screw head A */ struct ScrewDarkA : SVGScrew { ScrewDarkA() { sw->svg = SVG::load(assetPlugin(plugin, "res/ScrewDark.svg")); sw->wrap(); box.size = sw->box.size; } }; /** * @brief Alternative screw head A */ struct ScrewLight : SVGScrew { ScrewLight() { sw->svg = SVG::load(assetPlugin(plugin, "res/ScrewLight.svg")); sw->wrap(); box.size = sw->box.size; } }; /** * @brief Custom switch based on original Rack files */ struct LRSwitch : SVGSwitch, ToggleSwitch { LRSwitch() { addFrame(SVG::load(assetPlugin(plugin, "res/Switch0.svg"))); addFrame(SVG::load(assetPlugin(plugin, "res/Switch1.svg"))); } }; /** * @brief Standard LED Redlight */ struct LRLight : SmallLight { LRLight(); void draw(NVGcontext *vg) override; }; /** * @brief Standard LR module Panel */ struct LRPanel : SVGPanel { private: /** margin of gradient box */ static constexpr float MARGIN = 10; /** gradient colors */ NVGcolor inner = nvgRGBAf(1.5f * .369f, 1.5f * 0.357f, 1.5f * 0.3333f, 0.25f); NVGcolor outer = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.34f);; /** gradient offset */ Vec offset = Vec(30, -50); public: LRPanel(); LRPanel(float x, float y) { offset.x = x; offset.y = y; } void setInner(const NVGcolor &inner); void setOuter(const NVGcolor &outer); const NVGcolor &getInner() const; const NVGcolor &getOuter() const; void draw(NVGcontext *vg) override; }; /** * @brief Passive rotating SVG image */ struct SVGRotator : FramebufferWidget { TransformWidget *tw; SVGWidget *sw; /** angle to rotate per step */ float angle = 0; float inc; SVGRotator(); /** * @brief Factory method * @param pos Position * @param svg Pointer to SVG image * @param angle Increment angle per step */ SVGRotator static *create(Vec pos, std::shared_ptr svg, float inc) { SVGRotator *rotator = FramebufferWidget::create(pos); rotator->setSVG(svg); rotator->inc = inc; return rotator; } void setSVG(std::shared_ptr svg); void step() override; };