Skip to content

Commit

Permalink
Add a new envelope curve type (exp lerp)
Browse files Browse the repository at this point in the history
1. Add a new param metadata setting (exp(A + x(B--A))-C)/D
2. Make the AHDSRShapedSC and DAR envelope work with those shapes
   for SC by adding a new RangeProvider and range provide type
   constexpr
3. Make the modulation depth on cubic decibel work (separately)
  • Loading branch information
baconpaul committed Jun 3, 2024
1 parent 8da120a commit c1089bb
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 31 deletions.
16 changes: 15 additions & 1 deletion include/sst/basic-blocks/modulators/AHDSRShapedSC.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,21 @@ struct AHDSRShapedSC : DiscreteStagesEnvelope<BLOCK_SIZE, RangeProvider>

inline float dPhase(float x)
{
return srProvider->envelope_rate_linear_nowrap(x * base_t::etScale + base_t::etMin);
if constexpr (RangeProvider::phaseStrategy == DPhaseStrategies::ENVTIME_2TWOX)
{
return srProvider->envelope_rate_linear_nowrap(x * base_t::etScale + base_t::etMin);
}

if constexpr (RangeProvider::phaseStrategy == ENVTIME_EXP)
{
auto timeInSeconds =
(std::exp(RangeProvider::A + x * (RangeProvider::B - RangeProvider::A)) -
RangeProvider::C) /
RangeProvider::D;
auto dPhase = BLOCK_SIZE * srProvider->sampleRateInv / timeInSeconds;

return dPhase;
}
}

// from https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/
Expand Down
26 changes: 23 additions & 3 deletions include/sst/basic-blocks/modulators/DAREnvelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,34 @@ struct DAREnvelope : DiscreteStagesEnvelope<BLOCK_SIZE, RangeProvider>
base_t::resetCurrent();
}

// The value here is in natural units by now
inline float dPhase(float x)
{
if constexpr (RangeProvider::phaseStrategy == DPhaseStrategies::ENVTIME_2TWOX)
{
return srProvider->envelope_rate_linear_nowrap(x);
}

if constexpr (RangeProvider::phaseStrategy == ENVTIME_EXP)
{
auto timeInSeconds =
(std::exp(RangeProvider::A + x * (RangeProvider::B - RangeProvider::A)) -
RangeProvider::C) /
RangeProvider::D;
auto dPhase = BLOCK_SIZE * srProvider->sampleRateInv / timeInSeconds;

return dPhase;
}
}

