How to use custom lights

I have been searching for a standard solution to this but have not been successful so far. The question is

Is there a (simple) way to define custom lights (based on SVGs) in Rack?

Something like
led_temp

Thanks!


Seems like you are not supposed to use SVGs for lights…

struct LightWidget : TransparentWidget {
   NVGcolor bgColor = nvgRGBA(0, 0, 0, 0);
   NVGcolor color = nvgRGBA(0, 0, 0, 0);
   NVGcolor borderColor = nvgRGBA(0, 0, 0, 0);
   void draw(NVGcontext *vg) override;
   virtual void drawLight(NVGcontext *vg);
   virtual void drawHalo(NVGcontext *vg);
};
void LightWidget::drawLight(NVGcontext *vg) {
   float radius = box.size.x / 2.0;

   nvgBeginPath(vg);
   nvgCircle(vg, radius, radius, radius);

   // Background
   nvgFillColor(vg, bgColor);
   nvgFill(vg);

   // Foreground
   nvgFillColor(vg, color);
   nvgFill(vg);

   // Border
   nvgStrokeWidth(vg, 0.5);
   nvgStrokeColor(vg, borderColor);
   nvgStroke(vg);
}
2 Likes

drawLight is virtual so you can draw a rectangle instead of a circle in your Light subclass.

Thanks! I’ll do that!

It works! For future reference: Here is what I did (pseudocode):

struct RectLightWidget : LightWidget
{
	void drawLight(NVGcontext *vg) override
	{
		//copy/paste implementation from LightWidget::drawLight

		//change circle to rect
		nvgRect(vg, 0, 0, box.size.x, box.size.y);
	}
};

struct RectMultiLightWidget : RectLightWidget
{
	//copy/paste implementation from MultiLightWidget , no changes required
};

struct RectModuleLightWidget : RectMultiLightWidget
{
	//copy/paste implementation from ModuleLightWidget, no changes required
};

struct GrayRectModuleLightWidget : RectModuleLightWidget
{
	//copy/paste implementation from GrayModuleLightWidget
	//replace
		template <typename T = RectModuleLightWidget>
};

//optional: define light dimensions
template <typename BASE>
struct TinyRectLight : BASE {
	LSR3TinyLight() {
		this->box.size = Vec(5,3);
	}
};

//define colours
//example: red light
struct RectRedLight : GrayRectModuleLightWidget {
	RectRedLight() {
		addBaseColor(COLOR_RED);
	}
};

I am not sure if this is the most elegant solution though. All structs that inherit from the light widget have to be defined again for every desired light style while their implementations remain unchanged. That causes redundancy. A better solution would be to make the the light widget class more general so that it can represent different light styles.

2 Likes

Geez, that’s inelegant. Why are you subclassing LightWidget directly and recreating the entire LightWidget hieracarchy?

Hi Andrew, what would do in the same case ? :wink: please

struct RectModuleLightWidget : ModuleLightWidget {
	void drawLight(NVGcontext *vg) override {...}
};

struct MyRedRectModuleLightWidget : RectModuleLightWidget {
	MyRedRectModuleLightWidget() {
		bgColor = ...;
		borderColor = ...;
		addBaseColor(...);
		box.size = ...;
	}
};
1 Like

Because I was in a hurry and had other tasks while doing this. That is why I put that caveat note below the code, too. Consider it work in progress :slight_smile:
Thanks for taking time to read and comment on this stuff!

Here is what it looks like now:

struct LSR3ModuleLightWidget : ModuleLightWidget
{
	void drawLight(NVGcontext *vg) override
	{
		//replaced circle by rect
		nvgRect(vg, 0, 0, box.size.x, box.size.y);

	}
	void drawHalo(NVGcontext *vg) override
	{
		//made halo smaller
	}
};

struct LSR3GrayModuleLightWidget : LSR3ModuleLightWidget
{
	LSR3GrayModuleLightWidget() {
		bgColor    = nvgRGB(0x5a, 0x5a, 0x5a);
		borderColor = nvgRGBA(0, 0, 0, 0x60);
	}

};

template <typename BASE>
struct LSR3TinyLight : BASE {
	LSR3TinyLight() {
		this->box.size = Vec(5,3);
	}
};

struct LSR3RedLight : LSR3GrayModuleLightWidget {
	LSR3RedLight() {
		addBaseColor(nvgRGB(0xff, 0x00, 0x00));
	}
};
//same for other colours

I think I know why I intuitively went for LightWidget: A modification of LightWidget, maybe so that there are not just circles but several options to choose from, would eliminate the need for subclassing (unless you want something exotic). Then again, this is code outside the plugin, so I can not modify it. So the solution is subclassing. The last missing step was to do this at the lowest possible level.

Looks good.

Ok, one final question:

Updating the lights in every step seems to be expensive (~20 millisamples in my case). It is also unnecessary since the UI frame rate is much lower.

A solution would be to have a counter that is incremented each time step() is called. Once the desired number of steps (maybe corresponding to 10ms intervals) has been counted, the lights are updated and the counter is set back to 0 again.

Is there any reason not to do it like this?

Thank you so much for your time!

The counter works but brightness decay is VERY slow now. I suppose there is a time constant somewhere that needs to be adjusted.

Found it:
engine.hpp -> struct Light -> void setBrightnessSmooth(float brightness, float frames = 1.f);
The time constant and its default setting: float frames = 1.f
It needs to be set to the number of frames per counter reset.

Example:

  • sample rate 44.1kHz
  • 10 ms @44.1kHz is 441 frames
  • set lights[…].setBrightnessSmooth(vuMeter.getBrightness(j),framesPerCounterReset);
  • in this case, framesPerCounterReset == 441

The best method is to use VuMeter2 from https://github.com/VCVRack/Rack/blob/v1/include/dsp/vumeter.hpp which handles its own light decay, so you can set the light’s brightness with Light::setBrightness().

You’re effectively disabling light decay by doing this, so why not just use setBrightness()? Are you sure you don’t want light decay though?

1 Like

Just performed a little experiment: Set frames to 441, 44.1 and 4.41 for one light each.
Result: Brightness decays in roughly ~200 ms, ~2 s, ~20s.
So the decay is corrected for time scaling as expected.

I’ll take a look at VuMeter2 tomorrow (it is now almost 11:00 p.m. here).

Thanks again!