From 28c12f32f3d890844a69cbb77e2e49fa35567b02 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 26 Feb 2020 21:09:34 +0100 Subject: [PATCH 01/23] Initial effects --- src/CMakeLists.txt | 3 + src/sfizz/AudioSpan.h | 16 +++ src/sfizz/Config.h | 4 + src/sfizz/Effects.cpp | 137 ++++++++++++++++++++++ src/sfizz/Effects.h | 147 +++++++++++++++++++++++ src/sfizz/Region.cpp | 22 ++++ src/sfizz/Region.h | 13 +++ src/sfizz/Synth.cpp | 151 +++++++++++++++++++++--- src/sfizz/Synth.h | 15 ++- src/sfizz/effects/Lofi.cpp | 213 ++++++++++++++++++++++++++++++++++ src/sfizz/effects/Lofi.h | 79 +++++++++++++ src/sfizz/effects/Nothing.cpp | 31 +++++ src/sfizz/effects/Nothing.h | 35 ++++++ 13 files changed, 850 insertions(+), 16 deletions(-) create mode 100644 src/sfizz/Effects.cpp create mode 100644 src/sfizz/Effects.h create mode 100644 src/sfizz/effects/Lofi.cpp create mode 100644 src/sfizz/effects/Lofi.h create mode 100644 src/sfizz/effects/Nothing.cpp create mode 100644 src/sfizz/effects/Nothing.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 57c713122..3580130c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,9 @@ set (SFIZZ_SOURCES sfizz/FloatEnvelopes.cpp sfizz/Logger.cpp sfizz/SfzFilter.cpp + sfizz/Effects.cpp + sfizz/effects/Nothing.cpp + sfizz/effects/Lofi.cpp ) include (SfizzSIMDSourceFiles) diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index 312c1e1b2..c608848c9 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -209,6 +209,22 @@ class AudioSpan { return {}; } + /** + * @brief Convert implicitly to a pointer of channels + */ + operator const float* const *() const noexcept + { + return spans.data(); + } + + /** + * @brief Convert implicitly to a pointer of channels + */ + operator float* const *() noexcept + { + return spans.data(); + } + /** * @brief Get a Span corresponding to a specific channel * diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 0b629ccdb..b2a6f8a23 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -69,6 +69,10 @@ namespace config { */ const absl::string_view midnamManufacturer { "The Sfizz authors" }; const absl::string_view midnamModel { "Sfizz" }; + /** + Limit of how many "fxN" buses are accepted (in SFZv2, maximum is 4) + */ + constexpr int maxEffectBuses { 256 }; } // namespace config // Enable or disable SIMD accelerators by default diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp new file mode 100644 index 000000000..a5b3b9647 --- /dev/null +++ b/src/sfizz/Effects.cpp @@ -0,0 +1,137 @@ +// 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 "Effects.h" +#include "AudioSpan.h" +#include "Opcode.h" +#include "SIMDHelpers.h" +#include "Config.h" +#include "effects/Nothing.h" +#include "effects/Lofi.h" +#include + +namespace sfz { + +void EffectFactory::registerStandardEffectTypes() +{ + // TODO + registerEffectType("lofi", fx::Lofi::makeInstance); +} + +void EffectFactory::registerEffectType(absl::string_view name, Effect::MakeInstance& make) +{ + FactoryEntry ent; + ent.name = std::string(name); + ent.make = &make; + _entries.push_back(std::move(ent)); +} + +Effect* EffectFactory::makeEffect(absl::Span members) +{ + const Opcode* opcode = nullptr; + + for (auto it = members.rbegin(); it != members.rend() && !opcode; ++it) { + if (it->lettersOnlyHash == hash("type")) + opcode = &*it; + } + + if (!opcode) { + DBG("The effect does not specify a type"); + return new sfz::fx::Nothing; + } + + absl::string_view type = opcode->value; + + auto it = _entries.begin(); + auto end = _entries.end(); + for (; it != end && it->name != type; ++it) + ; + + if (it == end) { + DBG("Unsupported effect type: " << type); + return new sfz::fx::Nothing; + } + + Effect* fx = it->make(members); + if (!fx) { + DBG("Could not instantiate effect of type: " << type); + return new sfz::fx::Nothing; + } + + return fx; +} + +/// +EffectBus::EffectBus() +{ +} + +EffectBus::~EffectBus() +{ +} + +void EffectBus::addEffect(std::unique_ptr fx) +{ + _effects.emplace_back(std::move(fx)); +} + +void EffectBus::clearInputs(unsigned nframes) +{ + AudioSpan(_inputs).first(nframes).fill(0.0f); + AudioSpan(_outputs).first(nframes).fill(0.0f); +} + +void EffectBus::addToInputs(const float* const addInput[], float addGain, unsigned nframes) +{ + if (addGain == 0) + return; + + for (unsigned c = 0; c < EffectChannels; ++c) { + absl::Span addIn(addInput[c], nframes); + sfz::multiplyAdd(addGain, addIn, _inputs.getSpan(c)); + } +} + +void EffectBus::init(double sampleRate) +{ + for (const auto& effectPtr : _effects) + effectPtr->init(sampleRate); +} + +void EffectBus::clear() +{ + for (const auto& effectPtr : _effects) + effectPtr->clear(); +} + +void EffectBus::process(unsigned nframes) +{ + size_t numEffects = _effects.size(); + + if (numEffects > 0 && hasNonZeroOutput()) { + _effects[0]->process( + AudioSpan(_inputs), AudioSpan(_outputs), nframes); + for (size_t i = 1; i < numEffects; ++i) + _effects[i]->process( + AudioSpan(_outputs), AudioSpan(_outputs), nframes); + } else + fx::Nothing().process( + AudioSpan(_inputs), AudioSpan(_outputs), nframes); +} + +void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes) +{ + const float gainToMain = _gainToMain; + const float gainToMix = _gainToMix; + + for (unsigned c = 0; c < EffectChannels; ++c) { + absl::Span fxOut = _outputs.getConstSpan(c); + sfz::multiplyAdd(gainToMain, fxOut, absl::Span(mainOutput[c], nframes)); + sfz::multiplyAdd(gainToMix, fxOut, absl::Span(mixOutput[c], nframes)); + } +} + +} // namespace sfz diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h new file mode 100644 index 000000000..80462d0cb --- /dev/null +++ b/src/sfizz/Effects.h @@ -0,0 +1,147 @@ +// 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 "AudioBuffer.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include +#include +#include + +namespace sfz { +struct Opcode; + +enum { + // Number of channels processed by effects + EffectChannels = 2, +}; + +/** + @brief Abstract base of SFZ effects + */ +class Effect { +public: + virtual ~Effect() {} + + /** + @brief Initializes with the given sample rate. + */ + virtual void init(double sampleRate) = 0; + + /** + @brief Reset the state to initial. + */ + virtual void clear() = 0; + + /** + @brief Computes a cycle of the effect in stereo. + */ + virtual void process(const float* const inputs[], float* const outputs[], unsigned nframes) = 0; + + /** + @brief Type of the factory function used to instantiate an effect given + the contents of the block + */ + typedef Effect* (MakeInstance)(absl::Span members); +}; + +/** + @brief SFZ effects factory + */ +class EffectFactory { +public: + /** + @brief Registers all available standard effects into the factory. + */ + void registerStandardEffectTypes(); + + /** + @brief Registers a user-defined effect into the factory. + */ + void registerEffectType(absl::string_view name, Effect::MakeInstance& make); + + /** + @brief Instantiates an effect given the contents of the block. + */ + Effect* makeEffect(absl::Span members); + +private: + struct FactoryEntry { + std::string name; + Effect::MakeInstance* make; + }; + + std::vector _entries; +}; + +/** + @brief Sequence of effects processed in series + */ +class EffectBus { +public: + EffectBus(); + ~EffectBus(); + + /** + @brief Adds an effect at the end of the bus. + */ + void addEffect(std::unique_ptr fx); + + /** + @brief Checks whether this bus can produce output. + */ + bool hasNonZeroOutput() const { return _gainToMain != 0 || _gainToMix != 0; } + + /** + @brief Sets the amount of effect output going to the main. + */ + void setGainToMain(float gain) { _gainToMain = gain; } + + /** + @brief Sets the amount of effect output going to the mix. + */ + void setGainToMix(float gain) { _gainToMix = gain; } + + /** + @brief Resets the input buffers to zero. + */ + void clearInputs(unsigned nframes); + + /** + @brief Adds some audio into the input buffer. + */ + void addToInputs(const float* const addInput[], float addGain, unsigned nframes); + + /** + @brief Initializes all effects in the bus with the given sample rate. + */ + void init(double sampleRate); + + /** + @brief Resets the state of all effects in the bus. + */ + void clear(); + + /** + @brief Computes a cycle of the effect bus. + */ + void process(unsigned nframes); + + /** + @brief Mixes the outputs into a pair of stereo signals: Main and Mix. + */ + void mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes); + +private: + std::vector> _effects; + AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; + AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; + float _gainToMain = 0.0; + float _gainToMix = 0.0; +}; + +} // namespace sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index e05708a08..354752636 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -733,6 +733,20 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break; + case hash("effect"): // effect& + { + const auto effectNumber = opcode.backParameter(); + if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) + break; + auto value = readOpcode(opcode.value, {0, 100}); + if (!value) + break; + if (static_cast(*effectNumber + 1) > gainToEffect.size()) + gainToEffect.resize(*effectNumber + 1); + gainToEffect[*effectNumber] = *value / 100; + break; + } + // Ignored opcodes case hash("hichan"): case hash("lochan"): @@ -1073,3 +1087,11 @@ void sfz::Region::offsetAllKeys(int offset) noexcept crossfadeKeyOutRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); } } + +float sfz::Region::getGainToEffectBus(unsigned number) const noexcept +{ + if (number >= gainToEffect.size()) + return 0.0; + + return gainToEffect[number]; +} diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index d398a63e2..cf08657b8 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -39,6 +39,9 @@ struct Region { : midiState(midiState), defaultPath(std::move(defaultPath)) { ccSwitched.set(); + + gainToEffect.reserve(5); // sufficient room for main and fx1-4 + gainToEffect.push_back(1.0); // contribute 100% into the main bus } Region(const Region&) = default; ~Region() = default; @@ -206,6 +209,12 @@ struct Region { bool hasKeyswitches() const noexcept { return keyswitchDown || keyswitchUp || keyswitch || previousNote; } + /** + * @brief Get the gain this region contributes into the input of the Nth + * effect bus + */ + float getGainToEffectBus(unsigned number) const noexcept; + // Sound source: sample playback std::string sample {}; // Sample float delay { Default::delay }; // delay @@ -297,6 +306,10 @@ struct Region { EGDescription filterEG; bool isStereo { false }; + + // Effects + std::vector gainToEffect; + private: const MidiState& midiState; bool keySwitched { true }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index d65baa2f0..7ebbbb390 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -21,12 +21,16 @@ using namespace std::literals; sfz::Synth::Synth() + : Synth(config::numVoices) { - resetVoices(this->numVoices); } sfz::Synth::Synth(int numVoices) { + effectFactory.registerStandardEffectTypes(); + + effectBuses.reserve(5); // sufficient room for main and fx1-4 + resetVoices(numVoices); } @@ -70,7 +74,7 @@ void sfz::Synth::callback(absl::string_view header, const std::vector& m numCurves++; break; case hash("effect"): - // TODO: implement effects + handleEffectOpcodes(members); break; default: std::cerr << "Unknown header: " << header << '\n'; @@ -118,6 +122,10 @@ void sfz::Synth::clear() for (auto& list: ccActivationLists) list.clear(); regions.clear(); + effectBuses.clear(); + EffectBus* mainBus = new EffectBus; + effectBuses.emplace_back(mainBus); + mainBus->setGainToMain(1.0); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; @@ -185,6 +193,75 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) } } +void sfz::Synth::handleEffectOpcodes(const std::vector& members) +{ + absl::string_view busName = "main"; + + auto getOrCreateBus = [this](unsigned index) -> EffectBus& { + if (index + 1 > effectBuses.size()) + effectBuses.resize(index + 1); + EffectBusPtr &slot = effectBuses[index]; + if (!slot) + slot.reset(new EffectBus); + return *slot; + }; + + for (const Opcode& opcode : members) { + switch (opcode.lettersOnlyHash) { + case hash("bus"): + busName = opcode.value; + break; + + // note(jpc): gain opcodes are linear volumes in % units + + case hash("directtomain"): + if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + getOrCreateBus(0).setGainToMain(*valueOpt / 100); + break; + + case hash("fxtomain"): // fx&tomain + if (auto numberOpt = opcode.firstParameter()) { + unsigned number = *numberOpt; + if (number < 1 || number > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + getOrCreateBus(number).setGainToMain(*valueOpt / 100); + } + break; + + case hash("fxtomix"): // fx&tomix + if (auto numberOpt = opcode.firstParameter()) { + unsigned number = *numberOpt; + if (number < 1 || number > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + getOrCreateBus(number).setGainToMix(*valueOpt / 100); + } + break; + } + } + + unsigned busIndex; + if (busName.empty() || busName == "main") + busIndex = 0; + else if (busName.size() > 2 && busName.substr(0, 2) == "fx" && + absl::SimpleAtoi(busName.substr(2), &busIndex) && + busIndex >= 1 && busIndex <= config::maxEffectBuses) { + // an effect bus fxN, with N usually in [1,4] + } + else { + DBG("Unsupported effect bus: " << busName); + return; + } + + // create the effect and add it + EffectBus& bus = getOrCreateBus(busIndex); + Effect* fx = effectFactory.makeEffect(members); + bus.addEffect(std::unique_ptr(fx)); + + fx->init(sampleRate); +} + void addEndpointsToVelocityCurve(sfz::Region& region) { if (region.velocityPoints.size() > 0) { @@ -385,6 +462,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->samplesPerBlock = samplesPerBlock; this->tempBuffer.resize(samplesPerBlock); + this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); } @@ -402,13 +480,17 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept resources.filterPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate); + + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->init(sampleRate); + } } void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { ScopedFTZ ftz; - if (freeWheeling) resources.filePool.waitForBackgroundLoading(); @@ -416,32 +498,71 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept if (!canEnterCallback) return; + size_t numFrames = buffer.getNumFrames(); + size_t numEffectBuses = effectBuses.size(); + auto temp = AudioSpan(tempBuffer).first(numFrames); + auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); + + // Prepare the effect inputs. They are mixes of per-region outputs. + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->clearInputs(numFrames); + } + + // CallbackBreakdown callbackBreakdown; int numActiveVoices { 0 }; { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod }; buffer.fill(0.0f); + tempMixNode.fill(0.0f); resources.filePool.cleanupPromises(); - - auto tempSpan = AudioSpan(tempBuffer).first(buffer.getNumFrames()); for (auto& voice : voices) { - if (!voice->isFree()) { - numActiveVoices++; - voice->renderBlock(tempSpan); - buffer.add(tempSpan); - callbackBreakdown.data += voice->getLastDataDuration(); - callbackBreakdown.amplitude += voice->getLastAmplitudeDuration(); - callbackBreakdown.filters += voice->getLastFilterDuration(); - callbackBreakdown.panning += voice->getLastPanningDuration(); + if (voice->isFree()) + continue; + + const Region* region = voice->getRegion(); + + numActiveVoices++; + voice->renderBlock(temp); + + // Add the output into the effects linked to this region + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + float addGain = region->getGainToEffectBus(i); + bus->addToInputs(temp, addGain, numFrames); + } } + + callbackBreakdown.data += voice->getLastDataDuration(); + callbackBreakdown.amplitude += voice->getLastAmplitudeDuration(); + callbackBreakdown.filters += voice->getLastFilterDuration(); + callbackBreakdown.panning += voice->getLastPanningDuration(); } + } - buffer.applyGain(db2mag(volume)); + // Apply effect buses + // -- note(jpc) there is always a "main" bus which is initially empty. + // without any , the signal is just going to flow through it. + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + bus->process(numFrames); + bus->mixOutputsTo(buffer, tempMixNode, numFrames); + } } + // Add the Mix output (fxNtomix opcodes) + // -- note(jpc) the purpose of the Mix output is not known. + // perhaps it's designed as extension point for custom processing? + // as default behavior, it adds itself to the Main signal. + buffer.add(tempMixNode); + + // Apply the master volume + buffer.applyGain(db2mag(volume)); + callbackBreakdown.dispatch = dispatchDuration; - resources.logger.logCallbackTime(std::move(callbackBreakdown), numActiveVoices, buffer.getNumFrames()); + resources.logger.logCallbackTime(std::move(callbackBreakdown), numActiveVoices, numFrames); // Reset the dispatch counter dispatchDuration = Duration(0); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index f2c6fd119..3f94262db 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -9,6 +9,7 @@ #include "Parser.h" #include "Voice.h" #include "Region.h" +#include "Effects.h" #include "LeakDetector.h" #include "MidiState.h" #include "AudioSpan.h" @@ -400,6 +401,12 @@ class Synth : public Parser { * @param members the opcodes of the block */ void handleControlOpcodes(const std::vector& members); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleEffectOpcodes(const std::vector& members); /** * @brief Helper function to merge all the currently active opcodes * as set by the successive callbacks and create a new region to store @@ -441,8 +448,14 @@ class Synth : public Parser { std::array noteActivationLists; std::array ccActivationLists; - // Internal temporary buffer + // Effect factory and buses + EffectFactory effectFactory; + typedef std::unique_ptr EffectBusPtr; + std::vector effectBuses; // 0 is "main", 1-N are "fx1"-"fxN" + + // Intermediate buffers AudioBuffer tempBuffer { 2, config::defaultSamplesPerBlock }; + AudioBuffer tempMixNodeBuffer { 2, config::defaultSamplesPerBlock }; int samplesPerBlock { config::defaultSamplesPerBlock }; float sampleRate { config::defaultSampleRate }; diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp new file mode 100644 index 000000000..9c4e1f7ea --- /dev/null +++ b/src/sfizz/effects/Lofi.cpp @@ -0,0 +1,213 @@ +// 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] bitred +- [ ] bitred_oncc +- [ ] bitred_smoothcc +- [ ] bitred_stepcc +- [ ] bitred_curvecc + +- [x] decim +- [ ] decim_oncc +- [ ] decim_smoothcc +- [ ] decim_stepcc +- [ ] decim_curvecc + +- [ ] egN_bitred +- [ ] egN_bitred_oncc +- [ ] lfoN_bitred +- [ ] lfoN_bitred_oncc +- [ ] lfoN_bitred_smoothcc +- [ ] lfoN_bitred_stepcc + +- [ ] egN_decim +- [ ] egN_decim_oncc +- [ ] lfoN_decim +- [ ] lfoN_decim_oncc +- [ ] lfoN_decim_smoothcc +- [ ] lfoN_decim_stepcc + + */ + +#include "Lofi.h" +#include "Opcode.h" +#include +#include +#include +#include + +namespace sfz { +namespace fx { + + void Lofi::init(double sampleRate) + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _bitred[c].init(sampleRate); + _decim[c].init(sampleRate); + } + } + + void Lofi::clear() + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _bitred[c].clear(); + _decim[c].clear(); + } + } + + void Lofi::process(const float* const inputs[2], float* const outputs[2], unsigned nframes) + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _bitred[c].setDepth(_bitred_depth); + _bitred[c].process(inputs[c], outputs[c], nframes); + + _decim[c].setDepth(_decim_depth); + _decim[c].process(outputs[c], outputs[c], nframes); + } + } + + Effect* Lofi::makeInstance(absl::Span members) + { + std::unique_ptr fx { new Lofi }; + + for (const Opcode& opcode : members) { + switch (opcode.lettersOnlyHash) { + case hash("bitred"): + setValueFromOpcode(opcode, fx->_bitred_depth, { 0.0, 100.0 }); + break; + case hash("decim"): + setValueFromOpcode(opcode, fx->_decim_depth, { 0.0, 100.0 }); + break; + } + } + + return fx.release(); + } + + /// + void Lofi::Bitred::init(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 }; + fDownsampler2x.set_coefs(coefs2x); + } + + void Lofi::Bitred::clear() + { + fLastValue = 0.0; + fDownsampler2x.clear_buffers(); + } + + void Lofi::Bitred::setDepth(float depth) + { + fDepth = std::max(0.0f, std::min(100.0f, depth)); + } + + void Lofi::Bitred::process(const float* in, float* out, uint32_t nframes) + { + float depth = fDepth; + + if (depth == 0) { + if (in != out) + std::memcpy(out, in, nframes * sizeof(float)); + clear(); + return; + } + + float lastValue = fLastValue; + hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; + + float steps = (1.0f + (100.0f - depth)) * 0.75f; + + for (uint32_t i = 0; i < nframes; ++i) { + float x = in[i]; + + float y = std::copysign((int)(0.5f + std::fabs(x * steps)), x) * (1 / steps); + + float y2x[2]; + y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; + y2x[1] = y; + + lastValue = y; + + y = downsampler2x.process_sample(y2x); + out[i] = y; + } + + fLastValue = lastValue; + } + + /// + void Lofi::Decim::init(double sampleRate) + { + fSampleTime = 1.0 / 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 }; + fDownsampler2x.set_coefs(coefs2x); + } + + void Lofi::Decim::clear() + { + fPhase = 0.0; + fLastValue = 0.0; + fDownsampler2x.clear_buffers(); + } + + void Lofi::Decim::setDepth(float depth) + { + fDepth = std::max(0.0f, std::min(100.0f, depth)); + } + + void Lofi::Decim::process(const float* in, float* out, uint32_t nframes) + { + float depth = fDepth; + + if (depth == 0) { + if (in != out) + std::memcpy(out, in, nframes * sizeof(float)); + clear(); + return; + } + + float dt; + { + // exponential curve fit + float a = 1.289079e+00, b = 1.384141e-01, c = 1.313298e-04; + dt = std::pow(a, b * depth) * c - c; + dt = fSampleTime / dt; + } + + float phase = fPhase; + float lastValue = fLastValue; + hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; + + for (uint32_t i = 0; i < nframes; ++i) { + float x = in[i]; + + phase += dt; + float y = (phase > 1.0f) ? x : lastValue; + phase -= (int)phase; + + float y2x[2]; + y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; + y2x[1] = y; + + lastValue = y; + + y = downsampler2x.process_sample(y2x); + out[i] = y; + } + + fPhase = phase; + fLastValue = lastValue; + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h new file mode 100644 index 000000000..8bfda5d81 --- /dev/null +++ b/src/sfizz/effects/Lofi.h @@ -0,0 +1,79 @@ +// 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" + +namespace sfz { +namespace fx { + + /** + * @brief Bit crushing effect + */ + class Lofi : public Effect { + public: + /** + * @brief Initializes with the given sample rate. + */ + void init(double sampleRate) override; + + /** + * @brief Reset the state to initial. + */ + void clear() override; + + /** + * @brief Computes a cycle of the effect in stereo. + */ + void process(const float* const inputs[], float* const outputs[], unsigned nframes) override; + + /** + * @brief Instantiates given the contents of the block. + */ + static Effect* makeInstance(absl::Span members); + + private: + float _bitred_depth = 0; + float _decim_depth = 0; + + /// + class Bitred { + public: + void init(double sampleRate); + void clear(); + void setDepth(float depth); + void process(const float* in, float* out, uint32_t nframes); + + private: + float fDepth = 0.0; + float fLastValue = 0.0; + hiir::Downsampler2xFpu<12> fDownsampler2x; + }; + + /// + class Decim { + public: + void init(double sampleRate); + void clear(); + void setDepth(float depth); + void process(const float* in, float* out, uint32_t nframes); + + private: + float fSampleTime = 0.0; + float fDepth = 0.0; + float fPhase = 0.0; + float fLastValue = 0.0; + hiir::Downsampler2xFpu<12> fDownsampler2x; + }; + + /// + Bitred _bitred[EffectChannels]; + Decim _decim[EffectChannels]; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Nothing.cpp b/src/sfizz/effects/Nothing.cpp new file mode 100644 index 000000000..527ce5aec --- /dev/null +++ b/src/sfizz/effects/Nothing.cpp @@ -0,0 +1,31 @@ +// 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 "Nothing.h" +#include + +namespace sfz { +namespace fx { + + void Nothing::init(double sampleRate) + { + (void)sampleRate; + } + + void Nothing::clear() + { + } + + void Nothing::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + for (unsigned c = 0; c < EffectChannels; ++c) { + if (inputs[c] != outputs[c]) + std::memcpy(outputs[c], inputs[c], nframes * sizeof(float)); + } + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Nothing.h b/src/sfizz/effects/Nothing.h new file mode 100644 index 000000000..239697d81 --- /dev/null +++ b/src/sfizz/effects/Nothing.h @@ -0,0 +1,35 @@ +// 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" + +namespace sfz { +namespace fx { + + /** + * @brief Effect which does nothing + */ + class Nothing : public Effect { + public: + /** + * @brief Initializes with the given sample rate. + */ + void init(double sampleRate) 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; + }; + +} // namespace fx +} // namespace sfz From 75322915450ebbf5ba2c3e0ad061bfef28f8bd26 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:53:12 +0100 Subject: [PATCH 02/23] Moved the factory to unique pointers --- src/sfizz/Effects.cpp | 10 +++++----- src/sfizz/Effects.h | 4 ++-- src/sfizz/Synth.cpp | 5 ++--- src/sfizz/effects/Lofi.cpp | 6 +++--- src/sfizz/effects/Lofi.h | 2 +- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index a5b3b9647..5bfddf687 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -29,7 +29,7 @@ void EffectFactory::registerEffectType(absl::string_view name, Effect::MakeInsta _entries.push_back(std::move(ent)); } -Effect* EffectFactory::makeEffect(absl::Span members) +std::unique_ptr EffectFactory::makeEffect(absl::Span members) { const Opcode* opcode = nullptr; @@ -40,7 +40,7 @@ Effect* EffectFactory::makeEffect(absl::Span members) if (!opcode) { DBG("The effect does not specify a type"); - return new sfz::fx::Nothing; + return std::make_unique(); } absl::string_view type = opcode->value; @@ -52,13 +52,13 @@ Effect* EffectFactory::makeEffect(absl::Span members) if (it == end) { DBG("Unsupported effect type: " << type); - return new sfz::fx::Nothing; + return std::make_unique(); } - Effect* fx = it->make(members); + auto fx = std::unique_ptr(it->make(members)); if (!fx) { DBG("Could not instantiate effect of type: " << type); - return new sfz::fx::Nothing; + return std::make_unique(); } return fx; diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 80462d0cb..ff159d3c8 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -46,7 +46,7 @@ class Effect { @brief Type of the factory function used to instantiate an effect given the contents of the block */ - typedef Effect* (MakeInstance)(absl::Span members); + typedef std::unique_ptr (MakeInstance)(absl::Span members); }; /** @@ -67,7 +67,7 @@ class EffectFactory { /** @brief Instantiates an effect given the contents of the block. */ - Effect* makeEffect(absl::Span members); + std::unique_ptr makeEffect(absl::Span members); private: struct FactoryEntry { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 7ebbbb390..7c2c0a02e 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -256,10 +256,9 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) // create the effect and add it EffectBus& bus = getOrCreateBus(busIndex); - Effect* fx = effectFactory.makeEffect(members); - bus.addEffect(std::unique_ptr(fx)); - + auto fx = effectFactory.makeEffect(members); fx->init(sampleRate); + bus.addEffect(std::move(fx)); } void addEndpointsToVelocityCurve(sfz::Region& region) diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 9c4e1f7ea..758df4042 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -72,9 +72,9 @@ namespace fx { } } - Effect* Lofi::makeInstance(absl::Span members) + std::unique_ptr Lofi::makeInstance(absl::Span members) { - std::unique_ptr fx { new Lofi }; + auto fx = std::make_unique(); for (const Opcode& opcode : members) { switch (opcode.lettersOnlyHash) { @@ -87,7 +87,7 @@ namespace fx { } } - return fx.release(); + return fx; } /// diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 8bfda5d81..65b5e54e6 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -34,7 +34,7 @@ namespace fx { /** * @brief Instantiates given the contents of the block. */ - static Effect* makeInstance(absl::Span members); + static std::unique_ptr makeInstance(absl::Span members); private: float _bitred_depth = 0; From 70bc12f4849f86da25ea80727af7c5f7713da695 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:59:35 +0100 Subject: [PATCH 03/23] use `find_if` --- src/sfizz/Effects.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 5bfddf687..eb9746f6c 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -45,12 +45,8 @@ std::unique_ptr EffectFactory::makeEffect(absl::Span membe absl::string_view type = opcode->value; - auto it = _entries.begin(); - auto end = _entries.end(); - for (; it != end && it->name != type; ++it) - ; - - if (it == end) { + const auto it = absl::c_find_if(_entries, [&](auto&& entry) { return entry.name == type; }); + if (it == _entries.end()) { DBG("Unsupported effect type: " << type); return std::make_unique(); } From 92e58316d1eee286a52fb5c7a4b50103b5703e09 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 17:11:48 +0100 Subject: [PATCH 04/23] Const and cosmetics --- src/sfizz/Effects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index eb9746f6c..d9396516b 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -43,7 +43,7 @@ std::unique_ptr EffectFactory::makeEffect(absl::Span membe return std::make_unique(); } - absl::string_view type = opcode->value; + const absl::string_view type = opcode->value; const auto it = absl::c_find_if(_entries, [&](auto&& entry) { return entry.name == type; }); if (it == _entries.end()) { @@ -86,7 +86,7 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign return; for (unsigned c = 0; c < EffectChannels; ++c) { - absl::Span addIn(addInput[c], nframes); + absl::Span addIn{ addInput[c], nframes }; sfz::multiplyAdd(addGain, addIn, _inputs.getSpan(c)); } } From 3ce0dedc45fb1894d7cca4a07ecf9d3eef57c160 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 17:27:59 +0100 Subject: [PATCH 05/23] Mostly cosmetics --- src/sfizz/effects/Lofi.cpp | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 758df4042..3dbcec625 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -107,14 +107,12 @@ namespace fx { void Lofi::Bitred::setDepth(float depth) { - fDepth = std::max(0.0f, std::min(100.0f, depth)); + fDepth = clamp(depth, 0.0f, 100.0f); } void Lofi::Bitred::process(const float* in, float* out, uint32_t nframes) { - float depth = fDepth; - - if (depth == 0) { + if (fDepth == 0) { if (in != out) std::memcpy(out, in, nframes * sizeof(float)); clear(); @@ -124,12 +122,13 @@ namespace fx { float lastValue = fLastValue; hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; - float steps = (1.0f + (100.0f - depth)) * 0.75f; + const float steps = (1.0f + (100.0f - fDepth)) * 0.75f; + const float invSteps = 1.0f / steps; for (uint32_t i = 0; i < nframes; ++i) { float x = in[i]; - float y = std::copysign((int)(0.5f + std::fabs(x * steps)), x) * (1 / steps); + float y = std::copysign((int)(0.5f + std::fabs(x * steps)), x) * invSteps; float y2x[2]; y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; @@ -162,27 +161,24 @@ namespace fx { void Lofi::Decim::setDepth(float depth) { - fDepth = std::max(0.0f, std::min(100.0f, depth)); + fDepth = clamp(depth, 0.0f, 100.0f); } void Lofi::Decim::process(const float* in, float* out, uint32_t nframes) { - float depth = fDepth; - - if (depth == 0) { + if (fDepth == 0) { if (in != out) std::memcpy(out, in, nframes * sizeof(float)); clear(); return; } - float dt; - { + const float dt = [this]() { // exponential curve fit - float a = 1.289079e+00, b = 1.384141e-01, c = 1.313298e-04; - dt = std::pow(a, b * depth) * c - c; - dt = fSampleTime / dt; - } + const float a = 1.289079e+00, b = 1.384141e-01, c = 1.313298e-04; + const float denom = std::pow(a, b * fDepth) * c - c; + return fSampleTime / denom; + }(); float phase = fPhase; float lastValue = fLastValue; @@ -193,7 +189,7 @@ namespace fx { phase += dt; float y = (phase > 1.0f) ? x : lastValue; - phase -= (int)phase; + phase -= static_cast(phase); float y2x[2]; y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; From 7794861ce13041745cb1e815042c604d824aa857 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 18:08:32 +0100 Subject: [PATCH 06/23] Build the effect bus in the vector --- src/sfizz/Synth.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 7c2c0a02e..12db2223a 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -123,9 +123,8 @@ void sfz::Synth::clear() list.clear(); regions.clear(); effectBuses.clear(); - EffectBus* mainBus = new EffectBus; - effectBuses.emplace_back(mainBus); - mainBus->setGainToMain(1.0); + effectBuses.emplace_back(new EffectBus); + effectBuses[0]->setGainToMain(1.0); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; From 49a182d90f3d97fd98a0c08373d55eeccfae1e0a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 18:23:50 +0100 Subject: [PATCH 07/23] Add effects in the callback log --- src/sfizz/Logger.cpp | 3 ++- src/sfizz/Logger.h | 1 + src/sfizz/Synth.cpp | 43 +++++++++++++++++++++++++------------------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/sfizz/Logger.cpp b/src/sfizz/Logger.cpp index 357061674..53bfa66bf 100644 --- a/src/sfizz/Logger.cpp +++ b/src/sfizz/Logger.cpp @@ -83,7 +83,7 @@ sfz::Logger::~Logger() fs::path callbackLogPath{ fs::current_path() / callbackLogFilename.str() }; std::cout << "Logging " << callbackTimes.size() << " callback times to " << callbackLogPath.filename() << '\n'; std::ofstream callbackLogFile { callbackLogPath.string() }; - callbackLogFile << "Dispatch,RenderMethod,Data,Amplitude,Filters,Panning,NumVoices,NumSamples" << '\n'; + callbackLogFile << "Dispatch,RenderMethod,Data,Amplitude,Filters,Panning,Effects,NumVoices,NumSamples" << '\n'; for (auto& time: callbackTimes) callbackLogFile << time.breakdown.dispatch.count() << ',' << time.breakdown.renderMethod.count() << ',' @@ -91,6 +91,7 @@ sfz::Logger::~Logger() << time.breakdown.amplitude.count() << ',' << time.breakdown.filters.count() << ',' << time.breakdown.panning.count() << ',' + << time.breakdown.effects.count() << ',' << time.numVoices << ',' << time.numSamples << '\n'; } diff --git a/src/sfizz/Logger.h b/src/sfizz/Logger.h index 9861ba875..449c19d9d 100644 --- a/src/sfizz/Logger.h +++ b/src/sfizz/Logger.h @@ -61,6 +61,7 @@ struct CallbackBreakdown Duration amplitude { 0 }; Duration filters { 0 }; Duration panning { 0 }; + Duration effects { 0 }; }; struct CallbackTime diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 12db2223a..554c38947 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -501,14 +501,16 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); - // Prepare the effect inputs. They are mixes of per-region outputs. - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) - bus->clearInputs(numFrames); + CallbackBreakdown callbackBreakdown; + + { // Prepare the effect inputs. They are mixes of per-region outputs. + ScopedTiming logger { callbackBreakdown.effects }; + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->clearInputs(numFrames); + } } - // - CallbackBreakdown callbackBreakdown; int numActiveVoices { 0 }; { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod }; @@ -525,11 +527,13 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept numActiveVoices++; voice->renderBlock(temp); - // Add the output into the effects linked to this region - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { - float addGain = region->getGainToEffectBus(i); - bus->addToInputs(temp, addGain, numFrames); + { // Add the output into the effects linked to this region + ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + float addGain = region->getGainToEffectBus(i); + bus->addToInputs(temp, addGain, numFrames); + } } } @@ -540,13 +544,16 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept } } - // Apply effect buses - // -- note(jpc) there is always a "main" bus which is initially empty. - // without any , the signal is just going to flow through it. - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { - bus->process(numFrames); - bus->mixOutputsTo(buffer, tempMixNode, numFrames); + { // Apply effect buses + // -- note(jpc) there is always a "main" bus which is initially empty. + // without any , the signal is just going to flow through it. + ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + bus->process(numFrames); + bus->mixOutputsTo(buffer, tempMixNode, numFrames); + } } } From 9d3364889b75374be7ff8ae0f63609e4d0ff7a7d Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 19:02:45 +0100 Subject: [PATCH 08/23] Adapt the block size of the effect bus to the one of the Synth --- src/sfizz/Effects.cpp | 8 +++++++- src/sfizz/Effects.h | 9 ++++++++- src/sfizz/Synth.cpp | 4 ++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index d9396516b..c9fad083f 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -124,10 +124,16 @@ void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[] const float gainToMix = _gainToMix; for (unsigned c = 0; c < EffectChannels; ++c) { - absl::Span fxOut = _outputs.getConstSpan(c); + auto fxOut = _outputs.getConstSpan(c); sfz::multiplyAdd(gainToMain, fxOut, absl::Span(mainOutput[c], nframes)); sfz::multiplyAdd(gainToMix, fxOut, absl::Span(mixOutput[c], nframes)); } } +void EffectBus::setSamplesPerBlock(int samplesPerBlock) noexcept +{ + _inputs.resize(samplesPerBlock); + _outputs.resize(samplesPerBlock); +} + } // namespace sfz diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index ff159d3c8..0f7091fc9 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -136,10 +136,17 @@ class EffectBus { */ void mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes); + /** + * @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) noexcept; + private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; - AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; + AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; float _gainToMain = 0.0; float _gainToMix = 0.0; }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 554c38947..92cfa5526 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -125,6 +125,8 @@ void sfz::Synth::clear() effectBuses.clear(); effectBuses.emplace_back(new EffectBus); effectBuses[0]->setGainToMain(1.0); + effectBuses[0]->setSamplesPerBlock(samplesPerBlock); + effectBuses[0]->init(sampleRate); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; @@ -463,6 +465,8 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); + for (auto& bus: effectBuses) + bus->setSamplesPerBlock(samplesPerBlock); } void sfz::Synth::setSampleRate(float sampleRate) noexcept From dc9acbb3de572e618ad7340986d4c3b1beb98286 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 19:03:08 +0100 Subject: [PATCH 09/23] Corrected tests --- tests/SynthT.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index e5bca7c9b..072206c04 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -77,6 +77,7 @@ TEST_CASE("[Synth] Check that we can change the size of the preload before and a { sfz::Synth synth; synth.setPreloadSize(512); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.setPreloadSize(1024); @@ -92,6 +93,7 @@ TEST_CASE("[Synth] Check that we can change the oversampling factor before and a { sfz::Synth synth; synth.setOversamplingFactor(sfz::Oversampling::x2); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.setOversamplingFactor(sfz::Oversampling::x4); From dcc8a58377c97fdae8ee3c26f3d83090e30d923a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 23:07:00 +0100 Subject: [PATCH 10/23] Removed the downsampler reference --- src/sfizz/effects/Lofi.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 3dbcec625..7c1c95d44 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -120,7 +120,6 @@ namespace fx { } float lastValue = fLastValue; - hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; const float steps = (1.0f + (100.0f - fDepth)) * 0.75f; const float invSteps = 1.0f / steps; @@ -136,7 +135,7 @@ namespace fx { lastValue = y; - y = downsampler2x.process_sample(y2x); + y = fDownsampler2x.process_sample(y2x); out[i] = y; } @@ -182,7 +181,6 @@ namespace fx { float phase = fPhase; float lastValue = fLastValue; - hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; for (uint32_t i = 0; i < nframes; ++i) { float x = in[i]; @@ -197,7 +195,7 @@ namespace fx { lastValue = y; - y = downsampler2x.process_sample(y2x); + y = fDownsampler2x.process_sample(y2x); out[i] = y; } From 2e6402bae0c2d7753110d51cb5492ac4703499ca Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 23:34:10 +0100 Subject: [PATCH 11/23] Corrected formatting --- benchmarks/BM_multiplyAddFixedGain.cpp | 68 ++++++++++++++------------ src/sfizz/AudioSpan.h | 4 +- src/sfizz/Effects.cpp | 2 +- src/sfizz/Effects.h | 4 +- src/sfizz/Region.cpp | 22 ++++----- src/sfizz/Synth.cpp | 19 +++---- 6 files changed, 61 insertions(+), 58 deletions(-) diff --git a/benchmarks/BM_multiplyAddFixedGain.cpp b/benchmarks/BM_multiplyAddFixedGain.cpp index b484389dc..919516b2d 100644 --- a/benchmarks/BM_multiplyAddFixedGain.cpp +++ b/benchmarks/BM_multiplyAddFixedGain.cpp @@ -14,58 +14,64 @@ class MultiplyAddFixedGain : public benchmark::Fixture { public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0, 1 }; - input = std::vector(state.range(0)); - output = std::vector(state.range(0)); - gain = dist(gen); - std::fill(output.begin(), output.end(), 1.0f ); - std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); - } - - void TearDown(const ::benchmark::State& state [[maybe_unused]]) { + void SetUp(const ::benchmark::State& state) + { + std::random_device rd {}; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0, 1 }; + input = std::vector(state.range(0)); + output = std::vector(state.range(0)); + gain = dist(gen); + std::fill(output.begin(), output.end(), 1.0f); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } - } + void TearDown(const ::benchmark::State& state [[maybe_unused]]) + { + } - float gain = {}; - std::vector input; - std::vector output; + float gain = {}; + std::vector input; + std::vector output; }; -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Straight)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Straight) +(benchmark::State& state) +{ + for (auto _ : state) { for (int i = 0; i < state.range(0); ++i) output[i] += gain * input[i]; } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar_Unaligned) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD_Unaligned) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index c608848c9..f1bd0f915 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -212,7 +212,7 @@ class AudioSpan { /** * @brief Convert implicitly to a pointer of channels */ - operator const float* const *() const noexcept + operator const float* const*() const noexcept { return spans.data(); } @@ -220,7 +220,7 @@ class AudioSpan { /** * @brief Convert implicitly to a pointer of channels */ - operator float* const *() noexcept + operator float* const*() noexcept { return spans.data(); } diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index c9fad083f..fb77acf23 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -86,7 +86,7 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign return; for (unsigned c = 0; c < EffectChannels; ++c) { - absl::Span addIn{ addInput[c], nframes }; + absl::Span addIn { addInput[c], nframes }; sfz::multiplyAdd(addGain, addIn, _inputs.getSpan(c)); } } diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 0f7091fc9..1d0947ed6 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -46,7 +46,7 @@ class Effect { @brief Type of the factory function used to instantiate an effect given the contents of the block */ - typedef std::unique_ptr (MakeInstance)(absl::Span members); + typedef std::unique_ptr(MakeInstance)(absl::Span members); }; /** @@ -146,7 +146,7 @@ class EffectBus { private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; - AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; + AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; float _gainToMain = 0.0; float _gainToMix = 0.0; }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 354752636..d0f2e7064 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -734,18 +734,18 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("effect"): // effect& - { - const auto effectNumber = opcode.backParameter(); - if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) - break; - auto value = readOpcode(opcode.value, {0, 100}); - if (!value) - break; - if (static_cast(*effectNumber + 1) > gainToEffect.size()) - gainToEffect.resize(*effectNumber + 1); - gainToEffect[*effectNumber] = *value / 100; + { + const auto effectNumber = opcode.backParameter(); + if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) break; - } + auto value = readOpcode(opcode.value, { 0, 100 }); + if (!value) + break; + if (static_cast(*effectNumber + 1) > gainToEffect.size()) + gainToEffect.resize(*effectNumber + 1); + gainToEffect[*effectNumber] = *value / 100; + break; + } // Ignored opcodes case hash("hichan"): diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 92cfa5526..a6a6482a9 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -201,7 +201,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) auto getOrCreateBus = [this](unsigned index) -> EffectBus& { if (index + 1 > effectBuses.size()) effectBuses.resize(index + 1); - EffectBusPtr &slot = effectBuses[index]; + EffectBusPtr& slot = effectBuses[index]; if (!slot) slot.reset(new EffectBus); return *slot; @@ -213,10 +213,10 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) busName = opcode.value; break; - // note(jpc): gain opcodes are linear volumes in % units + // note(jpc): gain opcodes are linear volumes in % units case hash("directtomain"): - if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) getOrCreateBus(0).setGainToMain(*valueOpt / 100); break; @@ -225,7 +225,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) unsigned number = *numberOpt; if (number < 1 || number > config::maxEffectBuses) break; - if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) getOrCreateBus(number).setGainToMain(*valueOpt / 100); } break; @@ -235,7 +235,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) unsigned number = *numberOpt; if (number < 1 || number > config::maxEffectBuses) break; - if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) getOrCreateBus(number).setGainToMix(*valueOpt / 100); } break; @@ -245,12 +245,9 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) unsigned busIndex; if (busName.empty() || busName == "main") busIndex = 0; - else if (busName.size() > 2 && busName.substr(0, 2) == "fx" && - absl::SimpleAtoi(busName.substr(2), &busIndex) && - busIndex >= 1 && busIndex <= config::maxEffectBuses) { + else if (busName.size() > 2 && busName.substr(0, 2) == "fx" && absl::SimpleAtoi(busName.substr(2), &busIndex) && busIndex >= 1 && busIndex <= config::maxEffectBuses) { // an effect bus fxN, with N usually in [1,4] - } - else { + } else { DBG("Unsupported effect bus: " << busName); return; } @@ -465,7 +462,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - for (auto& bus: effectBuses) + for (auto& bus : effectBuses) bus->setSamplesPerBlock(samplesPerBlock); } From 4c83ca3a7895b8991419e6ee020262836a8e575c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 23:35:15 +0100 Subject: [PATCH 12/23] Put the timings in their proper field --- src/sfizz/Synth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a6a6482a9..a10fe5f5c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -529,7 +529,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept voice->renderBlock(temp); { // Add the output into the effects linked to this region - ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; for (size_t i = 0; i < numEffectBuses; ++i) { if (EffectBus* bus = effectBuses[i].get()) { float addGain = region->getGainToEffectBus(i); @@ -548,7 +548,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Apply effect buses // -- note(jpc) there is always a "main" bus which is initially empty. // without any , the signal is just going to flow through it. - ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; for (size_t i = 0; i < numEffectBuses; ++i) { if (EffectBus* bus = effectBuses[i].get()) { From 9d56e5560cdb845fce4b6c7815c0b64b4675300a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 00:22:09 +0100 Subject: [PATCH 13/23] No need to wrap in a unique_ptr --- src/sfizz/Effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index fb77acf23..9ae699fb5 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -51,7 +51,7 @@ std::unique_ptr EffectFactory::makeEffect(absl::Span membe return std::make_unique(); } - auto fx = std::unique_ptr(it->make(members)); + auto fx = it->make(members); if (!fx) { DBG("Could not instantiate effect of type: " << type); return std::make_unique(); From 090a5995db3246e0c6f7e94f339b4cbb0ca739e9 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:07:09 +0100 Subject: [PATCH 14/23] Add a view on the effect bus --- src/sfizz/Synth.cpp | 5 +++++ src/sfizz/Synth.h | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a10fe5f5c..eeedafe12 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -810,6 +810,11 @@ const sfz::Region* sfz::Synth::getRegionView(int idx) const noexcept return (size_t)idx < regions.size() ? regions[idx].get() : nullptr; } +const sfz::EffectBus* sfz::Synth::getEffectBusView(int idx) const noexcept +{ + return (size_t)idx < effectBuses.size() ? effectBuses[idx].get() : nullptr; +} + const sfz::Voice* sfz::Synth::getVoiceView(int idx) const noexcept { return (size_t)idx < voices.size() ? voices[idx].get() : nullptr; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 3f94262db..616cf60d2 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -132,6 +132,14 @@ class Synth : public Parser { * @return const Region* */ const Voice* getVoiceView(int idx) const noexcept; + /** + * @brief Get a raw view into a specific voice. This is mostly used + * for testing. + * + * @param idx + * @return const Region* + */ + const EffectBus* getEffectBusView(int idx) const noexcept; /** * @brief Get a list of unknown opcodes. The lifetime of the * string views in the code are linked to the currently loaded From 1ac68cc3d2d082555b26c5c5d2a229b633b779b8 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:07:56 +0100 Subject: [PATCH 15/23] Add introspection in the busses --- src/sfizz/Effects.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 1d0947ed6..17ded9dea 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -106,6 +106,20 @@ class EffectBus { */ void setGainToMix(float gain) { _gainToMix = gain; } + /** + * @brief Returns the gain for the main out + * + * @return float + */ + float gainToMain() const { return _gainToMain; } + + /** + * @brief Returns the gain for the mix out + * + * @return float + */ + float gainToMix() const { return _gainToMix; } + /** @brief Resets the input buffers to zero. */ @@ -143,6 +157,12 @@ class EffectBus { */ void setSamplesPerBlock(int samplesPerBlock) noexcept; + /** + * @brief Return the number of effects in the bus + * + * @return size_t + */ + size_t numEffects() const noexcept; private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; From 235069fd27a07076e02bc4ac2cdcb6cd5b7d397c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:09:27 +0100 Subject: [PATCH 16/23] Change init to setSampleRate andset the blockSize and sampleRate of new busses --- src/sfizz/Effects.cpp | 8 ++++++-- src/sfizz/Effects.h | 4 ++-- src/sfizz/Synth.cpp | 17 ++++++++++------- src/sfizz/effects/Lofi.cpp | 2 +- src/sfizz/effects/Lofi.h | 2 +- src/sfizz/effects/Nothing.cpp | 2 +- src/sfizz/effects/Nothing.h | 2 +- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 9ae699fb5..c31f245e7 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -91,10 +91,10 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign } } -void EffectBus::init(double sampleRate) +void EffectBus::setSampleRate(double sampleRate) { for (const auto& effectPtr : _effects) - effectPtr->init(sampleRate); + effectPtr->setSampleRate(sampleRate); } void EffectBus::clear() @@ -130,6 +130,10 @@ void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[] } } +size_t EffectBus::numEffects() const noexcept +{ + return _effects.size(); +} void EffectBus::setSamplesPerBlock(int samplesPerBlock) noexcept { _inputs.resize(samplesPerBlock); diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 17ded9dea..0ea8d195f 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -30,7 +30,7 @@ class Effect { /** @brief Initializes with the given sample rate. */ - virtual void init(double sampleRate) = 0; + virtual void setSampleRate(double sampleRate) = 0; /** @brief Reset the state to initial. @@ -133,7 +133,7 @@ class EffectBus { /** @brief Initializes all effects in the bus with the given sample rate. */ - void init(double sampleRate); + void setSampleRate(double sampleRate); /** @brief Resets the state of all effects in the bus. diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index eeedafe12..efefd1e70 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -126,7 +126,7 @@ void sfz::Synth::clear() effectBuses.emplace_back(new EffectBus); effectBuses[0]->setGainToMain(1.0); effectBuses[0]->setSamplesPerBlock(samplesPerBlock); - effectBuses[0]->init(sampleRate); + effectBuses[0]->setSampleRate(sampleRate); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; @@ -201,10 +201,13 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) auto getOrCreateBus = [this](unsigned index) -> EffectBus& { if (index + 1 > effectBuses.size()) effectBuses.resize(index + 1); - EffectBusPtr& slot = effectBuses[index]; - if (!slot) - slot.reset(new EffectBus); - return *slot; + EffectBusPtr& bus = effectBuses[index]; + if (!bus) { + bus.reset(new EffectBus); + bus->setSampleRate(sampleRate); + bus->setSamplesPerBlock(samplesPerBlock); + } + return *bus; }; for (const Opcode& opcode : members) { @@ -255,7 +258,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) // create the effect and add it EffectBus& bus = getOrCreateBus(busIndex); auto fx = effectFactory.makeEffect(members); - fx->init(sampleRate); + fx->setSampleRate(sampleRate); bus.addEffect(std::move(fx)); } @@ -482,7 +485,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { if (EffectBus* bus = effectBuses[i].get()) - bus->init(sampleRate); + bus->setSampleRate(sampleRate); } } diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 7c1c95d44..fdd511d1c 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -45,7 +45,7 @@ namespace sfz { namespace fx { - void Lofi::init(double sampleRate) + void Lofi::setSampleRate(double sampleRate) { for (unsigned c = 0; c < EffectChannels; ++c) { _bitred[c].init(sampleRate); diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 65b5e54e6..1c7451541 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -19,7 +19,7 @@ namespace fx { /** * @brief Initializes with the given sample rate. */ - void init(double sampleRate) override; + void setSampleRate(double sampleRate) override; /** * @brief Reset the state to initial. diff --git a/src/sfizz/effects/Nothing.cpp b/src/sfizz/effects/Nothing.cpp index 527ce5aec..47a3f8b03 100644 --- a/src/sfizz/effects/Nothing.cpp +++ b/src/sfizz/effects/Nothing.cpp @@ -10,7 +10,7 @@ namespace sfz { namespace fx { - void Nothing::init(double sampleRate) + void Nothing::setSampleRate(double sampleRate) { (void)sampleRate; } diff --git a/src/sfizz/effects/Nothing.h b/src/sfizz/effects/Nothing.h index 239697d81..12326897a 100644 --- a/src/sfizz/effects/Nothing.h +++ b/src/sfizz/effects/Nothing.h @@ -18,7 +18,7 @@ namespace fx { /** * @brief Initializes with the given sample rate. */ - void init(double sampleRate) override; + void setSampleRate(double sampleRate) override; /** * @brief Reset the state to initial. From 9ce32c636cc54bd107bdc2244ce53ccf099dea5f Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:10:56 +0100 Subject: [PATCH 17/23] WIP tests --- tests/SynthT.cpp | 53 +++++++++++++++++++++++- tests/TestFiles/Effects/base.sfz | 4 ++ tests/TestFiles/Effects/bitcrusher_1.sfz | 9 ++++ tests/TestFiles/Effects/bitcrusher_2.sfz | 13 ++++++ tests/TestFiles/Effects/bitcrusher_3.sfz | 13 ++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/TestFiles/Effects/base.sfz create mode 100644 tests/TestFiles/Effects/bitcrusher_1.sfz create mode 100644 tests/TestFiles/Effects/bitcrusher_2.sfz create mode 100644 tests/TestFiles/Effects/bitcrusher_3.sfz diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 072206c04..822f1d0a8 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -13,7 +13,7 @@ constexpr int blockSize { 256 }; TEST_CASE("[Synth] Play and check active voices") { sfz::Synth synth; - synth.setSamplesPerBlock(256); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); @@ -29,7 +29,7 @@ TEST_CASE("[Synth] Play and check active voices") TEST_CASE("[Synth] Change the number of voice while playing") { sfz::Synth synth; - synth.setSamplesPerBlock(256); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); @@ -210,3 +210,52 @@ TEST_CASE("[Synth] Trigger=release_key and an envelope properly kills the voice synth.renderBlock(buffer); REQUIRE( synth.getVoiceView(0)->isFree() ); } + +TEST_CASE("[Synth] Number of effect buses and resetting behavior") +{ + sfz::Synth synth; + synth.setSamplesPerBlock(blockSize); + sfz::AudioBuffer buffer { 2, blockSize }; + + REQUIRE( synth.getEffectBusView(0) == nullptr); // No effects at first + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/base.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_2.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + REQUIRE( synth.getEffectBusView(1) != nullptr); // and an FX bus + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/base.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + REQUIRE( synth.getEffectBusView(1) == nullptr); // and no FX bus + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_3.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + REQUIRE( synth.getEffectBusView(1) == nullptr); // empty/uninitialized fx bus + REQUIRE( synth.getEffectBusView(2) == nullptr); // empty/uninitialized fx bus + REQUIRE( synth.getEffectBusView(3) != nullptr); // and an FX bus (because we built up to fx3) + REQUIRE( synth.getEffectBusView(3)->numEffects() == 1); + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); +} + +TEST_CASE("[Synth] No effect in the main bus") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/base.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 1 ); + REQUIRE( bus->gainToMix() == 0 ); +} diff --git a/tests/TestFiles/Effects/base.sfz b/tests/TestFiles/Effects/base.sfz new file mode 100644 index 000000000..07b3874de --- /dev/null +++ b/tests/TestFiles/Effects/base.sfz @@ -0,0 +1,4 @@ + +lokey=0 +hikey=127 +sample=*sine diff --git a/tests/TestFiles/Effects/bitcrusher_1.sfz b/tests/TestFiles/Effects/bitcrusher_1.sfz new file mode 100644 index 000000000..dd28c5c44 --- /dev/null +++ b/tests/TestFiles/Effects/bitcrusher_1.sfz @@ -0,0 +1,9 @@ + +lokey=0 +hikey=127 +sample=*sine + + +type=lofi +bitred=90 +decim=10 diff --git a/tests/TestFiles/Effects/bitcrusher_2.sfz b/tests/TestFiles/Effects/bitcrusher_2.sfz new file mode 100644 index 000000000..a0544f268 --- /dev/null +++ b/tests/TestFiles/Effects/bitcrusher_2.sfz @@ -0,0 +1,13 @@ + +lokey=0 +hikey=127 +sample=*sine +effect1=100 + + +directtomain=50 +fx1tomain=50 +type=lofi +bus=fx1 +bitred=90 +decim=10 diff --git a/tests/TestFiles/Effects/bitcrusher_3.sfz b/tests/TestFiles/Effects/bitcrusher_3.sfz new file mode 100644 index 000000000..51585e31d --- /dev/null +++ b/tests/TestFiles/Effects/bitcrusher_3.sfz @@ -0,0 +1,13 @@ + +lokey=0 +hikey=127 +sample=*sine +effect1=100 + + +directtomain=50 +fx3tomain=50 +type=lofi +bus=fx3 +bitred=90 +decim=10 From 09cca79ab00d9dc1a31e4bf7ef131ba6ff256ad9 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:27:45 +0100 Subject: [PATCH 18/23] Rebased on the new parser --- src/sfizz/Region.cpp | 12 ++++++------ src/sfizz/Synth.cpp | 26 ++++++++++---------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index d0f2e7064..94d0218e0 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -733,17 +733,17 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break; - case hash("effect"): // effect& + case hash("effect&"): { - const auto effectNumber = opcode.backParameter(); - if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) + const auto effectNumber = opcode.parameters.back(); + if (!effectNumber || effectNumber < 1 || effectNumber > config::maxEffectBuses) break; auto value = readOpcode(opcode.value, { 0, 100 }); if (!value) break; - if (static_cast(*effectNumber + 1) > gainToEffect.size()) - gainToEffect.resize(*effectNumber + 1); - gainToEffect[*effectNumber] = *value / 100; + if (static_cast(effectNumber + 1) > gainToEffect.size()) + gainToEffect.resize(effectNumber + 1); + gainToEffect[effectNumber] = *value / 100; break; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index efefd1e70..67dadef16 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -223,24 +223,18 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) getOrCreateBus(0).setGainToMain(*valueOpt / 100); break; - case hash("fxtomain"): // fx&tomain - if (auto numberOpt = opcode.firstParameter()) { - unsigned number = *numberOpt; - if (number < 1 || number > config::maxEffectBuses) - break; - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) - getOrCreateBus(number).setGainToMain(*valueOpt / 100); - } + case hash("fx&tomain"): // fx&tomain + if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + getOrCreateBus(opcode.parameters.front()).setGainToMain(*valueOpt / 100); break; - case hash("fxtomix"): // fx&tomix - if (auto numberOpt = opcode.firstParameter()) { - unsigned number = *numberOpt; - if (number < 1 || number > config::maxEffectBuses) - break; - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) - getOrCreateBus(number).setGainToMix(*valueOpt / 100); - } + case hash("fx&tomix"): // fx&tomix + if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + getOrCreateBus(opcode.parameters.front()).setGainToMix(*valueOpt / 100); break; } } From 1f91bf0f66d29891bf51f756488b5aa4b08af94a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 23:43:30 +0100 Subject: [PATCH 19/23] Fix a potential null pointer error (effect buses can have gaps) --- src/sfizz/Synth.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 67dadef16..0ffcfdb8e 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -459,8 +459,10 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - for (auto& bus : effectBuses) - bus->setSamplesPerBlock(samplesPerBlock); + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->setSamplesPerBlock(samplesPerBlock); + } } void sfz::Synth::setSampleRate(float sampleRate) noexcept From 0360dfa3b682dae0ebff9e0fa67dd0e89c2dd60b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 23:50:11 +0100 Subject: [PATCH 20/23] Pass the buffer size to effects --- src/sfizz/Effects.cpp | 3 +++ src/sfizz/Effects.h | 6 ++++++ src/sfizz/effects/Lofi.cpp | 5 +++++ src/sfizz/effects/Lofi.h | 6 ++++++ src/sfizz/effects/Nothing.cpp | 5 +++++ src/sfizz/effects/Nothing.h | 6 ++++++ 6 files changed, 31 insertions(+) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index c31f245e7..512f2b5e5 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -138,6 +138,9 @@ void EffectBus::setSamplesPerBlock(int samplesPerBlock) noexcept { _inputs.resize(samplesPerBlock); _outputs.resize(samplesPerBlock); + + for (const auto& effectPtr : _effects) + effectPtr->setSamplesPerBlock(samplesPerBlock); } } // namespace sfz diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 0ea8d195f..1c1edfebf 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -32,6 +32,12 @@ class Effect { */ virtual void setSampleRate(double sampleRate) = 0; + /** + * @brief Sets the maximum number of frames to render at a time. The actual + * value can be lower but should never be higher. + */ + virtual void setSamplesPerBlock(int samplesPerBlock) = 0; + /** @brief Reset the state to initial. */ diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index fdd511d1c..1fcc33045 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -53,6 +53,11 @@ namespace fx { } } + void Lofi::setSamplesPerBlock(int samplesPerBlock) + { + (void)samplesPerBlock; + } + void Lofi::clear() { for (unsigned c = 0; c < EffectChannels; ++c) { diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 1c7451541..9b97c6e31 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -21,6 +21,12 @@ namespace fx { */ 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. */ diff --git a/src/sfizz/effects/Nothing.cpp b/src/sfizz/effects/Nothing.cpp index 47a3f8b03..d2101de14 100644 --- a/src/sfizz/effects/Nothing.cpp +++ b/src/sfizz/effects/Nothing.cpp @@ -15,6 +15,11 @@ namespace fx { (void)sampleRate; } + void Nothing::setSamplesPerBlock(int samplesPerBlock) + { + (void)samplesPerBlock; + } + void Nothing::clear() { } diff --git a/src/sfizz/effects/Nothing.h b/src/sfizz/effects/Nothing.h index 12326897a..2ac276004 100644 --- a/src/sfizz/effects/Nothing.h +++ b/src/sfizz/effects/Nothing.h @@ -20,6 +20,12 @@ namespace fx { */ 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. */ From 532cd88e96ef351dcf1665dce2a4dc560510d329 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 21:27:19 +0100 Subject: [PATCH 21/23] Add some tests on effect gains --- tests/SynthT.cpp | 60 ++++++++++++++++++++++++++++++ tests/TestFiles/Effects/to_mix.sfz | 11 ++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/TestFiles/Effects/to_mix.sfz diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 822f1d0a8..9ed0c73cc 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -259,3 +259,63 @@ TEST_CASE("[Synth] No effect in the main bus") REQUIRE( bus->gainToMain() == 1 ); REQUIRE( bus->gainToMix() == 0 ); } + +TEST_CASE("[Synth] One effect") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_1.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 1 ); + REQUIRE( bus->gainToMix() == 0 ); +} + +TEST_CASE("[Synth] Effect on a second bus") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_2.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); + bus = synth.getEffectBusView(1); + REQUIRE( bus != nullptr); + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); +} + + +TEST_CASE("[Synth] Effect on a third bus") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_3.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); + bus = synth.getEffectBusView(3); + REQUIRE( bus != nullptr); + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); +} + +TEST_CASE("[Synth] Gain to mix") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/to_mix.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 1 ); + REQUIRE( bus->gainToMix() == 0 ); + bus = synth.getEffectBusView(1); + REQUIRE( bus != nullptr); + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 0 ); + REQUIRE( bus->gainToMix() == 0.5 ); +} diff --git a/tests/TestFiles/Effects/to_mix.sfz b/tests/TestFiles/Effects/to_mix.sfz new file mode 100644 index 000000000..8f12e70e8 --- /dev/null +++ b/tests/TestFiles/Effects/to_mix.sfz @@ -0,0 +1,11 @@ + +lokey=0 +hikey=127 +sample=*sine + + +fx1tomix=50 +bus=fx1 +type=lofi +bitred=90 +decim=10 From af9231db33e2c1eb233ac340bc43957acb499c4e Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 21:32:55 +0100 Subject: [PATCH 22/23] Use foreach loops --- src/sfizz/Synth.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 0ffcfdb8e..deb58a8c0 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -459,8 +459,9 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { - if (EffectBus* bus = effectBuses[i].get()) + + for (auto& bus: effectBuses) { + if (bus) bus->setSamplesPerBlock(samplesPerBlock); } } @@ -479,8 +480,8 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept resources.filterPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate); - for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { - if (EffectBus* bus = effectBuses[i].get()) + for (auto& bus: effectBuses) { + if (bus) bus->setSampleRate(sampleRate); } } @@ -497,7 +498,6 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept return; size_t numFrames = buffer.getNumFrames(); - size_t numEffectBuses = effectBuses.size(); auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); @@ -505,8 +505,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Prepare the effect inputs. They are mixes of per-region outputs. ScopedTiming logger { callbackBreakdown.effects }; - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) + for (auto& bus: effectBuses) { + if (bus) bus->clearInputs(numFrames); } } @@ -529,8 +529,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Add the output into the effects linked to this region ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (auto& bus = effectBuses[i]) { float addGain = region->getGainToEffectBus(i); bus->addToInputs(temp, addGain, numFrames); } @@ -549,8 +549,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept // without any , the signal is just going to flow through it. ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { + for (auto& bus: effectBuses) { + if (bus) { bus->process(numFrames); bus->mixOutputsTo(buffer, tempMixNode, numFrames); } From e84af98d27425ce8ad7486e974ae28da8cf93f1c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 22:09:46 +0100 Subject: [PATCH 23/23] Added tests and a send to to_mix.sfz --- tests/RegionT.cpp | 17 +++++++++++++++++ tests/TestFiles/Effects/to_mix.sfz | 1 + 2 files changed, 18 insertions(+) diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index aa86f52b3..71063e741 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1350,6 +1350,23 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "eq1_freqcc15", "50000" }); REQUIRE(region.equalizers[0].frequencyCC[15] == 30000.0f); } + + SECTION("Effects send") + { + REQUIRE(region.gainToEffect.size() == 1); + REQUIRE(region.gainToEffect[0] == 1.0f); + region.parseOpcode({ "effect1", "50.4" }); + REQUIRE(region.gainToEffect.size() == 2); + REQUIRE(region.gainToEffect[1] == 0.504f); + region.parseOpcode({ "effect3", "100" }); + REQUIRE(region.gainToEffect.size() == 4); + REQUIRE(region.gainToEffect[2] == 0.0f); + REQUIRE(region.gainToEffect[3] == 1.0f); + region.parseOpcode({ "effect3", "150.1" }); + REQUIRE(region.gainToEffect[3] == 1.0f); + region.parseOpcode({ "effect3", "-50.65" }); + REQUIRE(region.gainToEffect[3] == 0.0f); + } } // Specific region bugs diff --git a/tests/TestFiles/Effects/to_mix.sfz b/tests/TestFiles/Effects/to_mix.sfz index 8f12e70e8..65cd18372 100644 --- a/tests/TestFiles/Effects/to_mix.sfz +++ b/tests/TestFiles/Effects/to_mix.sfz @@ -2,6 +2,7 @@ lokey=0 hikey=127 sample=*sine +effect1=100 fx1tomix=50