Add the autopan effect

This commit is contained in:
Jean Pierre Cimalando 2020-03-08 18:35:27 +01:00 committed by Paul Fd
parent 51be7a68ba
commit a6df5d9526
6 changed files with 322 additions and 0 deletions

View file

@ -18,6 +18,7 @@ set (SFIZZ_SOURCES
sfizz/effects/Nothing.cpp
sfizz/effects/Filter.cpp
sfizz/effects/Eq.cpp
sfizz/effects/Apan.cpp
sfizz/effects/Lofi.cpp
)
include (SfizzSIMDSourceFiles)

View file

@ -12,6 +12,7 @@
#include "effects/Nothing.h"
#include "effects/Filter.h"
#include "effects/Eq.h"
#include "effects/Apan.h"
#include "effects/Lofi.h"
#include <algorithm>
@ -22,6 +23,7 @@ void EffectFactory::registerStandardEffectTypes()
// TODO
registerEffectType("filter", fx::Filter::makeInstance);
registerEffectType("eq", fx::Eq::makeInstance);
registerEffectType("apan", fx::Apan::makeInstance);
registerEffectType("lofi", fx::Lofi::makeInstance);
}

154
src/sfizz/effects/Apan.cpp Normal file
View file

@ -0,0 +1,154 @@
// 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] apan_waveform
- [x] apan_freq
- [ ] apan_freq_oncc
- [x] apan_phase
- [ ] apan_phase_oncc
- [x] apan_dry
- [ ] apan_dry_oncc
- [x] apan_wet
- [ ] apan_wet_oncc
- [x] apan_depth
- [ ] apan_depth_oncc
*/
#include "Apan.h"
#include "CommonLFO.h"
#include "Opcode.h"
#include <limits>
#include <cmath>
namespace sfz {
namespace fx {
void Apan::setSampleRate(double sampleRate)
{
_samplePeriod = 1.0 / sampleRate;
}
void Apan::setSamplesPerBlock(int samplesPerBlock)
{
_lfoOutLeft.reset(new float[samplesPerBlock]);
_lfoOutRight.reset(new float[samplesPerBlock]);
}
void Apan::clear()
{
_lfoPhase = 0.0;
}
void Apan::process(const float* const inputs[], float* const outputs[], unsigned nframes)
{
float dry = _dry;
float wet = _wet;
float depth = _depth;
float* modL = _lfoOutLeft.get();
float* modR = _lfoOutRight.get();
computeLfos(modL, modR, nframes);
const float* inL = inputs[0];
const float* inR = inputs[1];
float* outL = outputs[0];
float* outR = outputs[1];
for (unsigned i = 0; i < nframes; ++i) {
float modDD = depth * 0.5f * (modL[i] - modR[i]); // LFO in ±depth
// uses a linear pan law
float gainL = 1 - modDD;
float gainR = 1 + modDD;
outL[i] = inL[i] * (gainL * wet + dry);
outR[i] = inR[i] * (gainR * wet + dry);
}
}
std::unique_ptr<Effect> Apan::makeInstance(absl::Span<const Opcode> members)
{
std::unique_ptr<Apan> fx { new Apan };
for (const Opcode& opc : members) {
switch (opc.lettersOnlyHash) {
case hash("apan_waveform"):
if (auto value = readOpcode(opc.value, Range<int> { 0, std::numeric_limits<int>::max() }))
fx->_lfoWave = *value;
break;
case hash("apan_freq"):
if (auto value = readOpcode(opc.value, Range<float> { 0.0, std::numeric_limits<float>::max() }))
fx->_lfoFrequency = *value;
break;
case hash("apan_phase"):
if (auto value = readOpcode(opc.value, Range<float> { 0.0, 360.0 })) {
float phase = *value / 360.0f;
phase -= (int)phase;
fx->_lfoPhaseOffset = phase;
}
break;
case hash("apan_dry"):
if (auto value = readOpcode(opc.value, Range<float> { 0.0, 100.0 }))
fx->_dry = *value / 100.0f;
break;
case hash("apan_wet"):
if (auto value = readOpcode(opc.value, Range<float> { 0.0, 100.0 }))
fx->_wet = *value / 100.0f;
break;
case hash("apan_depth"):
if (auto value = readOpcode(opc.value, Range<float> { 0.0, 100.0 }))
fx->_depth = *value / 100.0f;
break;
}
}
return fx;
}
void Apan::computeLfos(float* left, float* right, unsigned nframes)
{
switch (_lfoWave) {
#define CASE(X) case lfo::X: \
computeLfos<lfo::X>(left, right, nframes); break;
default:
CASE(kTriangle)
CASE(kSine)
CASE(kPulse75)
CASE(kSquare)
CASE(kPulse25)
CASE(kPulse12_5)
CASE(kRamp)
CASE(kSaw)
#undef CASE
}
}
template <int Wave> void Apan::computeLfos(float* left, float* right, unsigned nframes)
{
float samplePeriod = _samplePeriod;
float frequency = _lfoFrequency;
float offset = _lfoPhaseOffset;
float phaseLeft = _lfoPhase;
for (unsigned i = 0; i < nframes; ++i) {
float phaseRight = phaseLeft + offset;
phaseRight -= (int)phaseRight;
left[i] = lfo::evaluateAtPhase<Wave>(phaseLeft);
right[i] = lfo::evaluateAtPhase<Wave>(phaseRight);
phaseLeft += frequency * samplePeriod;
phaseLeft -= (int)phaseLeft;
}
_lfoPhase = phaseLeft;
}
} // namespace fx
} // namespace sfz

