Merge pull request #105 from jpcima/autopan

Add the autopan effect
This commit is contained in:
Paul Ferrand 2020-03-14 23:40:04 +01:00 committed by GitHub
commit d12aaa9f48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 338 additions and 5 deletions

View file

@ -1,5 +1,5 @@
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to be used")
set(CMAKE_C_STANDARD 99 CACHE STRING "C standard to be used")
# Export the compile_commands.json file
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)

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

@ -150,7 +150,7 @@ protected:
// The special member functions are not thread-safe.
AtomicQueueCommon() noexcept = default;
AtomicQueueCommon() = default;
AtomicQueueCommon(AtomicQueueCommon const& b) noexcept
: head_(b.head_.load(X))
@ -417,7 +417,7 @@ class AtomicQueue2 : public AtomicQueueCommon<AtomicQueue2<T, SIZE, MINIMIZE_CON
public:
using value_type = T;
AtomicQueue2() noexcept = default;
AtomicQueue2() = default;
AtomicQueue2(AtomicQueue2 const&) = delete;
AtomicQueue2& operator=(AtomicQueue2 const&) = delete;
};

View file

@ -206,5 +206,10 @@ namespace Default
constexpr bool checkSostenuto { true }; // sostenuto_sw
constexpr Range<int> octaveOffsetRange { -10, 10 }; // octave_offset
constexpr Range<int> noteOffsetRange { -127, 127 }; // note_offset
constexpr Range<int> apanWaveformRange { 0, std::numeric_limits<int>::max() };
constexpr Range<float> apanFrequencyRange { 0, std::numeric_limits<float>::max() };
constexpr Range<float> apanPhaseRange { 0.0, 360.0 };
constexpr Range<float> apanLevelRange { 0.0, 100.0 };
}
}

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);
}

View file

@ -47,6 +47,8 @@ public:
}
}
LeakDetector& operator=(const LeakDetector&) = default;
private:
struct ObjectCounter {
ObjectCounter() = default;

View file

@ -20,6 +20,9 @@
#if __cplusplus > 201103L
#define CXX14_CONSTEXPR constexpr
#define CXX11_MOVE(x) x
#else
#define CXX14_CONSTEXPR
#define CXX11_MOVE(x) std::move(x)
#endif

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

@ -0,0 +1,155 @@
// 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 "Macros.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.resize(samplesPerBlock);
_lfoOutRight.resize(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.data();
float* modR = _lfoOutRight.data();
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, Default::apanWaveformRange))
fx->_lfoWave = *value;
break;
case hash("apan_freq"):
if (auto value = readOpcode(opc.value, Default::apanFrequencyRange))
fx->_lfoFrequency = *value;
break;
case hash("apan_phase"):
if (auto value = readOpcode(opc.value, Default::apanPhaseRange)) {
float phase = *value / 360.0f;
phase -= static_cast<int>(phase);
fx->_lfoPhaseOffset = phase;
}
break;
case hash("apan_dry"):
if (auto value = readOpcode(opc.value, Default::apanLevelRange))
fx->_dry = *value / 100.0f;
break;
case hash("apan_wet"):
if (auto value = readOpcode(opc.value, Default::apanLevelRange))
fx->_wet = *value / 100.0f;
break;
case hash("apan_depth"):
if (auto value = readOpcode(opc.value, Default::apanLevelRange))
fx->_depth = *value / 100.0f;
break;
}
}
return CXX11_MOVE(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 -= static_cast<int>(phaseRight);
left[i] = lfo::evaluateAtPhase<Wave>(phaseLeft);
right[i] = lfo::evaluateAtPhase<Wave>(phaseRight);
phaseLeft += frequency * samplePeriod;
phaseLeft -= static_cast<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 "Buffer.h"
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;
sfz::Buffer<float> _lfoOutLeft { config::defaultSamplesPerBlock };
sfz::Buffer<float> _lfoOutRight { config::defaultSamplesPerBlock };
// 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

View file

@ -92,7 +92,7 @@ namespace fx {
}
}
return std::move(fx);
return CXX11_MOVE(fx);
}
///