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 }