67
src/sfizz/effects/Apan.h Normal file
View file

@ -0,0 +1,67 @@
// 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 <memory>
namespace sfz {
namespace fx {
/**
* @brief Effect which does nothing
*/
class Apan : 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 <effect> block.
*/
static std::unique_ptr<Effect> makeInstance(absl::Span<const Opcode> members);
private:
void computeLfos(float* left, float* right, unsigned nframes);
template <int Wave> void computeLfos(float* left, float* right, unsigned nframes);
private:
float _samplePeriod = 0.0;
std::unique_ptr<float[]> _lfoOutLeft;
std::unique_ptr<float[]> _lfoOutRight;
// Controls
float _dry = 0.0;
float _wet = 0.0;
float _depth = 0.0;
int _lfoWave = 0;
float _lfoFrequency = 0.0;
float _lfoPhaseOffset = 0.5;
// State
float _lfoPhase = 0.0;
};
} // namespace fx
} // namespace sfz

View file

@ -0,0 +1,30 @@
// 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 {
namespace fx {
namespace lfo {
enum Wave {
kTriangle,
kSine,
kPulse75,
kSquare,
kPulse25,
kPulse12_5,
kRamp,
kSaw,
};
template <int Wave> float evaluateAtPhase(float phase);
} // namespace lfo
} // namespace fx
} // namespace sfz
#include "CommonLFO.hpp"

View file

@ -0,0 +1,68 @@
// 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 "CommonLFO.h"
#include <cmath>
namespace sfz {
namespace fx {
namespace lfo {
template <>
inline float evaluateAtPhase<kTriangle>(float phase)
{
float y = -4 * phase + 2;
y = (phase < 0.25f) ? (4 * phase) : y;
y = (phase > 0.75f) ? (4 * phase - 4) : y;
return y;
}
template <>
inline float evaluateAtPhase<kSine>(float phase)
{
float x = phase + phase - 1;
return 4 * x * (1 - std::fabs(x));
}
template <>
inline float evaluateAtPhase<kPulse75>(float phase)
{
return (phase < 0.75f) ? +1.0f : -1.0f;
}
template <>
inline float evaluateAtPhase<kSquare>(float phase)
{
return (phase < 0.5f) ? +1.0f : -1.0f;
}
template <>
inline float evaluateAtPhase<kPulse25>(float phase)
{
return (phase < 0.25f) ? +1.0f : -1.0f;
}
template <>
inline float evaluateAtPhase<kPulse12_5>(float phase)
{
return (phase < 0.125f) ? +1.0f : -1.0f;
}
template <>
inline float evaluateAtPhase<kRamp>(float phase)
{
return 2 * phase - 1;
}
template <>
inline float evaluateAtPhase<kSaw>(float phase)
{
return 1 - 2 * phase;
}
} // namespace lfo
} // namespace fx
} // namespace sfz