/* * ZamComp mono compressor for Cardinal * Copyright (C) 2014-2019 Damien Zammit * Copyright (C) 2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the LICENSE file. */ #include "plugin.hpp" #include "widgets.hpp" // -------------------------------------------------------------------------------------------------------------------- struct ZamAudioCompModule : Module { enum ParamIds { PARAM_ATTACK, PARAM_RELEASE, PARAM_THRESHOLD, PARAM_RATIO, PARAM_KNEE, PARAM_SLEW, PARAM_MAKEUP, NUM_PARAMS }; enum InputIds { AUDIO_INPUT, SIDECHAIN_INPUT, NUM_INPUTS }; enum OutputIds { AUDIO_OUTPUT, NUM_OUTPUTS }; enum LightIds { OUTLEVEL, GAINREDUCTION, NUM_LIGHTS }; float gainred,outlevel; //lights float oldL_yl, oldL_y1, oldL_yg; //temp static inline float sanitize_denormal(float v) { if(!std::isnormal(v)) return 0.f; return v; } static inline float from_dB(float gdb) { return (expf(0.05f*gdb*logf(10.f))); } static inline float to_dB(float g) { return (20.f*log10f(g)); } ZamAudioCompModule() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(PARAM_ATTACK, 0.1f, 100.f, 10.f, "Attack", " ms"); configParam(PARAM_RELEASE, 1.f, 500.f, 80.f, "Release", " ms"); configParam(PARAM_THRESHOLD, -80.f, 0.f, 0.f, "Threshold", " db"); configParam(PARAM_RATIO, 1.f, 20.f, 4.f, "Ratio"); configParam(PARAM_KNEE, 0.f, 8.f, 0.f, "Knee", " dB"); configParam(PARAM_SLEW, 1.f, 150.f, 1.f, "Slew"); configParam(PARAM_MAKEUP, 0.f, 30.f, 0.f, "Makeup", " dB"); configInput(AUDIO_INPUT, "Audio"); configInput(SIDECHAIN_INPUT, "Sidechain"); configOutput(AUDIO_OUTPUT, "Audio"); configLight(OUTLEVEL, "Output Level"); configLight(GAINREDUCTION, "Gain Reduction"); gainred = 0.0f; outlevel = -45.0f; oldL_yl = oldL_y1 = oldL_yg = 0.f; } void process(const ProcessArgs& args) override { const float attack = params[PARAM_ATTACK].getValue(); const float release = params[PARAM_RELEASE].getValue(); const float thresdb = params[PARAM_THRESHOLD].getValue(); const float ratio = params[PARAM_RATIO].getValue(); const float knee = params[PARAM_KNEE].getValue(); const float slewfactor = params[PARAM_SLEW].getValue(); const float makeup = params[PARAM_MAKEUP].getValue(); const float srate = args.sampleRate; const float width = (6.f * knee) + 0.01; const float slewwidth = 1.8f; const float release_coeff = exp(-1000.f/(release * srate)); // const float gain = std::pow(params[0].getValue(), 2.f); const float in0 = inputs[AUDIO_INPUT].getVoltageSum() * 0.1f; const float in = inputs[SIDECHAIN_INPUT].isConnected() ? inputs[SIDECHAIN_INPUT].getVoltageSum() * 0.1f : in0; const float Lxg = sanitize_denormal(in == 0.f ? -160.f : to_dB(fabsf(in))); const float checkwidth = 2.f*fabsf(Lxg-thresdb); bool attslew = false; float Lyg; if (2.f*(Lxg-thresdb) < -width) { Lyg = Lxg; } else if (checkwidth <= width) { Lyg = thresdb + (Lxg-thresdb)/ratio; Lyg = sanitize_denormal(Lyg); if (checkwidth <= slewwidth) { if (Lyg >= oldL_yg) { attslew = true; } } } else if (2.f*(Lxg-thresdb) > width) { Lyg = thresdb + (Lxg-thresdb)/ratio; Lyg = sanitize_denormal(Lyg); } else { Lyg = Lxg + (1.f/ratio-1.f)*(Lxg-thresdb+width/2.f)*(Lxg-thresdb+width/2.f)/(2.f*width); } const float attack_coeff = attslew ? exp(-1000.f/((attack + 2.0*(slewfactor - 1)) * srate)) : exp(-1000.f/(attack * srate)); // Don't slew on release const float Lxl = Lxg - Lyg; const float Lyl = sanitize_denormal(Lxl < oldL_yl ? release_coeff * oldL_yl + (1.f-release_coeff)*Lxl : Lxl > oldL_yl ? attack_coeff * oldL_yl+(1.f-attack_coeff)*Lxl : Lxl); const float Lgain = from_dB(-Lyl); const float out = in0 * Lgain * from_dB(makeup); outputs[AUDIO_OUTPUT].setVoltage(out * 10.0f); oldL_yl = Lyl; oldL_yg = Lyg; gainred = Lyl; // const float max = (fabsf(out) > max) ? fabsf(outputs[0][i]) : sanitize_denormal(max); // outlevel = (max == 0.f) ? -45.f : to_dB(max); // relative to - thresdb; } }; // -------------------------------------------------------------------------------------------------------------------- struct ZamAudioCompModuleWidget : ZamAudioModuleWidget { typedef FundamentalBlackKnob<36> BigKnob; ZamAudioCompModule* const module; ZamAudioCompModuleWidget(ZamAudioCompModule* const m) : ZamAudioModuleWidget(), module(m) { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ZamComp.svg"))); addInput(createInput(Vec(32, 280), m, ZamAudioCompModule::AUDIO_INPUT)); addInput(createInput(Vec(32, 310), m, ZamAudioCompModule::SIDECHAIN_INPUT)); addOutput(createOutput(Vec(100, 310), m, ZamAudioCompModule::AUDIO_OUTPUT)); const float scale = 0.8f; addParam(createParamCentered(Vec(45.75f * scale, 121.25f * scale), m, ZamAudioCompModule::PARAM_ATTACK)); addParam(createParamCentered(Vec(127.75f * scale, 121.25f * scale), m, ZamAudioCompModule::PARAM_RELEASE)); addParam(createParamCentered(Vec(48.25f * scale, 208.f * scale), m, ZamAudioCompModule::PARAM_THRESHOLD)); addParam(createParamCentered(Vec(130.75f * scale, 208.f * scale), m, ZamAudioCompModule::PARAM_RATIO)); addParam(createParamCentered(Vec(48.25f * scale, 305.f * scale), m, ZamAudioCompModule::PARAM_KNEE)); addParam(createParamCentered(Vec(130.75f * scale, 305.f * scale), m, ZamAudioCompModule::PARAM_SLEW)); } }; // -------------------------------------------------------------------------------------------------------------------- Model* modelZamComp = createModel("ZamComp"); // --------------------------------------------------------------------------------------------------------------------