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