diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 58b366e4..76d7d7ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,10 @@ set (SFIZZ_SOURCES sfizz/effects/Eq.cpp sfizz/effects/Apan.cpp sfizz/effects/Lofi.cpp + sfizz/effects/Limiter.cpp + sfizz/effects/Rectify.cpp + sfizz/effects/Gain.cpp + sfizz/effects/Width.cpp ) include (SfizzSIMDSourceFiles) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 3398cc3d..9facbfcb 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -14,6 +14,10 @@ #include "effects/Eq.h" #include "effects/Apan.h" #include "effects/Lofi.h" +#include "effects/Limiter.h" +#include "effects/Rectify.h" +#include "effects/Gain.h" +#include "effects/Width.h" #include namespace sfz { @@ -25,6 +29,12 @@ void EffectFactory::registerStandardEffectTypes() registerEffectType("eq", fx::Eq::makeInstance); registerEffectType("apan", fx::Apan::makeInstance); registerEffectType("lofi", fx::Lofi::makeInstance); + registerEffectType("limiter", fx::Limiter::makeInstance); + + // extensions (book) + registerEffectType("rectify", fx::Rectify::makeInstance); + registerEffectType("gain", fx::Gain::makeInstance); + registerEffectType("width", fx::Width::makeInstance); } void EffectFactory::registerEffectType(absl::string_view name, Effect::MakeInstance& make) diff --git a/src/sfizz/effects/Gain.cpp b/src/sfizz/effects/Gain.cpp new file mode 100644 index 00000000..e5b46acc --- /dev/null +++ b/src/sfizz/effects/Gain.cpp @@ -0,0 +1,73 @@ +// 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): this effect is book-only, mentioned but not documented + + Note(jpc): implementation status + +- [x] gain +- [ ] gain_oncc + */ + +#include "Gain.h" +#include "Opcode.h" +#include "SIMDHelpers.h" +#include "absl/memory/memory.h" + +namespace sfz { +namespace fx { + + void Gain::setSampleRate(double sampleRate) + { + (void)sampleRate; + } + + void Gain::setSamplesPerBlock(int samplesPerBlock) + { + _tempBuffer.resize(samplesPerBlock); + } + + void Gain::clear() + { + } + + void Gain::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + const float baseGain = _gain; + + absl::Span gains = _tempBuffer.getSpan(0); + std::fill(gains.begin(), gains.end(), baseGain); + + // to linear + for (unsigned i = 0; i < nframes; ++i) + gains[i] = std::pow(10.0f, 0.05f * gains[i]); + + for (unsigned c = 0; c < EffectChannels; ++c) { + absl::Span input { inputs[c], nframes }; + absl::Span output { outputs[c], nframes }; + sfz::applyGain(absl::Span { gains }, input, output); + } + } + + std::unique_ptr Gain::makeInstance(absl::Span members) + { + auto fx = absl::make_unique(); + + for (const Opcode& opc : members) { + switch (opc.lettersOnlyHash) { + case hash("gain"): + setValueFromOpcode(opc, fx->_gain, {-96.0f, 96.0f}); + break; + } + } + + return CXX11_MOVE(fx); + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Gain.h b/src/sfizz/effects/Gain.h new file mode 100644 index 00000000..20a95f59 --- /dev/null +++ b/src/sfizz/effects/Gain.h @@ -0,0 +1,50 @@ +// 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" + +namespace sfz { +namespace fx { + + /** + * @brief Gain effect + */ + class Gain : public Effect { + public: + /** + * @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 Copy the input signal to the output + */ + 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: + float _gain = 0; // in dB + AudioBuffer _tempBuffer { 1, config::defaultSamplesPerBlock }; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Limiter.cpp b/src/sfizz/effects/Limiter.cpp new file mode 100644 index 00000000..70f317d8 --- /dev/null +++ b/src/sfizz/effects/Limiter.cpp @@ -0,0 +1,85 @@ +// 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 + + Complete (no opcodes) +*/ + +#include "Limiter.h" +#include "Opcode.h" +#include "AudioSpan.h" +#include "absl/memory/memory.h" + +static constexpr int _oversampling = 2; +#include "gen/limiter.cpp" + +namespace sfz { +namespace fx { + + Limiter::Limiter() + : _limiter(new faustLimiter) + { + _limiter->instanceResetUserInterface(); + } + + Limiter::~Limiter() + { + } + + void Limiter::setSampleRate(double sampleRate) + { + _limiter->classInit(sampleRate); + _limiter->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) { + _downsampler2x[c].set_coefs(coefs2x); + _upsampler2x[c].set_coefs(coefs2x); + } + + clear(); + } + + void Limiter::setSamplesPerBlock(int samplesPerBlock) + { + _tempBuffer2x.resize(2 * samplesPerBlock); + } + + void Limiter::clear() + { + _limiter->instanceClear(); + } + + void Limiter::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + auto inOut2x = AudioSpan( _tempBuffer2x).first(2 * nframes); + + for (unsigned c = 0; c < EffectChannels; ++c) + _upsampler2x[c].process_block(inOut2x.getSpan(c).data(), inputs[c], nframes); + + _limiter->compute(2 * nframes, inOut2x, inOut2x); + + for (unsigned c = 0; c < EffectChannels; ++c) + _downsampler2x[c].process_block(outputs[c], inOut2x.getSpan(c).data(), nframes); + } + + std::unique_ptr Limiter::makeInstance(absl::Span members) + { + auto fx = absl::make_unique(); + + for (const Opcode& opc : members) { + // no opcodes + (void)opc; + } + + return CXX11_MOVE(fx); + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Limiter.h b/src/sfizz/effects/Limiter.h new file mode 100644 index 00000000..30737377 --- /dev/null +++ b/src/sfizz/effects/Limiter.h @@ -0,0 +1,58 @@ +// 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" +class faustLimiter; + +namespace sfz { +namespace fx { + + /** + * @brief Limiter effect + */ + class Limiter : public Effect { + public: + Limiter(); + ~Limiter(); + + /** + * @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: + std::unique_ptr _limiter; + AudioBuffer _tempBuffer2x { 2, 2 * config::defaultSamplesPerBlock }; + hiir::Downsampler2xFpu<12> _downsampler2x[EffectChannels]; + hiir::Upsampler2xFpu<12> _upsampler2x[EffectChannels]; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Rectify.cpp b/src/sfizz/effects/Rectify.cpp new file mode 100644 index 00000000..1f88a771 --- /dev/null +++ b/src/sfizz/effects/Rectify.cpp @@ -0,0 +1,106 @@ +// 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): this effect is book-only, mentioned but not documented + + Note(jpc): implementation status + +- [x] rectify_mode +- [x] rectify +- [ ] rectify_oncc + */ + +#include "Rectify.h" +#include "Opcode.h" + +namespace sfz { +namespace fx { + + Rectify::Rectify() + { + } + + void Rectify::setSampleRate(double sampleRate) + { + (void)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) { + _downsampler2x[c].set_coefs(coefs2x); + _upsampler2x[c].set_coefs(coefs2x); + } + } + + void Rectify::setSamplesPerBlock(int samplesPerBlock) + { + _tempBuffer.resize(samplesPerBlock); + } + + void Rectify::clear() + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _downsampler2x[c].clear_buffers(); + _upsampler2x[c].clear_buffers(); + } + } + + void Rectify::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + // Note(jpc) I define opcode `rectify` to be a mix amount. + // half-wave rectification is achieved simply by halving it. + const float baseAmount = _amount * (_full ? 1.0f : 0.5f); + + absl::Span amounts = _tempBuffer.getSpan(0); + std::fill(amounts.begin(), amounts.end(), baseAmount); + + for (unsigned c = 0; c < EffectChannels; ++c) { + const float *input = inputs[c]; + float *output = outputs[c]; + + auto &up2x = _upsampler2x[c]; + auto &down2x = _downsampler2x[c]; + + for (unsigned i = 0; i < nframes; ++i) { + const float amount = normalizePercents(amounts[i]); + float in = input[i]; + + float in2x[2]; + up2x.process_sample(in2x[0], in2x[1], in); + + float out2x[2]; + out2x[0] = amount * std::fabs(in2x[0]) + (1.0f - amount) * in2x[0]; + out2x[1] = amount * std::fabs(in2x[1]) + (1.0f - amount) * in2x[1]; + + output[i] = down2x.process_sample(out2x); + } + } + } + + std::unique_ptr Rectify::makeInstance(absl::Span members) + { + auto fx = absl::make_unique(); + + for (const Opcode& opc : members) { + switch (opc.lettersOnlyHash) { + case hash("rectify_mode"): + if (opc.value == "full") + fx->_full = true; + else if (opc.value == "half") + fx->_full = false; + break; + case hash("rectify"): + setValueFromOpcode(opc, fx->_amount, { 0.0, 100.0 }); + break; + } + } + + return CXX11_MOVE(fx); + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Rectify.h b/src/sfizz/effects/Rectify.h new file mode 100644 index 00000000..b6c27a84 --- /dev/null +++ b/src/sfizz/effects/Rectify.h @@ -0,0 +1,58 @@ +// 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" + +namespace sfz { +namespace fx { + + /** + * @brief Half-wave and full-wave rectifier + */ + class Rectify : public Effect { + public: + Rectify(); + + /** + * @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 Copy the input signal to the output + */ + 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: + AudioBuffer _tempBuffer { 1, config::defaultSamplesPerBlock }; + hiir::Downsampler2xFpu<12> _downsampler2x[2]; + hiir::Upsampler2xFpu<12> _upsampler2x[2]; + + float _amount = 0; + bool _full = false; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Width.cpp b/src/sfizz/effects/Width.cpp new file mode 100644 index 00000000..8c3893ff --- /dev/null +++ b/src/sfizz/effects/Width.cpp @@ -0,0 +1,80 @@ +// 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): this effect is book-only, mentioned but not documented + + Note(jpc): implementation status + +- [x] width +- [ ] width_oncc + */ + +#include "Width.h" +#include "Opcode.h" +#include "SIMDHelpers.h" +#include "absl/memory/memory.h" + +namespace sfz { +namespace fx { + + void Width::setSampleRate(double sampleRate) + { + (void)sampleRate; + } + + void Width::setSamplesPerBlock(int samplesPerBlock) + { + _tempBuffer.resize(samplesPerBlock); + } + + void Width::clear() + { + } + + void Width::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + const float baseWidth = _width; + + absl::Span widths = _tempBuffer.getSpan(0); + std::fill(widths.begin(), widths.end(), baseWidth); + + absl::Span input1 { inputs[0], nframes }; + absl::Span input2 { inputs[1], nframes }; + absl::Span output1 { outputs[0], nframes }; + absl::Span output2 { outputs[1], nframes }; + + for (unsigned i = 0; i < nframes; ++i) { + const float l = input1[i]; + const float r = input2[i]; + + const float w = clamp((widths[i] + 100.0f) * 0.005f, 0.0f, 1.0f); + const float coeff1 = _internals::panLookup(w); + const float coeff2 = _internals::panLookup(1.0f - w); + + output1[i] = l * coeff2 + r * coeff1; + output2[i] = l * coeff1 + r * coeff2; + } + } + + std::unique_ptr Width::makeInstance(absl::Span members) + { + auto fx = absl::make_unique(); + + for (const Opcode& opc : members) { + switch (opc.lettersOnlyHash) { + case hash("width"): + setValueFromOpcode(opc, fx->_width, {-100.0f, 100.0f}); + break; + } + } + + return CXX11_MOVE(fx); + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Width.h b/src/sfizz/effects/Width.h new file mode 100644 index 00000000..82ebb7b8 --- /dev/null +++ b/src/sfizz/effects/Width.h @@ -0,0 +1,50 @@ +// 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" + +namespace sfz { +namespace fx { + + /** + * @brief Stereo width effect + */ + class Width : public Effect { + public: + /** + * @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 Copy the input signal to the output + */ + 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: + float _width = 100; + AudioBuffer _tempBuffer { 1, config::defaultSamplesPerBlock }; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/dsp/limiter.dsp b/src/sfizz/effects/dsp/limiter.dsp new file mode 100644 index 00000000..cbb14b26 --- /dev/null +++ b/src/sfizz/effects/dsp/limiter.dsp @@ -0,0 +1,11 @@ +import("stdfaust.lib"); + +limiter(x) = gain*x with { + att = 0.0008 * over; + rel = 0.5 * over; + over = fconstant(int _oversampling, ); + peak = x : an.amp_follower_ud(att, rel); + gain = ba.if(peak>1.0, 1.0/peak, 1.0) : si.smooth(ba.tau2pole(0.5*att)); +}; + +process = limiter, limiter; diff --git a/src/sfizz/effects/gen/limiter.cpp b/src/sfizz/effects/gen/limiter.cpp new file mode 100644 index 00000000..420f6728 --- /dev/null +++ b/src/sfizz/effects/gen/limiter.cpp @@ -0,0 +1,135 @@ +/* ------------------------------------------------------------ +name: "limiter" +Code generated with Faust 2.20.2 (https://faust.grame.fr) +Compilation options: -lang cpp -inpl -scal -ftz 0 +------------------------------------------------------------ */ + +#ifndef __faustLimiter_H__ +#define __faustLimiter_H__ + +#ifndef FAUSTFLOAT +#define FAUSTFLOAT float +#endif + +#include +#include +#include + +#ifndef FAUSTCLASS +#define FAUSTCLASS faustLimiter +#endif + +#ifdef __APPLE__ +#define exp10f __exp10f +#define exp10 __exp10 +#endif + +class faustLimiter { + +private: + int fSampleRate; + float fConst0; + float fConst1; + float fConst2; + float fConst3; + float fConst4; + float fConst5; + float fConst6; + float fRec2[2]; + float fRec1[2]; + float fRec0[2]; + float fRec5[2]; + float fRec4[2]; + float fRec3[2]; + +public: + static void classInit(int sample_rate) + { + (void)sample_rate; + } + + void instanceConstants(int sample_rate) + { + fSampleRate = sample_rate; + fConst0 = (std::min(192000.0f, std::max(1.0f, float(fSampleRate))) * float(_oversampling)); + fConst1 = std::exp((0.0f - (2500.0f / fConst0))); + fConst2 = (1.0f - fConst1); + fConst3 = std::exp((0.0f - (1250.0f / fConst0))); + fConst4 = (1.0f - fConst3); + fConst5 = std::exp((0.0f - (2.0f / fConst0))); + fConst6 = (1.0f - fConst5); + } + + void instanceResetUserInterface() + { + } + + 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; + } + for (int l3 = 0; (l3 < 2); l3 = (l3 + 1)) { + fRec5[l3] = 0.0f; + } + for (int l4 = 0; (l4 < 2); l4 = (l4 + 1)) { + fRec4[l4] = 0.0f; + } + for (int l5 = 0; (l5 < 2); l5 = (l5 + 1)) { + fRec3[l5] = 0.0f; + } + } + + void init(int sample_rate) + { + classInit(sample_rate); + instanceInit(sample_rate); + } + void instanceInit(int sample_rate) + { + instanceConstants(sample_rate); + instanceResetUserInterface(); + instanceClear(); + } + + int getSampleRate() + { + return fSampleRate; + } + + void compute(int count, const FAUSTFLOAT* const* inputs, FAUSTFLOAT* const* outputs) + { + const FAUSTFLOAT* input0 = inputs[0]; + const FAUSTFLOAT* input1 = inputs[1]; + FAUSTFLOAT* output0 = outputs[0]; + FAUSTFLOAT* output1 = outputs[1]; + for (int i = 0; (i < count); i = (i + 1)) { + float fTemp0 = float(input0[i]); + float fTemp1 = float(input1[i]); + float fTemp2 = std::fabs(fTemp0); + fRec2[0] = std::max(fTemp2, ((fConst5 * fRec2[1]) + (fConst6 * fTemp2))); + fRec1[0] = ((fConst3 * fRec1[1]) + (fConst4 * fRec2[0])); + fRec0[0] = ((fConst1 * fRec0[1]) + (fConst2 * ((fRec1[0] > 1.0f) ? (1.0f / fRec1[0]) : 1.0f))); + output0[i] = FAUSTFLOAT((fTemp0 * fRec0[0])); + float fTemp3 = std::fabs(fTemp1); + fRec5[0] = std::max(fTemp3, ((fConst5 * fRec5[1]) + (fConst6 * fTemp3))); + fRec4[0] = ((fConst3 * fRec4[1]) + (fConst4 * fRec5[0])); + fRec3[0] = ((fConst1 * fRec3[1]) + (fConst2 * ((fRec4[0] > 1.0f) ? (1.0f / fRec4[0]) : 1.0f))); + output1[i] = FAUSTFLOAT((fTemp1 * fRec3[0])); + fRec2[1] = fRec2[0]; + fRec1[1] = fRec1[0]; + fRec0[1] = fRec0[0]; + fRec5[1] = fRec5[0]; + fRec4[1] = fRec4[0]; + fRec3[1] = fRec3[0]; + } + } +}; + +#endif