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