template <bool gated> inline float stepDigital(const float d, const float a, const float r)
{
float target = 0;
switch (this->stage)
{
case base_t::s_delay:
{
phase += srProvider->envelope_rate_linear_nowrap(d);
phase += dPhase(d);
if (phase >= 1)
{
if constexpr (gated)
Expand All @@ -91,7 +111,7 @@ struct DAREnvelope : DiscreteStagesEnvelope<BLOCK_SIZE, RangeProvider>
break;
case base_t::s_attack:
{
phase += srProvider->envelope_rate_linear_nowrap(a);
phase += dPhase(a);
if (phase >= 1)
{
phase = 1;
Expand Down Expand Up @@ -123,7 +143,7 @@ struct DAREnvelope : DiscreteStagesEnvelope<BLOCK_SIZE, RangeProvider>
break;
case base_t::s_release:
{
phase -= srProvider->envelope_rate_linear_nowrap(r);
phase -= dPhase(r);
if (phase <= 0)
{
phase = 0;
Expand Down
47 changes: 43 additions & 4 deletions include/sst/basic-blocks/modulators/DiscreteStagesEnvelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,45 @@

namespace sst::basic_blocks::modulators
{

enum DPhaseStrategies
{
/*
* SR Provider provides envelope_rate_linear_nowrap(f) = blockSize * 2^-f / sampleRate
* and we scale into units in the 2^x space
*/
ENVTIME_2TWOX,
/*
* SRProvider isn't consulted. Instead we use an exp table (probably. For now use exp)
*/
ENVTIME_EXP
};
struct TenSecondRange
{
// 0.0039s -> 10s
static constexpr DPhaseStrategies phaseStrategy{ENVTIME_2TWOX};
static constexpr float etMin{-8}, etMax{3.32192809489};
};

struct ThirtyTwoSecondRange
{
// 0.0039s -> 32s
static constexpr DPhaseStrategies phaseStrategy{ENVTIME_2TWOX};
static constexpr float etMin{-8}, etMax{5};
};

struct TwoMinuteRange
{
// 0.0039s -> 120s
static constexpr DPhaseStrategies phaseStrategy{ENVTIME_2TWOX};
static constexpr float etMin{-8}, etMax{6.90689059561};
};

struct TwentyFiveSecondExp
{
static constexpr DPhaseStrategies phaseStrategy{ENVTIME_EXP};
static constexpr double A{0.6931471824646}, B{10.1267113685608}, C{-2.0}, D{1000.0};
};

template <int BLOCK_SIZE, typename RangeProvider> struct DiscreteStagesEnvelope
{
static constexpr float etMin{RangeProvider::etMin}, etMax{RangeProvider::etMax},
Expand Down Expand Up @@ -198,9 +218,28 @@ template <int BLOCK_SIZE, typename RangeProvider> struct DiscreteStagesEnvelope
memset(outputCacheCubed, 0, sizeof(outputCacheCubed));
}

float rateFrom01(float r01) { return r01 * etScale + etMin; }
float rateTo01(float r) { return (r - etMin) / etScale; }
float deltaTo01(float d) { return d / etScale; }
float rateFrom01(float r01)
{
// EXP works entirely in normalized params
if constexpr (RangeProvider::phaseStrategy == DPhaseStrategies::ENVTIME_EXP)
return r01;
else
return r01 * etScale + etMin;
}
float rateTo01(float r)
{
if constexpr (RangeProvider::phaseStrategy == DPhaseStrategies::ENVTIME_EXP)
return r;
else
return (r - etMin) / etScale;
}
float deltaTo01(float d)
{
if constexpr (RangeProvider::phaseStrategy == DPhaseStrategies::ENVTIME_EXP)
return d;
else
return d / etScale;
}
};
} // namespace sst::basic_blocks::modulators

Expand Down
163 changes: 140 additions & 23 deletions include/sst/basic-blocks/params/ParamMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,12 @@ struct ParamMetaData

enum DisplayScale
{
LINEAR, // out = A * r + B
A_TWO_TO_THE_B, // out = A 2^(B r + C)
CUBED_AS_DECIBEL, // the underlier is an amplitude applied as v*v*v and displayed as db
DECIBEL, // TODO - implement
UNORDERED_MAP, // out = discreteValues[(int)std::round(val)]
LINEAR, // out = A * r + B
A_TWO_TO_THE_B, // out = A 2^(B r + C)
CUBED_AS_DECIBEL, // the underlier is an amplitude applied as v*v*v and displayed as db
SCALED_OFFSET_EXP, // (exp(A + x ( B - A )) + C) / D
DECIBEL, // TODO - implement
UNORDERED_MAP, // out = discreteValues[(int)std::round(val)]
MIDI_NOTE, // uses C4 etc.. notation. The octaveOffset has 0 -> 69=A4, 1 = A5, -1 = A3
USER_PROVIDED // TODO - implement
} displayScale{LINEAR};
Expand Down Expand Up @@ -440,6 +441,20 @@ struct ParamMetaData
return res;
}

ParamMetaData withScaledOffsetExpFormatting(float A, float B, float C, float D,
const std::string &units)
{
auto res = *this;
res.svA = A;
res.svB = B;
res.svC = C;
res.svD = D;
res.unit = units;
res.displayScale = SCALED_OFFSET_EXP;
res.supportsStringConversion = true;
return res;
}

ParamMetaData withSemitoneZeroAt400Formatting()
{
return withATwoToTheBFormatting(440, 1.0 / 12.0, "Hz");
Expand Down Expand Up @@ -644,6 +659,17 @@ struct ParamMetaData
}
ParamMetaData asEnvelopeTime() { return asLog2SecondsRange(-8.f, 5.f, -1.f); }

// (exp(lerp(norm_val, 0.6931471824646, 10.1267113685608)) - 2.0)/1000.0
ParamMetaData as25SecondExpTime()
{
return withType(FLOAT)
.withRange(0, 1)
.withDefault(0.1)
.temposyncable()
.withScaledOffsetExpFormatting(0.6931471824646, 10.1267113685608, -2.0, 1000.0, "s")
.withMilisecondsBelowOneSecond();
}

ParamMetaData asAudibleFrequency()
{
return withType(FLOAT).withRange(-60, 70).withDefault(0).withSemitoneZeroAt400Formatting();
Expand Down Expand Up @@ -746,15 +772,6 @@ inline std::optional<std::string> ParamMetaData::valueToString(float val,
switch (displayScale)
{
case LINEAR:
if (val == minVal && !customMinDisplay.empty())
{
return customMinDisplay;
}
if (val == maxVal && !customMaxDisplay.empty())
{
return customMinDisplay;
}

if (alternateScaleWhen == NO_ALTERNATE)
{
return fmt::format("{:.{}f} {:s}", svA * val,
Expand All @@ -780,15 +797,6 @@ inline std::optional<std::string> ParamMetaData::valueToString(float val,
}
break;
case A_TWO_TO_THE_B:
if (val == minVal && !customMinDisplay.empty())
{
return customMinDisplay;
}
if (val == maxVal && !customMaxDisplay.empty())
{
return customMinDisplay;
}

if (alternateScaleWhen == NO_ALTERNATE)
{
return fmt::format("{:.{}f} {:s}", svA * pow(2.0, svB * val + svC),
Expand All @@ -813,6 +821,22 @@ inline std::optional<std::string> ParamMetaData::valueToString(float val,
}
}
break;
case SCALED_OFFSET_EXP:
{
auto dval = (std::exp(svA + val * (svB - svA)) + svC) / svD;
if (alternateScaleWhen == NO_ALTERNATE ||
(alternateScaleWhen == SCALE_BELOW && dval > alternateScaleCutoff) ||
(alternateScaleWhen == SCALE_ABOVE && dval < alternateScaleCutoff))
{
return fmt::format("{:.{}f} {:s}", dval,
(fs.isHighPrecision ? (decimalPlaces + 4) : decimalPlaces), unit);
}
// We must be in an alternate case
return fmt::format("{:.{}f} {:s}", dval * alternateScaleRescaling,
(fs.isHighPrecision ? (decimalPlaces + 4) : decimalPlaces),
alternateScaleUnits);
}
break;
case CUBED_AS_DECIBEL:
{
if (val <= 0)
Expand Down Expand Up @@ -969,6 +993,39 @@ inline std::optional<float> ParamMetaData::valueFromString(std::string_view v, s
}
}
break;
case SCALED_OFFSET_EXP:
{
try
{
auto r = std::stof(std::string(v));

if (alternateScaleWhen != NO_ALTERNATE)
{
auto ps = v.find(alternateScaleUnits);
if (ps != std::string::npos && alternateScaleRescaling != 0.f)
{
// We have a string containing the alterante units
r = r / alternateScaleRescaling;
}
}

// OK so its R = exp(A + X (B-A)) - C)/D
// D R + C = exp(A + X (B-a))
// log(DR + C) = A + X (B-A)
// (log (DR + C) - A) / (B - A) = X
auto drc = std::max(svD * r + svC, 0.00000001f);
auto xv = (std::log(drc) - svA) / (svB - svA);

return xv;
}
catch (const std::exception &)
{
errMsg = rangeMsg();
return std::nullopt;
}
return 0.f;
}
break;
case CUBED_AS_DECIBEL:
{
try
Expand Down Expand Up @@ -1111,6 +1168,66 @@ ParamMetaData::modulationNaturalToString(float naturalBaseVal, float modulationN

return result;
}
case SCALED_OFFSET_EXP:
{
}
break;
case CUBED_AS_DECIBEL:
{
auto nvu = std::max(naturalBaseVal + modulationNatural, 0.f);
auto nvd = std::max(naturalBaseVal - modulationNatural, 0.f);
auto v = std::max(naturalBaseVal, 0.f);

nvu = nvu * nvu * nvu;
nvd = nvd * nvd * nvd;

auto db = 20 * std::log10(v);
auto dbu = 20 * std::log10(nvu);
auto dbd = 20 * std::log10(nvd);

auto deltUp = dbu - db;
auto deltDn = dbd - db;

auto dp = (fs.isHighPrecision ? (decimalPlaces + 4) : decimalPlaces);
result.value = fmt::format("{:.{}f} {}", deltUp, dp, unit);
if (isBipolar)
{
if (deltDn > 0)
{
result.summary = fmt::format("+/- {:.{}f} {}", deltUp, dp, unit);
}
else
{
result.summary = fmt::format("-/+ {:.{}f} {}", -deltUp, dp, unit);
}
}
else
{
result.summary = fmt::format("{:.{}f} {}", deltUp, dp, unit);
}
result.changeUp = fmt::format("{:.{}f}", deltUp, dp);
if (isBipolar)
result.changeDown = fmt::format("{:.{}f}", deltDn, dp);
result.valUp = fmt::format("{:.{}f}", dbu, dp);

if (isBipolar)
result.valDown = fmt::format("{:.{}f}", dbd, dp);
auto v2s = valueToString(naturalBaseVal, fs);
if (v2s.has_value())
result.baseValue = *v2s;
else
result.baseValue = "-ERROR-";

if (isBipolar)
result.singleLineModulationSummary = fmt::format(
"{} {} < {} > {} {}", result.valDown, unit, result.baseValue, result.valUp, unit);
else
result.singleLineModulationSummary =
fmt::format("{} > {} {}", result.baseValue, result.valUp, unit);

return result;
}
break;
default:
break;
}
Expand Down
Loading

0 comments on commit c1089bb

Please sign in to comment.