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; }
|
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()
|
EffectCompressor2::EffectCompressor2()
|
||||||
: mIgnoreGuiEvents(false)
|
: mIgnoreGuiEvents(false)
|
||||||
{
|
{
|
||||||
@ -282,7 +381,8 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
|
|||||||
plot = mResponsePlot->GetPlotData(0);
|
plot = mResponsePlot->GetPlotData(0);
|
||||||
plot->pen = std::unique_ptr<wxPen>(
|
plot->pen = std::unique_ptr<wxPen>(
|
||||||
safenew wxPen(AColor::WideEnvelopePen));
|
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->ydata = {0, 0, 1, 1, 0, 0};
|
||||||
|
|
||||||
plot = mResponsePlot->GetPlotData(1);
|
plot = mResponsePlot->GetPlotData(1);
|
||||||
@ -290,6 +390,10 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
|
|||||||
safenew wxPen(AColor::WideEnvelopePen));
|
safenew wxPen(AColor::WideEnvelopePen));
|
||||||
plot->pen->SetColour(wxColor( 230,80,80 )); // Same color as TrackArtist RMS red.
|
plot->pen->SetColour(wxColor( 230,80,80 )); // Same color as TrackArtist RMS red.
|
||||||
plot->pen->SetWidth(2);
|
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();
|
S.EndHorizontalLay();
|
||||||
|
|
||||||
@ -519,13 +623,7 @@ void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
|||||||
void EffectCompressor2::UpdateUI()
|
void EffectCompressor2::UpdateUI()
|
||||||
{
|
{
|
||||||
UpdateCompressorPlot();
|
UpdateCompressorPlot();
|
||||||
|
UpdateResponsePlot();
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectCompressor2::UpdateCompressorPlot()
|
void EffectCompressor2::UpdateCompressorPlot()
|
||||||
@ -545,3 +643,43 @@ void EffectCompressor2::UpdateCompressorPlot()
|
|||||||
// "Compressor gain reduction: %.1f dB", plot->ydata[xsize-1]));
|
// "Compressor gain reduction: %.1f dB", plot->ydata[xsize-1]));
|
||||||
mGainPlot->Refresh(false);
|
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 Plot;
|
||||||
class ShuttleGui;
|
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
|
class EffectCompressor2 final : public Effect
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -65,6 +109,7 @@ private:
|
|||||||
void OnUpdateUI(wxCommandEvent & evt);
|
void OnUpdateUI(wxCommandEvent & evt);
|
||||||
void UpdateUI();
|
void UpdateUI();
|
||||||
void UpdateCompressorPlot();
|
void UpdateCompressorPlot();
|
||||||
|
void UpdateResponsePlot();
|
||||||
|
|
||||||
int mAlgorithm;
|
int mAlgorithm;
|
||||||
int mCompressBy;
|
int mCompressBy;
|
||||||
@ -84,6 +129,11 @@ private:
|
|||||||
double mMakeupGain;
|
double mMakeupGain;
|
||||||
double mMakeupGainDB;
|
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* mGainPlot;
|
||||||
Plot* mResponsePlot;
|
Plot* mResponsePlot;
|
||||||
bool mIgnoreGuiEvents;
|
bool mIgnoreGuiEvents;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user