From 8de4452e1518cf7b298f618dd75862c59b02a370 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 5 Aug 2020 04:20:20 +0200 Subject: [PATCH] Add compressor --- dpf.mk | 1 + scripts/generate_compressor.sh | 54 +++++++ src/CMakeLists.txt | 7 +- src/sfizz/Effects.cpp | 2 + src/sfizz/effects/Compressor.cpp | 204 +++++++++++++++++++++++++++ src/sfizz/effects/Compressor.h | 56 ++++++++ src/sfizz/effects/dsp/compressor.dsp | 11 ++ src/sfizz/effects/gen/compressor.cxx | 180 +++++++++++++++++++++++ 8 files changed, 514 insertions(+), 1 deletion(-) create mode 100755 scripts/generate_compressor.sh create mode 100644 src/sfizz/effects/Compressor.cpp create mode 100644 src/sfizz/effects/Compressor.h create mode 100644 src/sfizz/effects/dsp/compressor.dsp create mode 100644 src/sfizz/effects/gen/compressor.cxx diff --git a/dpf.mk b/dpf.mk index 7dd64e45..77482e90 100644 --- a/dpf.mk +++ b/dpf.mk @@ -60,6 +60,7 @@ SFIZZ_SOURCES = \ src/sfizz/Curve.cpp \ src/sfizz/effects/Apan.cpp \ src/sfizz/Effects.cpp \ + src/sfizz/effects/Compressor.cpp \ src/sfizz/effects/Eq.cpp \ src/sfizz/effects/Filter.cpp \ src/sfizz/effects/Gain.cpp \ diff --git a/scripts/generate_compressor.sh b/scripts/generate_compressor.sh new file mode 100755 index 00000000..7d799a5e --- /dev/null +++ b/scripts/generate_compressor.sh @@ -0,0 +1,54 @@ +#!/bin/sh +set -e + +if ! test -d "src"; then + echo "Please run this in the project root directory." + exit 1 +fi + +# Note: needs faust >= 2.27.1 for UI macros +FAUSTARGS="-uim -inpl" + +# support GNU sed only, use gsed on a Mac +test -z "$SED" && SED=sed + +faustgen() { + mkdir -p src/sfizz/effects/gen + local outfile=src/sfizz/effects/gen/compressor.cxx + + local code=`faust $FAUSTARGS -cn faustCompressor src/sfizz/effects/dsp/compressor.dsp` + + # suppress some faust-specific stuff we don't care + echo "$code" \ + | fgrep -v -- '->declare(' \ + | fgrep -v -- '->openHorizontalBox(' \ + | fgrep -v -- '->openVerticalBox(' \ + | fgrep -v -- '->closeBox(' \ + | fgrep -v -- '->addHorizontalSlider(' \ + | fgrep -v -- '->addVerticalSlider(' \ + > "$outfile" + + # remove metadata + $SED -r -i 's/void[ \t]+metadata[ \t]*\(Meta[ \t]*\*[ \t]*[a-zA-Z0-9_]+\)/void metadata()/' "$outfile" + + # remove UI + $SED -r -i 's/void[ \t]+buildUserInterface[ \t]*\(UI[ \t]*\*[ \t]*[a-zA-Z0-9_]+\)/void buildUserInterface()/' "$outfile" + + # remove inheritance + $SED -r -i 's/:[ \t]*public[ \t]+dsp\b\s*//' "$outfile" + + # remove virtual + $SED -r -i 's/\bvirtual\b\s*//' "$outfile" + + # remove undesired UIM + $SED -r -i '/^[ \t]*#define[ \t]+FAUST_(FILE_NAME|CLASS_NAME|INPUTS|OUTPUTS|ACTIVES|PASSIVES)/d' "$outfile" + $SED -r -i '/^[ \t]*FAUST_ADD.*/d' "$outfile" + + # direct access to parameter variables + $SED -r -i 's/\bprivate:/public:/' "$outfile" + + # remove trailing whitespace + $SED -r -i 's/[ \t]+$//' "$outfile" +} + +faustgen diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3e8b802..c7dea026 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,10 @@ set (FAUST_FILES sfizz/dsp/filters/filters_modulable.dsp sfizz/dsp/filters/rbj_filters.dsp sfizz/dsp/filters/sallenkey_modulable.dsp - sfizz/dsp/filters/sfz_filters.dsp) + sfizz/dsp/filters/sfz_filters.dsp + sfizz/effects/dsp/limiter.dsp + sfizz/effects/dsp/resonant_string.dsp + sfizz/effects/dsp/compressor.dsp) source_group ("Faust Files" FILES ${FAUST_FILES}) set (SFIZZ_HEADERS @@ -30,6 +33,7 @@ set (SFIZZ_HEADERS sfizz/effects/Apan.h sfizz/effects/CommonLFO.h sfizz/effects/CommonLFO.hpp + sfizz/effects/Compressor.h sfizz/effects/Eq.h sfizz/effects/Filter.h sfizz/effects/Gain.h @@ -122,6 +126,7 @@ set (SFIZZ_SOURCES sfizz/effects/Apan.cpp sfizz/effects/Lofi.cpp sfizz/effects/Limiter.cpp + sfizz/effects/Compressor.cpp sfizz/effects/Strings.cpp sfizz/effects/Rectify.cpp sfizz/effects/Gain.cpp diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 26ee10ae..b1ddd84b 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -15,6 +15,7 @@ #include "effects/Apan.h" #include "effects/Lofi.h" #include "effects/Limiter.h" +#include "effects/Compressor.h" #include "effects/Strings.h" #include "effects/Rectify.h" #include "effects/Gain.h" @@ -31,6 +32,7 @@ void EffectFactory::registerStandardEffectTypes() registerEffectType("apan", fx::Apan::makeInstance); registerEffectType("lofi", fx::Lofi::makeInstance); registerEffectType("limiter", fx::Limiter::makeInstance); + registerEffectType("comp", fx::Compressor::makeInstance); registerEffectType("strings", fx::Strings::makeInstance); // extensions (book) diff --git a/src/sfizz/effects/Compressor.cpp b/src/sfizz/effects/Compressor.cpp new file mode 100644 index 00000000..3aaf04a3 --- /dev/null +++ b/src/sfizz/effects/Compressor.cpp @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +/* + Note(jpc): implementation status + +- [x] comp_gain Gain (dB) +- [x] comp_attack Attack time (s) +- [x] comp_release Release time (s) +- [x] comp_ratio Ratio (linear gain) +- [x] comp_threshold Threshold (dB) +- [x] comp_stlink Stereo link (boolean) + +*/ + +#include "Compressor.h" +#include "Opcode.h" +#include "AudioSpan.h" +#include "MathHelpers.h" +#include "absl/memory/memory.h" + +static constexpr int _oversampling = 2; +#define FAUST_UIMACROS 1 +#include "gen/compressor.cxx" + +namespace sfz { +namespace fx { + + struct Compressor::Impl { + faustCompressor _compressor[2]; + bool _stlink = false; + float _inputGain = 1.0; + AudioBuffer _tempBuffer2x { 2, _oversampling * config::defaultSamplesPerBlock }; + AudioBuffer _gain2x { 2, _oversampling * config::defaultSamplesPerBlock }; + hiir::Downsampler2xFpu<12> _downsampler2x[EffectChannels]; + hiir::Upsampler2xFpu<12> _upsampler2x[EffectChannels]; + + #define DEFINE_SET_GET(type, ident, name, var, def, min, max, step) \ + float get_##ident(size_t i) const noexcept { return _compressor[i].var; } \ + void set_##ident(size_t i, float value) noexcept { _compressor[i].var = value; } + FAUST_LIST_ACTIVES(DEFINE_SET_GET); + #undef DEFINE_SET_GET + }; + + Compressor::Compressor() + : _impl(new Impl) + { + Impl& impl = *_impl; + for (faustCompressor& comp : impl._compressor) + comp.instanceResetUserInterface(); + } + + Compressor::~Compressor() + { + } + + void Compressor::setSampleRate(double sampleRate) + { + Impl& impl = *_impl; + for (faustCompressor& comp : impl._compressor) { + comp.classInit(sampleRate); + comp.instanceConstants(sampleRate); + } + + static constexpr double coefs2x[12] = { 0.036681502163648017, 0.13654762463195794, 0.27463175937945444, 0.42313861743656711, 0.56109869787919531, 0.67754004997416184, 0.76974183386322703, 0.83988962484963892, 0.89226081800387902, 0.9315419599631839, 0.96209454837808417, 0.98781637073289585 }; + + for (unsigned c = 0; c < EffectChannels; ++c) { + impl._downsampler2x[c].set_coefs(coefs2x); + impl._upsampler2x[c].set_coefs(coefs2x); + } + + clear(); + } + + void Compressor::setSamplesPerBlock(int samplesPerBlock) + { + Impl& impl = *_impl; + impl._tempBuffer2x.resize(_oversampling * samplesPerBlock); + impl._gain2x.resize(_oversampling * samplesPerBlock); + } + + void Compressor::clear() + { + Impl& impl = *_impl; + for (faustCompressor& comp : impl._compressor) + comp.instanceClear(); + } + + void Compressor::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + Impl& impl = *_impl; + auto inOut2x = AudioSpan(impl._tempBuffer2x).first(_oversampling * nframes); + + absl::Span left2x = inOut2x.getSpan(0); + absl::Span right2x = inOut2x.getSpan(1); + + impl._upsampler2x[0].process_block(left2x.data(), inputs[0], nframes); + impl._upsampler2x[1].process_block(right2x.data(), inputs[1], nframes); + + const float inputGain = impl._inputGain; + for (unsigned i = 0; i < _oversampling * nframes; ++i) { + left2x[i] *= inputGain; + right2x[i] *= inputGain; + } + + if (!impl._stlink) { + absl::Span leftGain2x = impl._gain2x.getSpan(0); + absl::Span rightGain2x = impl._gain2x.getSpan(1); + + { + faustCompressor& comp = impl._compressor[0]; + float* inputs[] = { left2x.data() }; + float* outputs[] = { leftGain2x.data() }; + comp.compute(_oversampling * nframes, inputs, outputs); + } + + { + faustCompressor& comp = impl._compressor[1]; + float* inputs[] = { right2x.data() }; + float* outputs[] = { rightGain2x.data() }; + comp.compute(_oversampling * nframes, inputs, outputs); + } + + for (unsigned i = 0; i < _oversampling * nframes; ++i) { + left2x[i] *= leftGain2x[i]; + right2x[i] *= rightGain2x[i]; + } + } + else { + absl::Span compIn2x = impl._gain2x.getSpan(0); + for (unsigned i = 0; i < _oversampling * nframes; ++i) + compIn2x[i] = std::abs(left2x[i]) + std::abs(right2x[1]); + + absl::Span gain2x = impl._gain2x.getSpan(1); + + { + faustCompressor& comp = impl._compressor[0]; + float* inputs[] = { compIn2x.data() }; + float* outputs[] = { gain2x.data() }; + comp.compute(_oversampling * nframes, inputs, outputs); + } + + for (unsigned i = 0; i < _oversampling * nframes; ++i) { + left2x[i] *= gain2x[i]; + right2x[i] *= gain2x[i]; + } + } + + impl._downsampler2x[0].process_block(outputs[0], left2x.data(), nframes); + impl._downsampler2x[1].process_block(outputs[1], right2x.data(), nframes); + } + + std::unique_ptr Compressor::makeInstance(absl::Span members) + { + Compressor* compressor = new Compressor; + std::unique_ptr fx { compressor }; + + Impl& impl = *compressor->_impl; + + for (const Opcode& opc : members) { + switch (opc.lettersOnlyHash) { + case hash("comp_attack"): + if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + for (size_t c = 0; c < 2; ++c) + impl.set_Attack(c, *value); + } + break; + case hash("comp_release"): + if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + for (size_t c = 0; c < 2; ++c) + impl.set_Release(c, *value); + } + break; + case hash("comp_threshold"): + if (auto value = readOpcode(opc.value, {-100.0, 0.0})) { + for (size_t c = 0; c < 2; ++c) + impl.set_Threshold(c, *value); + } + break; + case hash("comp_ratio"): + if (auto value = readOpcode(opc.value, {1.0, 50.0})) { + for (size_t c = 0; c < 2; ++c) + impl.set_Ratio(c, *value); + } + break; + case hash("comp_gain"): + if (auto value = readOpcode(opc.value, {-100.0, 100.0})) + impl._inputGain = db2mag(*value); + break; + case hash("comp_stlink"): + if (auto value = readBooleanFromOpcode(opc)) + impl._stlink = *value; + break; + } + } + + return fx; + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Compressor.h b/src/sfizz/effects/Compressor.h new file mode 100644 index 00000000..ed2b0e5d --- /dev/null +++ b/src/sfizz/effects/Compressor.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "Effects.h" +#include "hiir/Downsampler2xFpu.h" +#include "hiir/Upsampler2xFpu.h" +#include + +namespace sfz { +namespace fx { + + /** + * @brief Compressor effect + */ + class Compressor : public Effect { + public: + Compressor(); + ~Compressor(); + + /** + * @brief Initializes with the given sample rate. + */ + void setSampleRate(double sampleRate) override; + + /** + * @brief Sets the maximum number of frames to render at a time. The actual + * value can be lower but should never be higher. + */ + void setSamplesPerBlock(int samplesPerBlock) override; + + /** + * @brief Reset the state to initial. + */ + void clear() override; + + /** + * @brief Computes a cycle of the effect in stereo. + */ + void process(const float* const inputs[], float* const outputs[], unsigned nframes) override; + + /** + * @brief Instantiates given the contents of the block. + */ + static std::unique_ptr makeInstance(absl::Span members); + + private: + struct Impl; + std::unique_ptr _impl; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/dsp/compressor.dsp b/src/sfizz/effects/dsp/compressor.dsp new file mode 100644 index 00000000..6675e7c1 --- /dev/null +++ b/src/sfizz/effects/dsp/compressor.dsp @@ -0,0 +1,11 @@ +import("stdfaust.lib"); + +cgain = co.compression_gain_mono(ratio, thresh, att, rel) with { + ratio = hslider("[1] Ratio", 1.0, 1.0, 20.0, 0.01); + thresh = hslider("[2] Threshold [unit:dB]", 0.0, -60.0, 0.0, 0.01); + over = fconstant(int _oversampling, ); + att = hslider("[3] Attack [unit:s]", 0.0, 0.0, 0.5, 1e-3) : *(over); + rel = hslider("[4] Release [unit:s]", 0.0, 0.0, 5.0, 1e-3) : *(over); +}; + +process = cgain; diff --git a/src/sfizz/effects/gen/compressor.cxx b/src/sfizz/effects/gen/compressor.cxx new file mode 100644 index 00000000..6634dab0 --- /dev/null +++ b/src/sfizz/effects/gen/compressor.cxx @@ -0,0 +1,180 @@ +/* ------------------------------------------------------------ +name: "compressor" +Code generated with Faust 2.27.2 (https://faust.grame.fr) +Compilation options: -lang cpp -inpl -scal -ftz 0 +------------------------------------------------------------ */ + +#ifndef __faustCompressor_H__ +#define __faustCompressor_H__ + +#ifndef FAUSTFLOAT +#define FAUSTFLOAT float +#endif + +#include +#include +#include + + +#ifndef FAUSTCLASS +#define FAUSTCLASS faustCompressor +#endif + +#ifdef __APPLE__ +#define exp10f __exp10f +#define exp10 __exp10 +#endif + +class faustCompressor { + + public: + + float fConst0; + float fConst1; + FAUSTFLOAT fHslider0; + int fSampleRate; + float fConst2; + FAUSTFLOAT fHslider1; + FAUSTFLOAT fHslider2; + float fRec2[2]; + float fRec1[2]; + FAUSTFLOAT fHslider3; + float fRec0[2]; + + public: + + void metadata() { + } + + int getNumInputs() { + return 1; + } + int getNumOutputs() { + return 1; + } + int getInputRate(int channel) { + int rate; + switch ((channel)) { + case 0: { + rate = 1; + break; + } + default: { + rate = -1; + break; + } + } + return rate; + } + int getOutputRate(int channel) { + int rate; + switch ((channel)) { + case 0: { + rate = 1; + break; + } + default: { + rate = -1; + break; + } + } + return rate; + } + + static void classInit(int sample_rate) { + } + + void instanceConstants(int sample_rate) { + fSampleRate = sample_rate; + fConst0 = float(_oversampling); + fConst1 = (0.5f * fConst0); + fConst2 = (1.0f / std::min(192000.0f, std::max(1.0f, float(fSampleRate)))); + } + + void instanceResetUserInterface() { + fHslider0 = FAUSTFLOAT(0.0f); + fHslider1 = FAUSTFLOAT(1.0f); + fHslider2 = FAUSTFLOAT(0.0f); + fHslider3 = FAUSTFLOAT(0.0f); + } + + void instanceClear() { + for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { + fRec2[l0] = 0.0f; + } + for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { + fRec1[l1] = 0.0f; + } + for (int l2 = 0; (l2 < 2); l2 = (l2 + 1)) { + fRec0[l2] = 0.0f; + } + } + + void init(int sample_rate) { + classInit(sample_rate); + instanceInit(sample_rate); + } + void instanceInit(int sample_rate) { + instanceConstants(sample_rate); + instanceResetUserInterface(); + instanceClear(); + } + + faustCompressor* clone() { + return new faustCompressor(); + } + + int getSampleRate() { + return fSampleRate; + } + + void buildUserInterface() { + } + + void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { + FAUSTFLOAT* input0 = inputs[0]; + FAUSTFLOAT* output0 = outputs[0]; + float fSlow0 = float(fHslider0); + float fSlow1 = (fConst1 * fSlow0); + int iSlow2 = (std::fabs(fSlow1) < 1.1920929e-07f); + float fSlow3 = (iSlow2 ? 0.0f : std::exp((0.0f - (fConst2 / (iSlow2 ? 1.0f : fSlow1))))); + float fSlow4 = ((1.0f / std::max(1.00000001e-07f, float(fHslider1))) + -1.0f); + float fSlow5 = (fConst0 * fSlow0); + int iSlow6 = (std::fabs(fSlow5) < 1.1920929e-07f); + float fSlow7 = (iSlow6 ? 0.0f : std::exp((0.0f - (fConst2 / (iSlow6 ? 1.0f : fSlow5))))); + float fSlow8 = (fConst0 * float(fHslider2)); + int iSlow9 = (std::fabs(fSlow8) < 1.1920929e-07f); + float fSlow10 = (iSlow9 ? 0.0f : std::exp((0.0f - (fConst2 / (iSlow9 ? 1.0f : fSlow8))))); + float fSlow11 = float(fHslider3); + float fSlow12 = (1.0f - fSlow3); + for (int i = 0; (i < count); i = (i + 1)) { + float fTemp0 = float(input0[i]); + float fTemp1 = std::fabs(fTemp0); + float fTemp2 = ((fRec1[1] > fTemp1) ? fSlow10 : fSlow7); + fRec2[0] = ((fRec2[1] * fTemp2) + (fTemp1 * (1.0f - fTemp2))); + fRec1[0] = fRec2[0]; + fRec0[0] = ((fRec0[1] * fSlow3) + (fSlow4 * (std::max(((20.0f * std::log10(fRec1[0])) - fSlow11), 0.0f) * fSlow12))); + output0[i] = FAUSTFLOAT(std::pow(10.0f, (0.0500000007f * fRec0[0]))); + fRec2[1] = fRec2[0]; + fRec1[1] = fRec1[0]; + fRec0[1] = fRec0[0]; + } + } + +}; + +#ifdef FAUST_UIMACROS + + + + #define FAUST_LIST_ACTIVES(p) \ + p(HORIZONTALSLIDER, Ratio, "Ratio", fHslider1, 1.0f, 1.0f, 20.0f, 0.01f) \ + p(HORIZONTALSLIDER, Threshold, "Threshold", fHslider3, 0.0f, -60.0f, 0.0f, 0.01f) \ + p(HORIZONTALSLIDER, Attack, "Attack", fHslider0, 0.0f, 0.0f, 0.5f, 0.001f) \ + p(HORIZONTALSLIDER, Release, "Release", fHslider2, 0.0f, 0.0f, 5.0f, 0.001f) \ + + #define FAUST_LIST_PASSIVES(p) \ + +#endif + +#endif