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(T) : DynamicsProcessor!T 19 { 20 public: 21 nothrow: 22 @nogc: 23 24 override T getNextSample(const T 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 protected: 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 unittest 75 { 76 Compressor!float compressor = new Compressor!float(); 77 } 78 79 /// Basic look-ahead limiter that is based on the compressor from before. 80 class Limiter(T) : Compressor!T 81 { 82 private import ddsp.util.buffer; 83 private import dplug.core.nogc; 84 nothrow: 85 @nogc: 86 public: 87 88 /// maxLookAhead is 300 by default. If you intend to use a longer look-ahead 89 /// time then it is best to specify it here so that no reallocation is needed 90 /// later. 91 this(int maxLookAhead = 300) 92 { 93 _lookAheadAmount = msToSamples(maxLookAhead, _sampleRate); 94 _buffer = mallocNew!(Buffer!float)(cast(size_t)_lookAheadAmount); 95 _ratio = float.infinity; 96 _limit = true; 97 } 98 99 override T getNextSample(const T input) 100 { 101 _buffer.write(input); 102 float lookAheadOutput = _buffer.read(); 103 detector.detect(input); 104 float detectorValue = floatToDecibel(detector.getEnvelope()); 105 return lookAheadOutput * calcCompressorGain(detectorValue, _threshold, _ratio, _kneeWidth); 106 } 107 108 /// 109 void setLookAhead(int msLookAhead) 110 { 111 _lookAheadAmount = msToSamples(msLookAhead, _sampleRate); 112 _buffer.setSize(cast(size_t)_lookAheadAmount); 113 } 114 private: 115 /// Circular buffer that holds delay elements for look-ahead feature 116 Buffer!float _buffer; 117 118 /// Current amount of lookahead being used in samples. 119 float _lookAheadAmount; 120 } 121 122 unittest 123 { 124 Limiter!float limiter = new Limiter!float(); 125 }