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 }