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