From 5cd5db877abcaa837a0e95dca294456dc13659c9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 18 Mar 2020 01:53:47 +0100 Subject: [PATCH] Add the rectifier --- src/CMakeLists.txt | 1 + src/sfizz/Effects.cpp | 4 ++ src/sfizz/effects/Rectify.cpp | 106 ++++++++++++++++++++++++++++++++++ src/sfizz/effects/Rectify.h | 58 +++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 src/sfizz/effects/Rectify.cpp create mode 100644 src/sfizz/effects/Rectify.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9d93e047..2fcaeb88 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ set (SFIZZ_SOURCES sfizz/effects/Apan.cpp sfizz/effects/Lofi.cpp sfizz/effects/Limiter.cpp + sfizz/effects/Rectify.cpp ) include (SfizzSIMDSourceFiles) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 78ec6c96..eb6625f3 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/Rectify.h" #include namespace sfz { @@ -27,6 +28,9 @@ void EffectFactory::registerStandardEffectTypes() registerEffectType("apan", fx::Apan::makeInstance); registerEffectType("lofi", fx::Lofi::makeInstance); registerEffectType("limiter", fx::Limiter::makeInstance); + + // extensions (book) + registerEffectType("rectify", fx::Rectify::makeInstance); } void EffectFactory::registerEffectType(absl::string_view name, Effect::MakeInstance& make) diff --git a/src/sfizz/effects/Rectify.cpp b/src/sfizz/effects/Rectify.cpp new file mode 100644 index 00000000..015e5d29 --- /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) { + float amount = amounts[i] * 0.01f; + 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