BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

widgets.cpp (16202B)


      1 
      2 #include "widgets.hpp"
      3 #include "skins.hpp"
      4 #include "dsp/signal.hpp"
      5 
      6 using namespace bogaudio;
      7 using namespace bogaudio::dsp;
      8 
      9 DisplayWidget::DisplayWidget(Module* module) : _module(module) {
     10 }
     11 
     12 bool DisplayWidget::isLit() {
     13 	return _module && !_module->isBypassed();
     14 }
     15 
     16 bool DisplayWidget::isScreenshot() {
     17 	return !_module;
     18 }
     19 
     20 void DisplayWidget::draw(const DrawArgs& args) {
     21 	if (!isLit()) {
     22 		drawOnce(args, isScreenshot(), false);
     23 	}
     24 }
     25 
     26 void DisplayWidget::drawLit(const DrawArgs& args) {
     27 	if (isLit()) {
     28 		drawOnce(args, false, true);
     29 	}
     30 }
     31 
     32 
     33 std::string SkinnableWidget::skinSVG(const std::string& base, const std::string& skin) {
     34 	std::string s = skin;
     35 	if (s == "default") {
     36 		s = Skins::skins().defaultKey();
     37 	}
     38 	std::string svg = "res/" + base;
     39 	if (s != "light") {
     40 		svg += "-";
     41 		svg += s;
     42 	}
     43 	svg += ".svg";
     44 	return svg;
     45 }
     46 
     47 
     48 Screw::Screw() {
     49 	skinChanged("default");
     50 }
     51 
     52 void Screw::skinChanged(const std::string& skin) {
     53 	const char* svg = "res/ComponentLibrary/ScrewSilver.svg";
     54 	const char* backgroundFill = Skins::skins().skinCssValue(skin, "background-fill");
     55 	if (backgroundFill) {
     56 		NVGcolor c = Skins::cssColorToNVGColor(backgroundFill, nvgRGBA(0xdd, 0xdd, 0xdd, 0xff));
     57 		if ((c.r + c.g + c.b) / 3.0f < 0.5f) {
     58 			svg = "res/ComponentLibrary/ScrewBlack.svg";
     59 		}
     60 	}
     61 	setSvg(APP->window->loadSvg(asset::system(svg)));
     62 	fb->dirty = true;
     63 }
     64 
     65 
     66 BGKnob::BGKnob(const char* svgBase, int dim) {
     67 	_svgBase = svgBase;
     68 	setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG(_svgBase.c_str()).c_str())));
     69 	box.size = Vec(dim, dim);
     70 	shadow->blurRadius = 2.0;
     71 	// k->shadow->opacity = 0.15;
     72 	shadow->box.pos = Vec(0.0, 3.0);
     73 }
     74 
     75 void BGKnob::redraw() {
     76 	event::Change c;
     77 	onChange(c);
     78 }
     79 
     80 void BGKnob::skinChanged(const std::string& skin) {
     81 	setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG(_svgBase.c_str(), skin).c_str())));
     82 	fb->dirty = true;
     83 }
     84 
     85 
     86 Knob16::Knob16() : BGKnob("knob_16px", 16) {
     87 	shadow->box.pos = Vec(0.0, 2.5);
     88 }
     89 
     90 
     91 Knob19::Knob19() : BGKnob("knob_19px", 19) {
     92 	shadow->box.pos = Vec(0.0, 2.5);
     93 }
     94 
     95 
     96 Knob26::Knob26() : BGKnob("knob_26px", 26) {
     97 }
     98 
     99 
    100 Knob29::Knob29() : BGKnob("knob_29px", 29) {
    101 }
    102 
    103 
    104 Knob38::Knob38() : BGKnob("knob_38px", 38) {
    105 }
    106 
    107 
    108 Knob45::Knob45() : BGKnob("knob_45px", 45) {
    109 }
    110 
    111 
    112 Knob68::Knob68() : BGKnob("knob_68px", 68) {
    113 	shadow->box.pos = Vec(0.0, 4.0);
    114 }
    115 
    116 
    117 void IndicatorKnob::IKWidget::setAngle(float a) {
    118 	assert(a >= -1.0f && a <= 1.0f);
    119 
    120 	const float range = 0.83f * M_PI;
    121 	_angle = a * range;
    122 	if (_unipolarCB && _unipolarCB()) {
    123 		_angle = 2.0f * _angle - range;
    124 	}
    125 	if (a < 0.0f) {
    126 		_color.r = 1.0f; // 0xff
    127 		_color.g = 0.6f; // 0x99
    128 		_color.b = 0.0f; // 0x00
    129 		_color.a = -a;
    130 	}
    131 	else {
    132 		_color.r = 0.333f; // 0x55
    133 		_color.g = 1.0f; // 0xff
    134 		_color.b = 0.333f; // 0x55
    135 		_color.a = a;
    136 	}
    137 }
    138 
    139 void IndicatorKnob::IKWidget::draw(const DrawArgs& args) {
    140 	nvgSave(args.vg);
    141 
    142 	float c = box.size.x * 0.5f;
    143 	nvgTranslate(args.vg, c, c);
    144 	nvgRotate(args.vg, _angle);
    145 	nvgTranslate(args.vg, -c, -c);
    146 
    147 	float r = c - 0.2f; // FIXME: need to scale everything if there is ever a dim != 19.
    148 	nvgBeginPath(args.vg);
    149 	nvgCircle(args.vg, c, c, r);
    150 	nvgFillColor(args.vg, _rim);
    151 	nvgFill(args.vg);
    152 
    153 	r -= 2.0f;
    154 	nvgBeginPath(args.vg);
    155 	nvgCircle(args.vg, c, c, r);
    156 	nvgFillColor(args.vg, _center);
    157 	nvgFill(args.vg);
    158 	if (!_drawColorsCB || _drawColorsCB()) {
    159 		nvgBeginPath(args.vg);
    160 		nvgCircle(args.vg, c, c, r);
    161 		nvgFillColor(args.vg, _color);
    162 		nvgFill(args.vg);
    163 
    164 		r -= 0.15f;
    165 		nvgBeginPath(args.vg);
    166 		nvgCircle(args.vg, c, c, r);
    167 		nvgStrokeColor(args.vg, nvgRGBA(0x66, 0x66, 0x66, 0x7f));
    168 		nvgStrokeWidth(args.vg, 0.3f);
    169 		nvgStroke(args.vg);
    170 
    171 		r -= 0.3f;
    172 		nvgBeginPath(args.vg);
    173 		nvgCircle(args.vg, c, c, r);
    174 		nvgStrokeColor(args.vg, nvgRGBA(0x77, 0x77, 0x77, 0x7f));
    175 		nvgStrokeWidth(args.vg, 0.3f);
    176 		nvgStroke(args.vg);
    177 
    178 		r -= 0.3f;
    179 		nvgBeginPath(args.vg);
    180 		nvgCircle(args.vg, c, c, r);
    181 		nvgStrokeColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0x7f));
    182 		nvgStrokeWidth(args.vg, 0.3f);
    183 		nvgStroke(args.vg);
    184 
    185 		r -= 0.3f;
    186 		nvgBeginPath(args.vg);
    187 		nvgCircle(args.vg, c, c, r);
    188 		nvgStrokeColor(args.vg, nvgRGBA(0x99, 0x99, 0x99, 0x7f));
    189 		nvgStrokeWidth(args.vg, 0.3f);
    190 		nvgStroke(args.vg);
    191 
    192 		r -= 0.15f;
    193 		nvgBeginPath(args.vg);
    194 		nvgCircle(args.vg, c, c, r);
    195 		nvgFillColor(args.vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0x7f));
    196 		nvgFill(args.vg);
    197 	}
    198 
    199 	nvgBeginPath(args.vg);
    200 	nvgCircle(args.vg, c, 1.6f, 1.3f);
    201 	nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0xff));
    202 	nvgFill(args.vg);
    203 
    204 	nvgBeginPath(args.vg);
    205 	nvgCircle(args.vg, c, 1.9f, 1.3f);
    206 	nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0xff));
    207 	nvgFill(args.vg);
    208 
    209 	nvgRestore(args.vg);
    210 }
    211 
    212 IndicatorKnob::IndicatorKnob(int dim) {
    213 	box.size = math::Vec(dim, dim);
    214 	box.pos = math::Vec(0, 0);
    215 	fb = new widget::FramebufferWidget;
    216 	addChild(fb);
    217 	fb->box.size = box.size;
    218 
    219 	shadow = new CircularShadow;
    220 	shadow->box.size = box.size;
    221 	shadow->blurRadius = 2.0;
    222 	// shadow->opacity = 0.15;
    223 	shadow->box.pos = Vec(0.0, 3.0);
    224 	fb->addChild(shadow);
    225 
    226 	w = new IKWidget;
    227 	w->box.size = box.size;
    228 	w->box.pos = math::Vec(0, 0);
    229 	fb->addChild(w);
    230 
    231 	skinChanged("default");
    232 }
    233 
    234 void IndicatorKnob::onHover(const event::Hover& e) {
    235 	math::Vec c = box.size.div(2);
    236 	float dist = e.pos.minus(c).norm();
    237 	if (dist <= c.x) {
    238 		Knob::onHover(e);
    239 	}
    240 }
    241 
    242 void IndicatorKnob::onLeave(const LeaveEvent& e) {
    243 	Knob::onLeave(e);
    244 	redraw();
    245 }
    246 
    247 void IndicatorKnob::onChange(const event::Change& e) {
    248 	fb->dirty = true;
    249 	if (getParamQuantity()) {
    250 		w->setAngle(getParamQuantity()->getValue());
    251 	}
    252 	Knob::onChange(e);
    253 }
    254 
    255 void IndicatorKnob::redraw() {
    256 	event::Change c;
    257 	onChange(c);
    258 }
    259 
    260 bool IndicatorKnob::isLit() {
    261 	return module && !module->isBypassed() && getParamQuantity() &&
    262 		(getParamQuantity()->getValue() < -0.01f || getParamQuantity()->getValue() > 0.01f) &&
    263 		(!w->_drawColorsCB || w->_drawColorsCB());
    264 }
    265 
    266 void IndicatorKnob::draw(const DrawArgs& args) {
    267 	if (!isLit()) {
    268 		Knob::draw(args);
    269 	}
    270 }
    271 
    272 void IndicatorKnob::drawLit(const DrawArgs& args) {
    273 	Knob::draw(args);
    274 }
    275 
    276 void IndicatorKnob::skinChanged(const std::string& skin) {
    277 	const Skins& skins = Skins::skins();
    278 	const char* knobRim = skins.skinCssValue(skin, "knob-rim");
    279 	if (knobRim) {
    280 		w->_rim = Skins::cssColorToNVGColor(knobRim, w->_rim);
    281 	}
    282 	const char* knobCenter = skins.skinCssValue(skin, "knob-center");
    283 	if (knobCenter) {
    284 		w->_center = Skins::cssColorToNVGColor(knobCenter, w->_center);
    285 	}
    286 	fb->dirty = true;
    287 }
    288 
    289 
    290 Port24::Port24() {
    291 	setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG("port").c_str())));
    292 	box.size = Vec(24, 24);
    293 	shadow->blurRadius = 1.0;
    294 	shadow->box.pos = Vec(0.0, 1.5);
    295 }
    296 
    297 void Port24::skinChanged(const std::string& skin) {
    298 	setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG("port", skin).c_str())));
    299 	fb->dirty = true;
    300 }
    301 
    302 
    303 BlankPort24::BlankPort24() {
    304 	setSvg(NULL);
    305 	box.size = Vec(24, 24);
    306 }
    307 
    308 
    309 SliderSwitch::SliderSwitch() {
    310 	shadow = new CircularShadow();
    311 	addChild(shadow);
    312 	shadow->box.size = Vec();
    313 }
    314 
    315 
    316 SliderSwitch2State14::SliderSwitch2State14() {
    317 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/slider_switch_2_14px_0.svg")));
    318 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/slider_switch_2_14px_1.svg")));
    319 	shadow->box.size = Vec(14.0, 24.0);
    320 	shadow->blurRadius = 1.0;
    321 	shadow->box.pos = Vec(0.0, 7.0);
    322 }
    323 
    324 
    325 Button18::Button18() {
    326 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_0.svg")));
    327 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_1.svg")));
    328 	box.size = Vec(18, 18);
    329 	momentary = true;
    330 }
    331 
    332 
    333 StatefulButton::StatefulButton(const char* offSvgPath, const char* onSvgPath) {
    334 	shadow = new CircularShadow();
    335 	addChild(shadow);
    336 
    337 	_svgWidget = new SvgWidget();
    338 	addChild(_svgWidget);
    339 
    340 	auto svg = APP->window->loadSvg(asset::plugin(pluginInstance, offSvgPath));
    341 	_frames.push_back(svg);
    342 	_frames.push_back(APP->window->loadSvg(asset::plugin(pluginInstance, onSvgPath)));
    343 
    344 	_svgWidget->setSvg(svg);
    345 	box.size = _svgWidget->box.size;
    346 	shadow->box.size = _svgWidget->box.size;
    347 	shadow->blurRadius = 1.0;
    348 	shadow->box.pos = Vec(0.0, 1.0);
    349 }
    350 
    351 void StatefulButton::onDragStart(const event::DragStart& e) {
    352 	if (getParamQuantity()) {
    353 		_svgWidget->setSvg(_frames[1]);
    354 		if (getParamQuantity()->getValue() >= getParamQuantity()->getMaxValue()) {
    355 			getParamQuantity()->setValue(getParamQuantity()->getMinValue());
    356 		}
    357 		else {
    358 			getParamQuantity()->setValue(getParamQuantity()->getValue() + 1.0);
    359 		}
    360 	}
    361 }
    362 
    363 void StatefulButton::onDragEnd(const event::DragEnd& e) {
    364 	_svgWidget->setSvg(_frames[0]);
    365 }
    366 
    367 
    368 StatefulButton9::StatefulButton9() : StatefulButton("res/button_9px_0.svg", "res/button_9px_1.svg") {
    369 }
    370 
    371 
    372 StatefulButton18::StatefulButton18() : StatefulButton("res/button_18px_0.svg", "res/button_18px_1.svg") {
    373 }
    374 
    375 
    376 ToggleButton18::ToggleButton18() {
    377 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_0.svg")));
    378 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_1.svg")));
    379 }
    380 
    381 
    382 IndicatorButtonGreen9::IndicatorButtonGreen9() {
    383 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_9px_0.svg")));
    384 	addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_9px_1_green.svg")));
    385 }
    386 
    387 bool IndicatorButtonGreen9::isLit() {
    388 	return module && !module->isBypassed() && getParamQuantity() && getParamQuantity()->getValue() > 0.0f;
    389 }
    390 
    391 void IndicatorButtonGreen9::draw(const DrawArgs& args) {
    392 	if (!isLit()) {
    393 		SvgSwitch::draw(args);
    394 	}
    395 }
    396 
    397 void IndicatorButtonGreen9::drawLit(const DrawArgs& args) {
    398 	SvgSwitch::draw(args);
    399 }
    400 
    401 
    402 void InvertingIndicatorButton::IIBWidget::setValue(float v) {
    403 	assert(v >= -1.0f && v <= 1.0f);
    404 
    405 	if (v < 0.0f) {
    406 		_color.r = 1.0f; // 0xff
    407 		_color.g = 0.6f; // 0x99
    408 		_color.b = 0.0f; // 0x00
    409 		_color.a = -v;
    410 	}
    411 	else {
    412 		_color.r = 0.333f; // 0x55
    413 		_color.g = 1.0f; // 0xff
    414 		_color.b = 0.333f; // 0x55
    415 		_color.a = v;
    416 	}
    417 }
    418 
    419 void InvertingIndicatorButton::IIBWidget::draw(const DrawArgs& args) {
    420 	nvgSave(args.vg);
    421 
    422 	float c = box.size.x * 0.5f;
    423 	float s = ((float)_dim) / 18.0f;
    424 	float r = c - 0.1f;
    425 	nvgBeginPath(args.vg);
    426 	nvgCircle(args.vg, c, c, r);
    427 	nvgFillColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0xff));
    428 	nvgFill(args.vg);
    429 
    430 	r -= std::max(0.3f, 0.5f * s);
    431 	nvgBeginPath(args.vg);
    432 	nvgCircle(args.vg, c, c, r);
    433 	nvgFillColor(args.vg, nvgRGBA(0x33, 0x33, 0x33, 0xff));
    434 	nvgFill(args.vg);
    435 
    436 	r -= std::max(0.2f, 0.6f * s);
    437 	nvgBeginPath(args.vg);
    438 	nvgCircle(args.vg, c, c, r);
    439 	nvgFillColor(args.vg, nvgRGBA(0xee, 0xee, 0xee, 0xff));
    440 	nvgFill(args.vg);
    441 	nvgBeginPath(args.vg);
    442 	nvgCircle(args.vg, c, c, r);
    443 	nvgFillColor(args.vg, _color);
    444 	nvgFill(args.vg);
    445 	nvgCircle(args.vg, c, c, r);
    446 	nvgFillColor(args.vg, nvgRGBA(0x66, 0x66, 0x66, 0x7f));
    447 	nvgFill(args.vg);
    448 
    449 	r -= std::max(0.2f, 0.6f * s);
    450 	nvgBeginPath(args.vg);
    451 	nvgCircle(args.vg, c, c, r);
    452 	nvgFillColor(args.vg, nvgRGBA(0xee, 0xee, 0xee, 0xff));
    453 	nvgFill(args.vg);
    454 	nvgBeginPath(args.vg);
    455 	nvgCircle(args.vg, c, c, r);
    456 	nvgFillColor(args.vg, _color);
    457 	nvgFill(args.vg);
    458 	nvgCircle(args.vg, c, c, r);
    459 	nvgFillColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0x7f));
    460 	nvgFill(args.vg);
    461 
    462 	r -= std::max(0.2f, 0.6f * s);
    463 	nvgBeginPath(args.vg);
    464 	nvgCircle(args.vg, c, c, r);
    465 	nvgFillColor(args.vg, nvgRGBA(0xee, 0xee, 0xee, 0xff));
    466 	nvgFill(args.vg);
    467 	nvgBeginPath(args.vg);
    468 	nvgCircle(args.vg, c, c, r);
    469 	nvgFillColor(args.vg, _color);
    470 	nvgFill(args.vg);
    471 	nvgCircle(args.vg, c, c, r);
    472 	nvgFillColor(args.vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0x7f));
    473 	nvgFill(args.vg);
    474 
    475 	nvgRestore(args.vg);
    476 }
    477 
    478 InvertingIndicatorButton::InvertingIndicatorButton(int dim) {
    479 	box.size = math::Vec(dim, dim);
    480 	// box.pos = math::Vec(0, 0);
    481 	fb = new widget::FramebufferWidget;
    482 	addChild(fb);
    483 	fb->box.size = box.size;
    484 
    485 	shadow = new CircularShadow;
    486 	shadow->box.size = box.size;
    487 	shadow->blurRadius = 2.0;
    488 	shadow->box.pos = Vec(0.0, 1.0);
    489 	fb->addChild(shadow);
    490 
    491 	w = new IIBWidget(dim);
    492 	w->box.size = box.size;
    493 	// w->box.pos = math::Vec(0, 0);
    494 	fb->addChild(w);
    495 }
    496 
    497 void InvertingIndicatorButton::onHover(const event::Hover& e) {
    498 	math::Vec c = box.size.div(2);
    499 	float dist = e.pos.minus(c).norm();
    500 	if (dist <= c.x) {
    501 		ParamWidget::onHover(e);
    502 	}
    503 }
    504 
    505 void InvertingIndicatorButton::onButton(const event::Button& e) {
    506 	ParamWidget::onButton(e);
    507 	if (!getParamQuantity() || !(e.action == GLFW_PRESS && (e.mods & RACK_MOD_MASK) == 0) || e.button == GLFW_MOUSE_BUTTON_RIGHT) {
    508 		return;
    509 	}
    510 
    511 	float value = getParamQuantity()->getValue();
    512 	if (value <= -1.0f) {
    513 		getParamQuantity()->setValue(0.0f);
    514 	}
    515 	else if (value < 1.0f) {
    516 		getParamQuantity()->setValue(1.0f);
    517 	}
    518 	else if (getParamQuantity()->minValue < 0.0f && (!clickToInvertCB || clickToInvertCB())) {
    519 		getParamQuantity()->setValue(-1.0f);
    520 	}
    521 	else {
    522 		getParamQuantity()->setValue(0.0f);
    523 	}
    524 }
    525 
    526 void InvertingIndicatorButton::onChange(const event::Change& e) {
    527 	fb->dirty = true;
    528 	if (getParamQuantity()) {
    529 		float v = getParamQuantity()->getValue();
    530 		w->setValue(v);
    531 		if (onChangeCB) {
    532 			onChangeCB(getParamQuantity()->paramId, v);
    533 		}
    534 	}
    535 	ParamWidget::onChange(e);
    536 }
    537 
    538 bool InvertingIndicatorButton::isLit() {
    539 	return module && !module->isBypassed() && getParamQuantity() &&
    540 		(getParamQuantity()->getValue() < -0.01f || getParamQuantity()->getValue() > 0.01f);
    541 }
    542 
    543 void InvertingIndicatorButton::draw(const DrawArgs& args) {
    544 	if (!isLit()) {
    545 		ParamWidget::draw(args);
    546 	}
    547 }
    548 
    549 void InvertingIndicatorButton::drawLit(const DrawArgs& args) {
    550 	ParamWidget::draw(args);
    551 }
    552 
    553 
    554 NVGcolor bogaudio::decibelsToColor(float db) {
    555 	if (db < -80.0f) {
    556 		return nvgRGBA(0x00, 0x00, 0x00, 0x00);
    557 	}
    558 	if (db < -24.0f) {
    559 		return nvgRGBA(0x55, 0xff, 0x00, (1.0f - (db + 24.0f) / -56.0f) * (float)0xff);
    560 	}
    561 	if (db < 0.0f) {
    562 		return nvgRGBA((1.0f - db / -24.0f) * 0xff, 0xff, 0x00, 0xff);
    563 	}
    564 	return nvgRGBA(0xff, (1.0f - std::min(db, 9.0f) / 9.0f) * 0xff, 0x00, 0xff);
    565 }
    566 
    567 
    568 bool VUSlider::isLit() {
    569 	float db = _vuLevel ? *_vuLevel : 0.0f;
    570 	bool stereo = false;
    571 	float stereoDb = 0.0f;
    572 	if (_stereoVuLevel) {
    573 		stereo = true;
    574 		stereoDb = *_stereoVuLevel;
    575 	}
    576 	return module && !module->isBypassed() && (db > 0.0f || (stereo && stereoDb > 0.0f));
    577 }
    578 
    579 void VUSlider::draw(const DrawArgs& args) {
    580 
    581 	nvgSave(args.vg);
    582 	{
    583 		nvgBeginPath(args.vg);
    584 		nvgRoundedRect(args.vg, 6, 3, 6, box.size.y - 6, 2);
    585 		nvgFillColor(args.vg, nvgRGBA(0x22, 0x22, 0x22, 0xff));
    586 		nvgFill(args.vg);
    587 		nvgStrokeColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0xff));
    588 		nvgStroke(args.vg);
    589 	}
    590 	nvgRestore(args.vg);
    591 
    592 	nvgSave(args.vg);
    593 	{
    594 		drawTranslate(args);
    595 		nvgBeginPath(args.vg);
    596 		nvgRoundedRect(args.vg, 0, 0, 18, 13, 1.5);
    597 		nvgFillColor(args.vg, nvgRGBA(0x77, 0x77, 0x77, 0xff));
    598 		nvgFill(args.vg);
    599 
    600 		nvgBeginPath(args.vg);
    601 		nvgRect(args.vg, 0, 2, 18, 9);
    602 		nvgFillColor(args.vg, nvgRGBA(0x44, 0x44, 0x44, 0xff));
    603 		nvgFill(args.vg);
    604 
    605 		nvgBeginPath(args.vg);
    606 		nvgRect(args.vg, 0, 6, 18, 1);
    607 		nvgFillColor(args.vg, nvgRGBA(0xfa, 0xfa, 0xfa, 0xff));
    608 		nvgFill(args.vg);
    609 
    610 		nvgBeginPath(args.vg);
    611 		nvgRoundedRect(args.vg, 2, 4, 14, 5, 1.0);
    612 		nvgFillColor(args.vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0xff));
    613 		nvgFill(args.vg);
    614 	}
    615 	nvgRestore(args.vg);
    616 }
    617 
    618 void VUSlider::drawLit(const DrawArgs& args) {
    619 	float db = _vuLevel ? *_vuLevel : 0.0f;
    620 	bool stereo = false;
    621 	float stereoDb = 0.0f;
    622 	if (_stereoVuLevel) {
    623 		stereo = true;
    624 		stereoDb = *_stereoVuLevel;
    625 	}
    626 
    627 	nvgSave(args.vg);
    628 	drawTranslate(args);
    629 	if (db > 0.0f) {
    630 		nvgSave(args.vg);
    631 		nvgBeginPath(args.vg);
    632 		if (stereo) {
    633 			nvgRoundedRect(args.vg, 2, 4, stereo ? 7 : 14, 5, 1.0);
    634 		}
    635 		else {
    636 			nvgRoundedRect(args.vg, 2, 4, 14, 5, 1.0);
    637 		}
    638 		nvgFillColor(args.vg, decibelsToColor(amplitudeToDecibels(db)));
    639 		nvgFill(args.vg);
    640 		nvgRestore(args.vg);
    641 	}
    642 	if (stereo && stereoDb > 0.0f) {
    643 		nvgSave(args.vg);
    644 		nvgBeginPath(args.vg);
    645 		nvgRoundedRect(args.vg, 9, 4, 7, 5, 1.0);
    646 		nvgFillColor(args.vg, decibelsToColor(amplitudeToDecibels(stereoDb)));
    647 		nvgFill(args.vg);
    648 		nvgRestore(args.vg);
    649 	}
    650 	nvgRestore(args.vg);
    651 }
    652 
    653 void VUSlider::drawTranslate(const DrawArgs& args) {
    654 	float level = 0.0f;
    655 	if (getParamQuantity()) {
    656 		level = getParamQuantity()->getValue();
    657 	}
    658 	else {
    659 		float minDb = -60.0f;
    660 		float maxDb = 6.0f;
    661 		level = fabsf(minDb) / (maxDb - minDb);
    662 	}
    663 	nvgTranslate(args.vg, 0, (box.size.y - 13.0f) * (1.0f - level));
    664 }