mirror of
https://github.com/cookiengineer/audacity
synced 2025-09-18 17:10:55 +02:00
Implement ExpFit and Pt1 envelope detectors including preview.
Signed-off-by: Max Maisel <max.maisel@posteo.de>
This commit is contained in:
parent
61e88b39ab
commit
94e21d8f1c
@ -196,6 +196,157 @@ float SlidingMaxPreprocessor::DoProcessSample(float value)
|
|||||||
return std::max(mMaxes[currentHead], mMaxes[nextHead]);
|
return std::max(mMaxes[currentHead], mMaxes[nextHead]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnvelopeDetector::EnvelopeDetector(size_t buffer_size)
|
||||||
|
: mPos(0),
|
||||||
|
mLookaheadBuffer(buffer_size, 0),
|
||||||
|
mProcessingBuffer(buffer_size, 0),
|
||||||
|
mProcessedBuffer(buffer_size, 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnvelopeDetector::ProcessSample(float value)
|
||||||
|
{
|
||||||
|
float retval = mProcessedBuffer[mPos];
|
||||||
|
mLookaheadBuffer[mPos++] = value;
|
||||||
|
if(mPos == mProcessingBuffer.size())
|
||||||
|
{
|
||||||
|
Follow();
|
||||||
|
mPos = 0;
|
||||||
|
mProcessedBuffer.swap(mProcessingBuffer);
|
||||||
|
mLookaheadBuffer.swap(mProcessingBuffer);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t EnvelopeDetector::GetBlockSize() const
|
||||||
|
{
|
||||||
|
wxASSERT(mProcessedBuffer.size() == mProcessingBuffer.size());
|
||||||
|
wxASSERT(mProcessedBuffer.size() == mLookaheadBuffer.size());
|
||||||
|
return mLookaheadBuffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpFitEnvelopeDetector::ExpFitEnvelopeDetector(
|
||||||
|
float rate, float attackTime, float releaseTime)
|
||||||
|
: EnvelopeDetector(TAU_FACTOR * (attackTime + 1.0) * rate)
|
||||||
|
{
|
||||||
|
mAttackFactor = exp(-1.0 / (rate * attackTime));
|
||||||
|
mReleaseFactor = exp(-1.0 / (rate * releaseTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpFitEnvelopeDetector::Follow()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
"Follow"ing algorithm by Roger B. Dannenberg, taken from
|
||||||
|
Nyquist. His description follows. -DMM
|
||||||
|
|
||||||
|
Description: this is a sophisticated envelope follower.
|
||||||
|
The input is an envelope, e.g. something produced with
|
||||||
|
the AVG function. The purpose of this function is to
|
||||||
|
generate a smooth envelope that is generally not less
|
||||||
|
than the input signal. In other words, we want to "ride"
|
||||||
|
the peaks of the signal with a smooth function. The
|
||||||
|
algorithm is as follows: keep a current output value
|
||||||
|
(called the "value"). The value is allowed to increase
|
||||||
|
by at most rise_factor and decrease by at most fall_factor.
|
||||||
|
Therefore, the next value should be between
|
||||||
|
value * rise_factor and value * fall_factor. If the input
|
||||||
|
is in this range, then the next value is simply the input.
|
||||||
|
If the input is less than value * fall_factor, then the
|
||||||
|
next value is just value * fall_factor, which will be greater
|
||||||
|
than the input signal. If the input is greater than value *
|
||||||
|
rise_factor, then we compute a rising envelope that meets
|
||||||
|
the input value by working backwards in time, changing the
|
||||||
|
previous values to input / rise_factor, input / rise_factor^2,
|
||||||
|
input / rise_factor^3, etc. until this NEW envelope intersects
|
||||||
|
the previously computed values. There is only a limited buffer
|
||||||
|
in which we can work backwards, so if the NEW envelope does not
|
||||||
|
intersect the old one, then make yet another pass, this time
|
||||||
|
from the oldest buffered value forward, increasing on each
|
||||||
|
sample by rise_factor to produce a maximal envelope. This will
|
||||||
|
still be less than the input.
|
||||||
|
|
||||||
|
The value has a lower limit of floor to make sure value has a
|
||||||
|
reasonable positive value from which to begin an attack.
|
||||||
|
*/
|
||||||
|
wxASSERT(mProcessedBuffer.size() == mProcessingBuffer.size());
|
||||||
|
wxASSERT(mProcessedBuffer.size() == mLookaheadBuffer.size());
|
||||||
|
|
||||||
|
// First apply a peak detect with the requested release rate.
|
||||||
|
size_t buffer_size = mProcessingBuffer.size();
|
||||||
|
double env = mProcessedBuffer[buffer_size-1];
|
||||||
|
for(size_t i = 0; i < buffer_size; ++i)
|
||||||
|
{
|
||||||
|
env *= mReleaseFactor;
|
||||||
|
if(mProcessingBuffer[i] > env)
|
||||||
|
env = mProcessingBuffer[i];
|
||||||
|
mProcessingBuffer[i] = env;
|
||||||
|
}
|
||||||
|
// Preprocess lookahead buffer as well.
|
||||||
|
for(size_t i = 0; i < buffer_size; ++i)
|
||||||
|
{
|
||||||
|
env *= mReleaseFactor;
|
||||||
|
if(mLookaheadBuffer[i] > env)
|
||||||
|
env = mLookaheadBuffer[i];
|
||||||
|
mLookaheadBuffer[i] = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next do the same process in reverse direction to get the
|
||||||
|
// requested attack rate and preprocess lookahead buffer.
|
||||||
|
for(ssize_t i = buffer_size - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
env *= mAttackFactor;
|
||||||
|
if(mLookaheadBuffer[i] < env)
|
||||||
|
mLookaheadBuffer[i] = env;
|
||||||
|
else
|
||||||
|
env = mLookaheadBuffer[i];
|
||||||
|
}
|
||||||
|
for(ssize_t i = buffer_size - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
if(mProcessingBuffer[i] < env * mAttackFactor)
|
||||||
|
{
|
||||||
|
env *= mAttackFactor;
|
||||||
|
mProcessingBuffer[i] = env;
|
||||||
|
}
|
||||||
|
else if(mProcessingBuffer[i] > env)
|
||||||
|
// Intersected the previous envelope buffer, so we are finished
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
; // Do nothing if we are on a plateau from peak look-around
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pt1EnvelopeDetector::Pt1EnvelopeDetector(
|
||||||
|
float rate, float attackTime, float releaseTime, bool correctGain)
|
||||||
|
: EnvelopeDetector(TAU_FACTOR * (attackTime + 1.0) * rate)
|
||||||
|
{
|
||||||
|
// Approximate peak amplitude correction factor.
|
||||||
|
if(correctGain)
|
||||||
|
mGainCorrection = 1.0 + exp(attackTime / 30.0);
|
||||||
|
else
|
||||||
|
mGainCorrection = 1.0;
|
||||||
|
|
||||||
|
mAttackFactor = 1.0 / (attackTime * rate);
|
||||||
|
mReleaseFactor = 1.0 / (releaseTime * rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pt1EnvelopeDetector::Follow()
|
||||||
|
{
|
||||||
|
wxASSERT(mProcessedBuffer.size() == mProcessingBuffer.size());
|
||||||
|
wxASSERT(mProcessedBuffer.size() == mLookaheadBuffer.size());
|
||||||
|
|
||||||
|
// Simulate analog compressor with PT1 characteristic.
|
||||||
|
size_t buffer_size = mProcessingBuffer.size();
|
||||||
|
float level = mProcessedBuffer[buffer_size-1] / mGainCorrection;
|
||||||
|
for(size_t i = 0; i < buffer_size; ++i)
|
||||||
|
{
|
||||||
|
if(mProcessingBuffer[i] >= level)
|
||||||
|
level += mAttackFactor * (mProcessingBuffer[i] - level);
|
||||||
|
else
|
||||||
|
level += mReleaseFactor * (mProcessingBuffer[i] - level);
|
||||||
|
mProcessingBuffer[i] = level * mGainCorrection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EffectCompressor2::EffectCompressor2()
|
EffectCompressor2::EffectCompressor2()
|
||||||
: mIgnoreGuiEvents(false)
|
: mIgnoreGuiEvents(false)
|
||||||
{
|
{
|
||||||
@ -612,7 +763,6 @@ double EffectCompressor2::CompressorGain(double env)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
||||||
{
|
{
|
||||||
if(!mIgnoreGuiEvents)
|
if(!mIgnoreGuiEvents)
|
||||||
@ -651,6 +801,7 @@ void EffectCompressor2::UpdateResponsePlot()
|
|||||||
wxASSERT(plot->xdata.size() == plot->ydata.size());
|
wxASSERT(plot->xdata.size() == plot->ydata.size());
|
||||||
|
|
||||||
std::unique_ptr<SamplePreprocessor> preproc;
|
std::unique_ptr<SamplePreprocessor> preproc;
|
||||||
|
std::unique_ptr<EnvelopeDetector> envelope;
|
||||||
float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
|
float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
|
||||||
|
|
||||||
size_t window_size =
|
size_t window_size =
|
||||||
@ -665,21 +816,27 @@ void EffectCompressor2::UpdateResponsePlot()
|
|||||||
preproc = std::unique_ptr<SamplePreprocessor>(
|
preproc = std::unique_ptr<SamplePreprocessor>(
|
||||||
safenew SlidingMaxPreprocessor(window_size));
|
safenew SlidingMaxPreprocessor(window_size));
|
||||||
|
|
||||||
|
if(mAlgorithm == kExpFit)
|
||||||
|
envelope = std::unique_ptr<EnvelopeDetector>(
|
||||||
|
safenew ExpFitEnvelopeDetector(plot_rate, mAttackTime, mReleaseTime));
|
||||||
|
else
|
||||||
|
envelope = std::unique_ptr<EnvelopeDetector>(
|
||||||
|
safenew Pt1EnvelopeDetector(plot_rate, mAttackTime, mReleaseTime, false));
|
||||||
|
|
||||||
ssize_t step_start = RESPONSE_PLOT_STEP_START * plot_rate - lookahead_size;
|
ssize_t step_start = RESPONSE_PLOT_STEP_START * plot_rate - lookahead_size;
|
||||||
ssize_t step_stop = RESPONSE_PLOT_STEP_STOP * plot_rate - lookahead_size;
|
ssize_t step_stop = RESPONSE_PLOT_STEP_STOP * plot_rate - lookahead_size;
|
||||||
|
|
||||||
float value;
|
|
||||||
ssize_t xsize = plot->xdata.size();
|
ssize_t xsize = plot->xdata.size();
|
||||||
for(ssize_t i = -lookahead_size; i < xsize; ++i)
|
ssize_t block_size = envelope->GetBlockSize();
|
||||||
|
for(ssize_t i = -lookahead_size; i < 2*block_size; ++i)
|
||||||
{
|
{
|
||||||
if(i < step_start || i > step_stop)
|
if(i < step_start || i > step_stop)
|
||||||
value = preproc->ProcessSample(0);
|
envelope->ProcessSample(preproc->ProcessSample(0));
|
||||||
else
|
else
|
||||||
value = preproc->ProcessSample(1);
|
envelope->ProcessSample(preproc->ProcessSample(1));
|
||||||
|
|
||||||
if(i >= 0)
|
|
||||||
plot->ydata[i] = value;
|
|
||||||
}
|
}
|
||||||
|
for(ssize_t i = 0; i < xsize; ++i)
|
||||||
|
plot->ydata[i] = envelope->ProcessSample(preproc->ProcessSample(0));
|
||||||
|
|
||||||
mResponsePlot->Refresh(false);
|
mResponsePlot->Refresh(false);
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,50 @@ class SlidingMaxPreprocessor : public SamplePreprocessor
|
|||||||
inline float DoProcessSample(float value);
|
inline float DoProcessSample(float value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class EnvelopeDetector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EnvelopeDetector(size_t buffer_size);
|
||||||
|
|
||||||
|
float ProcessSample(float value);
|
||||||
|
size_t GetBlockSize() const;
|
||||||
|
protected:
|
||||||
|
static const int TAU_FACTOR = 5;
|
||||||
|
|
||||||
|
size_t mPos;
|
||||||
|
std::vector<float> mLookaheadBuffer;
|
||||||
|
std::vector<float> mProcessingBuffer;
|
||||||
|
std::vector<float> mProcessedBuffer;
|
||||||
|
|
||||||
|
virtual void Follow() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExpFitEnvelopeDetector : public EnvelopeDetector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExpFitEnvelopeDetector(float rate, float attackTime, float releaseTime);
|
||||||
|
|
||||||
|
private:
|
||||||
|
double mAttackFactor;
|
||||||
|
double mReleaseFactor;
|
||||||
|
|
||||||
|
virtual void Follow();
|
||||||
|
};
|
||||||
|
|
||||||
|
class Pt1EnvelopeDetector : public EnvelopeDetector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Pt1EnvelopeDetector(float rate, float attackTime, float releaseTime,
|
||||||
|
bool correctGain = true);
|
||||||
|
|
||||||
|
private:
|
||||||
|
double mGainCorrection;
|
||||||
|
double mAttackFactor;
|
||||||
|
double mReleaseFactor;
|
||||||
|
|
||||||
|
virtual void Follow();
|
||||||
|
};
|
||||||
|
|
||||||
class EffectCompressor2 final : public Effect
|
class EffectCompressor2 final : public Effect
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user