diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index 5696f712..0f5ebcf1 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14d469f6..f1378612 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/external/atomic_queue/atomic_queue.h b/src/external/atomic_queue/atomic_queue.h index e7bcf0f2..731d9a11 100644 --- a/src/external/atomic_queue/atomic_queue.h +++ b/src/external/atomic_queue/atomic_queue.h @@ -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 octaveOffsetRange { -10, 10 }; // octave_offset constexpr Range noteOffsetRange { -127, 127 }; // note_offset + + constexpr Range apanWaveformRange { 0, std::numeric_limits::max() }; + constexpr Range apanFrequencyRange { 0, std::numeric_limits::max() }; + constexpr Range apanPhaseRange { 0.0, 360.0 }; + constexpr Range apanLevelRange { 0.0, 100.0 }; } } diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 7031582f..3398cc3d 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -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 @@ -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); } diff --git a/src/sfizz/LeakDetector.h b/src/sfizz/LeakDetector.h index a8333c63..02e3da41 100644 --- a/src/sfizz/LeakDetector.h +++ b/src/sfizz/LeakDetector.h @@ -47,6 +47,8 @@ public: } } + LeakDetector& operator=(const LeakDetector&) = default; + private: struct ObjectCounter { ObjectCounter() = default; diff --git a/src/sfizz/Macros.h b/src/sfizz/Macros.h index f610eefb..4eaa1a11 100644 --- a/src/sfizz/Macros.h +++ b/src/sfizz/Macros.h @@ -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 + diff --git a/src/sfizz/effects/Apan.cpp b/src/sfizz/effects/Apan.cpp new file mode 100644 index 00000000..473985fd --- /dev/null +++ b/src/sfizz/effects/Apan.cpp @@ -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 +#include + +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 Apan::makeInstance(absl::Span members) + { + std::unique_ptr 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(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(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 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(phaseRight); + + left[i] = lfo::evaluateAtPhase(phaseLeft); + right[i] = lfo::evaluateAtPhase(phaseRight); + + phaseLeft += frequency * samplePeriod; + phaseLeft -= static_cast(phaseLeft); + } + + _lfoPhase = phaseLeft; + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Apan.h b/src/sfizz/effects/Apan.h new file mode 100644 index 00000000..52b85dc6 --- /dev/null +++ b/src/sfizz/effects/Apan.h @@ -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 block. + */ + static std::unique_ptr makeInstance(absl::Span members); + + private: + void computeLfos(float* left, float* right, unsigned nframes); + template void computeLfos(float* left, float* right, unsigned nframes); + + private: + float _samplePeriod = 0.0; + sfz::Buffer _lfoOutLeft { config::defaultSamplesPerBlock }; + sfz::Buffer _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 diff --git a/src/sfizz/effects/CommonLFO.h b/src/sfizz/effects/CommonLFO.h new file mode 100644 index 00000000..3129e1c5 --- /dev/null +++ b/src/sfizz/effects/CommonLFO.h @@ -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 float evaluateAtPhase(float phase); + +} // namespace lfo +} // namespace fx +} // namespace sfz + +#include "CommonLFO.hpp" diff --git a/src/sfizz/effects/CommonLFO.hpp b/src/sfizz/effects/CommonLFO.hpp new file mode 100644 index 00000000..ef6eef36 --- /dev/null +++ b/src/sfizz/effects/CommonLFO.hpp @@ -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 + +namespace sfz { +namespace fx { +namespace lfo { + + template <> + inline float evaluateAtPhase(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(float phase) + { + float x = phase + phase - 1; + return 4 * x * (1 - std::fabs(x)); + } + + template <> + inline float evaluateAtPhase(float phase) + { + return (phase < 0.75f) ? +1.0f : -1.0f; + } + + template <> + inline float evaluateAtPhase(float phase) + { + return (phase < 0.5f) ? +1.0f : -1.0f; + } + + template <> + inline float evaluateAtPhase(float phase) + { + return (phase < 0.25f) ? +1.0f : -1.0f; + } + + template <> + inline float evaluateAtPhase(float phase) + { + return (phase < 0.125f) ? +1.0f : -1.0f; + } + + template <> + inline float evaluateAtPhase(float phase) + { + return 2 * phase - 1; + } + + template <> + inline float evaluateAtPhase(float phase) + { + return 1 - 2 * phase; + } + +} // namespace lfo +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index f29d9950..87d64264 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -92,7 +92,7 @@ namespace fx { } } - return std::move(fx); + return CXX11_MOVE(fx); } ///