Compare commits
2 Commits
bfb88d6fe1
...
7e02cfd77b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e02cfd77b | ||
|
|
767906a31e |
@ -8,11 +8,11 @@ A collection of basic effects, available as plugins and re-usable open-source (M
|
|||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
Each effect can be used just by including the corresponding header (e.g. `limiter.h`). This defines two classes `LimiterFloat` and `LimiterDouble`. Some of these have initialisation arguments (e.g. maximum lookahead) but these are always optional.
|
Each effect can be used just by including the corresponding header (e.g. `limiter.h`). This defines two classes `...Float` and `...Double`. Some of these have initialisation arguments (e.g. maximum lookahead) but these are always optional.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// Limiter with maximum attack/lookahead of 100ms
|
// Limiter with maximum attack/lookahead of 100ms
|
||||||
signalsmith::basics::Limiter effect(100);
|
signalsmith::basics::LimiterFloat effect(100);
|
||||||
```
|
```
|
||||||
|
|
||||||
Before use, these classes must be configured. The `.configure()` method returns `true` if the config was accepted. They can be configured multiple times, but (obviously) not while actively processing audio.
|
Before use, these classes must be configured. The `.configure()` method returns `true` if the config was accepted. They can be configured multiple times, but (obviously) not while actively processing audio.
|
||||||
@ -42,3 +42,5 @@ Integer parameters are declared as `ParamStepped`s. This is a similarly opaque
|
|||||||
### Implementation
|
### Implementation
|
||||||
|
|
||||||
The `.state()` method of these templates contain a lot of detail, almost all of which is ignored (and optimised away) when using the `...Float`/`...Double` classes.
|
The `.state()` method of these templates contain a lot of detail, almost all of which is ignored (and optimised away) when using the `...Float`/`...Double` classes.
|
||||||
|
|
||||||
|
The `.configureSTFX()` and `.processSTFX()` methods are wrapped into more typical
|
||||||
|
|||||||
@ -9,6 +9,25 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace stfx {
|
namespace stfx {
|
||||||
|
class UnitRangeMap {
|
||||||
|
double a, b, d;
|
||||||
|
public:
|
||||||
|
UnitRangeMap() : a(0), b(1), d(0) {} // identity
|
||||||
|
UnitRangeMap(double min, double mid, double max) {
|
||||||
|
double k = (mid - min)/(max - mid);
|
||||||
|
a = min;
|
||||||
|
b = max*k - min;
|
||||||
|
d = k - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double toUnit(double value) const {
|
||||||
|
return (value - a)/(b - value*d);
|
||||||
|
}
|
||||||
|
double fromUnit(double unit) const {
|
||||||
|
return (a + unit*b)/(1 + unit*d);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct RangeParamInfo {
|
struct RangeParamInfo {
|
||||||
double defaultValue;
|
double defaultValue;
|
||||||
double low, mid, high;
|
double low, mid, high;
|
||||||
@ -38,12 +57,12 @@ namespace stfx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef double((*DoubleMap)(double));
|
typedef double((*DoubleMap)(double));
|
||||||
RangeParamInfo & unit(std::string suffix, int precision=2, DoubleMap fromUnit=identityMap, DoubleMap toUnit=identityMap, double validLow=doubleLowest, double validHigh=doubleMax) {
|
RangeParamInfo & unit(std::string suffix, int precision=2, DoubleMap fromDisplay=identityMap, DoubleMap toDisplay=identityMap, double validLow=doubleLowest, double validHigh=doubleMax) {
|
||||||
unitOptions.emplace_back(suffix, fromUnit, toUnit, validLow, validHigh, precision);
|
unitOptions.emplace_back(suffix, fromDisplay, toDisplay, validLow, validHigh, precision);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
RangeParamInfo & unit(std::string suffix, DoubleMap fromUnit, DoubleMap toUnit, double validLow=doubleLowest, double validHigh=doubleMax) {
|
RangeParamInfo & unit(std::string suffix, DoubleMap fromDisplay, DoubleMap toDisplay, double validLow=doubleLowest, double validHigh=doubleMax) {
|
||||||
return unit(suffix, 2, fromUnit, toUnit, validLow, validHigh);
|
return unit(suffix, 2, fromDisplay, toDisplay, validLow, validHigh);
|
||||||
}
|
}
|
||||||
RangeParamInfo & unit(std::string suffix, int precision, double validLow, double validHigh) {
|
RangeParamInfo & unit(std::string suffix, int precision, double validLow, double validHigh) {
|
||||||
return unit(suffix, precision, identityMap, identityMap, validLow, validHigh);
|
return unit(suffix, precision, identityMap, identityMap, validLow, validHigh);
|
||||||
@ -130,7 +149,7 @@ namespace stfx {
|
|||||||
|
|
||||||
for (const auto &u : unitOptions) {
|
for (const auto &u : unitOptions) {
|
||||||
if (u.unit == unit) {
|
if (u.unit == unit) {
|
||||||
return u.fromUnit(result);
|
return u.fromDisplay(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -152,10 +171,10 @@ namespace stfx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double toUnit(double value) const {
|
double toUnit(double value) const {
|
||||||
return rangeMap.toUnitRange(value);
|
return rangeMap.toUnit(value);
|
||||||
}
|
}
|
||||||
double fromUnit(double unit) const {
|
double fromUnit(double unit) const {
|
||||||
return rangeMap.fromUnitRange(unit);
|
return rangeMap.fromUnit(unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -166,25 +185,7 @@ namespace stfx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A map from [0, 1] to another range with specified midpoint, based on a 1/x curve
|
// A map from [0, 1] to another range with specified midpoint, based on a 1/x curve
|
||||||
class UnitRangeMapReciprocal {
|
UnitRangeMap rangeMap;
|
||||||
double vMin, vTopFactor, vBottomFactor;
|
|
||||||
public:
|
|
||||||
UnitRangeMapReciprocal() : vMin(0), vTopFactor(1), vBottomFactor(0) {} // identity
|
|
||||||
UnitRangeMapReciprocal(double min, double mid, double max) {
|
|
||||||
vMin = min;
|
|
||||||
double k = (mid - min)/(max - mid);
|
|
||||||
vTopFactor = max*k - min;
|
|
||||||
vBottomFactor = k - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
double toUnitRange(double value) const {
|
|
||||||
return (value - vMin)/(vTopFactor - value*vBottomFactor);
|
|
||||||
}
|
|
||||||
double fromUnitRange(double unit) const {
|
|
||||||
return (vMin + unit*vTopFactor)/(1 + unit*vBottomFactor);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UnitRangeMapReciprocal rangeMap;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct ExactEntry {
|
struct ExactEntry {
|
||||||
@ -196,12 +197,12 @@ namespace stfx {
|
|||||||
std::string fixedDisplay = "";
|
std::string fixedDisplay = "";
|
||||||
std::string unit;
|
std::string unit;
|
||||||
bool addSpace = false, useFixed = false, keepZeros = false;
|
bool addSpace = false, useFixed = false, keepZeros = false;
|
||||||
DoubleMap fromUnit, toUnit;
|
DoubleMap fromDisplay, toDisplay;
|
||||||
double validLow, validHigh;
|
double validLow, validHigh;
|
||||||
int precision;
|
int precision;
|
||||||
double precisionOffset = 0;
|
double precisionOffset = 0;
|
||||||
|
|
||||||
UnitEntry(std::string unit, DoubleMap fromUnit, DoubleMap toUnit, double validLow, double validHigh, int precision=2) : unit(unit), fromUnit(fromUnit), toUnit(toUnit), validLow(validLow), validHigh(validHigh), precision(precision) {
|
UnitEntry(std::string unit, DoubleMap fromDisplay, DoubleMap toDisplay, double validLow, double validHigh, int precision=2) : unit(unit), fromDisplay(fromDisplay), toDisplay(toDisplay), validLow(validLow), validHigh(validHigh), precision(precision) {
|
||||||
if (validLow > validHigh) {
|
if (validLow > validHigh) {
|
||||||
std::swap(validLow, validHigh);
|
std::swap(validLow, validHigh);
|
||||||
keepZeros = true;
|
keepZeros = true;
|
||||||
@ -223,9 +224,9 @@ namespace stfx {
|
|||||||
oss << std::fixed;
|
oss << std::fixed;
|
||||||
double offset = 0;
|
double offset = 0;
|
||||||
if (precision > 0) {
|
if (precision > 0) {
|
||||||
oss << toUnit(value) + offset;
|
oss << toDisplay(value) + offset;
|
||||||
} else {
|
} else {
|
||||||
oss << (int)(toUnit(value) + offset);
|
oss << (int)(toDisplay(value) + offset);
|
||||||
}
|
}
|
||||||
valueString = oss.str();
|
valueString = oss.str();
|
||||||
// Strip trailing zeroes
|
// Strip trailing zeroes
|
||||||
@ -324,13 +325,6 @@ namespace stfx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double toUnit(int value) const {
|
|
||||||
return (value - low)/double(high - low);
|
|
||||||
}
|
|
||||||
int fromUnit(double unit) const {
|
|
||||||
double value = low + unit*(high - low);
|
|
||||||
return int(std::round(value));
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
int nameIndex;
|
int nameIndex;
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@ -353,6 +353,12 @@ struct Plugin : public clap_plugin {
|
|||||||
entry.steppedParam = ¶m;
|
entry.steppedParam = ¶m;
|
||||||
return entry.steppedInfo.emplace(param);
|
return entry.steppedInfo.emplace(param);
|
||||||
}
|
}
|
||||||
|
template<class V>
|
||||||
|
bool changed(const char *key, V &v) {
|
||||||
|
(*this)(key, v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void invalidate(const char *) {}
|
||||||
};
|
};
|
||||||
void scanParams() {
|
void scanParams() {
|
||||||
params.resize(0);
|
params.resize(0);
|
||||||
@ -514,7 +520,6 @@ struct Plugin : public clap_plugin {
|
|||||||
|
|
||||||
void info(const char *, const char *) {}
|
void info(const char *, const char *) {}
|
||||||
int version(int v) {
|
int version(int v) {
|
||||||
LOG_EXPR(v);
|
|
||||||
(*this)("version", v);
|
(*this)("version", v);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@ -530,6 +535,12 @@ struct Plugin : public clap_plugin {
|
|||||||
(*this)(key, v);
|
(*this)(key, v);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
template<class V>
|
||||||
|
bool changed(const char *key, V &v) {
|
||||||
|
(*this)(key, v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void invalidate(const char *) {}
|
||||||
};
|
};
|
||||||
struct StateReader : public StorageCborReader<true, StateReader> {
|
struct StateReader : public StorageCborReader<true, StateReader> {
|
||||||
using StorageCborReader<true, StateReader>::StorageCborReader;
|
using StorageCborReader<true, StateReader>::StorageCborReader;
|
||||||
@ -553,6 +564,13 @@ struct Plugin : public clap_plugin {
|
|||||||
param = v;
|
param = v;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
template<class V>
|
||||||
|
bool changed(const char *key, V &v) {
|
||||||
|
V prev = v;
|
||||||
|
(*this)(key, v);
|
||||||
|
return v == prev;
|
||||||
|
}
|
||||||
|
void invalidate(const char *) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<unsigned char> stateBuffer;
|
std::vector<unsigned char> stateBuffer;
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace stfx {
|
namespace stfx {
|
||||||
// Convenient units for range parameters - not really part of the main STFX API
|
// Convenient units for range parameters - not really part of the main STFX API
|
||||||
@ -285,17 +286,18 @@ namespace stfx {
|
|||||||
// Parameters can be assigned using `=`, and store their own history for transitions
|
// Parameters can be assigned using `=`, and store their own history for transitions
|
||||||
template<typename Value>
|
template<typename Value>
|
||||||
class LibraryParam {
|
class LibraryParam {
|
||||||
Value current, prevBlock;
|
std::atomic<Value> current;
|
||||||
|
Value prevBlock; // inter-block fade
|
||||||
// Used for the 20ms parameter fade
|
// Used for the 20ms parameter fade
|
||||||
Value _from, _to;
|
Value _from, _to;
|
||||||
public:
|
public:
|
||||||
LibraryParam(const Value &initial, const Value &) : LibraryParam(initial) {}
|
LibraryParam(const Value &initial, const Value &) : LibraryParam(initial) {}
|
||||||
LibraryParam(const Value &initial) : current(initial), prevBlock(initial), _from(initial), _to(initial) {}
|
LibraryParam(const Value &initial) : current(initial), prevBlock(initial), _from(initial), _to(initial) {}
|
||||||
operator Value () const {
|
operator Value () const {
|
||||||
return current;
|
return current.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
LibraryParam & operator =(const Value &v) {
|
LibraryParam & operator =(const Value &v) {
|
||||||
current = v;
|
current.store(v, std::memory_order_relaxed);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
// Return the current fade
|
// Return the current fade
|
||||||
@ -308,21 +310,25 @@ namespace stfx {
|
|||||||
|
|
||||||
template<class Storage>
|
template<class Storage>
|
||||||
void state(Storage &storage) {
|
void state(Storage &storage) {
|
||||||
storage("value", current);
|
auto v = current.load(std::memory_order_relaxed);
|
||||||
|
auto vPrev = v;
|
||||||
|
storage("value", v);
|
||||||
|
if (v != vPrev) current.store(v, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following are only called from `.process()`
|
||||||
// Shuffle the internal values along to start a new fade, return whether it's actually changing
|
// Shuffle the internal values along to start a new fade, return whether it's actually changing
|
||||||
bool _libStartFade() {
|
bool _libStartFade() {
|
||||||
_from = _to;
|
_from = _to;
|
||||||
_to = current;
|
_to = current.load(std::memory_order_relaxed);
|
||||||
return (_to != _from);
|
return (_to != _from);
|
||||||
}
|
}
|
||||||
// Store previous value for block-level automation
|
// Store previous value for block-level automation
|
||||||
void _libEndBlock() {
|
void _libEndBlock() {
|
||||||
prevBlock = current;
|
prevBlock = current.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
Value _libCurrent() {
|
Value _libCurrent() {
|
||||||
return current;
|
return current.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
Value _libPrevBlock() {
|
Value _libPrevBlock() {
|
||||||
return prevBlock;
|
return prevBlock;
|
||||||
@ -378,12 +384,18 @@ namespace stfx {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop any name/description we're given
|
||||||
|
template<class ...Args>
|
||||||
|
void info(Args...) {}
|
||||||
// The effect might ask us to store/fetch the serialisation version, we just echo it back
|
// The effect might ask us to store/fetch the serialisation version, we just echo it back
|
||||||
static int version(int v) {return v;}
|
int version(int v) {return v;}
|
||||||
// Ignore the UI/synchronisation stuff
|
// Ignore the UI/synchronisation stuff
|
||||||
static bool extra() {return false;}
|
bool extra() {return false;}
|
||||||
static bool extra(const char *, const char *) {return false;}
|
bool extra(const char *, const char *) {return false;}
|
||||||
static void invalidate(const char *) {}
|
void invalidate(const char *) {}
|
||||||
|
// This storage only reads values, never changes them
|
||||||
|
template<class T>
|
||||||
|
bool changed(const char *, T &v) {return false;}
|
||||||
|
|
||||||
// We ignore any basic type
|
// We ignore any basic type
|
||||||
void operator()(const char *, bool) {}
|
void operator()(const char *, bool) {}
|
||||||
@ -393,23 +405,18 @@ namespace stfx {
|
|||||||
void operator()(const char *, float) {}
|
void operator()(const char *, float) {}
|
||||||
// And strings
|
// And strings
|
||||||
void operator()(const char *, std::string &) {}
|
void operator()(const char *, std::string &) {}
|
||||||
|
|
||||||
// Iterate through vectors
|
// Iterate through vectors
|
||||||
template<class Item>
|
template<class Item>
|
||||||
void operator()(const char *label, std::vector<Item> &vector) {
|
void operator()(const char *label, std::vector<Item> &vector) {
|
||||||
for (auto &item : vector) (*this)(label, item);
|
for (auto &item : vector) (*this)(label, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume all other arguments have a `.state()`, and recurse into it
|
// Assume all other arguments have a `.state()`, and recurse into it
|
||||||
template<class OtherObject>
|
template<class OtherObject>
|
||||||
void operator()(const char *, OtherObject &obj) {
|
void operator()(const char *, OtherObject &obj) {
|
||||||
obj.state(*this);
|
obj.state(*this);
|
||||||
}
|
}
|
||||||
template<class Fn>
|
|
||||||
void group(const char *, Fn fn) {
|
|
||||||
fn(*this);
|
|
||||||
}
|
|
||||||
// Drop any name/description we're given
|
|
||||||
template<class ...Args>
|
|
||||||
void info(Args...) {}
|
|
||||||
} params;
|
} params;
|
||||||
|
|
||||||
bool justHadReset = true;
|
bool justHadReset = true;
|
||||||
|
|||||||
@ -164,6 +164,7 @@ struct StorageCborReader {
|
|||||||
readValue(v);
|
readValue(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
template<class Obj>
|
template<class Obj>
|
||||||
void readValue(Obj &obj) {
|
void readValue(Obj &obj) {
|
||||||
if (compact) {
|
if (compact) {
|
||||||
@ -224,7 +225,6 @@ struct StorageCborReader {
|
|||||||
});
|
});
|
||||||
array.resize(length);
|
array.resize(length);
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
protected:
|
protected:
|
||||||
Cbor cbor;
|
Cbor cbor;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user