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 }