mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-29 15:19:44 +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]);
|
||||
}
|
||||
|
||||
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()
|
||||
: mIgnoreGuiEvents(false)
|
||||
{
|
||||
@ -612,7 +763,6 @@ double EffectCompressor2::CompressorGain(double env)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
||||
{
|
||||
if(!mIgnoreGuiEvents)
|
||||
@ -651,6 +801,7 @@ void EffectCompressor2::UpdateResponsePlot()
|
||||
wxASSERT(plot->xdata.size() == plot->ydata.size());
|
||||
|
||||
std::unique_ptr<SamplePreprocessor> preproc;
|
||||
std::unique_ptr<EnvelopeDetector> envelope;
|
||||
float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
|
||||
|
||||
size_t window_size =
|
||||
@ -665,21 +816,27 @@ void EffectCompressor2::UpdateResponsePlot()
|
||||
preproc = std::unique_ptr<SamplePreprocessor>(
|
||||
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_stop = RESPONSE_PLOT_STEP_STOP * plot_rate - lookahead_size;
|
||||
|
||||
float value;
|
||||
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)
|
||||
value = preproc->ProcessSample(0);
|
||||
envelope->ProcessSample(preproc->ProcessSample(0));
|
||||
else
|
||||
value = preproc->ProcessSample(1);
|
||||
|
||||
if(i >= 0)
|
||||
plot->ydata[i] = value;
|
||||
envelope->ProcessSample(preproc->ProcessSample(1));
|
||||
}
|
||||
for(ssize_t i = 0; i < xsize; ++i)
|
||||
plot->ydata[i] = envelope->ProcessSample(preproc->ProcessSample(0));
|
||||
|
||||
mResponsePlot->Refresh(false);
|
||||
}
|
||||
|
@ -67,6 +67,50 @@ class SlidingMaxPreprocessor : public SamplePreprocessor
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
Loading…
x
Reference in New Issue
Block a user