refactor(lfo): advance by sample count and separate phase offset
Add advance(numSamples) so the engine can step LFOs per sub-block instead of calling process() in a sample loop. process() now delegates to advance(1). Split phaseOffset from phase so setParams doesn't overwrite the running phase; getPhase() adds the offset and wraps. Fix sync mode to divide by beat multiplier instead of multiplying. Move SampleHold randomization into advance() based on cycle count. Validate shape data in setShapeData with isfinite and jlimit.
This commit is contained in:
+33
-20
@@ -10,15 +10,17 @@ void LFO::reset()
|
|||||||
delayCounter = 0.0;
|
delayCounter = 0.0;
|
||||||
fadeCounter = 0.0;
|
fadeCounter = 0.0;
|
||||||
fadeVal = 1.0f;
|
fadeVal = 1.0f;
|
||||||
holdValue = 0.0f;
|
holdValue = rng.nextFloat() * 2.0f - 1.0f;
|
||||||
prevPhase = 0.0;
|
|
||||||
prevDelayParam = -1.0f;
|
prevDelayParam = -1.0f;
|
||||||
prevFadeParam = -1.0f;
|
prevFadeParam = -1.0f;
|
||||||
shapeBuffer.assign ((size_t) kShapePoints, 0.0f);
|
if (shapeBuffer.empty())
|
||||||
|
{
|
||||||
|
shapeBuffer.resize ((size_t) kShapePoints);
|
||||||
// default step sequence
|
// default step sequence
|
||||||
for (int i = 0; i < kShapePoints; ++i)
|
for (int i = 0; i < kShapePoints; ++i)
|
||||||
shapeBuffer[(size_t) i] = (i % 2 == 0) ? 1.0f : -1.0f;
|
shapeBuffer[(size_t) i] = (i % 2 == 0) ? 1.0f : -1.0f;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LFO::setParams (float rateNorm, bool s, float b, int shp, float ph,
|
void LFO::setParams (float rateNorm, bool s, float b, int shp, float ph,
|
||||||
float fade, float delay)
|
float fade, float delay)
|
||||||
@@ -26,10 +28,10 @@ void LFO::setParams (float rateNorm, bool s, float b, int shp, float ph,
|
|||||||
sync = s;
|
sync = s;
|
||||||
beat = b;
|
beat = b;
|
||||||
shape = juce::jlimit (0, (int) LfoShape::Count - 1, shp);
|
shape = juce::jlimit (0, (int) LfoShape::Count - 1, shp);
|
||||||
phase = juce::jlimit (0.0f, 1.0f, ph);
|
phaseOffset = juce::jlimit (0.0f, 1.0f, ph);
|
||||||
|
|
||||||
if (sync)
|
if (sync)
|
||||||
rateHz = maps::beatToMultiplier (beat) * (tempo / 60.0);
|
rateHz = (tempo / 60.0) / maps::beatToMultiplier (beat);
|
||||||
else
|
else
|
||||||
rateHz = maps::rateToHz (rateNorm);
|
rateHz = maps::rateToHz (rateNorm);
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ void LFO::setParams (float rateNorm, bool s, float b, int shp, float ph,
|
|||||||
prevDelayParam = delay;
|
prevDelayParam = delay;
|
||||||
prevFadeParam = fade;
|
prevFadeParam = fade;
|
||||||
}
|
}
|
||||||
|
value = delayCounter > 0.0 ? 0.0f : shapeValue() * fadeVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LFO::setShapeData (const std::vector<float>& data, int steps)
|
void LFO::setShapeData (const std::vector<float>& data, int steps)
|
||||||
@@ -54,17 +57,18 @@ void LFO::setShapeData (const std::vector<float>& data, int steps)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
shapeSteps = juce::jlimit (2, kShapePoints, steps);
|
shapeSteps = juce::jlimit (2, kShapePoints, steps);
|
||||||
shapeBuffer.assign (data.begin(), data.end());
|
for (size_t i = 0; i < shapeBuffer.size(); ++i)
|
||||||
shapeBuffer.resize ((size_t) kShapePoints, 0.0f);
|
shapeBuffer[i] = i < data.size() && std::isfinite (data[i])
|
||||||
|
? juce::jlimit (-1.0f, 1.0f, data[i]) : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float LFO::shapeValue() noexcept
|
float LFO::shapeValue() noexcept
|
||||||
{
|
{
|
||||||
const float p = (float) phase;
|
const float p = getPhase();
|
||||||
switch ((LfoShape) shape)
|
switch ((LfoShape) shape)
|
||||||
{
|
{
|
||||||
case LfoShape::Sine:
|
case LfoShape::Sine:
|
||||||
return std::sin (p * 6.28318530717958647692f);
|
return std::sin (p * juce::MathConstants<float>::twoPi);
|
||||||
case LfoShape::Triangle:
|
case LfoShape::Triangle:
|
||||||
return 1.0f - 4.0f * std::abs (p - 0.5f);
|
return 1.0f - 4.0f * std::abs (p - 0.5f);
|
||||||
case LfoShape::Saw:
|
case LfoShape::Saw:
|
||||||
@@ -72,11 +76,7 @@ float LFO::shapeValue() noexcept
|
|||||||
case LfoShape::Square:
|
case LfoShape::Square:
|
||||||
return (p < 0.5f) ? 1.0f : -1.0f;
|
return (p < 0.5f) ? 1.0f : -1.0f;
|
||||||
case LfoShape::SampleHold:
|
case LfoShape::SampleHold:
|
||||||
{
|
|
||||||
if (phase < prevPhase)
|
|
||||||
holdValue = rng.nextFloat() * 2.0f - 1.0f;
|
|
||||||
return holdValue;
|
return holdValue;
|
||||||
}
|
|
||||||
case LfoShape::StepSeq:
|
case LfoShape::StepSeq:
|
||||||
{
|
{
|
||||||
const int idx = juce::jlimit (0, shapeSteps - 1, (int) (p * shapeSteps));
|
const int idx = juce::jlimit (0, shapeSteps - 1, (int) (p * shapeSteps));
|
||||||
@@ -97,27 +97,40 @@ float LFO::shapeValue() noexcept
|
|||||||
|
|
||||||
float LFO::process() noexcept
|
float LFO::process() noexcept
|
||||||
{
|
{
|
||||||
|
return advance (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float LFO::advance (int numSamples) noexcept
|
||||||
|
{
|
||||||
|
if (numSamples <= 0)
|
||||||
|
return value;
|
||||||
|
|
||||||
// Start delay.
|
// Start delay.
|
||||||
if (delayCounter > 0.0)
|
if (delayCounter > 0.0)
|
||||||
{
|
{
|
||||||
delayCounter -= 1.0;
|
const int skipped = (int) juce::jmin ((double) numSamples, std::ceil (delayCounter));
|
||||||
value = 0.0f;
|
delayCounter = juce::jmax (0.0, delayCounter - skipped);
|
||||||
return 0.0f;
|
numSamples -= skipped;
|
||||||
|
if (numSamples == 0)
|
||||||
|
return value = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fade-in ramp.
|
// Fade-in ramp.
|
||||||
if (fadeVal < 1.0f)
|
if (fadeVal < 1.0f)
|
||||||
{
|
{
|
||||||
fadeCounter += 1.0;
|
fadeCounter += numSamples;
|
||||||
if (fadeSeconds > 0.0)
|
if (fadeSeconds > 0.0)
|
||||||
fadeVal = (float) juce::jlimit (0.0, 1.0, fadeCounter / (fadeSeconds * sr));
|
fadeVal = (float) juce::jlimit (0.0, 1.0, fadeCounter / (fadeSeconds * sr));
|
||||||
else
|
else
|
||||||
fadeVal = 1.0f;
|
fadeVal = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
prevPhase = phase;
|
phase += rateHz * numSamples / sr;
|
||||||
phase += rateHz / sr;
|
const double cycles = std::floor (phase);
|
||||||
phase -= std::floor (phase);
|
phase -= cycles;
|
||||||
|
if (shape == (int) LfoShape::SampleHold)
|
||||||
|
for (int i = 0; i < (int) cycles; ++i)
|
||||||
|
holdValue = rng.nextFloat() * 2.0f - 1.0f;
|
||||||
|
|
||||||
value = shapeValue() * fadeVal;
|
value = shapeValue() * fadeVal;
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
+4
-2
@@ -15,6 +15,7 @@ class LFO
|
|||||||
public:
|
public:
|
||||||
static constexpr int kShapePoints = 64;
|
static constexpr int kShapePoints = 64;
|
||||||
|
|
||||||
|
LFO() { reset(); }
|
||||||
void prepare (double sampleRate) { sr = sampleRate; reset(); }
|
void prepare (double sampleRate) { sr = sampleRate; reset(); }
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
@@ -24,7 +25,8 @@ public:
|
|||||||
void setShapeData (const std::vector<float>& data, int steps);
|
void setShapeData (const std::vector<float>& data, int steps);
|
||||||
|
|
||||||
float process() noexcept; // advance and return current value
|
float process() noexcept; // advance and return current value
|
||||||
float getPhase() const noexcept { return (float) phase; }
|
float advance (int numSamples) noexcept;
|
||||||
|
float getPhase() const noexcept { return (float) (phase + phaseOffset - std::floor (phase + phaseOffset)); }
|
||||||
float getValue() const noexcept { return value; }
|
float getValue() const noexcept { return value; }
|
||||||
|
|
||||||
const std::vector<float>& getShapeData() const noexcept { return shapeBuffer; }
|
const std::vector<float>& getShapeData() const noexcept { return shapeBuffer; }
|
||||||
@@ -35,6 +37,7 @@ private:
|
|||||||
double tempo = 120.0;
|
double tempo = 120.0;
|
||||||
|
|
||||||
double phase = 0.0;
|
double phase = 0.0;
|
||||||
|
double phaseOffset = 0.0;
|
||||||
double rateHz = 1.0;
|
double rateHz = 1.0;
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
|
|
||||||
@@ -55,7 +58,6 @@ private:
|
|||||||
|
|
||||||
juce::Random rng;
|
juce::Random rng;
|
||||||
float holdValue = 0.0f;
|
float holdValue = 0.0f;
|
||||||
double prevPhase = 0.0;
|
|
||||||
|
|
||||||
float shapeValue() noexcept;
|
float shapeValue() noexcept;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user