Skip to content

Commit

Permalink
Initial effects
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcima committed Mar 2, 2020
1 parent 2e5cdba commit f5f0315
Show file tree
Hide file tree
Showing 13 changed files with 850 additions and 16 deletions.
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
16 changes: 16 additions & 0 deletions src/sfizz/AudioSpan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type> corresponding to a specific channel
*
Expand Down
4 changes: 4 additions & 0 deletions src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
137 changes: 137 additions & 0 deletions src/sfizz/Effects.cpp
Original file line number Diff line number Diff line change
@@ -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 <algorithm>

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<const Opcode> 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<Effect> fx)
{
_effects.emplace_back(std::move(fx));
}

void EffectBus::clearInputs(unsigned nframes)
{
AudioSpan<float>(_inputs).first(nframes).fill(0.0f);
AudioSpan<float>(_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<const float> 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<float>(_inputs), AudioSpan<float>(_outputs), nframes);
for (size_t i = 1; i < numEffects; ++i)
_effects[i]->process(
AudioSpan<float>(_outputs), AudioSpan<float>(_outputs), nframes);
} else
fx::Nothing().process(
AudioSpan<float>(_inputs), AudioSpan<float>(_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<const float> fxOut = _outputs.getConstSpan(c);
sfz::multiplyAdd(gainToMain, fxOut, absl::Span<float>(mainOutput[c], nframes));
sfz::multiplyAdd(gainToMix, fxOut, absl::Span<float>(mixOutput[c], nframes));
}
}

} // namespace sfz
147 changes: 147 additions & 0 deletions src/sfizz/Effects.h
Original file line number Diff line number Diff line change
@@ -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 <array>
#include <vector>
#include <memory>

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 <effect> block
*/
typedef Effect* (MakeInstance)(absl::Span<const Opcode> 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 <effect> block.
*/
Effect* makeEffect(absl::Span<const Opcode> members);

private:
struct FactoryEntry {
std::string name;
Effect::MakeInstance* make;
};

std::vector<FactoryEntry> _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<Effect> 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<std::unique_ptr<Effect>> _effects;
AudioBuffer<float> _inputs { EffectChannels, config::defaultSamplesPerBlock };
AudioBuffer<float> _outputs { EffectChannels, config::defaultSamplesPerBlock };
float _gainToMain = 0.0;
float _gainToMix = 0.0;
};

} // namespace sfz
22 changes: 22 additions & 0 deletions src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,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<float>(opcode.value, {0, 100});
if (!value)
break;
if (static_cast<size_t>(*effectNumber + 1) > gainToEffect.size())
gainToEffect.resize(*effectNumber + 1);
gainToEffect[*effectNumber] = *value / 100;
break;
}

// Ignored opcodes
case hash("hichan"):
case hash("lochan"):
Expand Down Expand Up @@ -1091,3 +1105,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];
}
13 changes: 13 additions & 0 deletions src/sfizz/Region.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -297,6 +306,10 @@ struct Region {
EGDescription filterEG;

bool isStereo { false };

// Effects
std::vector<float> gainToEffect;

private:
const MidiState& midiState;
bool keySwitched { true };
Expand Down
Loading

0 comments on commit f5f0315

Please sign in to comment.