1 /**
2 Copyright: Guillaume Piolat 2015-2017.
3 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 */
5 module main;
6 
7 import std.math;
8 
9 import dplug.core,
10        dplug.client,
11        dplug.dsp;
12 
13 import gui;
14 import ar;
15 
16 // This define entry points for plugin formats, 
17 // depending on which version identifiers are defined.
18 mixin(pluginEntryPoints!DistortClient);
19 
20 enum : int
21 {
22     paramInput,
23     paramDrive,
24     paramBias,
25     paramOutput,
26     paramOnOff,
27 }
28 
29 /// Example mono/stereo distortion plugin.
30 final class DistortClient : dplug.client.Client
31 {
32 public:
33 nothrow:
34 @nogc:
35 
36     this()
37     {
38     }
39 
40     ~this()
41     {
42         _inputRMS.reallocBuffer(0);
43         _outputRMS.reallocBuffer(0);
44     }
45 
46     override PluginInfo buildPluginInfo()
47     {
48         // Plugin info is parsed from plugin.json here at compile time.
49         // Indeed it is strongly recommended that you do not fill PluginInfo 
50         // manually, else the information could diverge.
51         static immutable PluginInfo pluginInfo = parsePluginInfo(import("plugin.json"));
52         return pluginInfo;
53     }
54 
55     // This is an optional overload, default is zero parameter.
56     // Caution when adding parameters: always add the indices
57     // in the same order than the parameter enum.
58     override Parameter[] buildParameters()
59     {
60         auto params = makeVec!Parameter();
61         params ~= mallocNew!GainParameter(paramInput, "input", 6.0, 0.0);
62         params ~= mallocNew!LinearFloatParameter(paramDrive, "drive", "%", 0.0f, 100.0f, 20.0f);
63         params ~= mallocNew!LinearFloatParameter(paramBias, "bias", "%", 0.0f, 100.0f, 50.0f);
64         params ~= mallocNew!GainParameter(paramOutput, "output", 6.0, 0.0);
65         params ~= mallocNew!BoolParameter(paramOnOff, "enabled", true);
66         return params.releaseData();
67     }
68 
69     override LegalIO[] buildLegalIO()
70     {
71         auto io = makeVec!LegalIO();
72         io ~= LegalIO(1, 1);
73         io ~= LegalIO(2, 2);
74         return io.releaseData();
75     }
76 
77     // This override is optional, this supports plugin delay compensation in hosts.
78     // By default, 0 samples of latency.
79     override int latencySamples(double sampleRate) pure const 
80     {
81         return 0;
82     }
83 
84     // This override is optional, the default implementation will
85     // have one default preset.
86     override Preset[] buildPresets()
87     {
88         auto presets = makeVec!Preset();
89         presets ~= makeDefaultPreset();
90 
91         static immutable float[] silenceParams = [0.0f, 0.0f, 0.0f, 1.0f, 0];
92         presets ~= mallocNew!Preset("Silence", silenceParams, defaultStateData());
93 
94         static immutable float[] fullOnParams = [1.0f, 1.0f, 0.4f, 1.0f, 0];
95         presets ~= mallocNew!Preset("Full-on", fullOnParams, defaultStateData());
96         return presets.releaseData();
97     }
98 
99     // This override is also optional. It allows to split audio buffers in order to never
100     // exceed some amount of frames at once.
101     // This can be useful as a cheap chunking for parameter smoothing.
102     // Buffer splitting also allows to allocate statically or on the stack with less worries.
103     // It also makes the plugin uses constant memory in case of large buffer sizes.
104     // In VST3, parameter automation gets more precise when this value is small.
105     override int maxFramesInProcess() const
106     {
107         return 512;
108     }
109 
110     override void reset(double sampleRate, int maxFrames, int numInputs, int numOutputs)
111     {
112         // Clear here any state and delay buffers you might have.
113         assert(maxFrames <= 512); // guaranteed by audio buffer splitting
114 
115         _sampleRate = sampleRate;
116 
117         _levelInput.initialize(sampleRate);
118         _levelOutput.initialize(sampleRate);
119 
120         _inputRMS.reallocBuffer(maxFrames);
121         _outputRMS.reallocBuffer(maxFrames);
122 
123         foreach(channel; 0..2)
124         {            
125             _hpState[channel].initialize();
126         }
127     }
128 
129     override void processAudio(const(float*)[] inputs, float*[]outputs, int frames, TimeInfo info)
130     {
131         assert(frames <= 512); // guaranteed by audio buffer splitting
132 
133         int numInputs = cast(int)inputs.length;
134         int numOutputs = cast(int)outputs.length;
135         assert(numInputs == numOutputs);
136 
137         const float inputGain = convertDecibelToLinearGain(readParam!float(paramInput));
138         float drive = readParam!float(paramDrive) * 0.01f;
139         float bias = readParam!float(paramBias) * 0.01f;
140         const float outputGain = convertDecibelToLinearGain(readParam!float(paramOutput));
141 
142         const bool enabled = readParam!bool(paramOnOff);
143 
144         if (enabled)
145         {
146             BiquadCoeff highpassCoeff = biquadRBJHighPass(150, _sampleRate, SQRT1_2);
147             for (int chan = 0; chan < numOutputs; ++chan)
148             {
149                 // Distort and put the result in output buffers
150                 for (int f = 0; f < frames; ++f)
151                 {
152                     const float inputSample = inputGain * 2.0 * inputs[chan][f];
153 
154                     // Distort signal
155                     const float distorted = tanh(inputSample * drive * 10.0f + bias) * 0.9f;
156                     outputs[chan][f] = outputGain * distorted;
157                 }
158 
159                 // Highpass to remove bias
160                 _hpState[chan].nextBuffer(outputs[chan], outputs[chan], frames, highpassCoeff);
161             }
162         }
163         else
164         {
165             // Bypass mode
166             for (int chan = 0; chan < numOutputs; ++chan)
167                 outputs[chan][0..frames] = inputs[chan][0..frames];
168         }
169 
170         // Compute feedback for the UI.
171         // We take the first channel (left) and process it, then send it to the widgets.
172         _levelInput.nextBuffer(inputs[0], _inputRMS.ptr, frames);
173         _levelOutput.nextBuffer(outputs[0], _outputRMS.ptr, frames);
174 
175         // Update meters from the audio callback.
176         if (DistortGUI gui = cast(DistortGUI) graphicsAcquire())
177         {
178             gui.sendFeedbackToUI(_inputRMS.ptr, _outputRMS.ptr, frames, _sampleRate);
179             graphicsRelease();
180         }
181     }
182 
183     override IGraphics createGraphics()
184     {
185         return mallocNew!DistortGUI(this);
186     }
187 
188     version(futureBinState)
189     {
190         /// Important: See documentation in `Client.saveState`.
191         ///            Right now saving extra state is fraught with peril!
192         override void saveState(ref Vec!ubyte chunk)
193         {
194             // dplug.core.binrange allows to write arbitrary chunk bytes here.
195             // You are responsible for versioning, correct UI interaction, etc.
196             //
197             // `loadState` will be called with your own state chunks without regards for
198             // your plugin major version.
199             //
200             // See `saveState` definition in client.d for highly-recommended information.
201             writeLE!uint(chunk, getPublicVersion().major);
202         }
203 
204         /// Important: See documentation in `Client.loadState`.
205         override bool loadState(const(ubyte)[] chunk)
206         {
207             // Parsing is done with error codes.
208             const(ubyte)[] c = chunk;
209             bool err;
210             int major = popLE!uint(c, &err);
211             if (err)
212                 return false;
213 
214             // You're supposed to refuse a chunk that you are not compatible with, with your own
215             // versioning. For example, maybe you break your state chunk compat on plugin majors
216             // versions.
217             if (major != getPublicVersion().major)
218                 return false;
219 
220             return true; // no issue parsing the chunk, and acting on it
221         }
222     }
223 
224 private:
225     LevelComputation _levelInput;
226     LevelComputation _levelOutput;
227     BiquadDelay[2] _hpState;
228     float _sampleRate;
229     float[] _inputRMS, _outputRMS;
230 }
231 
232 
233 /// Sliding dB computation.
234 /// Simple envelope follower, filters the envelope with 24db/oct lowpass.
235 struct LevelComputation
236 {
237 public:
238 nothrow:
239 @nogc:
240 
241     void initialize(float samplerate)
242     {
243         float attackSecs = 0.001;
244         float releaseSecs = 0.001;
245         float initValue = -140;
246         _envelope.initialize(samplerate, attackSecs, releaseSecs, initValue);
247     }
248 
249     // take audio samples, output RMS values.
250     void nextBuffer(const(float)* input, float* output, int frames)
251     {
252         // Compute squared value
253         for(int i = 0; i < frames; ++i)
254             output[i] = input[i] * input[i] + 1e-10f; // avoid -inf
255 
256         // Take log
257         for (int n = 0; n < frames; ++n)
258         {
259             output[n] = convertLinearGainToDecibel(output[n]);
260         }
261 
262         _envelope.nextBuffer(output, output, frames);
263     }
264 
265 private:
266     AttackRelease!float _envelope;
267 }
268 
269 
270