-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DOesn't optimally use the SIMD across voices; consider that extension later. But works!
- Loading branch information
Showing
1 changed file
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
/* | ||
* sst-effects - an open source library of audio effects | ||
* built by Surge Synth Team. | ||
* | ||
* Copyright 2018-2023, various authors, as described in the GitHub | ||
* transaction log. | ||
* | ||
* sst-effects is released under the GNU General Public Licence v3 | ||
* or later (GPL-3.0-or-later). The license is found in the "LICENSE" | ||
* file in the root of this repository, or at | ||
* https://www.gnu.org/licenses/gpl-3.0.en.html | ||
* | ||
* The majority of these effects at initiation were factored from | ||
* Surge XT, and so git history prior to April 2023 is found in the | ||
* surge repo, https://github.com/surge-synthesizer/surge | ||
* | ||
* All source in sst-effects available at | ||
* https://github.com/surge-synthesizer/sst-effects | ||
*/ | ||
|
||
#ifndef INCLUDE_SST_VOICE_EFFECTS_FILTER_SSTFILTERS_H | ||
#define INCLUDE_SST_VOICE_EFFECTS_FILTER_SSTFILTERS_H | ||
|
||
#include "sst/basic-blocks/params/ParamMetadata.h" | ||
|
||
#include "../VoiceEffectCore.h" | ||
|
||
#include <iostream> | ||
#include <array> | ||
|
||
#include "sst/basic-blocks/mechanics/block-ops.h" | ||
#include "sst/filters.h" | ||
#include "sst/filters/FilterConfiguration.h" | ||
#include "sst/filters/FilterConfigurationLabels.h" | ||
|
||
namespace sst::voice_effects::filter | ||
{ | ||
template <typename VFXConfig> struct SSTFilters : core::VoiceEffectTemplateBase<VFXConfig> | ||
{ | ||
static constexpr const char *effectName{"Surge Synth Team Filters"}; | ||
|
||
static constexpr int numFloatParams{2}; | ||
static constexpr int numIntParams{2}; | ||
|
||
static constexpr size_t combBlockSize{ | ||
sizeof(float) * | ||
(sst::filters::utilities::MAX_FB_COMB + sst::filters::utilities::SincTable::FIRipol_N)}; | ||
|
||
SSTFilters() : core::VoiceEffectTemplateBase<VFXConfig>() | ||
{ | ||
std::fill(mLastIParam.begin(), mLastIParam.end(), -1); | ||
std::fill(mLastParam.begin(), mLastParam.end(), -188888.f); | ||
this->preReservePool(combBlockSize); | ||
memset(&qfus, 0, sizeof(sst::filters::QuadFilterUnitState)); | ||
} | ||
|
||
~SSTFilters() | ||
{ | ||
for (int i = 0; i < 4; ++i) | ||
{ | ||
if (qfus.DB[i]) | ||
{ | ||
VFXConfig::returnBlock(this, (uint8_t *)(&qfus.DB[i][0]), combBlockSize); | ||
} | ||
} | ||
} | ||
|
||
static constexpr int fpCutoff{0}; | ||
static constexpr int fpResonance{1}; | ||
|
||
static constexpr int ipType{0}; | ||
static constexpr int ipSubType{1}; | ||
|
||
sst::filters::QuadFilterUnitState qfus; | ||
sst::filters::FilterCoefficientMaker<> coefMaker; | ||
sst::filters::FilterUnitQFPtr filterUnitPtr{nullptr}; | ||
|
||
basic_blocks::params::ParamMetaData paramAt(int idx) const | ||
{ | ||
assert(idx >= 0 && idx < numFloatParams); | ||
using pmd = basic_blocks::params::ParamMetaData; | ||
|
||
switch (idx) | ||
{ | ||
case fpCutoff: | ||
if (keytrackOn) | ||
{ | ||
return pmd() | ||
.asFloat() | ||
.withRange(-48, 48) | ||
.withName("Offset") | ||
.withDefault(0) | ||
.withLinearScaleFormatting("semitones"); | ||
} | ||
return pmd().asAudibleFrequency().withName("Cutoff").withDefault(0); | ||
|
||
case fpResonance: | ||
return pmd() | ||
.asPercent() | ||
.withDefault(0.7f) | ||
.withName("Resonance") | ||
.withLinearScaleFormatting(""); | ||
|
||
default: | ||
break; | ||
} | ||
|
||
return pmd().withName("Unknown " + std::to_string(idx)).asPercent(); | ||
} | ||
|
||
basic_blocks::params::ParamMetaData intParamAt(int idx) const | ||
{ | ||
using pmd = basic_blocks::params::ParamMetaData; | ||
|
||
if (idx == ipType) | ||
{ | ||
auto res = pmd() | ||
.asInt() | ||
.withRange(0, sst::filters::FilterType::num_filter_types - 1) | ||
.withName("Type"); | ||
std::unordered_map<int, std::string> nameMap; | ||
for (int i = 0; i < sst::filters::FilterType::num_filter_types; ++i) | ||
{ | ||
nameMap.emplace(i, sst::filters::filter_type_names[i]); | ||
} | ||
return res.withUnorderedMapFormatting(nameMap); | ||
} | ||
|
||
if (idx == ipSubType) | ||
{ | ||
auto ft = this->getIntParam(ipType); | ||
auto nft = sst::filters::fut_subcount[ft]; | ||
if (nft != 0) | ||
{ | ||
auto res = pmd().asInt().withRange(0, nft - 1).withName("SubType"); | ||
|
||
std::unordered_map<int, std::string> nameMap; | ||
for (int i = 0; i < nft; ++i) | ||
{ | ||
nameMap.emplace(i, sst::filters::subtypeLabel(ft, i)); | ||
} | ||
return res.withUnorderedMapFormatting(nameMap); | ||
} | ||
else | ||
{ | ||
return pmd().asInt().withRange(0, 0).withName("SubType").withDefault(0); | ||
} | ||
} | ||
|
||
return pmd(); | ||
} | ||
|
||
void initVoiceEffect() { resetFilter(false); } | ||
void initVoiceEffectParams() { this->initToParamMetadataDefault(this); } | ||
|
||
void resetFilter(bool stereo) | ||
{ | ||
std::fill(qfus.R, &qfus.R[sst::filters::n_filter_registers], _mm_setzero_ps()); | ||
std::fill(qfus.C, &qfus.C[sst::filters::n_cm_coeffs], _mm_setzero_ps()); | ||
|
||
auto type = (sst::filters::FilterType)this->getIntParam(ipType); | ||
|
||
for (int i = 0; i < 4; ++i) | ||
{ | ||
qfus.active[i] = i < (stereo ? 2 : 1) ? 0xffffffff : 0; | ||
qfus.WP[i] = 0; | ||
if (type == filters::fut_comb_neg || type == filters::fut_comb_pos) | ||
{ | ||
if (i < (stereo ? 2 : 1)) | ||
{ | ||
if (!qfus.DB[i]) | ||
{ | ||
qfus.DB[i] = (float *)VFXConfig::checkoutBlock(this, combBlockSize); | ||
} | ||
memset(&(qfus.DB[i][0]), 0, combBlockSize); | ||
} | ||
else | ||
{ | ||
qfus.DB[i] = nullptr; | ||
} | ||
} | ||
else | ||
{ | ||
if (qfus.DB[i]) | ||
{ | ||
VFXConfig::returnBlock(this, (uint8_t *)(&qfus.DB[i][0]), combBlockSize); | ||
} | ||
qfus.DB[i] = nullptr; | ||
} | ||
} | ||
|
||
filterUnitPtr = sst::filters::GetQFPtrFilterUnit( | ||
type, (sst::filters::FilterSubType)this->getIntParam(ipSubType)); | ||
|
||
coefMaker.setSampleRateAndBlockSize(this->getSampleRate(), VFXConfig::blockSize); | ||
} | ||
|
||
void updateCoefficients(float pitch, bool stereo) | ||
{ | ||
std::array<float, numFloatParams> param; | ||
std::array<int, numIntParams> iparam; | ||
bool diff{false}, idiff{false}; | ||
for (int i = 0; i < numFloatParams; i++) | ||
{ | ||
param[i] = this->getFloatParam(i); | ||
if (i == 0 && keytrackOn) | ||
{ | ||
param[i] += pitch; | ||
} | ||
diff = diff || (mLastParam[i] != param[i]); | ||
} | ||
for (int i = 0; i < numIntParams; ++i) | ||
{ | ||
iparam[i] = this->getIntParam(i); | ||
idiff = idiff || (mLastIParam[i] != iparam[i]); | ||
} | ||
idiff = idiff || (stereo != lastBuildWasStereo); | ||
lastBuildWasStereo = stereo; | ||
|
||
if (idiff) | ||
{ | ||
resetFilter(stereo); | ||
} | ||
|
||
for (int f = 0; f < sst::filters::n_cm_coeffs; ++f) | ||
{ | ||
coefMaker.C[f] = qfus.C[f][0]; | ||
} | ||
|
||
coefMaker.MakeCoeffs( | ||
param[0], param[1], (sst::filters::FilterType)this->getIntParam(ipType), | ||
(sst::filters::FilterSubType)this->getIntParam(ipSubType), nullptr, false); | ||
|
||
coefMaker.updateState(qfus); | ||
|
||
mLastIParam = iparam; | ||
mLastParam = param; | ||
} | ||
|
||
void processStereo(float *datainL, float *datainR, float *dataoutL, float *dataoutR, | ||
float pitch) | ||
{ | ||
updateCoefficients(pitch, true); | ||
if (filterUnitPtr) | ||
{ | ||
for (int i = 0; i < VFXConfig::blockSize; ++i) | ||
{ | ||
float r alignas(16)[4]; | ||
r[0] = datainL[i]; | ||
r[1] = datainR[i]; | ||
r[2] = 0.f; | ||
r[3] = 0.f; | ||
|
||
auto resS = filterUnitPtr(&qfus, _mm_load_ps(r)); | ||
float res alignas(16)[4]; | ||
_mm_store_ps(res, resS); | ||
dataoutL[i] = res[0]; | ||
dataoutR[i] = res[1]; | ||
} | ||
} | ||
else | ||
{ | ||
sst::basic_blocks::mechanics::copy_from_to<VFXConfig::blockSize>(datainL, dataoutL); | ||
sst::basic_blocks::mechanics::copy_from_to<VFXConfig::blockSize>(datainR, dataoutR); | ||
} | ||
} | ||
|
||
void processMonoToMono(float *datainL, float *dataoutL, float pitch) | ||
{ | ||
updateCoefficients(pitch, false); | ||
|
||
if (filterUnitPtr) | ||
{ | ||
for (int i = 0; i < VFXConfig::blockSize; ++i) | ||
{ | ||
float r alignas(16)[4]; | ||
r[0] = datainL[i]; | ||
r[1] = 0.f; | ||
r[2] = 0.f; | ||
r[3] = 0.f; | ||
|
||
auto resS = filterUnitPtr(&qfus, _mm_load_ps(r)); | ||
float res alignas(16)[4]; | ||
_mm_store_ps(res, resS); | ||
dataoutL[i] = res[0]; | ||
} | ||
} | ||
else | ||
{ | ||
sst::basic_blocks::mechanics::copy_from_to<VFXConfig::blockSize>(datainL, dataoutL); | ||
} | ||
} | ||
|
||
bool enableKeytrack(bool b) | ||
{ | ||
auto res = (b != keytrackOn); | ||
keytrackOn = b; | ||
return res; | ||
} | ||
bool getKeytrack() const { return keytrackOn; } | ||
|
||
bool checkParameterConsistency() | ||
{ | ||
auto ft = this->getIntParam(ipType); | ||
auto st = this->getIntParam(ipSubType); | ||
|
||
if (st >= sst::filters::fut_subcount[ft]) | ||
{ | ||
this->setIntParam(ipSubType, 0); | ||
} | ||
return true; | ||
} | ||
|
||
protected: | ||
bool keytrackOn{false}, wasKeytrackOn{false}, lastBuildWasStereo{false}; | ||
std::array<float, numFloatParams> mLastParam{}; | ||
std::array<int, numIntParams> mLastIParam{}; | ||
}; | ||
|
||
} // namespace sst::voice_effects::filter | ||
#endif // SHORTCIRCUITXT_CYTOMICSVF_H |