diff --git a/scripts/generate_filters.sh b/scripts/generate_filters.sh new file mode 100755 index 00000000..305d3973 --- /dev/null +++ b/scripts/generate_filters.sh @@ -0,0 +1,64 @@ +#!/bin/sh +set -e + +if ! test -d "src"; then + echo "Please run this in the project root directory." + exit 1 +fi + +FAUSTARGS="-double -inpl" + +# support GNU sed only, use gsed on a Mac +test -z "$SED" && SED=sed + +faustgen() { + mkdir -p src/sfizz/gen/filters + local outfile=src/sfizz/gen/filters/sfz"$1".cxx + + local code=`faust $FAUSTARGS -pn sfz"$1" -cn faust"$1" src/sfizz/dsp/filters/sfz_filters.dsp` + + # find variable names of our controls + local cutoffVar=`echo "$code" | $SED -r 's%.*\("Cutoff", &[ \t]*([a-zA-Z0-9_]+).*%\1%;t;d'` + local resoVar=`echo "$code" | $SED -r 's%.*\("Resonance", &[ \t]*([a-zA-Z0-9_]+).*%\1%;t;d'` + local pkshVar=`echo "$code" | $SED -r 's%.*\("Peak/shelf gain", &[ \t]*([a-zA-Z0-9_]+).*%\1%;t;d'` + + # 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" + + # direct access to parameter variables + $SED -r -i 's/\bprivate:/public:/' "$outfile" + # no virtuals please + $SED -r -i 's/\bvirtual\b//' "$outfile" + + # rename the variables for us to access more easily + if test ! -z "$cutoffVar"; then + $SED -r -i 's/\b'"$cutoffVar"'\b/fCutoff/' "$outfile" + fi + if test ! -z "$resoVar"; then + $SED -r -i 's/\b'"$resoVar"'\b/fQ/' "$outfile" + fi + if test ! -z "$pkshVar"; then + $SED -r -i 's/\b'"$pkshVar"'\b/fPkShGain/' "$outfile" + fi +} + +for f in \ + Lpf1p Lpf2p Lpf4p Lpf6p \ + Hpf1p Hpf2p Hpf4p Hpf6p \ + Bpf1p Bpf2p Bpf4p Bpf6p \ + Apf1p \ + Brf1p Brf2p \ + Lsh Hsh Peq \ + Pink \ + Lpf2pSv Hpf2pSv Bpf2pSv Brf2pSv +do + faustgen "$f" + faustgen "2ch$f" +done diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1306e016..265e08f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,7 @@ set (SFIZZ_SOURCES sfizz/Oversampler.cpp sfizz/FloatEnvelopes.cpp sfizz/Logger.cpp + sfizz/SfzFilter.cpp ) include (SfizzSIMDSourceFilesCheck) diff --git a/src/sfizz/SfzFilter.cpp b/src/sfizz/SfzFilter.cpp new file mode 100644 index 00000000..35b3f210 --- /dev/null +++ b/src/sfizz/SfzFilter.cpp @@ -0,0 +1,256 @@ +// 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 + +#include "SfzFilter.h" +#include "SfzFilterDefs.h" +#include "SfzFilterImpls.cxx" +#include +#include + +namespace sfz { + +template +struct Filter::Impl { + FilterType fType = kFilterNone; + + sfzLpf1p fDspLpf1p; + sfzLpf2p fDspLpf2p; + sfzLpf4p fDspLpf4p; + sfzLpf6p fDspLpf6p; + sfzHpf1p fDspHpf1p; + sfzHpf2p fDspHpf2p; + sfzHpf4p fDspHpf4p; + sfzHpf6p fDspHpf6p; + sfzBpf1p fDspBpf1p; + sfzBpf2p fDspBpf2p; + sfzBpf4p fDspBpf4p; + sfzBpf6p fDspBpf6p; + sfzApf1p fDspApf1p; + sfzBrf1p fDspBrf1p; + sfzBrf2p fDspBrf2p; + sfzPink fDspPink; + sfzLpf2pSv fDspLpf2pSv; + sfzHpf2pSv fDspHpf2pSv; + sfzBpf2pSv fDspBpf2pSv; + sfzBrf2pSv fDspBrf2pSv; + sfzLsh fDspLsh; + sfzHsh fDspHsh; + sfzPeq fDspPeq; + + template static void process(F &filter, const float *const in[NCh], float *const out[NCh], float cutoff, float q, float pksh, unsigned nframes); + template static void processModulated(F &filter, const float *const in[NCh], float *const out[NCh], const float *cutoff, const float *q, const float *pksh, unsigned nframes); +}; + +template +Filter::Filter() + : P{new Impl} +{ +} + +template +Filter::~Filter() +{ +} + +template +void Filter::init(double sampleRate) +{ + P->fDspLpf1p.init(sampleRate); + P->fDspLpf2p.init(sampleRate); + P->fDspLpf4p.init(sampleRate); + P->fDspLpf6p.init(sampleRate); + P->fDspHpf1p.init(sampleRate); + P->fDspHpf2p.init(sampleRate); + P->fDspHpf4p.init(sampleRate); + P->fDspHpf6p.init(sampleRate); + P->fDspBpf1p.init(sampleRate); + P->fDspBpf2p.init(sampleRate); + P->fDspBpf4p.init(sampleRate); + P->fDspBpf6p.init(sampleRate); + P->fDspApf1p.init(sampleRate); + P->fDspBrf1p.init(sampleRate); + P->fDspBrf2p.init(sampleRate); + P->fDspPink.init(sampleRate); + P->fDspLpf2pSv.init(sampleRate); + P->fDspHpf2pSv.init(sampleRate); + P->fDspBpf2pSv.init(sampleRate); + P->fDspBrf2pSv.init(sampleRate); + P->fDspLsh.init(sampleRate); + P->fDspHsh.init(sampleRate); + P->fDspPeq.init(sampleRate); +} + +template +void Filter::clear() +{ + switch (P->fType) { + case kFilterNone: break; + case kFilterApf1p: P->fDspApf1p.instanceClear(); break; + case kFilterBpf1p: P->fDspBpf1p.instanceClear(); break; + case kFilterBpf2p: P->fDspBpf2p.instanceClear(); break; + case kFilterBpf4p: P->fDspBpf4p.instanceClear(); break; + case kFilterBpf6p: P->fDspBpf6p.instanceClear(); break; + case kFilterBrf1p: P->fDspBrf1p.instanceClear(); break; + case kFilterBrf2p: P->fDspBrf2p.instanceClear(); break; + case kFilterHpf1p: P->fDspHpf1p.instanceClear(); break; + case kFilterHpf2p: P->fDspHpf2p.instanceClear(); break; + case kFilterHpf4p: P->fDspHpf4p.instanceClear(); break; + case kFilterHpf6p: P->fDspHpf6p.instanceClear(); break; + case kFilterLpf1p: P->fDspLpf1p.instanceClear(); break; + case kFilterLpf2p: P->fDspLpf2p.instanceClear(); break; + case kFilterLpf4p: P->fDspLpf4p.instanceClear(); break; + case kFilterLpf6p: P->fDspLpf6p.instanceClear(); break; + case kFilterPink: P->fDspPink.instanceClear(); break; + case kFilterLpf2pSv: P->fDspLpf2pSv.instanceClear(); break; + case kFilterHpf2pSv: P->fDspHpf2pSv.instanceClear(); break; + case kFilterBpf2pSv: P->fDspBpf2pSv.instanceClear(); break; + case kFilterBrf2pSv: P->fDspBrf2pSv.instanceClear(); break; + case kFilterLsh: P->fDspLsh.instanceClear(); break; + case kFilterHsh: P->fDspHsh.instanceClear(); break; + case kFilterPeq: P->fDspPeq.instanceClear(); break; + } +} + +template +template +void Filter::Impl::process(F &filter, const float *const in[NCh], float *const out[NCh], float cutoff, float q, float pksh, unsigned nframes) +{ + filter.setCutoff(cutoff); + filter.setQ(q); + filter.setPkShGain(pksh); + filter.compute(nframes, const_cast(in), const_cast(out)); +} + +template +void Filter::process(const float *const in[NCh], float *const out[NCh], float cutoff, float q, float pksh, unsigned nframes) +{ + if (P->fType == kFilterNone) { + for (unsigned c = 0; c < NCh; ++c) { + const float *ch_in = in[c]; + float *ch_out = out[c]; + if (ch_in != ch_out) + std::memcpy(ch_out, ch_in, nframes * sizeof(float)); + } + return; + } + + switch (P->fType) { + case kFilterApf1p: P->process(P->fDspApf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf1p: P->process(P->fDspBpf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf2p: P->process(P->fDspBpf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf4p: P->process(P->fDspBpf4p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf6p: P->process(P->fDspBpf6p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBrf1p: P->process(P->fDspBrf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBrf2p: P->process(P->fDspBrf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf1p: P->process(P->fDspHpf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf2p: P->process(P->fDspHpf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf4p: P->process(P->fDspHpf4p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf6p: P->process(P->fDspHpf6p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf1p: P->process(P->fDspLpf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf2p: P->process(P->fDspLpf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf4p: P->process(P->fDspLpf4p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf6p: P->process(P->fDspLpf6p, in, out, cutoff, q, pksh, nframes); break; + case kFilterPink: P->process(P->fDspPink, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf2pSv: P->process(P->fDspLpf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf2pSv: P->process(P->fDspHpf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf2pSv: P->process(P->fDspBpf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterBrf2pSv: P->process(P->fDspBrf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterLsh: P->process(P->fDspLsh, in, out, cutoff, q, pksh, nframes); break; + case kFilterHsh: P->process(P->fDspHsh, in, out, cutoff, q, pksh, nframes); break; + case kFilterPeq: P->process(P->fDspPeq, in, out, cutoff, q, pksh, nframes); break; + default: assert(false); + } +} + +template +template +void Filter::Impl::processModulated(F &filter, const float *const in[NCh], float *const out[NCh], const float *cutoff, const float *q, const float *pksh, unsigned nframes) +{ + unsigned frame = 0; + + while (frame < nframes) { + unsigned current = nframes - frame; + + if (current > kFilterControlInterval) + current = kFilterControlInterval; + + const float *current_in[NCh]; + float *current_out[NCh]; + + for (unsigned c = 0; c < NCh; ++c) { + current_in[c] = in[c] + frame; + current_out[c] = out[c] + frame; + } + + filter.setCutoff(cutoff[frame]); + filter.setQ(q[frame]); + filter.setPkShGain(pksh[frame]); + filter.compute(current, const_cast(current_in), const_cast(current_out)); + + frame += current; + } +} + +template +void Filter::processModulated(const float *const in[NCh], float *const out[NCh], const float *cutoff, const float *q, const float *pksh, unsigned nframes) +{ + if (P->fType == kFilterNone) { + for (unsigned c = 0; c < NCh; ++c) { + const float *ch_in = in[c]; + float *ch_out = out[c]; + if (ch_in != ch_out) + std::memcpy(ch_out, ch_in, nframes * sizeof(float)); + } + return; + } + + switch (P->fType) { + case kFilterApf1p: P->processModulated(P->fDspApf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf1p: P->processModulated(P->fDspBpf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf2p: P->processModulated(P->fDspBpf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf4p: P->processModulated(P->fDspBpf4p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf6p: P->processModulated(P->fDspBpf6p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBrf1p: P->processModulated(P->fDspBrf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterBrf2p: P->processModulated(P->fDspBrf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf1p: P->processModulated(P->fDspHpf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf2p: P->processModulated(P->fDspHpf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf4p: P->processModulated(P->fDspHpf4p, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf6p: P->processModulated(P->fDspHpf6p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf1p: P->processModulated(P->fDspLpf1p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf2p: P->processModulated(P->fDspLpf2p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf4p: P->processModulated(P->fDspLpf4p, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf6p: P->processModulated(P->fDspLpf6p, in, out, cutoff, q, pksh, nframes); break; + case kFilterPink: P->processModulated(P->fDspPink, in, out, cutoff, q, pksh, nframes); break; + case kFilterLpf2pSv: P->processModulated(P->fDspLpf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterHpf2pSv: P->processModulated(P->fDspHpf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterBpf2pSv: P->processModulated(P->fDspBpf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterBrf2pSv: P->processModulated(P->fDspBrf2pSv, in, out, cutoff, q, pksh, nframes); break; + case kFilterLsh: P->processModulated(P->fDspLsh, in, out, cutoff, q, pksh, nframes); break; + case kFilterHsh: P->processModulated(P->fDspHsh, in, out, cutoff, q, pksh, nframes); break; + case kFilterPeq: P->processModulated(P->fDspPeq, in, out, cutoff, q, pksh, nframes); break; + default: assert(false); + } +} + +template +FilterType Filter::type() const +{ + return P->fType; +} + +template +void Filter::setType(FilterType type) +{ + if (P->fType != type) { + P->fType = type; + clear(); + } +} + +template class Filter<1>; +template class Filter<2>; + +} // namespace sfz diff --git a/src/sfizz/SfzFilter.h b/src/sfizz/SfzFilter.h new file mode 100644 index 00000000..d9c36286 --- /dev/null +++ b/src/sfizz/SfzFilter.h @@ -0,0 +1,100 @@ +// 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 + +namespace sfz { + +enum FilterType : int; + +/** + Multi-mode filter for SFZ v2 + Available for mono and stereo. (NCh=1, NCh=2) + + Parameters: + `cutoff`: it's the opcode `filN_cutoff` (Hz) + `q`: it's the opcode `filN_resonance` (dB) + `pksh`: it's the opcode `filN_gain` (dB) + */ +template +class Filter { +public: + Filter(); + ~Filter(); + + /** + Set up the filter constants. + Run it exactly once after instantiating. + */ + void init(double sampleRate); + + /** + Reinitialize the filter memory to zeros. + */ + void clear(); + + /** + Process one cycle of the filter without modulating cutoff or Q. + `cutoff` is a frequency expressed in Hz. + `q` is a resonance expressed in dB. + `pksh` is a peak/shelf gain expressed in dB. + `in[i]` and `out[i]` may refer to identical buffers, for in-place processing + */ + void process(const float *const in[NCh], float *const out[NCh], float cutoff, float q, float pksh, unsigned nframes); + + /** + Process one cycle of the filter with cutoff and Q values varying over time. + `cutoff` is a frequency expressed in Hz. + `q` is a resonance expressed in dB. + `pksh` is a peak/shelf gain expressed in dB. + `in[i]` and `out[i]` may refer to identical buffers, for in-place processing + */ + void processModulated(const float *const in[NCh], float *const out[NCh], const float *cutoff, const float *q, const float *pksh, unsigned nframes); + + /** + Get the type of filter. + */ + FilterType type() const; + + /** + Set the type of filter. + */ + void setType(FilterType type); + +private: + struct Impl; + std::unique_ptr P; +}; + +enum FilterType : int { + kFilterNone, + kFilterApf1p, + kFilterBpf1p, + kFilterBpf2p, + kFilterBpf4p, + kFilterBpf6p, + kFilterBrf1p, + kFilterBrf2p, + kFilterHpf1p, + kFilterHpf2p, + kFilterHpf4p, + kFilterHpf6p, + kFilterLpf1p, + kFilterLpf2p, + kFilterLpf4p, + kFilterLpf6p, + kFilterPink, + kFilterLpf2pSv, + kFilterHpf2pSv, + kFilterBpf2pSv, + kFilterBrf2pSv, + kFilterLsh, + kFilterHsh, + kFilterPeq, +}; + +} // namespace sfz diff --git a/src/sfizz/SfzFilterDefs.h b/src/sfizz/SfzFilterDefs.h new file mode 100644 index 00000000..85a219a6 --- /dev/null +++ b/src/sfizz/SfzFilterDefs.h @@ -0,0 +1,19 @@ +// 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 + +namespace sfz { + +enum { + /** + Minimum interval in frames between recomputations of coefficients of the + modulated filter. The lower, the more CPU resources are consumed. + */ + kFilterControlInterval = 16, +}; + +} // namespace sfz diff --git a/src/sfizz/SfzFilterImpls.cxx b/src/sfizz/SfzFilterImpls.cxx new file mode 100644 index 00000000..519731d5 --- /dev/null +++ b/src/sfizz/SfzFilterImpls.cxx @@ -0,0 +1,153 @@ +// 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 + +struct dsp {}; +struct Meta {}; +struct UI {}; + +#include "gen/filters/sfzApf1p.cxx" +#include "gen/filters/sfzBpf1p.cxx" +#include "gen/filters/sfzBpf2p.cxx" +#include "gen/filters/sfzBpf4p.cxx" +#include "gen/filters/sfzBpf6p.cxx" +#include "gen/filters/sfzBrf1p.cxx" +#include "gen/filters/sfzBrf2p.cxx" +#include "gen/filters/sfzHpf1p.cxx" +#include "gen/filters/sfzHpf2p.cxx" +#include "gen/filters/sfzHpf4p.cxx" +#include "gen/filters/sfzHpf6p.cxx" +#include "gen/filters/sfzLpf1p.cxx" +#include "gen/filters/sfzLpf2p.cxx" +#include "gen/filters/sfzLpf4p.cxx" +#include "gen/filters/sfzLpf6p.cxx" +#include "gen/filters/sfzPink.cxx" +#include "gen/filters/sfzLpf2pSv.cxx" +#include "gen/filters/sfzHpf2pSv.cxx" +#include "gen/filters/sfzBpf2pSv.cxx" +#include "gen/filters/sfzBrf2pSv.cxx" +#include "gen/filters/sfzLsh.cxx" +#include "gen/filters/sfzHsh.cxx" +#include "gen/filters/sfzPeq.cxx" + +#include "gen/filters/sfz2chApf1p.cxx" +#include "gen/filters/sfz2chBpf1p.cxx" +#include "gen/filters/sfz2chBpf2p.cxx" +#include "gen/filters/sfz2chBpf4p.cxx" +#include "gen/filters/sfz2chBpf6p.cxx" +#include "gen/filters/sfz2chBrf1p.cxx" +#include "gen/filters/sfz2chBrf2p.cxx" +#include "gen/filters/sfz2chHpf1p.cxx" +#include "gen/filters/sfz2chHpf2p.cxx" +#include "gen/filters/sfz2chHpf4p.cxx" +#include "gen/filters/sfz2chHpf6p.cxx" +#include "gen/filters/sfz2chLpf1p.cxx" +#include "gen/filters/sfz2chLpf2p.cxx" +#include "gen/filters/sfz2chLpf4p.cxx" +#include "gen/filters/sfz2chLpf6p.cxx" +#include "gen/filters/sfz2chPink.cxx" +#include "gen/filters/sfz2chLpf2pSv.cxx" +#include "gen/filters/sfz2chHpf2pSv.cxx" +#include "gen/filters/sfz2chBpf2pSv.cxx" +#include "gen/filters/sfz2chBrf2pSv.cxx" +#include "gen/filters/sfz2chLsh.cxx" +#include "gen/filters/sfz2chHsh.cxx" +#include "gen/filters/sfz2chPeq.cxx" + +template struct sfzFilter : public F { + void setCutoff(float v) { F::fCutoff = v; } + void setQ(float v) { F::fQ = v; } + void setPkShGain(float) {} +}; + +template struct sfzFilterNoQ : public F { + void setCutoff(float v) { F::fCutoff = v; } + void setQ(float) {} + void setPkShGain(float) {} +}; + +template struct sfzFilterNoCutoff : public F { + void setCutoff(float) {} + void setQ(float) {} + void setPkShGain(float) {} +}; + +template struct sfzFilterEq : public F { + void setCutoff(float v) { F::fCutoff = v; } + void setQ(float v) { F::fQ = v; } + void setPkShGain(float v) { F::fPkShGain = v; } +}; + +template struct sfzLpf1p; +template struct sfzLpf2p; +template struct sfzLpf4p; +template struct sfzLpf6p; +template struct sfzHpf1p; +template struct sfzHpf2p; +template struct sfzHpf4p; +template struct sfzHpf6p; +template struct sfzBpf1p; +template struct sfzBpf2p; +template struct sfzBpf4p; +template struct sfzBpf6p; +template struct sfzApf1p; +template struct sfzBrf1p; +template struct sfzBrf2p; +template struct sfzPink; +template struct sfzLpf2pSv; +template struct sfzHpf2pSv; +template struct sfzBpf2pSv; +template struct sfzBrf2pSv; +template struct sfzLsh; +template struct sfzHsh; +template struct sfzPeq; + +template<> struct sfzLpf1p<1> : public sfzFilterNoQ {}; +template<> struct sfzLpf2p<1> : public sfzFilter {}; +template<> struct sfzLpf4p<1> : public sfzFilter {}; +template<> struct sfzLpf6p<1> : public sfzFilter {}; +template<> struct sfzHpf1p<1> : public sfzFilterNoQ {}; +template<> struct sfzHpf2p<1> : public sfzFilter {}; +template<> struct sfzHpf4p<1> : public sfzFilter {}; +template<> struct sfzHpf6p<1> : public sfzFilter {}; +template<> struct sfzBpf1p<1> : public sfzFilterNoQ {}; +template<> struct sfzBpf2p<1> : public sfzFilter {}; +template<> struct sfzBpf4p<1> : public sfzFilter {}; +template<> struct sfzBpf6p<1> : public sfzFilter {}; +template<> struct sfzApf1p<1> : public sfzFilterNoQ {}; +template<> struct sfzBrf1p<1> : public sfzFilterNoQ {}; +template<> struct sfzBrf2p<1> : public sfzFilter {}; +template<> struct sfzPink<1> : public sfzFilterNoCutoff {}; +template<> struct sfzLpf2pSv<1> : public sfzFilter {}; +template<> struct sfzHpf2pSv<1> : public sfzFilter {}; +template<> struct sfzBpf2pSv<1> : public sfzFilter {}; +template<> struct sfzBrf2pSv<1> : public sfzFilter {}; +template<> struct sfzLsh<1> : public sfzFilterEq {}; +template<> struct sfzHsh<1> : public sfzFilterEq {}; +template<> struct sfzPeq<1> : public sfzFilterEq {}; + +template<> struct sfzLpf1p<2> : public sfzFilterNoQ {}; +template<> struct sfzLpf2p<2> : public sfzFilter {}; +template<> struct sfzLpf4p<2> : public sfzFilter {}; +template<> struct sfzLpf6p<2> : public sfzFilter {}; +template<> struct sfzHpf1p<2> : public sfzFilterNoQ {}; +template<> struct sfzHpf2p<2> : public sfzFilter {}; +template<> struct sfzHpf4p<2> : public sfzFilter {}; +template<> struct sfzHpf6p<2> : public sfzFilter {}; +template<> struct sfzBpf1p<2> : public sfzFilterNoQ {}; +template<> struct sfzBpf2p<2> : public sfzFilter {}; +template<> struct sfzBpf4p<2> : public sfzFilter {}; +template<> struct sfzBpf6p<2> : public sfzFilter {}; +template<> struct sfzApf1p<2> : public sfzFilterNoQ {}; +template<> struct sfzBrf1p<2> : public sfzFilterNoQ {}; +template<> struct sfzBrf2p<2> : public sfzFilter {}; +template<> struct sfzPink<2> : public sfzFilterNoCutoff {}; +template<> struct sfzLpf2pSv<2> : public sfzFilter {}; +template<> struct sfzHpf2pSv<2> : public sfzFilter {}; +template<> struct sfzBpf2pSv<2> : public sfzFilter {}; +template<> struct sfzBrf2pSv<2> : public sfzFilter {}; +template<> struct sfzLsh<2> : public sfzFilterEq {}; +template<> struct sfzHsh<2> : public sfzFilterEq {}; +template<> struct sfzPeq<2> : public sfzFilterEq {}; diff --git a/src/sfizz/dsp/filters/filters_modulable.dsp b/src/sfizz/dsp/filters/filters_modulable.dsp new file mode 100644 index 00000000..d142465d --- /dev/null +++ b/src/sfizz/dsp/filters/filters_modulable.dsp @@ -0,0 +1,72 @@ +// -*- mode: faust; -*- + +declare author "Jean Pierre Cimalando"; +declare license "BSD-2-Clause"; + +import("stdfaust.lib"); +rbj = library("rbj_filters.dsp"); + +//------------------------------------------------------------------------- +// Biquad filter from normalized coefficients +// b0,b1,b2,a1,a2 : normalized coefficients +//------------------------------------------------------------------------- +biquad(b0,b1,b2,a1,a2) = fi.iir((b0,b1,b2),(a1,a2)); + +//------------------------------------------------------------------------- +// Biquad filter using smoothed coefficients +// s : a smoothing function applied to each coefficient +// b0,b1,b2,a1,a2 : normalized coefficients +//------------------------------------------------------------------------- +smoothBiquad(s,b0,b1,b2,a1,a2) = biquad(b0:s,b1:s,b2:s,a1:s,a2:s); + +//------------------------------------------------------------------------- +// RBJ filter of a specific type using smoothed coefficients +// s : a smoothing function applied to each coefficient +// f : cutoff frequency +// g : gain in decibel, for peaking and shelving types only +// q : height of the resonant peak in linear units +//------------------------------------------------------------------------- +rbjLpfSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).LPF,x) : smoothBiquad(s); +rbjHpfSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).HPF,x) : smoothBiquad(s); +rbjBpfSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).BPF,x) : smoothBiquad(s); +rbjNotchSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).notch,x) : smoothBiquad(s); +rbjApfSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).APF,x) : smoothBiquad(s); +rbjPeakingEqSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).peakingEQ,x) : smoothBiquad(s); +rbjPeakingNotchSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).peakNotch,x) : smoothBiquad(s); +rbjLowShelfSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).lowShelf,x) : smoothBiquad(s); +rbjHighShelfSmooth(s,f,g,q,x) = (rbj.filtercoeff(f,g,q).highShelf,x) : smoothBiquad(s); + +//------------------------------------------------------------------------- +// 1-pole low-pass filter +// s : a smoothing function applied to each coefficient +// f : cutoff frequency +//------------------------------------------------------------------------- +lp1Smooth(s,f) = fi.iir((1-p),(0-p)) with { + p = exp(-2.*ma.PI*f/ma.SR) : s; +}; + +//------------------------------------------------------------------------- +// 1-pole high-pass filter +// s : a smoothing function applied to each coefficient +// f : cutoff frequency +//------------------------------------------------------------------------- +hp1Smooth(s,f) = fi.iir((0.5*(1.+p),-0.5*(1+p)),(0.-p)) with { + p = exp(-2.*ma.PI*f/ma.SR) : s; +}; + +//------------------------------------------------------------------------- +// 1-pole all-pass filter +// s : a smoothing function applied to each coefficient +// f : cutoff frequency +//------------------------------------------------------------------------- +ap1Smooth(s,f) = fi.iir((a,1.),(a)) with { + a = (-1.+2.*ma.PI*f/ma.SR) : s; +}; + +//------------------------------------------------------------------------------ +// Example +//------------------------------------------------------------------------------ +// process = rbjLpfSmooth(si.smoo, cutoff, 0.0, resonance : ba.db2linear) with { +// cutoff = hslider("[1] Cutoff [unit:Hz] [scale:log]", 440.0, 50.0, 10000.0, 1.0); +// resonance = hslider("[2] Resonance [unit:dB]", 0.0, 0.0, 40.0, 0.1); +// }; diff --git a/src/sfizz/dsp/filters/rbj_filters.dsp b/src/sfizz/dsp/filters/rbj_filters.dsp new file mode 100644 index 00000000..1aca5bda --- /dev/null +++ b/src/sfizz/dsp/filters/rbj_filters.dsp @@ -0,0 +1,212 @@ +/** + Note(jpc): This is RBJ filter extracted from faustlibraries master branch. + Unlike some prior versions found in faust 2.x, this one has been + permissively relicensed. + */ + +/************************************************************************ +************************************************************************ +FAUST library file +Copyright (C) 2019-2020 GRAME, Centre National de Creation Musicale +--------------------------------------------------------------------- +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with the GNU C Library; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +EXCEPTION TO THE LGPL LICENSE : As a special exception, you may create a +larger FAUST program which directly or indirectly imports this library +file and still distribute the compiled code generated by the FAUST +compiler, or a modified version of this compiled code, under your own +copyright and license. This EXCEPTION TO THE LGPL LICENSE explicitly +grants you the right to freely choose the license for the resulting +compiled code. In particular the resulting compiled code has no obligation +to be LGPL or GPL. For example you are free to choose a commerci +************************************************************************ +************************************************************************/ + +declare name "MaxMSP compatibility Library"; +declare author "GRAME"; +declare copyright "GRAME"; +declare version "1.1"; +declare license "LGPL with exception"; + +ba = library("basics.lib"); +ma = library("maths.lib"); + +//------------------------------------------------------------------------- +// +// Implementation of MaxMSP filtercoeff +// +// from : Cookbook formulae for audio EQ biquad filter coefficients +// by : Robert Bristow-Johnson +// URL : http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt +// +//------------------------------------------------------------------------- + +filtercoeff(f0, dBgain, Q) = environment +{ + //---------------------------------------- + // biquad coeffs for various filters + // usage : filtercoeff(f0, dBgain, Q).LPF + //---------------------------------------- + + LPF = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = (1 - cos(w0))/2; + b1 = 1 - cos(w0); + b2 = (1 - cos(w0))/2; + a0 = 1 + alpha; + a1 = -2*cos(w0); + a2 = 1 - alpha; + }; + + HPF = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = (1 + cos(w0))/2; + b1 = -1 - cos(w0); + b2 = (1 + cos(w0))/2; + a0 = 1 + alpha; + a1 = -2*cos(w0); + a2 = 1 - alpha; + }; + + BPF = rbjcoef(a0, a1, a2, b0, b1, b2) // constant 0 dB peak gain + with { + b0 = alpha; + b1 = 0; + b2 = -alpha; + a0 = 1 + alpha; + a1 = -2*cos(w0); + a2 = 1 - alpha; + }; + + notch = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = 1; + b1 = -2*cos(w0); + b2 = 1; + a0 = 1 + alpha; + a1 = -2*cos(w0); + a2 = 1 - alpha; + }; + + APF = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = 1 - alpha; + b1 = -2*cos(w0); + b2 = 1 + alpha; + a0 = 1 + alpha; + a1 = -2*cos(w0); + a2 = 1 - alpha; + }; + + peakingEQ = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = 1 + alpha*A; + b1 = -2*cos(w0); + b2 = 1 - alpha*A; + a0 = 1 + alpha/A; + a1 = -2*cos(w0); + a2 = 1 - alpha/A; + }; + + peakNotch = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = 1 + alpha*G; + b1 = -2*cos(w0); + b2 = 1 - alpha*G; + a0 = 1 + alpha/G; + a1 = -2*cos(w0); + a2 = 1 - alpha/G; + }; + + lowShelf = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = A*((A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha); + b1 = 2*A*((A-1) - (A+1)*cos(w0)); + b2 = A*((A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha); + a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha; + a1 = -2*((A-1) + (A+1)*cos(w0)); + a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha; + }; + + highShelf = rbjcoef(a0, a1, a2, b0, b1, b2) + with { + b0 = A*((A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha); + b1 = -2*A*((A-1) + (A+1)*cos(w0)); + b2 = A*((A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha); + a0 = (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha; + a1 = 2*((A-1) - (A+1)*cos(w0)); + a2 = (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha; + }; + + // --------------------- implementation ------------------------------ + + // convert rbj coeffs to biquad coeffs + rbjcoef(a0,a1,a2,b0,b1,b2) = (b0/a0, b1/a0, b2/a0, a1/a0, a2/a0); + + // common values +// alpha = sin(w0)/(2*Q); +// w0 = 2*ma.PI*f0/Fs; + alpha = sin(w0)/(2*max(0.001,Q)); + w0 = 2*ma.PI*max(0,f0)/Fs; + Fs = ma.SR; + A = 10^(dBgain/40); // (for peaking and shelving EQ filters only) + G = sqrt(max(0.00001, dBgain)); // When gain is a linear values (i.e. not in dB) +}; + + +//------------------------------------------------------------------------- +// Implementation of MaxMSP biquad~ +// y[n] = a0 * x[n] + a1 * x[n-1] + a2 * x[n-2] - b1 * y[n-1] - b2 * y[n-2] +//------------------------------------------------------------------------- + +biquad(x,a0,a1,a2,b1,b2) = x : + ~ ((-1)*conv2(b1, b2)) : conv3(a0, a1, a2) + with { + conv2(c0,c1,x) = c0*x+c1*x'; + conv3(c0,c1,c2,x) = c0*x+c1*x'+c2*x''; + }; + +//------------------------------------------------------------------------- +// +// Filters using filtercoeff and biquad +// +//------------------------------------------------------------------------- + +// Low Pass Filter +LPF(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).LPF : biquad; + +// High Pass Filter +HPF(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).HPF : biquad; + +// Band Pass Filter +BPF(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).BPF : biquad; + +// notch Filter +notch(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).notch : biquad; + +// All Pass Filter +APF(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).APF : biquad; + +// ???? +peakingEQ(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).peakingEQ : biquad; + +// Max peakNotch is like peakingEQ but with a linear gain +peakNotch(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).peakNotch : biquad; + +// ???? +lowShelf(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).lowShelf : biquad; + +// ???? +highShelf(x, f0, gain, Q) = x , filtercoeff(f0,gain,Q).highShelf : biquad; diff --git a/src/sfizz/dsp/filters/sallenkey_modulable.dsp b/src/sfizz/dsp/filters/sallenkey_modulable.dsp new file mode 100644 index 00000000..9fce5935 --- /dev/null +++ b/src/sfizz/dsp/filters/sallenkey_modulable.dsp @@ -0,0 +1,140 @@ +/** + Note(jpc): this is a personal edit of the Sallen-Key state-variable filter + from `vaeffects.lib`. This changes: + - the frequency control, now in Hz instead of 0-1 + - the smooth parameter, allowing for slower parameter transitions + while keeping some expensive computation out of the frame loop +*/ + +import("stdfaust.lib"); + +declare author "Eric Tarr"; +declare license "MIT-style STK-4.3 license"; + +//================================Sallen Key Filters====================================== +// The following filters were implemented based on VA models of synthesizer +// filters. +// +// The modeling approach is based on a Topology Preserving Transform (TPT) to +// resolve the delay-free feedback loop in the corresponding analog filters. +// +// The primary processing block used to build other filters (Moog, Korg, etc.) is +// based on a 1st-order Sallen-Key filter. +// +// The filters included in this script are 1st-order LPF/HPF and 2nd-order +// state-variable filters capable of LPF, HPF, and BPF. +// +// #### Resources: +// +// * Vadim Zavalishin (2018) "The Art of VA Filter Design", v2.1.0 +// +// * Will Pirkle (2014) "Resolving Delay-Free Loops in Recursive Filters Using +// the Modified Härmä Method", AES 137 +// * Description and diagrams of 1st- and 2nd-order TPT filters: +// +//======================================================================================== + +//------------------`(fi.)sallenKey2ndOrder`----------------- +// Sallen-Key generic multi-outputs 2nd order filter. +// +// This is a 2nd-order Sallen-Key state-variable filter. The idea is that by +// "tapping" into different points in the circuit, different filters +// (LPF,BPF,HPF) can be achieved. See Figure 4.6 of +// +// +// This is also a good example of the next step for generalizing the Faust +// programming approach used for all these VA filters. In this case, there are +// three things to calculate each recursive step (y,s1,s2). For each thing, the +// circuit is only calculated up to that point. +// +// Comparing the LPF to BPF, the output signal (y) is calculated similarly. +// Except, the output of the BPF stops earlier in the circuit. Similarly, the +// states (s1 and s2) only differ in that s2 includes a couple more terms +// beyond what is used for s1. +// +// #### Usage +// +// ``` +// _ : sallenKey2ndOrder(smooth,freq,Q) : _,_,_ +// ``` +// +// Where: +// +// * `freq`: cutoff frequency +// * `Q`: q +//--------------------------------------------------------------------- +sallenKey2ndOrder(smooth,freq,Q) = _<:(s1,s2,ylpf,ybpf,yhpf) : !,!,_,_,_ +letrec{ + 's1 = -(s2):-(s1*FBs1):*(alpha0):*(g*2):+(s1); + 's2 = -(s2):-(s1*FBs1):*(alpha0):*(g):+(s1):*(g*2):+(s2); + // Compute the LPF, BPF, HPF outputs + 'ylpf = -(s2):-(s1*FBs1):*(alpha0):*(g*2):+(s1):*(g):+(s2); + 'ybpf = -(s2):-(s1*FBs1):*(alpha0):*(g):+(s1); + 'yhpf = -(s2):-(s1*FBs1):*(alpha0); +} +with{ + wd = 2*ma.PI*freq; + T = 1/ma.SR; + wa = (2/T)*tan(wd*T/2); + g = (wa*T/2):smooth; + G = g/(1.0 + g); + R = 1/(2*Q); + FBs1 = (2*R+g):smooth; + alpha0 = (1/(1 + 2*R*g + g*g)):smooth; +}; + +//------------------`(fi.)sallenKey2ndOrderLPF`----------------- +// Sallen-Key 2nd order lowpass filter (see description above). +// +// +// #### Usage +// +// ``` +// _ : sallenKey2ndOrderLPF(smooth,freq,Q) : _ +// ``` +// +// Where: +// +// * `freq`: cutoff frequency +// * `Q`: q +//--------------------------------------------------------------------- +// Specialize the generic implementation: keep the first LPF output, the compiler will only generate the needed code +sallenKey2ndOrderLPF(smooth,freq,Q) = sallenKey2ndOrder(smooth,freq,Q) : _,!,!; + + +//------------------`(fi.)sallenKey2ndOrderBPF`----------------- +// Sallen-Key 2nd order bandpass filter (see description above). +// +// +// #### Usage +// +// ``` +// _ : sallenKey2ndOrderBPF(smooth,freq,Q) : _ +// ``` +// +// Where: +// +// * `freq`: cutoff frequency +// * `Q`: q +//--------------------------------------------------------------------- +// Specialize the generic implementation: keep the second BPF output, the compiler will only generate the needed code +sallenKey2ndOrderBPF(smooth,freq,Q) = sallenKey2ndOrder(smooth,freq,Q) : !,_,!; + + +//------------------`(fi.)sallenKey2ndOrderHPF`----------------- +// Sallen-Key 2nd order highpass filter (see description above). +// +// +// #### Usage +// +// ``` +// _ : sallenKey2ndOrderHPF(smooth,freq,Q) : _ +// ``` +// +// Where: +// +// * `freq`: cutoff frequency +// * `Q`: q +//--------------------------------------------------------------------- +// Specialize the generic implementation: keep the third HPF output, the compiler will only generate the needed code +sallenKey2ndOrderHPF(smooth,freq,Q) = sallenKey2ndOrder(smooth,freq,Q) : !,!,_; diff --git a/src/sfizz/dsp/filters/sfz_filters.dsp b/src/sfizz/dsp/filters/sfz_filters.dsp new file mode 100644 index 00000000..27aa26e3 --- /dev/null +++ b/src/sfizz/dsp/filters/sfz_filters.dsp @@ -0,0 +1,128 @@ +// -*- mode: faust; -*- + +declare author "Jean Pierre Cimalando"; +declare license "BSD-2-Clause"; + +import("stdfaust.lib"); +fm = library("filters_modulable.dsp"); +sk = library("sallenkey_modulable.dsp"); + +//============================================================================== +// Generators + +// the SFZ *noise generator +sfzNoise = no.noise : *(gate) : *(0.25); + +//============================================================================== +// Filters + +// the SFZ lowpass 1-pole filter +sfzLpf1p = fm.lp1Smooth(smoothCoefs,cutoff); + +// the SFZ lowpass 2-pole filter +sfzLpf2p = fm.rbjLpfSmooth(smoothCoefs,cutoff,0.,Q); + +// the SFZ lowpass 4-pole filter +sfzLpf4p = sfzLpf2p : sfzLpf2p; + +// the SFZ lowpass 6-pole filter +sfzLpf6p = sfzLpf2p : sfzLpf2p : sfzLpf2p; + +// the SFZ highpass 1-pole filter +sfzHpf1p = fm.hp1Smooth(smoothCoefs,cutoff); + +// the SFZ highpass 2-pole filter +sfzHpf2p = fm.rbjHpfSmooth(smoothCoefs,cutoff,0.,Q); + +// the SFZ highpass 4-pole filter +sfzHpf4p = sfzHpf2p : sfzHpf2p; + +// the SFZ highpass 6-pole filter +sfzHpf6p = sfzHpf2p : sfzHpf2p : sfzHpf2p; + +// the SFZ bandpass 1-pole filter +sfzBpf1p = sfzLpf1p : sfzHpf1p; + +// the SFZ bandpass 2-pole filter +sfzBpf2p = fm.rbjBpfSmooth(smoothCoefs,cutoff,0.,Q); + +// the SFZ bandpass 4-pole filter +// Note: bpf_4p not in specification but here anyway +sfzBpf4p = sfzBpf2p : sfzBpf2p; + +// the SFZ bandpass 6-pole filter +// Note: bpf_6p not in specification but here anyway +sfzBpf6p = sfzBpf2p : sfzBpf2p : sfzBpf2p; + +// the SFZ allpass 1-pole filter +sfzApf1p = fm.ap1Smooth(smoothCoefs,cutoff); + +// the SFZ notch 1-pole filter +// Note: this thing is my invention, may not be correct. +// in Sforzando, 1p seems implemented the same as 2p. +sfzBrf1p = _ <: (_, (sfzApf1p : sfzApf1p)) :> +; + +// the SFZ notch 2-pole filter +sfzBrf2p = fm.rbjNotchSmooth(smoothCoefs,cutoff,0.,Q); + +// the SFZ pink filter +sfzPink = no.pink_filter; + +// the SFZ 2-pole state-variable lowpass filter +sfzLpf2pSv = sk.sallenKey2ndOrderLPF(smoothCoefs,cutoff,Q); + +// the SFZ 2-pole state-variable highpass filter +sfzHpf2pSv = sk.sallenKey2ndOrderHPF(smoothCoefs,cutoff,Q); + +// the SFZ 2-pole state-variable bandpass filter +// Note: attenuate in order to have the resonant peak at 0 dB (same as sfzBpf2p) +sfzBpf2pSv = sk.sallenKey2ndOrderBPF(smoothCoefs,cutoff,Q) : *((1./Q):smoothCoefs); + +// the SFZ 2-pole state-variable notch filter +sfzBrf2pSv = _ <: (sfzLpf2pSv, sfzHpf2pSv) :> +; + +// the SFZ low-shelf filter +sfzLsh = fm.rbjLowShelfSmooth(smoothCoefs,cutoff,pkShGain,Q); + +// the SFZ high-shelf filter +sfzHsh = fm.rbjHighShelfSmooth(smoothCoefs,cutoff,pkShGain,Q); + +// the SFZ peaking EQ filter +sfzPeq = fm.rbjPeakingEqSmooth(smoothCoefs,cutoff,pkShGain,Q); + +//============================================================================== +// Filters (stereo) + +sfz2chLpf1p = par(i,2,sfzLpf1p); +sfz2chLpf2p = par(i,2,sfzLpf2p); +sfz2chLpf4p = par(i,2,sfzLpf4p); +sfz2chLpf6p = par(i,2,sfzLpf6p); +sfz2chHpf1p = par(i,2,sfzHpf1p); +sfz2chHpf2p = par(i,2,sfzHpf2p); +sfz2chHpf4p = par(i,2,sfzHpf4p); +sfz2chHpf6p = par(i,2,sfzHpf6p); +sfz2chBpf1p = par(i,2,sfzBpf1p); +sfz2chBpf2p = par(i,2,sfzBpf2p); +sfz2chBpf4p = par(i,2,sfzBpf4p); +sfz2chBpf6p = par(i,2,sfzBpf6p); +sfz2chApf1p = par(i,2,sfzApf1p); +sfz2chBrf1p = par(i,2,sfzBrf1p); +sfz2chBrf2p = par(i,2,sfzBrf2p); +sfz2chPink = par(i,2,sfzPink); +sfz2chLpf2pSv = par(i,2,sfzLpf2pSv); +sfz2chHpf2pSv = par(i,2,sfzHpf2pSv); +sfz2chBpf2pSv = par(i,2,sfzBpf2pSv); +sfz2chBrf2pSv = par(i,2,sfzBrf2pSv); +sfz2chLsh = par(i,2,sfzLsh); +sfz2chHsh = par(i,2,sfzHsh); +sfz2chPeq = par(i,2,sfzPeq); + +//============================================================================== +// Filter parameters + +cutoff = hslider("[01] Cutoff [unit:Hz] [scale:log]", 440.0, 50.0, 10000.0, 1.0); +Q = vslider("[02] Resonance [unit:dB]", 0.0, 0.0, 40.0, 0.1) : ba.db2linear; +pkShGain = vslider("[03] Peak/shelf gain [unit:dB]", 0.0, 0.0, 40.0, 0.1); + +// smoothing function to prevent fast changes of filter coefficients +smoothCoefs = si.smoo; // TODO check if this is appropriate otherwise replace