1 /** 2 * Copyright 2017 Cut Through Recordings 3 * License: MIT License 4 * Author(s): Ethan Reker 5 */ 6 module ddsp.util.envelope; 7 8 import std.math; 9 import std.algorithm; 10 import ddsp.util.buffer; 11 import dplug.core.nogc; 12 import dplug.core.ringbuf; 13 14 /// Envelop Detector with adjustable attack and release times. Great for compressors 15 /// and meters. 16 /+ 17 http://www.musicdsp.org/showArchiveComment.php?ArchiveID=97 18 +/ 19 class EnvelopeDetector(T) 20 { 21 public: 22 nothrow: 23 @nogc: 24 25 this() 26 { 27 _envelope = 0f; 28 setTimeConstant(0.01); 29 } 30 31 void setSampleRate(const float sampleRate) 32 { 33 _sampleRate = sampleRate; 34 } 35 36 37 void setTimeConstant(const float timeConstant) { 38 _timeConstant = log10(timeConstant); 39 } 40 41 /** 42 * Set the attack and release times of the envelope detector in milliseconds 43 */ 44 void setEnvelope(const float attackTime, const float releaseTime) 45 { 46 _ga = exp( _timeConstant /(_sampleRate * attackTime * 0.001)); 47 _gr = exp( _timeConstant / (_sampleRate * releaseTime * 0.001)); 48 } 49 50 T detect(T input) 51 { 52 T envIn = processInput(input); 53 54 if(_envelope < envIn) 55 _envelope = _envelope * _ga + (1 - _ga) * envIn; 56 else 57 _envelope = _envelope * _gr + (1 - _gr) * envIn; 58 59 if(isNaN(_envelope)) { 60 _envelope = 0.0f; 61 } 62 return _envelope; 63 } 64 65 T getEnvelope() 66 { 67 return _envelope; 68 } 69 70 void reset() 71 { 72 _envelope = 0; 73 } 74 75 abstract T processInput(T input); 76 77 private: 78 /// Attack coefficient 79 float _ga; 80 81 /// Release coefficient 82 float _gr; 83 84 /// stores the current value of the envelope; 85 float _envelope; 86 87 /// Sample Rate 88 float _sampleRate; 89 90 /// time constant used to calculate attack and release times 91 float _timeConstant; 92 } 93 94 /// Simple Peak envelope follower, useful for meters. 95 /+ 96 http://www.musicdsp.org/archive.php?classid=2#19 97 +/ 98 class PeakDetector(T): EnvelopeDetector!T 99 { 100 public: 101 nothrow: 102 @nogc: 103 104 this() 105 { 106 super(); 107 } 108 109 override T processInput(T input) 110 { 111 return abs(input); 112 } 113 114 private: 115 float _decay; 116 117 float _sampleRate; 118 } 119 120 unittest 121 { 122 PeakDetector!float peakDetector = new PeakDetector!float(); 123 } 124 125 class RMSDetector(T): EnvelopeDetector!T 126 { 127 public: 128 nothrow: 129 @nogc: 130 131 this(int windowSize) 132 { 133 super(); 134 _windowSize = windowSize; 135 _buffer = mallocNew!(Buffer!T)(_windowSize); 136 _counter = 0; 137 runningMean = 0; 138 } 139 140 override T processInput(T input) 141 { 142 immutable T poppedVal = _buffer.read(); 143 144 if(_counter < _windowSize) 145 { 146 ++_counter; 147 } 148 else 149 { 150 runningMean -= (poppedVal * poppedVal) / _windowSize; 151 } 152 runningMean += (input * input) / _windowSize; 153 _buffer.write(input); 154 return sqrt(runningMean); 155 } 156 157 private: 158 int _windowSize; 159 int _counter; 160 Buffer!T _buffer; 161 T runningMean; 162 } 163 164 unittest 165 { 166 import std.stdio; 167 float[] values = [0, 0, 1, 1, 0, 1, 0, 1, 1, 1]; 168 169 auto rmsDetector = new RMSDetector!float(10); 170 rmsDetector.setSampleRate(44100); 171 rmsDetector.setEnvelope(0, 0); 172 for(int i = 0; i < 10; ++i) 173 { 174 foreach(val; values) 175 { 176 auto result = rmsDetector.detect(val); 177 // writeln(result); 178 } 179 } 180 181 auto peakDetector = new PeakDetector!float(); 182 peakDetector.setSampleRate(44100); 183 peakDetector.setEnvelope(0.1, 1); 184 for(int i = 0; i < 10; ++i) 185 { 186 foreach(val; values) 187 { 188 auto result = peakDetector.detect(val); 189 // writeln(result); 190 } 191 } 192 } 193 194 /** 195 * This class is used for calculating a moving average. It's useful for things such 196 * as smoothing meter values. 197 */ 198 class MovingAverage(T) 199 { 200 public: 201 nothrow: 202 @nogc: 203 204 this(int windowSize) 205 { 206 _windowSize = windowSize; 207 _buffer = makeRingBufferNoGC!T(_windowSize); 208 _avg = 0; 209 } 210 211 T process(double sample) 212 { 213 T prevSample = _buffer.isFull() ? _buffer.popFront() : 0; 214 _avg -= prevSample / _windowSize; 215 _avg += sample / _windowSize; 216 _buffer.pushBack(sample); 217 return _avg; 218 } 219 220 T getAverage() 221 { 222 return _avg; 223 } 224 225 private: 226 RingBufferNoGC!T _buffer; 227 int _windowSize; 228 T _avg; 229 230 } 231 232 unittest 233 { 234 auto movingAverage = mallocNew!(MovingAverage!float)(100); 235 }