Strings resonator effect

This commit is contained in:
Jean Pierre Cimalando 2020-03-30 13:59:18 +02:00
parent f489b88df0
commit c0127fe02f
6 changed files with 399 additions and 0 deletions

View file

@ -25,6 +25,7 @@ set (SFIZZ_SOURCES
sfizz/effects/Apan.cpp
sfizz/effects/Lofi.cpp
sfizz/effects/Limiter.cpp
sfizz/effects/Strings.cpp
sfizz/effects/Rectify.cpp
sfizz/effects/Gain.cpp
sfizz/effects/Width.cpp

View file

@ -15,6 +15,7 @@
#include "effects/Apan.h"
#include "effects/Lofi.h"
#include "effects/Limiter.h"
#include "effects/Strings.h"
#include "effects/Rectify.h"
#include "effects/Gain.h"
#include "effects/Width.h"
@ -30,6 +31,7 @@ void EffectFactory::registerStandardEffectTypes()
registerEffectType("apan", fx::Apan::makeInstance);
registerEffectType("lofi", fx::Lofi::makeInstance);
registerEffectType("limiter", fx::Limiter::makeInstance);
registerEffectType("strings", fx::Strings::makeInstance);
// extensions (book)
registerEffectType("rectify", fx::Rectify::makeInstance);

View file

@ -39,6 +39,18 @@ constexpr T min(T op1, Args... rest)
return min(op1, min(rest...));
}
/**
* @brief Compute the square of the value
*
* @param op
* @return T
*/
template<class T>
constexpr T power2(T in)
{
return in * in;
}
/**
* @brief Converts db values into power (applies 10**(in/10))
*

View file

@ -0,0 +1,145 @@
// 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] strings_number
- [ ] strings_wet_oncc
Extensions
- [x] strings_wet
*/
#include "Strings.h"
#include "StringsPrivate.h"
#include "Opcode.h"
#include "MathHelpers.h"
#include "SIMDHelpers.h"
#include "absl/memory/memory.h"
#include <cmath>
namespace sfz {
namespace fx {
struct Strings::ResonantString {
Bw2BPF bpf;
WgResonator res;
};
Strings::Strings()
: _strings(new ResonantString[MaximumNumStrings])
{
}
Strings::~Strings()
{
}
void Strings::setSampleRate(double sampleRate)
{
for (unsigned i = 0, n = _numStrings; i < n; ++i) {
ResonantString& rs = _strings[i];
rs.bpf.init(sampleRate);
rs.res.init(sampleRate);
int midiNote = i + 24;
double midiFrequency = 440.0 * std::exp2((midiNote - 69) * (1.0 / 12.0));
// 1 Hz works decently as compromise of selectivity/speed
double bpfBandwidth = 1.0;
rs.bpf.setCutoff(
midiFrequency - 0.5 * bpfBandwidth,
midiFrequency + 0.5 * bpfBandwidth);
rs.res.setFrequency(midiFrequency);
// TODO(jpc) find how to adjust the string feedbacks
// for now set a fixed release time for all strings
double releaseTime = 50e-3;
double releaseFeedback = std::exp(-6.91 / (releaseTime * sampleRate));
rs.res.setFeedback(releaseFeedback);
}
}
void Strings::setSamplesPerBlock(int samplesPerBlock)
{
_tempBuffer.resize(samplesPerBlock);
}
void Strings::clear()
{
for (unsigned i = 0, n = _numStrings; i < n; ++i) {
ResonantString& rs = _strings[i];
rs.bpf.clear();
rs.res.clear();
}
}
void Strings::process(const float* const inputs[], float* const outputs[], unsigned nframes)
{
auto inputL = absl::MakeConstSpan(inputs[0], nframes);
auto inputR = absl::MakeConstSpan(inputs[1], nframes);
// mix down the stereo signal to create the resonator excitation source
absl::Span<float> resInput = _tempBuffer.getSpan(0).first(nframes);
sfz::applyGain<float>(M_SQRT1_2, inputL, resInput);
sfz::multiplyAdd<float>(M_SQRT1_2, inputR, resInput);
// generate the strings summed into a common buffer
absl::Span<float> resOutput = _tempBuffer.getSpan(1).first(nframes);
sfz::fill(resOutput, 0.0f);
for (unsigned is = 0, ns = _numStrings; is < ns; ++is) {
ResonantString& rs = _strings[is];
for (unsigned i = 0; i < nframes; ++i) {
float sample = resInput[i];
sample = rs.bpf.process(sample);
sample = rs.res.process(sample);
resOutput[i] += sample;
}
}
// TODO(jpc) damping of the high frequencies
// it's easiest apply individual gains to resonating strings
// or pass resonator output through LPF
// mix the resonator into the output
auto outputL = absl::MakeSpan(outputs[0], nframes);
auto outputR = absl::MakeSpan(outputs[1], nframes);
constexpr float resAttenuate = 1e-3; // need significant attenuation, here -60dB
absl::Span<float> wet = _tempBuffer.getSpan(2).first(nframes);
sfz::fill(wet, 0.01f * resAttenuate *_wet); // TOD strings_wet_oncc modulation...
sfz::copy(inputL, outputL);
sfz::copy(inputR, outputR);
sfz::multiplyAdd<float>(wet, resOutput, outputL);
sfz::multiplyAdd<float>(wet, resOutput, outputR);
}
std::unique_ptr<Effect> Strings::makeInstance(absl::Span<const Opcode> members)
{
auto fx = absl::make_unique<Strings>();
for (const Opcode& opc : members) {
switch (opc.lettersOnlyHash) {
case hash("strings_number"):
setValueFromOpcode(opc, fx->_numStrings, {0, MaximumNumStrings});
break;
case hash("strings_wet"):
setValueFromOpcode(opc, fx->_wet, {0.0f, 100.0f});
break;
}
}
return CXX11_MOVE(fx);
}
} // namespace fx
} // namespace sfz

