1 /**
2 * Copyright: Copyright Auburn Sounds 2015-2017
3 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 * Authors:   Guillaume Piolat
5 */
6 module main;
7 
8 import std.math;
9 
10 import dplug.core,
11        dplug.client,
12        dplug.dsp;
13 
14 import gui;
15 
16 mixin(DLLEntryPoint!());
17 
18 version(VST)
19 {
20     import dplug.vst;
21     mixin(VSTEntryPoint!DistortClient);
22 }
23 
24 version(AU)
25 {
26     import dplug.au;
27     mixin(AUEntryPoint!DistortClient);
28 }
29 
30 enum : int
31 {
32     paramInput,
33     paramDrive,
34     paramOutput,
35     paramOnOff,
36 }
37 
38 
39 /// Example mono/stereo distortion plugin.
40 final class DistortClient : dplug.client.Client
41 {
42 public:
43 nothrow:
44 @nogc:
45 
46     this()
47     {
48     }
49 
50     override PluginInfo buildPluginInfo()
51     {
52         // Plugin info is parsed from plugin.json here at compile time.
53         // Indeed it is strongly recommended that you do not fill PluginInfo 
54         // manually, else the information could diverge.
55         static immutable PluginInfo pluginInfo = parsePluginInfo(import("plugin.json"));
56         return pluginInfo;
57     }
58 
59     // This is an optional overload, default is zero parameter.
60     // Caution when adding parameters: always add the indices
61     // in the same order than the parameter enum.
62     override Parameter[] buildParameters()
63     {
64         auto params = makeVec!Parameter();
65         params.pushBack( mallocNew!GainParameter(paramInput, "input", 6.0, 0.0) );
66         params.pushBack( mallocNew!LinearFloatParameter(paramDrive, "drive", "%", 1.0f, 2.0f, 1.0f) );
67         params.pushBack( mallocNew!GainParameter(paramOutput, "output", 6.0, 0.0) );
68         params.pushBack( mallocNew!BoolParameter(paramOnOff, "on/off", true) );
69         return params.releaseData();
70     }
71 
72     override LegalIO[] buildLegalIO()
73     {
74         auto io = makeVec!LegalIO();
75         io.pushBack(LegalIO(1, 1));
76         io.pushBack(LegalIO(1, 2));
77         io.pushBack(LegalIO(2, 1));
78         io.pushBack(LegalIO(2, 2));
79         return io.releaseData();
80     }
81 
82     // This override is optional, this supports plugin delay compensation in hosts.
83     // By default, 0 samples of latency.
84     override int latencySamples(double sampleRate) pure const 
85     {
86         return 0;
87     }
88 
89     // This override is optional, the default implementation will
90     // have one default preset.
91     override Preset[] buildPresets() nothrow @nogc
92     {
93         auto presets = makeVec!Preset();
94         presets.pushBack( makeDefaultPreset() );
95 
96         static immutable float[] silenceParams = [0.0f, 0.0f, 0.0f, 1.0f, 0];
97         presets.pushBack( mallocNew!Preset("Silence", silenceParams) );
98 
99         static immutable float[] fullOnParams = [1.0f, 1.0f, 0.4f, 1.0f, 0];
100         presets.pushBack( mallocNew!Preset("Full-on", fullOnParams) );
101         return presets.releaseData();
102     }
103 
104     // This override is also optional. It allows to split audio buffers in order to never
105     // exceed some amount of frames at once.
106     // This can be useful as a cheap chunking for parameter smoothing.
107     // Buffer splitting also allows to allocate statically or on the stack with less worries.
108     override int maxFramesInProcess() const //nothrow @nogc
109     {
110         return 512;
111     }
112 
113     override void reset(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc
114     {
115         // Clear here any state and delay buffers you might have.
116 
117         assert(maxFrames <= 512); // guaranteed by audio buffer splitting
118 
119         foreach(channel; 0..2)
120         {
121             _inputRMS[channel].initialize(sampleRate);
122             _outputRMS[channel].initialize(sampleRate);
123         }
124     }
125 
126     override void processAudio(const(float*)[] inputs, float*[]outputs, int frames,
127                                TimeInfo info) nothrow @nogc
128     {
129         assert(frames <= 512); // guaranteed by audio buffer splitting
130 
131         int numInputs = cast(int)inputs.length;
132         int numOutputs = cast(int)outputs.length;
133 
134         int minChan = numInputs > numOutputs ? numOutputs : numInputs;
135 
136         float inputGain = deciBelToFloat(readFloatParamValue(paramInput));
137         float drive = readFloatParamValue(paramDrive);
138         float outputGain = deciBelToFloat(readFloatParamValue(paramOutput));
139 
140         bool enabled = readBoolParamValue(paramOnOff);
141 
142         float[2] RMS = 0;
143 
144         if (enabled)
145         {
146             for (int chan = 0; chan < minChan; ++chan)
147             {
148                 for (int f = 0; f < frames; ++f)
149                 {
150                     float inputSample = inputGain * 2.0 * inputs[chan][f];
151 
152                     // Feed the input RMS computation
153                     _inputRMS[chan].nextSample(inputSample);
154 
155                     // Distort signal
156                     float distorted = tanh(inputSample * drive) / drive;
157                     float outputSample = outputGain * distorted;
158                     outputs[chan][f] = outputSample;
159 
160                     // Feed the output RMS computation
161                     _outputRMS[chan].nextSample(outputSample);
162                 }
163             }
164         }
165         else
166         {
167             // Bypass mode
168             for (int chan = 0; chan < minChan; ++chan)
169                 outputs[chan][0..frames] = inputs[chan][0..frames];
170         }
171 
172         // fill with zero the remaining channels
173         for (int chan = minChan; chan < numOutputs; ++chan)
174             outputs[chan][0..frames] = 0; // D has array slices assignments and operations
175 
176         // Update RMS meters from the audio callback
177         // The IGraphics object must be acquired and released, so that it does not
178         // disappear under your feet
179         if (DistortGUI gui = cast(DistortGUI) graphicsAcquire())
180         {
181             float[2] inputLevels;
182             inputLevels[0] = floatToDeciBel(_inputRMS[0].RMS());
183             inputLevels[1] = minChan >= 1 ? floatToDeciBel(_inputRMS[1].RMS()) : inputLevels[0];
184             gui.inputBargraph.setValues(inputLevels);
185 
186             float[2] outputLevels;
187             outputLevels[0] = floatToDeciBel(_outputRMS[0].RMS());
188             outputLevels[1] = minChan >= 1 ? floatToDeciBel(_outputRMS[1].RMS()) : outputLevels[0];
189             gui.outputBargraph.setValues(outputLevels);
190 
191             graphicsRelease();
192         }
193     }
194 
195     override IGraphics createGraphics()
196     {
197         return mallocNew!DistortGUI(this);
198     }
199 
200 private:
201     CoarseRMS[2] _inputRMS;
202     CoarseRMS[2] _outputRMS;
203 }
204 
205 
206 
207 /// Simple envelope follower, filters the envelope with 24db/oct lowpass.
208 struct EnvelopeFollower
209 {
210 public:
211 
212     // typical frequency would be is 10-30hz
213     void initialize(float cutoffInHz, float samplerate) nothrow @nogc
214     {
215         _coeff = biquadRBJLowPass(cutoffInHz, samplerate);
216         _delay0.initialize();
217         _delay1.initialize();
218     }
219 
220     // takes on sample, return mean amplitude
221     float nextSample(float x) nothrow @nogc
222     {
223         float l = abs(x);
224         l = _delay0.nextSample(l, _coeff);
225         l = _delay1.nextSample(l, _coeff);
226         return l;
227     }
228 
229     void nextBuffer(const(float)* input, float* output, int frames) nothrow @nogc
230     {
231         for(int i = 0; i < frames; ++i)
232             output[i] = abs(input[i]);
233 
234         _delay0.nextBuffer(output, output, frames, _coeff);
235         _delay1.nextBuffer(output, output, frames, _coeff);
236     }
237 
238 private:
239     BiquadCoeff _coeff;
240     BiquadDelay _delay0;
241     BiquadDelay _delay1;
242 }
243 
244 /// Sliding RMS computation
245 /// To use for coarse grained levels for visual display.
246 struct CoarseRMS
247 {
248 public:
249     void initialize(double sampleRate) nothrow @nogc
250     {
251         // In Reaper, default RMS window is 500 ms
252         _envelope.initialize(20, sampleRate);
253 
254         _last = 0;
255     }
256 
257     /// Process a chunk of samples and return a value in dB (could be -infinity)
258     void nextSample(float input) nothrow @nogc
259     {
260         _last = _envelope.nextSample(input * input);
261     }
262 
263     void nextBuffer(float* input, int frames) nothrow @nogc
264     {
265         if (frames == 0)
266             return;
267 
268         for (int i = 0; i < frames - 1; ++i)
269             _envelope.nextSample(input[i] * input[i]);
270 
271         _last = _envelope.nextSample(input[frames - 1] * input[frames - 1]);
272     }
273 
274     float RMS() nothrow @nogc
275     {
276         return sqrt(_last);
277     }
278 
279 private:
280     EnvelopeFollower _envelope;
281     float _last;
282 }
283