From b70e1941394868ba0edfcc8ee9a0db8d89578754 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 5 Aug 2020 05:27:01 +0200 Subject: [PATCH] Add gate --- dpf.mk | 1 + scripts/generate_gate.sh | 54 +++++++++ src/CMakeLists.txt | 5 +- src/sfizz/Effects.cpp | 2 + src/sfizz/effects/Gate.cpp | 203 +++++++++++++++++++++++++++++++++ src/sfizz/effects/Gate.h | 56 +++++++++ src/sfizz/effects/dsp/gate.dsp | 11 ++ src/sfizz/effects/gen/gate.cxx | 195 +++++++++++++++++++++++++++++++ 8 files changed, 526 insertions(+), 1 deletion(-) create mode 100755 scripts/generate_gate.sh create mode 100644 src/sfizz/effects/Gate.cpp create mode 100644 src/sfizz/effects/Gate.h create mode 100644 src/sfizz/effects/dsp/gate.dsp create mode 100644 src/sfizz/effects/gen/gate.cxx diff --git a/dpf.mk b/dpf.mk index 77482e90..ba675096 100644 --- a/dpf.mk +++ b/dpf.mk @@ -64,6 +64,7 @@ SFIZZ_SOURCES = \ src/sfizz/effects/Eq.cpp \ src/sfizz/effects/Filter.cpp \ src/sfizz/effects/Gain.cpp \ + src/sfizz/effects/Gate.cpp \ src/sfizz/effects/impl/ResonantArrayAVX.cpp \ src/sfizz/effects/impl/ResonantArray.cpp \ src/sfizz/effects/impl/ResonantArraySSE.cpp \ diff --git a/scripts/generate_gate.sh b/scripts/generate_gate.sh new file mode 100755 index 00000000..6cb32422 --- /dev/null +++ b/scripts/generate_gate.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/gate.cxx + + local code=`faust $FAUSTARGS -cn faustGate src/sfizz/effects/dsp/gate.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 c7dea026..b8ec8e0c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,8 @@ set (FAUST_FILES sfizz/dsp/filters/sfz_filters.dsp sfizz/effects/dsp/limiter.dsp sfizz/effects/dsp/resonant_string.dsp - sfizz/effects/dsp/compressor.dsp) + sfizz/effects/dsp/compressor.dsp + sfizz/effects/dsp/gate.dsp) source_group ("Faust Files" FILES ${FAUST_FILES}) set (SFIZZ_HEADERS @@ -37,6 +38,7 @@ set (SFIZZ_HEADERS sfizz/effects/Eq.h sfizz/effects/Filter.h sfizz/effects/Gain.h + sfizz/effects/Gate.h sfizz/effects/Limiter.h sfizz/effects/Lofi.h sfizz/effects/Nothing.h @@ -127,6 +129,7 @@ set (SFIZZ_SOURCES sfizz/effects/Lofi.cpp sfizz/effects/Limiter.cpp sfizz/effects/Compressor.cpp + sfizz/effects/Gate.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 b1ddd84b..f41f29dc 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -16,6 +16,7 @@ #include "effects/Lofi.h" #include "effects/Limiter.h" #include "effects/Compressor.h" +#include "effects/Gate.h" #include "effects/Strings.h" #include "effects/Rectify.h" #include "effects/Gain.h" @@ -33,6 +34,7 @@ void EffectFactory::registerStandardEffectTypes() registerEffectType("lofi", fx::Lofi::makeInstance); registerEffectType("limiter", fx::Limiter::makeInstance); registerEffectType("comp", fx::Compressor::makeInstance); + registerEffectType("gate", fx::Gate::makeInstance); registerEffectType("strings", fx::Strings::makeInstance); // extensions (book) diff --git a/src/sfizz/effects/Gate.cpp b/src/sfizz/effects/Gate.cpp new file mode 100644 index 00000000..0b9c8992 --- /dev/null +++ b/src/sfizz/effects/Gate.cpp @@ -0,0 +1,203 @@ +// 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] gate_attack Attack time (s) +- [x] gate_release Release time (s) +- [x] gate_threshold Threshold (dB) +- [x] gate_stlink Stereo link (boolean) +- [ ] gate_onccN Gate manual control (% - 0%=on, 100%=off) + + Sfizz Extra + +- [x] gate_hold Hold time (s) + +*/ + +#include "Gate.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/gate.cxx" + +namespace sfz { +namespace fx { + + struct Gate::Impl { + faustGate _gate[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 _gate[i].var; } \ + void set_##ident(size_t i, float value) noexcept { _gate[i].var = value; } + FAUST_LIST_ACTIVES(DEFINE_SET_GET); + #undef DEFINE_SET_GET + }; + + Gate::Gate() + : _impl(new Impl) + { + Impl& impl = *_impl; + for (faustGate& gate : impl._gate) + gate.instanceResetUserInterface(); + } + + Gate::~Gate() + { + } + + void Gate::setSampleRate(double sampleRate) + { + Impl& impl = *_impl; + for (faustGate& gate : impl._gate) { + gate.classInit(sampleRate); + gate.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 Gate::setSamplesPerBlock(int samplesPerBlock) + { + Impl& impl = *_impl; + impl._tempBuffer2x.resize(_oversampling * samplesPerBlock); + impl._gain2x.resize(_oversampling * samplesPerBlock); + } + + void Gate::clear() + { + Impl& impl = *_impl; + for (faustGate& gate : impl._gate) + gate.instanceClear(); + } + + void Gate::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); + + { + faustGate& gate = impl._gate[0]; + float* inputs[] = { left2x.data() }; + float* outputs[] = { leftGain2x.data() }; + gate.compute(_oversampling * nframes, inputs, outputs); + } + + { + faustGate& gate = impl._gate[1]; + float* inputs[] = { right2x.data() }; + float* outputs[] = { rightGain2x.data() }; + gate.compute(_oversampling * nframes, inputs, outputs); + } + + for (unsigned i = 0; i < _oversampling * nframes; ++i) { + left2x[i] *= leftGain2x[i]; + right2x[i] *= rightGain2x[i]; + } + } + else { + absl::Span gateIn2x = impl._gain2x.getSpan(0); + for (unsigned i = 0; i < _oversampling * nframes; ++i) + gateIn2x[i] = std::abs(left2x[i]) + std::abs(right2x[1]); + + absl::Span gain2x = impl._gain2x.getSpan(1); + + { + faustGate& gate = impl._gate[0]; + float* inputs[] = { gateIn2x.data() }; + float* outputs[] = { gain2x.data() }; + gate.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 Gate::makeInstance(absl::Span members) + { + Gate* gate = new Gate; + std::unique_ptr fx { gate }; + + Impl& impl = *gate->_impl; + + for (const Opcode& opc : members) { + switch (opc.lettersOnlyHash) { + case hash("gate_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("gate_hold"): + if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + for (size_t c = 0; c < 2; ++c) + impl.set_Hold(c, *value); + } + break; + case hash("gate_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("gate_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("gate_stlink"): + if (auto value = readBooleanFromOpcode(opc)) + impl._stlink = *value; + break; + } + } + + return fx; + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Gate.h b/src/sfizz/effects/Gate.h new file mode 100644 index 00000000..bfe3ccfd --- /dev/null +++ b/src/sfizz/effects/Gate.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 Gate effect + */ + class Gate : public Effect { + public: + Gate(); + ~Gate(); + + /** + * @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/gate.dsp b/src/sfizz/effects/dsp/gate.dsp new file mode 100644 index 00000000..7bac7f27 --- /dev/null +++ b/src/sfizz/effects/dsp/gate.dsp @@ -0,0 +1,11 @@ +import("stdfaust.lib"); + +ggain = ef.gate_gain_mono(thresh, att, hold, rel) with { + thresh = hslider("[1] Threshold [unit:dB]", 0.0, -60.0, 0.0, 0.01); + over = fconstant(int _oversampling, ); + att = hslider("[2] Attack [unit:s]", 0.0, 0.0, 10.0, 1e-3) : *(over); + hold = hslider("[3] Hold [unit:s]", 0.0, 0.0, 10.0, 1e-3) : *(over); + rel = hslider("[4] Release [unit:s]", 0.0, 0.0, 5.0, 1e-3) : *(over); +}; + +process = ggain; diff --git a/src/sfizz/effects/gen/gate.cxx b/src/sfizz/effects/gen/gate.cxx new file mode 100644 index 00000000..099eb3c5 --- /dev/null +++ b/src/sfizz/effects/gen/gate.cxx @@ -0,0 +1,195 @@ +/* ------------------------------------------------------------ +name: "gate" +Code generated with Faust 2.27.2 (https://faust.grame.fr) +Compilation options: -lang cpp -inpl -scal -ftz 0 +------------------------------------------------------------ */ + +#ifndef __faustGate_H__ +#define __faustGate_H__ + +#ifndef FAUSTFLOAT +#define FAUSTFLOAT float +#endif + +#include +#include +#include + + +#ifndef FAUSTCLASS +#define FAUSTCLASS faustGate +#endif + +#ifdef __APPLE__ +#define exp10f __exp10f +#define exp10 __exp10 +#endif + +class faustGate { + + public: + + float fConst0; + FAUSTFLOAT fHslider0; + FAUSTFLOAT fHslider1; + int fSampleRate; + float fConst1; + float fConst2; + float fRec3[2]; + FAUSTFLOAT fHslider2; + int iVec0[2]; + float fConst3; + FAUSTFLOAT fHslider3; + int iRec4[2]; + float fRec1[2]; + 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 = std::min(192000.0f, std::max(1.0f, float(fSampleRate))); + fConst2 = (1.0f / fConst1); + fConst3 = (fConst1 * fConst0); + } + + void instanceResetUserInterface() { + fHslider0 = FAUSTFLOAT(0.0f); + fHslider1 = FAUSTFLOAT(0.0f); + fHslider2 = FAUSTFLOAT(0.0f); + fHslider3 = FAUSTFLOAT(0.0f); + } + + void instanceClear() { + for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { + fRec3[l0] = 0.0f; + } + for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { + iVec0[l1] = 0; + } + for (int l2 = 0; (l2 < 2); l2 = (l2 + 1)) { + iRec4[l2] = 0; + } + for (int l3 = 0; (l3 < 2); l3 = (l3 + 1)) { + fRec1[l3] = 0.0f; + } + for (int l4 = 0; (l4 < 2); l4 = (l4 + 1)) { + fRec0[l4] = 0.0f; + } + } + + void init(int sample_rate) { + classInit(sample_rate); + instanceInit(sample_rate); + } + void instanceInit(int sample_rate) { + instanceConstants(sample_rate); + instanceResetUserInterface(); + instanceClear(); + } + + faustGate* clone() { + return new faustGate(); + } + + int getSampleRate() { + return fSampleRate; + } + + void buildUserInterface() { + } + + void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { + FAUSTFLOAT* input0 = inputs[0]; + FAUSTFLOAT* output0 = outputs[0]; + float fSlow0 = (fConst0 * float(fHslider0)); + float fSlow1 = (fConst0 * float(fHslider1)); + float fSlow2 = std::min(fSlow0, fSlow1); + int iSlow3 = (std::fabs(fSlow2) < 1.1920929e-07f); + float fSlow4 = (iSlow3 ? 0.0f : std::exp((0.0f - (fConst2 / (iSlow3 ? 1.0f : fSlow2))))); + float fSlow5 = (1.0f - fSlow4); + float fSlow6 = std::pow(10.0f, (0.0500000007f * float(fHslider2))); + int iSlow7 = int((fConst3 * float(fHslider3))); + int iSlow8 = (std::fabs(fSlow0) < 1.1920929e-07f); + float fSlow9 = (iSlow8 ? 0.0f : std::exp((0.0f - (fConst2 / (iSlow8 ? 1.0f : fSlow0))))); + int iSlow10 = (std::fabs(fSlow1) < 1.1920929e-07f); + float fSlow11 = (iSlow10 ? 0.0f : std::exp((0.0f - (fConst2 / (iSlow10 ? 1.0f : fSlow1))))); + for (int i = 0; (i < count); i = (i + 1)) { + float fTemp0 = float(input0[i]); + fRec3[0] = ((fRec3[1] * fSlow4) + (std::fabs(fTemp0) * fSlow5)); + float fRec2 = fRec3[0]; + int iTemp1 = (fRec2 > fSlow6); + iVec0[0] = iTemp1; + iRec4[0] = std::max(int((iSlow7 * (iTemp1 < iVec0[1]))), int((iRec4[1] + -1))); + float fTemp2 = std::fabs(std::max(float(iTemp1), float((iRec4[0] > 0)))); + float fTemp3 = ((fRec0[1] > fTemp2) ? fSlow11 : fSlow9); + fRec1[0] = ((fRec1[1] * fTemp3) + (fTemp2 * (1.0f - fTemp3))); + fRec0[0] = fRec1[0]; + output0[i] = FAUSTFLOAT(fRec0[0]); + fRec3[1] = fRec3[0]; + iVec0[1] = iVec0[0]; + iRec4[1] = iRec4[0]; + fRec1[1] = fRec1[0]; + fRec0[1] = fRec0[0]; + } + } + +}; + +#ifdef FAUST_UIMACROS + + + + #define FAUST_LIST_ACTIVES(p) \ + p(HORIZONTALSLIDER, Threshold, "Threshold", fHslider2, 0.0f, -60.0f, 0.0f, 0.01f) \ + p(HORIZONTALSLIDER, Attack, "Attack", fHslider0, 0.0f, 0.0f, 10.0f, 0.001f) \ + p(HORIZONTALSLIDER, Hold, "Hold", fHslider3, 0.0f, 0.0f, 10.0f, 0.001f) \ + p(HORIZONTALSLIDER, Release, "Release", fHslider1, 0.0f, 0.0f, 5.0f, 0.001f) \ + + #define FAUST_LIST_PASSIVES(p) \ + +#endif + +#endif