mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-29 15:19:44 +02:00
Implement sliding RMS and sliding max sample preprocessors.
Signed-off-by: Max Maisel <max.maisel@posteo.de>
This commit is contained in:
parent
7326edbefe
commit
61e88b39ab
@ -97,6 +97,105 @@ const ComponentInterfaceSymbol EffectCompressor2::Symbol
|
||||
|
||||
namespace{ BuiltinEffectsModule::Registration< EffectCompressor2 > reg; }
|
||||
|
||||
SlidingRmsPreprocessor::SlidingRmsPreprocessor(size_t windowSize, float gain)
|
||||
: mSum(0),
|
||||
mGain(gain),
|
||||
mWindow(windowSize, 0),
|
||||
mPos(0),
|
||||
mInsertCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
float SlidingRmsPreprocessor::ProcessSample(float value)
|
||||
{
|
||||
return DoProcessSample(value * value);
|
||||
}
|
||||
|
||||
float SlidingRmsPreprocessor::ProcessSample(float valueL, float valueR)
|
||||
{
|
||||
return DoProcessSample((valueL * valueL + valueR * valueR) / 2.0);
|
||||
}
|
||||
|
||||
float SlidingRmsPreprocessor::DoProcessSample(float value)
|
||||
{
|
||||
if(mInsertCount > REFRESH_WINDOW_EVERY)
|
||||
{
|
||||
// Update RMS sum directly from the circle buffer every
|
||||
// REFRESH_WINDOW_EVERY samples to avoid accumulation of rounding errors.
|
||||
mWindow[mPos] = value;
|
||||
Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate current level from root-mean-squared of
|
||||
// circular buffer ("RMS").
|
||||
mSum -= mWindow[mPos];
|
||||
mWindow[mPos] = value;
|
||||
mSum += mWindow[mPos];
|
||||
++mInsertCount;
|
||||
}
|
||||
|
||||
// Also refresh if there are severe rounding errors that
|
||||
// caused mRMSSum to be negative.
|
||||
if(mSum < 0)
|
||||
Refresh();
|
||||
|
||||
mPos = (mPos + 1) % mWindow.size();
|
||||
|
||||
// Multiply by gain (usually two) to approximately correct peak level
|
||||
// of standard audio (avoid clipping).
|
||||
return mGain * sqrt(mSum/float(mWindow.size()));
|
||||
}
|
||||
|
||||
void SlidingRmsPreprocessor::Refresh()
|
||||
{
|
||||
// Recompute the RMS sum periodically to prevent accumulation
|
||||
// of rounding errors during long waveforms.
|
||||
mSum = 0;
|
||||
for(const auto& sample : mWindow)
|
||||
mSum += sample;
|
||||
mInsertCount = 0;
|
||||
}
|
||||
|
||||
SlidingMaxPreprocessor::SlidingMaxPreprocessor(size_t windowSize)
|
||||
: mWindow(windowSize, 0),
|
||||
mMaxes(windowSize, 0),
|
||||
mPos(0)
|
||||
{
|
||||
}
|
||||
|
||||
float SlidingMaxPreprocessor::ProcessSample(float value)
|
||||
{
|
||||
return DoProcessSample(value);
|
||||
}
|
||||
|
||||
float SlidingMaxPreprocessor::ProcessSample(float valueL, float valueR)
|
||||
{
|
||||
return DoProcessSample((fabs(valueL) + fabs(valueR)) / 2.0);
|
||||
}
|
||||
|
||||
float SlidingMaxPreprocessor::DoProcessSample(float value)
|
||||
{
|
||||
size_t oldHead = (mPos-1) % mWindow.size();
|
||||
size_t currentHead = mPos;
|
||||
size_t nextHead = (mPos+1) % mWindow.size();
|
||||
mWindow[mPos] = value;
|
||||
mMaxes[mPos] = std::max(value, mMaxes[oldHead]);
|
||||
|
||||
if(mPos % ((mWindow.size()+1)/2) == 0)
|
||||
{
|
||||
mMaxes[mPos] = mWindow[mPos];
|
||||
for(size_t i = 1; i < mWindow.size(); ++i)
|
||||
{
|
||||
size_t pos1 = (mPos-i+mWindow.size()) % mWindow.size();
|
||||
size_t pos2 = (mPos-i+mWindow.size()+1) % mWindow.size();
|
||||
mMaxes[pos1] = std::max(mWindow[pos1], mMaxes[pos2]);
|
||||
}
|
||||
}
|
||||
mPos = nextHead;
|
||||
return std::max(mMaxes[currentHead], mMaxes[nextHead]);
|
||||
}
|
||||
|
||||
EffectCompressor2::EffectCompressor2()
|
||||
: mIgnoreGuiEvents(false)
|
||||
{
|
||||
@ -282,7 +381,8 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
|
||||
plot = mResponsePlot->GetPlotData(0);
|
||||
plot->pen = std::unique_ptr<wxPen>(
|
||||
safenew wxPen(AColor::WideEnvelopePen));
|
||||
plot->xdata = {0, 2, 2, 3, 3, 5};
|
||||
plot->xdata = {0, RESPONSE_PLOT_STEP_START, RESPONSE_PLOT_STEP_START,
|
||||
RESPONSE_PLOT_STEP_STOP, RESPONSE_PLOT_STEP_STOP, 5};
|
||||
plot->ydata = {0, 0, 1, 1, 0, 0};
|
||||
|
||||
plot = mResponsePlot->GetPlotData(1);
|
||||
@ -290,6 +390,10 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
|
||||
safenew wxPen(AColor::WideEnvelopePen));
|
||||
plot->pen->SetColour(wxColor( 230,80,80 )); // Same color as TrackArtist RMS red.
|
||||
plot->pen->SetWidth(2);
|
||||
plot->xdata.resize(RESPONSE_PLOT_SAMPLES+1);
|
||||
plot->ydata.resize(RESPONSE_PLOT_SAMPLES+1);
|
||||
for(size_t x = 0; x < plot->xdata.size(); ++x)
|
||||
plot->xdata[x] = x * float(RESPONSE_PLOT_TIME) / float(RESPONSE_PLOT_SAMPLES);
|
||||
}
|
||||
S.EndHorizontalLay();
|
||||
|
||||
@ -519,13 +623,7 @@ void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
||||
void EffectCompressor2::UpdateUI()
|
||||
{
|
||||
UpdateCompressorPlot();
|
||||
|
||||
// TODO: update plots
|
||||
PlotData* plot;
|
||||
plot = mResponsePlot->GetPlotData(1);
|
||||
plot->xdata = {0, 2, 2, 3, 3, 5};
|
||||
plot->ydata = {0, 0.5, 1, 1, 0.5, 0};
|
||||
mResponsePlot->Refresh(false);
|
||||
UpdateResponsePlot();
|
||||
}
|
||||
|
||||
void EffectCompressor2::UpdateCompressorPlot()
|
||||
@ -545,3 +643,43 @@ void EffectCompressor2::UpdateCompressorPlot()
|
||||
// "Compressor gain reduction: %.1f dB", plot->ydata[xsize-1]));
|
||||
mGainPlot->Refresh(false);
|
||||
}
|
||||
|
||||
void EffectCompressor2::UpdateResponsePlot()
|
||||
{
|
||||
PlotData* plot;
|
||||
plot = mResponsePlot->GetPlotData(1);
|
||||
wxASSERT(plot->xdata.size() == plot->ydata.size());
|
||||
|
||||
std::unique_ptr<SamplePreprocessor> preproc;
|
||||
float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
|
||||
|
||||
size_t window_size =
|
||||
std::max(1, int(round((mLookaheadTime + mLookbehindTime) * plot_rate)));
|
||||
size_t lookahead_size =
|
||||
std::max(0, int(round(mLookaheadTime * plot_rate)));
|
||||
|
||||
if(mCompressBy == kRMS)
|
||||
preproc = std::unique_ptr<SamplePreprocessor>(
|
||||
safenew SlidingRmsPreprocessor(window_size, 1.0));
|
||||
else
|
||||
preproc = std::unique_ptr<SamplePreprocessor>(
|
||||
safenew SlidingMaxPreprocessor(window_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;
|
||||
|
||||
float value;
|
||||
ssize_t xsize = plot->xdata.size();
|
||||
for(ssize_t i = -lookahead_size; i < xsize; ++i)
|
||||
{
|
||||
if(i < step_start || i > step_stop)
|
||||
value = preproc->ProcessSample(0);
|
||||
else
|
||||
value = preproc->ProcessSample(1);
|
||||
|
||||
if(i >= 0)
|
||||
plot->ydata[i] = value;
|
||||
}
|
||||
|
||||
mResponsePlot->Refresh(false);
|
||||
}
|
||||
|
@ -23,6 +23,50 @@
|
||||
class Plot;
|
||||
class ShuttleGui;
|
||||
|
||||
class SamplePreprocessor
|
||||
{
|
||||
public:
|
||||
virtual float ProcessSample(float value) = 0;
|
||||
virtual float ProcessSample(float valueL, float valueR) = 0;
|
||||
};
|
||||
|
||||
class SlidingRmsPreprocessor : public SamplePreprocessor
|
||||
{
|
||||
public:
|
||||
SlidingRmsPreprocessor(size_t windowSize, float gain = 2.0);
|
||||
|
||||
virtual float ProcessSample(float value);
|
||||
virtual float ProcessSample(float valueL, float valueR);
|
||||
|
||||
static const size_t REFRESH_WINDOW_EVERY = 1048576; // 1 MB
|
||||
|
||||
private:
|
||||
float mSum;
|
||||
float mGain;
|
||||
std::vector<float> mWindow;
|
||||
size_t mPos;
|
||||
size_t mInsertCount;
|
||||
|
||||
inline float DoProcessSample(float value);
|
||||
void Refresh();
|
||||
};
|
||||
|
||||
class SlidingMaxPreprocessor : public SamplePreprocessor
|
||||
{
|
||||
public:
|
||||
SlidingMaxPreprocessor(size_t windowSize);
|
||||
|
||||
virtual float ProcessSample(float value);
|
||||
virtual float ProcessSample(float valueL, float valueR);
|
||||
|
||||
private:
|
||||
std::vector<float> mWindow;
|
||||
std::vector<float> mMaxes;
|
||||
size_t mPos;
|
||||
|
||||
inline float DoProcessSample(float value);
|
||||
};
|
||||
|
||||
class EffectCompressor2 final : public Effect
|
||||
{
|
||||
public:
|
||||
@ -65,6 +109,7 @@ private:
|
||||
void OnUpdateUI(wxCommandEvent & evt);
|
||||
void UpdateUI();
|
||||
void UpdateCompressorPlot();
|
||||
void UpdateResponsePlot();
|
||||
|
||||
int mAlgorithm;
|
||||
int mCompressBy;
|
||||
@ -84,6 +129,11 @@ private:
|
||||
double mMakeupGain;
|
||||
double mMakeupGainDB;
|
||||
|
||||
static const size_t RESPONSE_PLOT_SAMPLES = 200;
|
||||
static const size_t RESPONSE_PLOT_TIME = 5;
|
||||
static const size_t RESPONSE_PLOT_STEP_START = 2;
|
||||
static const size_t RESPONSE_PLOT_STEP_STOP = 3;
|
||||
|
||||
Plot* mGainPlot;
|
||||
Plot* mResponsePlot;
|
||||
bool mIgnoreGuiEvents;
|
||||
|
Loading…
x
Reference in New Issue
Block a user