diff --git a/Makefile b/Makefile index d4f4c37..6162ca1 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ plugins: dgl $(MAKE) all -C plugins/Compressor $(MAKE) all -C plugins/ConvolutionReverb $(MAKE) all -C plugins/DevilDistortion + $(MAKE) all -C plugins/Filter # $(MAKE) all -C plugins/Sampler ifneq ($(CROSS_COMPILING),true) @@ -45,6 +46,7 @@ clean: $(MAKE) clean -C plugins/Compressor $(MAKE) clean -C plugins/ConvolutionReverb $(MAKE) clean -C plugins/DevilDistortion + $(MAKE) clean -C plugins/Filter # $(MAKE) clean -C plugins/Sampler rm -rf bin build dpf-widgets/opengl/*.d dpf-widgets/opengl/*.o rm -f 3rd-party/FFTConvolver/*.d 3rd-party/FFTConvolver/*.o diff --git a/dpf b/dpf index 63dfb76..df2afed 160000 --- a/dpf +++ b/dpf @@ -1 +1 @@ -Subproject commit 63dfb7610bc37dee69f4a303f3e3362529d95f24 +Subproject commit df2afed246113e4459808b112583d54cf7e433e9 diff --git a/dpf-widgets b/dpf-widgets index ea26d28..73e9aa6 160000 --- a/dpf-widgets +++ b/dpf-widgets @@ -1 +1 @@ -Subproject commit ea26d2842b4285bec90876fa63a201447b64716d +Subproject commit 73e9aa67b92430db863d7d7b292b66cd7d2c8190 diff --git a/plugins/Filter/Biquad.cpp b/plugins/Filter/Biquad.cpp new file mode 100644 index 0000000..d01e29b --- /dev/null +++ b/plugins/Filter/Biquad.cpp @@ -0,0 +1,165 @@ +// +// Biquad.cpp +// +// Created by Nigel Redmon on 11/24/12 +// EarLevel Engineering: earlevel.com +// Copyright 2012 Nigel Redmon +// +// For a complete explanation of the Biquad code: +// http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ +// +// License: +// +// This source code is provided as is, without warranty. +// You may copy and distribute verbatim copies of this document. +// You may modify and use this source code to create binary code +// for your own purposes, free or commercial. +// + +#include +#include "Biquad.h" + +Biquad::Biquad() { + type = bq_type_lowpass; + a0 = 1.0; + a1 = a2 = b1 = b2 = 0.0; + Fc = 0.50; + Q = 0.707; + peakGain = 0.0; + z1 = z2 = 0.0; +} + +Biquad::Biquad(int type, double Fc, double Q, double peakGainDB) { + setBiquad(type, Fc, Q, peakGainDB); + z1 = z2 = 0.0; +} + +Biquad::~Biquad() { +} + +void Biquad::setType(int type) { + this->type = type; + calcBiquad(); +} + +void Biquad::setQ(double Q) { + this->Q = Q; + calcBiquad(); +} + +void Biquad::setFc(double Fc) { + this->Fc = Fc; + calcBiquad(); +} + +void Biquad::setPeakGain(double peakGainDB) { + this->peakGain = peakGainDB; + calcBiquad(); +} + +void Biquad::setBiquad(int type, double Fc, double Q, double peakGainDB) { + this->type = type; + this->Q = Q; + this->Fc = Fc; + setPeakGain(peakGainDB); +} + +void Biquad::calcBiquad(void) { + double norm; + double V = pow(10, fabs(peakGain) / 20.0); + double K = tan(M_PI * Fc); + switch (this->type) { + case bq_type_lowpass: + norm = 1 / (1 + K / Q + K * K); + a0 = K * K * norm; + a1 = 2 * a0; + a2 = a0; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_highpass: + norm = 1 / (1 + K / Q + K * K); + a0 = 1 * norm; + a1 = -2 * a0; + a2 = a0; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_bandpass: + norm = 1 / (1 + K / Q + K * K); + a0 = K / Q * norm; + a1 = 0; + a2 = -a0; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_notch: + norm = 1 / (1 + K / Q + K * K); + a0 = (1 + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = a0; + b1 = a1; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_peak: + if (peakGain >= 0) { // boost + norm = 1 / (1 + 1/Q * K + K * K); + a0 = (1 + V/Q * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - V/Q * K + K * K) * norm; + b1 = a1; + b2 = (1 - 1/Q * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + V/Q * K + K * K); + a0 = (1 + 1/Q * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - 1/Q * K + K * K) * norm; + b1 = a1; + b2 = (1 - V/Q * K + K * K) * norm; + } + break; + case bq_type_lowshelf: + if (peakGain >= 0) { // boost + norm = 1 / (1 + sqrt(2) * K + K * K); + a0 = (1 + sqrt(2*V) * K + V * K * K) * norm; + a1 = 2 * (V * K * K - 1) * norm; + a2 = (1 - sqrt(2*V) * K + V * K * K) * norm; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - sqrt(2) * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + sqrt(2*V) * K + V * K * K); + a0 = (1 + sqrt(2) * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - sqrt(2) * K + K * K) * norm; + b1 = 2 * (V * K * K - 1) * norm; + b2 = (1 - sqrt(2*V) * K + V * K * K) * norm; + } + break; + case bq_type_highshelf: + if (peakGain >= 0) { // boost + norm = 1 / (1 + sqrt(2) * K + K * K); + a0 = (V + sqrt(2*V) * K + K * K) * norm; + a1 = 2 * (K * K - V) * norm; + a2 = (V - sqrt(2*V) * K + K * K) * norm; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - sqrt(2) * K + K * K) * norm; + } + else { // cut + norm = 1 / (V + sqrt(2*V) * K + K * K); + a0 = (1 + sqrt(2) * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - sqrt(2) * K + K * K) * norm; + b1 = 2 * (K * K - V) * norm; + b2 = (V - sqrt(2*V) * K + K * K) * norm; + } + break; + } + + return; +} diff --git a/plugins/Filter/Biquad.h b/plugins/Filter/Biquad.h new file mode 100644 index 0000000..ee1b955 --- /dev/null +++ b/plugins/Filter/Biquad.h @@ -0,0 +1,60 @@ +// +// Biquad.h +// +// Created by Nigel Redmon on 11/24/12 +// EarLevel Engineering: earlevel.com +// Copyright 2012 Nigel Redmon +// +// For a complete explanation of the Biquad code: +// http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ +// +// License: +// +// This source code is provided as is, without warranty. +// You may copy and distribute verbatim copies of this document. +// You may modify and use this source code to create binary code +// for your own purposes, free or commercial. +// + +#ifndef Biquad_h +#define Biquad_h + +enum { + bq_type_lowpass = 0, + bq_type_highpass, + bq_type_bandpass, + bq_type_notch, + bq_type_peak, + bq_type_lowshelf, + bq_type_highshelf +}; + +class Biquad { +public: + Biquad(); + Biquad(int type, double Fc, double Q, double peakGainDB); + ~Biquad(); + void setType(int type); + void setQ(double Q); + void setFc(double Fc); + void setPeakGain(double peakGainDB); + void setBiquad(int type, double Fc, double Q, double peakGainDB); + float process(float in); + +protected: + void calcBiquad(void); + + int type; + double a0, a1, a2, b1, b2; + double Fc, Q, peakGain; + double z1, z2; +}; + +inline float Biquad::process(float in) { + double out = in * a0 + z1; + z1 = in * a1 + z2 - b1 * out; + z2 = in * a2 - b2 * out; + return out; +} + +#endif // Biquad_h diff --git a/plugins/Filter/DistrhoPluginInfo.h b/plugins/Filter/DistrhoPluginInfo.h new file mode 100644 index 0000000..d7b4105 --- /dev/null +++ b/plugins/Filter/DistrhoPluginInfo.h @@ -0,0 +1,64 @@ +/* + * DISTRHO OneKnob Filter + * Copyright (C) 2023 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 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. + */ + +#pragma once + +#include "OneKnobPluginInfo.h" +#include "Biquad.h" + +#define DISTRHO_PLUGIN_NAME "OneKnob Filter" +#define DISTRHO_PLUGIN_URI "https://distrho.kx.studio/plugins/oneknob#Filter" +#define DISTRHO_PLUGIN_CLAP_ID "studio.kx.distrho.oneknob.Filter" + +#define DISTRHO_PLUGIN_CLAP_FEATURES "audio-effect", "filter", "stereo" +#define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:FilterPlugin" +#define DISTRHO_PLUGIN_VST3_CATEGORIES "Fx|Filter|Stereo" + +#ifdef __MOD_DEVICES__ +#undef DISTRHO_PLUGIN_NUM_INPUTS +#undef DISTRHO_PLUGIN_NUM_OUTPUTS +#define DISTRHO_PLUGIN_NUM_INPUTS 1 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 1 +#endif + +enum Parameters { + kParameterType = 0, + kParameterFrequency, + kParameterQ, + kParameterGain, + kParameterBypass, + kParameterCount +}; + +enum Programs { + kProgramDefault, + kProgramCount +}; + +enum States { + kStateCount +}; + +static constexpr const struct OneKnobParameterRanges { + float min, def, max; +} kParameterRanges[kParameterCount] = { + { bq_type_lowpass, bq_type_lowpass, bq_type_highshelf }, + { 20.f, 5000.f, 20000.f }, + { 0.f, 0.707f, 1.f }, + { -20.f, 0.f, 20.f }, + {} +}; diff --git a/plugins/Filter/Makefile b/plugins/Filter/Makefile new file mode 100644 index 0000000..bb7d7c1 --- /dev/null +++ b/plugins/Filter/Makefile @@ -0,0 +1,35 @@ +#!/usr/bin/make -f +# Makefile for DISTRHO Plugins # +# ---------------------------- # +# Created by falkTX +# + +# -------------------------------------------------------------- +# Project name, used for binaries + +NAME = OK-Filter + +# -------------------------------------------------------------- +# Files to build + +FILES_DSP = \ + OneKnobPlugin.cpp \ + Biquad.cpp + +# -------------------------------------------------------------- +# Do some magic + +include ../../dpf/Makefile.plugins.mk + +BUILD_CXX_FLAGS += -I../common +BUILD_CXX_FLAGS += -I../../dpf-widgets/opengl +LINK_FLAGS += $(SHARED_MEMORY_LIBS) + +# -------------------------------------------------------------- +# Enable all possible plugin types + +TARGETS += lv2_sep + +all: $(TARGETS) + +# -------------------------------------------------------------- diff --git a/plugins/Filter/OneKnobPlugin.cpp b/plugins/Filter/OneKnobPlugin.cpp new file mode 100644 index 0000000..6df50c1 --- /dev/null +++ b/plugins/Filter/OneKnobPlugin.cpp @@ -0,0 +1,280 @@ +/* + * DISTRHO OneKnob Filter + * Copyright (C) 2023 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 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. + */ + +// IDE helper (not needed for building) +#include "DistrhoPluginInfo.h" + +#include "OneKnobPlugin.hpp" +#include "Biquad.h" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- + +class OneKnobFilterPlugin : public OneKnobPlugin +{ +public: + OneKnobFilterPlugin() + : OneKnobPlugin(), + filter(bq_type_lowpass, + kParameterRanges[kParameterFrequency].def / getSampleRate(), + kParameterRanges[kParameterQ].def, + kParameterRanges[kParameterGain].def) + #if DISTRHO_PLUGIN_NUM_INPUTS == 2 + , filter2(bq_type_lowpass, + kParameterRanges[kParameterFrequency].def / getSampleRate(), + kParameterRanges[kParameterQ].def, + kParameterRanges[kParameterGain].def) + #endif + { + init(); + } + +protected: + // ------------------------------------------------------------------- + // Information + + const char* getDescription() const override + { + return "" + "One-Knob Filter is a multi-type filter used for plugin design.\n" + "\n" + + "Type: Selects the type of filter.\n" + "Frequency: Sets the center frequency for emphasis or attenuation.\n" + "Q/Order: Adjusts the width/order of the affected frequency range. Does NOT have any effect with the Low Shelf and High Shelf types.\n" + "Gain: Controls the overall amplitude or level of the filtered frequencies. Does NOT have any effect with the Low Pass and High Pass filters."; + } + + const char* getLicense() const noexcept override + { + return "GPLv2+"; + } + + int64_t getUniqueId() const noexcept override + { + return d_cconst('O', 'K', 'f', 't'); + } + + // ------------------------------------------------------------------- + // Init + + void initParameter(uint32_t index, Parameter& parameter) override + { + switch (index) + { + case kParameterType: + parameter.hints = kParameterIsAutomatable | kParameterIsInteger; + parameter.name = "Type"; + parameter.symbol = "type"; + parameter.ranges.def = kParameterRanges[kParameterType].def; + parameter.ranges.min = kParameterRanges[kParameterType].min; + parameter.ranges.max = kParameterRanges[kParameterType].max; + if (ParameterEnumerationValue* const values = new ParameterEnumerationValue[7]) + { + parameter.enumValues.count = 7; + parameter.enumValues.values = values; + parameter.enumValues.restrictedMode = true; + + values[0].label = "Lowpass"; + values[0].value = bq_type_lowpass; + values[1].label = "Highpass"; + values[1].value = bq_type_highpass; + values[2].label = "Bandpass"; + values[2].value = bq_type_bandpass; + values[3].label = "Notch"; + values[3].value = bq_type_notch; + values[4].label = "Peak"; + values[4].value = bq_type_peak; + values[5].label = "Lowshelf"; + values[5].value = bq_type_lowshelf; + values[6].label = "Highshelf"; + values[6].value = bq_type_highshelf; + } + break; + case kParameterFrequency: + parameter.hints = kParameterIsAutomatable | kParameterIsLogarithmic; + parameter.name = "Frequency"; + parameter.symbol = "frequency"; + parameter.unit = "Hz"; + parameter.ranges.def = kParameterRanges[kParameterFrequency].def; + parameter.ranges.min = kParameterRanges[kParameterFrequency].min; + parameter.ranges.max = kParameterRanges[kParameterFrequency].max; + break; + case kParameterQ: + parameter.hints = kParameterIsAutomatable; + parameter.name = "Q"; + parameter.symbol = "q"; + parameter.unit = ""; + parameter.ranges.def = kParameterRanges[kParameterQ].def; + parameter.ranges.min = kParameterRanges[kParameterQ].min; + parameter.ranges.max = kParameterRanges[kParameterQ].max; + break; + case kParameterGain: + parameter.hints = kParameterIsAutomatable; + parameter.name = "Gain"; + parameter.symbol = "Gain"; + parameter.unit = "dB"; + parameter.ranges.def = kParameterRanges[kParameterGain].def; + parameter.ranges.min = kParameterRanges[kParameterGain].min; + parameter.ranges.max = kParameterRanges[kParameterGain].max; + break; + case kParameterBypass: + parameter.initDesignation(kParameterDesignationBypass); + break; + } + } + + void initProgramName(uint32_t index, String& programName) override + { + switch (index) + { + case kProgramDefault: + programName = "Default"; + break; + } + } + + // ------------------------------------------------------------------- + // Internal data + + void setParameterValue(uint32_t index, float value) override + { + switch (index) + { + case kParameterType: + filter.setType(static_cast(value + 0.5f)); + #if DISTRHO_PLUGIN_NUM_INPUTS == 2 + filter2.setType(static_cast(value + 0.5f)); + #endif + break; + case kParameterFrequency: + filter.setFc(value / getSampleRate()); + #if DISTRHO_PLUGIN_NUM_INPUTS == 2 + filter2.setFc(value / getSampleRate()); + #endif + break; + case kParameterQ: + filter.setQ(value); + #if DISTRHO_PLUGIN_NUM_INPUTS == 2 + filter2.setQ(value); + #endif + break; + case kParameterGain: + filter.setPeakGain(value); + #if DISTRHO_PLUGIN_NUM_INPUTS == 2 + filter2.setPeakGain(value); + #endif + break; + } + + OneKnobPlugin::setParameterValue(index, value); + } + + void loadProgram(uint32_t index) override + { + switch (index) + { + case kProgramDefault: + loadDefaultParameterValues(); + break; + } + + // activate filter parameters + activate(); + } + + // ------------------------------------------------------------------- + // Process + + void run(const float** const inputs, float** const outputs, const uint32_t frames) override + { + const float* in = inputs[0]; + float* out = outputs[0]; + + const bool bypassed = parameters[kParameterBypass] > 0.5f; + + #if DISTRHO_PLUGIN_NUM_INPUTS == 1 + if (bypassed) + { + if (out != in) + std::memcpy(out, in, sizeof(float)*frames); + return; + } + + for (uint32_t i = 0; i < frames; ++i) + { + if (!std::isfinite(in[i])) + __builtin_unreachable(); + + out[i] = filter.process(in[i]); + + if (!std::isfinite(out[i])) + __builtin_unreachable(); + } + #else + const float* in2 = inputs[1]; + float* out2 = outputs[1]; + + if (bypassed) + { + if (out != in) + std::memcpy(out, in, sizeof(float)*frames); + if (out2 != in2) + std::memcpy(out2, in2, sizeof(float)*frames); + return; + } + + for (uint32_t i = 0; i < frames; ++i) + { + if (!std::isfinite(in[i])) + __builtin_unreachable(); + if (!std::isfinite(in2[i])) + __builtin_unreachable(); + + out[i] = filter.process(in[i]); + out2[i] = filter.process(in2[i]); + + if (!std::isfinite(out[i])) + __builtin_unreachable(); + if (!std::isfinite(out2[i])) + __builtin_unreachable(); + } + #endif + } + + // ------------------------------------------------------------------- + +private: + Biquad filter; + #if DISTRHO_PLUGIN_NUM_INPUTS == 2 + Biquad filter2; + #endif + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OneKnobFilterPlugin) +}; + +// ----------------------------------------------------------------------- + +Plugin* createPlugin() +{ + return new OneKnobFilterPlugin(); +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO