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