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 }