View file

@ -0,0 +1,65 @@
// 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 "AudioBuffer.h"
#include <memory>
namespace sfz {
namespace fx {
class Bw2BPF;
class WgResonator;
/**
* @brief String resonance effect
*/
class Strings : public Effect {
public:
Strings();
~Strings();
/**
* @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 <effect> block.
*/
static std::unique_ptr<Effect> makeInstance(absl::Span<const Opcode> members);
private:
enum { MaximumNumStrings = 88 };
unsigned _numStrings = MaximumNumStrings;
float _wet = 0;
struct ResonantString;
std::unique_ptr<ResonantString[]> _strings;
AudioBuffer<float, 3> _tempBuffer { 3, config::defaultSamplesPerBlock };
};
} // namespace fx
} // namespace sfz

View file

@ -0,0 +1,174 @@
// 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 "MathHelpers.h"
#include <cmath>
namespace sfz {
namespace fx {
// Butterworth 2nd order bandpass (faust -double -os)
/*
import("stdfaust.lib");
process = fi.bandpass(1, loF, hiF) with {
loF = hslider("[1] Lo frequency [unit:Hz]", 1, 0, 1000, 1);
hiF = hslider("[2] Hi frequency [unit:Hz]", 1, 0, 1000, 1);
};
*/
class Bw2BPF {
private:
typedef float FAUSTFLOAT;
public:
/**
* @brief Initialize.
*/
void init(double sampleRate)
{
fConst0 = sampleRate;
fConst1 = (2.0 / fConst0);
fConst2 = (2.0 * fConst0);
fConst3 = (3.1415926535897931 / fConst0);
fConst4 = (0.5 / fConst0);
fConst5 = (4.0 * power2(fConst0));
fConst6 = power2((1.0 / fConst0));
fConst7 = (2.0 * fConst6);
clear();
}
/**
* @brief Clear the memory of the filter.
*/
void clear()
{
for (int l0 = 0; (l0 < 3); l0 = (l0 + 1)) {
fRec0[l0] = 0.0;
}
}
/**
* @brief Set the BPF low and high frequencies for -3dB response.
*
* The center frequency is (loF+hiF)/2.
*/
void setCutoff(double loF, double hiF)
{
fControl[0] = std::tan((fConst3 * double(hiF)));
fControl[1] = power2(std::sqrt((fConst5 * (fControl[0] * std::tan((fConst3 * double(loF)))))));
fControl[2] = ((fConst2 * fControl[0]) - (fConst4 * (fControl[1] / fControl[0])));
fControl[3] = (fConst6 * fControl[1]);
fControl[4] = (fConst1 * fControl[2]);
fControl[5] = ((fControl[3] + fControl[4]) + 4.0);
fControl[6] = (fConst1 * (fControl[2] / fControl[5]));
fControl[7] = (1.0 / fControl[5]);
fControl[8] = ((fConst7 * fControl[1]) + -8.0);
fControl[9] = (fControl[3] + (4.0 - fControl[4]));
fControl[10] = (0.0 - fControl[6]);
}
/**
* @brief Process the next filtered sample.
*/
FAUSTFLOAT process(FAUSTFLOAT input)
{
fRec0[0] = (double(input) - (fControl[7] * ((fControl[8] * fRec0[1]) + (fControl[9] * fRec0[2]))));
FAUSTFLOAT output = FAUSTFLOAT(((fControl[6] * fRec0[0]) + (fControl[10] * fRec0[2])));
fRec0[2] = fRec0[1];
fRec0[1] = fRec0[0];
return output;
}
private:
double fRec0[3] {};
double fControl[11] {};
double fConst0 {};
double fConst1 {};
double fConst2 {};
double fConst3 {};
double fConst4 {};
double fConst5 {};
double fConst6 {};
double fConst7 {};
};
//--------------------------------------------------------------------------
// Waveguide resonator (faust -os)
/*
import("stdfaust.lib");
process = fi.nlf2(f, r) : (_,!) with {
f = hslider("[1] Resonance frequency [unit:Hz]", 1, 0, 1000, 1);
r = hslider("[2] Resonance feedback", 0, 0, 1, 1e-3);
};
*/
class WgResonator {
private:
typedef float FAUSTFLOAT;
public:
/**
* @brief Initialize.
*/
void init(float sampleRate)
{
fConst0 = (6.28318548f / sampleRate);
clear();
}
/**
* @brief Clear the memory of the resonator.
*/
void clear()
{
for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) {
fRec0[l0] = 0.0f;
}
for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) {
fRec1[l1] = 0.0f;
}
}
/**
* @brief Set the resonance frequency.
*/
void setFrequency(float frequency)
{
fControl[1] = (fConst0 * float(frequency));
fControl[2] = std::sin(fControl[1]);
fControl[3] = std::cos(fControl[1]);
}
/**
* @brief Set the resonance feedback.
*/
void setFeedback(float feedback)
{
fControl[0] = float(feedback);
}
/**
* @brief Process the next resonance sample.
*/
FAUSTFLOAT process(FAUSTFLOAT input)
{
fRec0[0] = (fControl[0] * ((fControl[2] * fRec1[1]) + (fControl[3] * fRec0[1])));
fRec1[0] = ((float(input) + (fControl[3] * fRec1[1])) - (fControl[2] * fRec0[1]));
FAUSTFLOAT output = FAUSTFLOAT(fRec0[0]);
fRec0[1] = fRec0[0];
fRec1[1] = fRec1[0];
return output;
}
private:
float fRec0[2] {};
float fRec1[2] {};
float fControl[4];
float fConst0 {};
};
} // namespace sfz
} // namespace fx