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 }