1 /** 2 * PBR widget: bargraph. 3 * 4 * Copyright: Copyright Auburn Sounds 2015 and later. 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 * Authors: Guillaume Piolat 7 */ 8 module dplug.pbrwidgets.bargraph; 9 10 import core.atomic; 11 import std.math; 12 13 import dplug.gui.element; 14 import dplug.core.sync; 15 import dplug.core.math; 16 import std.algorithm.comparison: clamp; 17 18 // Vertical bargraphs made of LEDs 19 class UIBargraph : UIElement 20 { 21 public: 22 nothrow: 23 @nogc: 24 25 struct LED 26 { 27 RGBA diffuse; 28 } 29 30 /// Creates a new bargraph. 31 /// [minValue .. maxValue] is the interval of values that will span [0..1] once remapped. 32 this(UIContext context, int numChannels, float minValue, float maxValue, 33 int redLeds = 0, int orangeLeds = 3, int yellowLeds = 0, int magentaLeds = 9) 34 { 35 super(context); 36 37 _values = mallocSliceNoInit!float(numChannels); 38 _values[] = 0; 39 40 _minValue = minValue; 41 _maxValue = maxValue; 42 43 _leds = makeVec!LED(); 44 45 foreach (i; 0..redLeds) 46 _leds.pushBack( LED(RGBA(255, 32, 0, 255)) ); 47 48 foreach (i; 0..orangeLeds) 49 _leds.pushBack( LED(RGBA(255, 128, 64, 255)) ); 50 51 foreach (i; 0..yellowLeds) 52 _leds.pushBack( LED(RGBA(255, 255, 64, 255)) ); 53 54 foreach (i; 0..magentaLeds) 55 _leds.pushBack( LED(RGBA(226, 120, 249, 255)) ); 56 57 _valueMutex = makeMutex(); 58 } 59 60 ~this() 61 { 62 _values.freeSlice(); 63 } 64 65 66 override void onDraw(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc 67 { 68 int numLeds = cast(int)_leds.length; 69 int numChannels = cast(int)_values.length; 70 int width = _position.width; 71 int height = _position.height; 72 float border = width * 0.06f; 73 74 box2f available = box2f(border, border, width - border, height - border); 75 76 float heightPerLed = cast(float)(available.height) / cast(float)numLeds; 77 float widthPerLed = cast(float)(available.width) / cast(float)numChannels; 78 79 float tolerance = 1.0f / numLeds; 80 81 foreach(channel; 0..numChannels) 82 { 83 float value = getValue(channel); 84 float x0 = border + widthPerLed * (channel + 0.15f); 85 float x1 = x0 + widthPerLed * 0.7f; 86 87 foreach(i; 0..numLeds) 88 { 89 float y0 = border + heightPerLed * (i + 0.1f); 90 float y1 = y0 + heightPerLed * 0.8f; 91 92 depthMap.aaFillRectFloat!false(x0, y0, x1, y1, L16(16000)); 93 94 float ratio = 1 - i / cast(float)(numLeds - 1); 95 96 ubyte shininess = cast(ubyte)(0.5f + 160.0f * (1 - smoothStep(value - tolerance, value, ratio))); 97 98 RGBA color = _leds[i].diffuse; 99 color.r = (color.r * (255 + shininess) + 255) / 510; 100 color.g = (color.g * (255 + shininess) + 255) / 510; 101 color.b = (color.b * (255 + shininess) + 255) / 510; 102 color.a = shininess; 103 diffuseMap.aaFillRectFloat!false(x0, y0, x1, y1, color); 104 105 materialMap.aaFillRectFloat!false(x0, y0, x1, y1, RGBA(0, 128, 255, 255)); 106 107 } 108 } 109 } 110 111 override void onAnimate(double dt, double time) nothrow @nogc 112 { 113 bool wasChanged = cas(&_valuesUpdated, true, false); 114 if (wasChanged) 115 { 116 setDirtyWhole(); 117 } 118 } 119 120 // To be called by audio thread. So this function cannot call setDirtyWhole directly. 121 void setValues(const(float)[] values) nothrow @nogc 122 { 123 { 124 _valueMutex.lock(); 125 assert(values.length == _values.length); 126 127 // remap all values 128 foreach(i; 0..values.length) 129 { 130 _values[i] = linmap!float(values[i], _minValue, _maxValue, 0, 1); 131 _values[i] = clamp!float(_values[i], 0, 1); 132 } 133 _valueMutex.unlock(); 134 } 135 atomicStore(_valuesUpdated, true); 136 } 137 138 float getValue(int channel) nothrow @nogc 139 { 140 float res = void; 141 _valueMutex.lock(); 142 res = _values[channel]; 143 _valueMutex.unlock(); 144 return res; 145 } 146 147 protected: 148 149 Vec!LED _leds; 150 151 UncheckedMutex _valueMutex; 152 float[] _values; 153 float _minValue; 154 float _maxValue; 155 156 shared(bool) _valuesUpdated = true; 157 }