1 /** 2 * Copyright 2017 Cut Through Recordings 3 * License: MIT License 4 * Author(s): Ethan Reker 5 */ 6 module ddsp.effect.compressor; 7 8 import ddsp.util.functions; 9 import ddsp.util.envelope; 10 import ddsp.effect.dynamics; 11 12 import dplug.core.nogc; 13 14 import std.algorithm; 15 import std.math; 16 17 /// Basic compressor 18 class Compressor : DynamicsProcessor 19 { 20 public: 21 nothrow: 22 @nogc: 23 24 override float getNextSample(const float input) 25 { 26 detector.detect(input * 4); 27 28 float detectorValue; 29 if(linkedDetector !is null) 30 { 31 float thisDetector = detector.getEnvelope(); 32 float otherDetector = linkedDetector.getEnvelope(); 33 detectorValue = floatToDecibel((thisDetector + otherDetector) * 0.5); 34 } 35 else 36 detectorValue = floatToDecibel(detector.getEnvelope()); 37 38 return input * calcCompressorGain(detectorValue, _threshold, _ratio, _kneeWidth); 39 } 40 41 private: 42 43 /// If set to true, ratio will become infinite and result in limiting 44 bool _limit; 45 46 /// This is the function that does most of the work with calculating compression 47 float calcCompressorGain(float detectorValue, float threshold, float ratio, float kneeWidth) 48 { 49 float CS = 1.0f - 1.0f / ratio; 50 51 if(_limit) 52 CS = 1.0f; 53 54 if(kneeWidth > 0 && detectorValue > (threshold - kneeWidth / 2.0f) && 55 detectorValue < threshold + kneeWidth / 2.0f) 56 { 57 x[0] = threshold - kneeWidth / 2.0f; 58 x[1] = threshold + kneeWidth / 2.0f; 59 x[1] = clamp(x[1], -96.0f, 0.0f); 60 y[0] = 0; 61 y[1] = CS; 62 63 CS = lagrpol(x, y, 2, detectorValue); 64 } 65 66 float yG = CS * (threshold - detectorValue); 67 68 yG = clamp(yG, -96.0, 0); 69 70 return pow(10.0f, yG / 20.0f); 71 } 72 } 73 74 /// Basic look-ahead limiter that is based on the compressor from before. 75 class Limiter : Compressor 76 { 77 private import ddsp.util.buffer; 78 private import dplug.core.nogc; 79 nothrow: 80 @nogc: 81 public: 82 83 /// maxLookAhead is 300 by default. If you intend to use a longer look-ahead 84 /// time then it is best to specify it here so that no reallocation is needed 85 /// later. 86 this(int maxLookAhead = 300) 87 { 88 _lookAheadAmount = msToSamples(maxLookAhead, _sampleRate); 89 _buffer = mallocNew!(Buffer!float)(cast(size_t)_lookAheadAmount); 90 _ratio = float.infinity; 91 _limit = true; 92 } 93 94 override float getNextSample(const float input) 95 { 96 _buffer.write(input); 97 float lookAheadOutput = _buffer.read(); 98 detector.detect(input); 99 float detectorValue = floatToDecibel(detector.getEnvelope()); 100 return lookAheadOutput * calcCompressorGain(detectorValue, _threshold, _ratio, _kneeWidth); 101 } 102 103 /// 104 void setLookAhead(int msLookAhead) 105 { 106 _lookAheadAmount = msToSamples(msLookAhead, _sampleRate); 107 _buffer.setSize(cast(size_t)_lookAheadAmount); 108 } 109 private: 110 /// Circular buffer that holds delay elements for look-ahead feature 111 Buffer!float _buffer; 112 113 /// Current amount of lookahead being used in samples. 114 float _lookAheadAmount; 115 }