1 /**
2 Copyright: Guillaume Piolat 2015-2017.
3 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 */
5 module leveldisplay;
6 
7 import gui;
8 
9 import core.atomic;
10 import dplug.core;
11 import dplug.gui;
12 import dplug.canvas;
13 
14 /// This widgets demonstrates how to:
15 /// - do a custom widget
16 /// - use dplug:canvas
17 /// - use TimedFIFO for UI feedback with sub-buffer latency
18 /// - render to both the Raw and PBR layer
19 /// For more custom widget tips, see "Dplug Tutorials 3 - Anatomy of a custom widget".
20 final class UILevelDisplay : UIElement
21 {
22 public:
23 nothrow:
24 @nogc:
25 
26     enum READ_OVERSAMPLING = 180;
27     enum INPUT_SUBSAMPLING = 16;
28     enum SAMPLES_IN_FIFO = 1024;
29     enum int MIN_DISPLAYABLE_DB = -100;
30     enum int MAX_DISPLAYABLE_DB =  0;
31 
32     this(UIContext context)
33     {
34         super(context, flagRaw | flagPBR | flagAnimated); 
35         _timedFIFO.initialize(SAMPLES_IN_FIFO, INPUT_SUBSAMPLING);
36         _stateToDisplay[] = -140.0f;
37     }
38 
39     override void onAnimate(double dt, double time)
40     {
41         bool needRedraw = false;
42         // Note that readOldestDataAndDropSome return the number of samples 
43         // stored in _stateToDisplay[0..ret].
44         if (_timedFIFO.readOldestDataAndDropSome(_stateToDisplay[], dt, READ_OVERSAMPLING))
45         {
46             needRedraw = true;
47         }
48 
49         // Only redraw the Raw layer. This is key to have low-CPU UI widgets that can 
50         // still render on the PBR layer.
51         // Note: You can further improve CPU usage by not redrawing if the displayed data
52         // has been only zeroes for a while.
53         if (needRedraw)
54             setDirtyWhole(UILayer.rawOnly);
55     }
56 
57     float mapValueY(float normalized)
58     {
59         float W = position.width;
60         float H = position.height;
61         return _B + (H - 2 * _B) * (1 - normalized);
62     }
63 
64     float mapValueDbY(float dB)
65     {
66         // clip in order to never exceed visual range
67         if (dB > MAX_DISPLAYABLE_DB) dB = MAX_DISPLAYABLE_DB;
68         if (dB < MIN_DISPLAYABLE_DB) dB = MIN_DISPLAYABLE_DB;
69         float normalized = linmap!float(dB, MIN_DISPLAYABLE_DB, MAX_DISPLAYABLE_DB, 0, 1);
70         return mapValueY(normalized);
71     }
72 
73     override void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc
74     {
75         // Make a hole
76         foreach(dirtyRect; dirtyRects)
77         {
78             depthMap.cropImageRef(dirtyRect).fillAll(L16(15000));
79         }
80     }
81 
82     override void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects)
83     {
84         float W = position.width;
85         float H = position.height;
86 
87         // detector feedback, integrate to smooth things
88         double detectorLevel = 0.0f;
89 
90         float openness = 0.0f;
91         double squaredSum = 0;
92         float GR_linear_sum = 0.0f;
93         for (int sample = 0; sample < READ_OVERSAMPLING; ++sample)
94         {
95             double lvl = _stateToDisplay[sample];
96             detectorLevel += lvl;
97             squaredSum += lvl*lvl;
98         }
99         detectorLevel /= READ_OVERSAMPLING;
100         double variance = squaredSum / READ_OVERSAMPLING - detectorLevel * detectorLevel;
101         if (variance < 0) variance = 0;
102         float stddev = fast_sqrt(variance);
103 
104         float detectorY = mapValueDbY(detectorLevel);
105         float detectorYp1 = mapValueDbY(detectorLevel + stddev);
106 
107         foreach(dirtyRect; dirtyRects)
108         {
109             auto cRaw = rawMap.cropImageRef(dirtyRect);
110             canvas.initialize(cRaw);
111             canvas.translate(-dirtyRect.min.x, -dirtyRect.min.y);
112 
113             // Fill with dark color
114             canvas.fillStyle = "rgba(0, 0, 0, 10%)";
115             canvas.fillRect(0, 0, position.width, position.height);
116 
117             canvas.fillStyle = RGBA(236, 255, 128, 255);
118             canvas.fillRect(_B, detectorY, W-2*_B, H-_B - detectorY);
119 
120             canvas.fillStyle = RGBA(236, 255, 128, 128);
121             canvas.fillRect(_B, detectorYp1, W-2*_B, H-_B - detectorYp1);
122         }
123     }
124 
125     void sendFeedbackToUI(float* measuredLevel_dB, 
126                           int frames, 
127                           float sampleRate) nothrow @nogc
128     {
129         if (_storeTemp.length < frames)
130             _storeTemp.reallocBuffer(frames);
131 
132         for(int n = 0; n < frames; ++n)
133         {
134             _storeTemp[n] = measuredLevel_dB[n];
135         }
136         
137         _timedFIFO.pushData(_storeTemp[0..frames], sampleRate);
138     }
139 
140     override void reflow()
141     {
142         float H = position.height;
143         _S = H / 130.0f;
144         _B = _S * 10; // border
145     }
146 
147 private:
148     float _B, _S;
149     Canvas canvas;
150     TimedFIFO!float _timedFIFO;
151     float[READ_OVERSAMPLING] _stateToDisplay; // samples, integrated for drawing
152     float[] _storeTemp; // used for gathering input
153 }