1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-29 23:29:41 +02:00

Add realtime support to Compressor2 effect.

Signed-off-by: Max Maisel <max.maisel@posteo.de>
This commit is contained in:
Max Maisel 2020-09-15 15:23:42 +02:00
parent 2a6c2aaf80
commit aa619de49c
2 changed files with 190 additions and 22 deletions

View File

@ -133,6 +133,12 @@ void SlidingRmsPreprocessor::Reset()
std::fill(mWindow.begin(), mWindow.end(), 0);
}
void SlidingRmsPreprocessor::SetWindowSize(size_t windowSize)
{
mWindow.resize(windowSize);
Reset();
}
float SlidingRmsPreprocessor::DoProcessSample(float value)
{
if(mInsertCount > REFRESH_WINDOW_EVERY)
@ -198,6 +204,13 @@ void SlidingMaxPreprocessor::Reset()
std::fill(mMaxes.begin(), mMaxes.end(), 0);
}
void SlidingMaxPreprocessor::SetWindowSize(size_t windowSize)
{
mWindow.resize(windowSize);
mMaxes.resize(windowSize);
Reset();
}
float SlidingMaxPreprocessor::DoProcessSample(float value)
{
size_t oldHead = (mPos-1) % mWindow.size();
@ -272,8 +285,14 @@ ExpFitEnvelopeDetector::ExpFitEnvelopeDetector(
float rate, float attackTime, float releaseTime, size_t bufferSize)
: EnvelopeDetector(bufferSize)
{
mAttackFactor = exp(-1.0 / (rate * attackTime));
mReleaseFactor = exp(-1.0 / (rate * releaseTime));
SetParams(rate, attackTime, releaseTime);
}
void ExpFitEnvelopeDetector::SetParams(
float sampleRate, float attackTime, float releaseTime)
{
mAttackFactor = exp(-1.0 / (sampleRate * attackTime));
mReleaseFactor = exp(-1.0 / (sampleRate * releaseTime));
}
void ExpFitEnvelopeDetector::Follow()
@ -361,17 +380,24 @@ void ExpFitEnvelopeDetector::Follow()
Pt1EnvelopeDetector::Pt1EnvelopeDetector(
float rate, float attackTime, float releaseTime, size_t bufferSize,
bool correctGain)
: EnvelopeDetector(bufferSize)
: EnvelopeDetector(bufferSize),
mCorrectGain(correctGain)
{
SetParams(rate, attackTime, releaseTime);
}
void Pt1EnvelopeDetector::SetParams(
float sampleRate, float attackTime, float releaseTime)
{
// Approximate peak amplitude correction factor.
if(correctGain)
if(mCorrectGain)
mGainCorrection = 1.0 + exp(attackTime / 30.0);
else
mGainCorrection = 1.0;
mAttackFactor = 1.0 / (attackTime * rate);
mReleaseFactor = 1.0 / (releaseTime * rate);
mInitialBlockSize = std::min(size_t(rate * sqrt(attackTime)), bufferSize);
mAttackFactor = 1.0 / (attackTime * sampleRate);
mReleaseFactor = 1.0 / (releaseTime * sampleRate);
mInitialBlockSize = std::min(size_t(sampleRate * sqrt(attackTime)), mLookaheadBuffer.size());
}
void Pt1EnvelopeDetector::CalcInitialCondition(float value)
@ -461,7 +487,11 @@ void PipelineBuffer::free()
}
EffectCompressor2::EffectCompressor2()
: mIgnoreGuiEvents(false)
: mIgnoreGuiEvents(false),
mAlgorithmCtrl(0),
mPreprocCtrl(0),
mAttackTimeCtrl(0),
mLookaheadTimeCtrl(0)
{
mAlgorithm = DEF_Algorithm;
mCompressBy = DEF_CompressBy;
@ -508,6 +538,84 @@ EffectType EffectCompressor2::GetType()
return EffectTypeProcess;
}
bool EffectCompressor2::SupportsRealtime()
{
#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
return true;
#else
return false;
#endif
}
unsigned EffectCompressor2::GetAudioInCount()
{
return 2;
}
unsigned EffectCompressor2::GetAudioOutCount()
{
return 2;
}
bool EffectCompressor2::RealtimeInitialize()
{
SetBlockSize(512);
AllocRealtimePipeline();
mAlgorithmCtrl->Enable(false);
mPreprocCtrl->Enable(false);
mLookaheadTimeCtrl->Enable(false);
if(mAlgorithm == kExpFit)
mAttackTimeCtrl->Enable(false);
return true;
}
bool EffectCompressor2::RealtimeAddProcessor(
unsigned WXUNUSED(numChannels), float sampleRate)
{
mSampleRate = sampleRate;
mProcStereo = true;
mPreproc = InitPreprocessor(mSampleRate);
mEnvelope = InitEnvelope(mSampleRate, mPipeline[0].size);
return true;
}
bool EffectCompressor2::RealtimeFinalize()
{
mPreproc.reset(nullptr);
mEnvelope.reset(nullptr);
FreePipeline();
mAlgorithmCtrl->Enable(true);
mPreprocCtrl->Enable(true);
mLookaheadTimeCtrl->Enable(true);
if(mAlgorithm == kExpFit)
mAttackTimeCtrl->Enable(true);
return true;
}
size_t EffectCompressor2::RealtimeProcess(
int group, float **inbuf, float **outbuf, size_t numSamples)
{
std::lock_guard<std::mutex> guard(mRealtimeMutex);
const size_t j = PIPELINE_DEPTH-1;
for(size_t i = 0; i < numSamples; ++i)
{
if(mPipeline[j].trackSize == mPipeline[j].size)
{
ProcessPipeline();
mPipeline[j].trackSize = 0;
SwapPipeline();
}
outbuf[0][i] = mPipeline[j][0][mPipeline[j].trackSize];
outbuf[1][i] = mPipeline[j][1][mPipeline[j].trackSize];
mPipeline[j][0][mPipeline[j].trackSize] = inbuf[0][i];
mPipeline[j][1][mPipeline[j].trackSize] = inbuf[1][i];
++mPipeline[j].trackSize;
}
return numSamples;
}
// EffectClientInterface implementation
bool EffectCompressor2::DefineParams( ShuttleParams & S )
{
@ -714,24 +822,22 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
{
S.SetStretchyCol(1);
wxChoice* ctrl = nullptr;
ctrl = S.Validator<wxGenericValidator>(&mAlgorithm)
mAlgorithmCtrl = S.Validator<wxGenericValidator>(&mAlgorithm)
.AddChoice(XO("Envelope Algorithm:"),
Msgids(kAlgorithmStrings, nAlgos),
mAlgorithm);
wxSize box_size = ctrl->GetMinSize();
wxSize box_size = mAlgorithmCtrl->GetMinSize();
int width = S.GetParent()->GetTextExtent(wxString::Format(
"%sxxxx", kAlgorithmStrings[nAlgos-1].Translation())).GetWidth();
box_size.SetWidth(width);
ctrl->SetMinSize(box_size);
mAlgorithmCtrl->SetMinSize(box_size);
ctrl = S.Validator<wxGenericValidator>(&mCompressBy)
mPreprocCtrl = S.Validator<wxGenericValidator>(&mCompressBy)
.AddChoice(XO("Compress based on:"),
Msgids(kCompressByStrings, nBy),
mCompressBy);
ctrl->SetMinSize(box_size);
mPreprocCtrl->SetMinSize(box_size);
S.Validator<wxGenericValidator>(&mStereoInd)
.AddCheckBox(XO("Compress stereo channels independently"),
@ -784,12 +890,12 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
S.AddVariableText(XO("Attack Time:"), true,
wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
ctrl = S.Name(XO("Attack Time"))
mAttackTimeCtrl = S.Name(XO("Attack Time"))
.Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
.AddSliderTextCtrl({}, DEF_AttackTime, MAX_AttackTime,
MIN_AttackTime, ScaleToPrecision(SCL_AttackTime),
&mAttackTime, SCL_AttackTime / 1000);
ctrl->SetMinTextboxWidth(textbox_width);
mAttackTimeCtrl->SetMinTextboxWidth(textbox_width);
S.AddVariableText(XO("s"), true,
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
@ -806,12 +912,12 @@ void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
S.AddVariableText(XO("Lookahead Time:"), true,
wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
ctrl = S.Name(XO("Lookahead Time"))
mLookaheadTimeCtrl = S.Name(XO("Lookahead Time"))
.Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
.AddSliderTextCtrl({}, DEF_LookaheadTime, MAX_LookaheadTime,
MIN_LookaheadTime, ScaleToPrecision(SCL_LookaheadTime),
&mLookaheadTime);
ctrl->SetMinTextboxWidth(textbox_width);
mLookaheadTimeCtrl->SetMinTextboxWidth(textbox_width);
S.AddVariableText(XO("s"), true,
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
@ -988,6 +1094,24 @@ void EffectCompressor2::AllocPipeline()
mPipeline[i].init(capacity, stereoTrackFound);
}
void EffectCompressor2::AllocRealtimePipeline()
{
mLookaheadLength =
std::max(0, int(round(mLookaheadTime * mSampleRate)));
size_t blockSize = std::max(mLookaheadLength, size_t(512));
if(mAlgorithm == kExpFit)
{
size_t riseTime = round(5.0 * (0.1 + mAttackTime)) * mSampleRate;
blockSize = std::max(blockSize, riseTime);
}
for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
{
mPipeline[i].init(blockSize, true);
mPipeline[i].size = blockSize;
}
}
void EffectCompressor2::FreePipeline()
{
for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
@ -1379,6 +1503,8 @@ void EffectCompressor2::UpdateUI()
{
UpdateCompressorPlot();
UpdateResponsePlot();
if(mEnvelope.get() != nullptr)
UpdateRealtimeParams();
}
void EffectCompressor2::UpdateCompressorPlot()
@ -1432,3 +1558,15 @@ void EffectCompressor2::UpdateResponsePlot()
mResponsePlot->Refresh(false);
}
void EffectCompressor2::UpdateRealtimeParams()
{
std::lock_guard<std::mutex> guard(mRealtimeMutex);
// TODO: extract it
size_t window_size =
std::max(1, int(round((mLookaheadTime + mLookbehindTime) * mSampleRate)));
mLookaheadLength = // TODO: depup this everywhere
std::max(0, int(round(mLookaheadTime * mSampleRate)));
mPreproc->SetWindowSize(window_size);
mEnvelope->SetParams(mSampleRate, mAttackTime, mReleaseTime);
}

View File

@ -22,6 +22,7 @@
class Plot;
class ShuttleGui;
class SliderTextCtrl;
class SamplePreprocessor
{
@ -29,6 +30,7 @@ class SamplePreprocessor
virtual float ProcessSample(float value) = 0;
virtual float ProcessSample(float valueL, float valueR) = 0;
virtual void Reset() = 0;
virtual void SetWindowSize(size_t windowSize) = 0;
};
class SlidingRmsPreprocessor : public SamplePreprocessor
@ -39,6 +41,7 @@ class SlidingRmsPreprocessor : public SamplePreprocessor
virtual float ProcessSample(float value);
virtual float ProcessSample(float valueL, float valueR);
virtual void Reset();
virtual void SetWindowSize(size_t windowSize);
static const size_t REFRESH_WINDOW_EVERY = 1048576; // 1 MB
@ -61,6 +64,7 @@ class SlidingMaxPreprocessor : public SamplePreprocessor
virtual float ProcessSample(float value);
virtual float ProcessSample(float valueL, float valueR);
virtual void Reset();
virtual void SetWindowSize(size_t windowSize);
private:
std::vector<float> mWindow;
@ -82,6 +86,10 @@ class EnvelopeDetector
virtual void CalcInitialCondition(float value);
inline float InitialCondition() const { return mInitialCondition; }
inline size_t InitialConditionSize() const { return mInitialBlockSize; }
virtual void SetParams(float sampleRate, float attackTime,
float releaseTime) = 0;
protected:
size_t mPos;
float mInitialCondition;
@ -97,7 +105,10 @@ class ExpFitEnvelopeDetector : public EnvelopeDetector
{
public:
ExpFitEnvelopeDetector(float rate, float attackTime, float releaseTime,
size_t buffer_size = 0);
size_t buffer_size);
virtual void SetParams(float sampleRate, float attackTime,
float releaseTime);
private:
double mAttackFactor;
@ -110,10 +121,14 @@ class Pt1EnvelopeDetector : public EnvelopeDetector
{
public:
Pt1EnvelopeDetector(float rate, float attackTime, float releaseTime,
size_t buffer_size = 0, bool correctGain = true);
size_t buffer_size, bool correctGain = true);
virtual void CalcInitialCondition(float value);
virtual void SetParams(float sampleRate, float attackTime,
float releaseTime);
private:
bool mCorrectGain;
double mGainCorrection;
double mAttackFactor;
double mReleaseFactor;
@ -160,9 +175,17 @@ public:
// EffectDefinitionInterface implementation
EffectType GetType() override;
bool SupportsRealtime() override;
// EffectClientInterface implementation
unsigned GetAudioInCount() override;
unsigned GetAudioOutCount() override;
bool RealtimeInitialize() override;
bool RealtimeAddProcessor(unsigned numChannels, float sampleRate) override;
bool RealtimeFinalize() override;
size_t RealtimeProcess(int group, float **inbuf, float **outbuf,
size_t numSamples) override;
bool DefineParams( ShuttleParams & S ) override;
bool GetAutomationParameters(CommandParameters & parms) override;
bool SetAutomationParameters(CommandParameters & parms) override;
@ -187,6 +210,7 @@ private:
size_t CalcBufferSize(size_t sampleRate);
void AllocPipeline();
void AllocRealtimePipeline();
void FreePipeline();
void SwapPipeline();
bool ProcessOne(TrackIterRange<WaveTrack> range);
@ -205,6 +229,7 @@ private:
void UpdateUI();
void UpdateCompressorPlot();
void UpdateResponsePlot();
void UpdateRealtimeParams();
static const int TAU_FACTOR = 5;
static const size_t MIN_BUFFER_CAPACITY = 1048576; // 1MB
@ -218,6 +243,7 @@ private:
double mTrackLen;
bool mProcStereo;
std::mutex mRealtimeMutex;
std::unique_ptr<SamplePreprocessor> mPreproc;
std::unique_ptr<EnvelopeDetector> mEnvelope;
@ -246,9 +272,13 @@ private:
static const size_t RESPONSE_PLOT_STEP_START = 2;
static const size_t RESPONSE_PLOT_STEP_STOP = 3;
bool mIgnoreGuiEvents;
Plot* mGainPlot;
Plot* mResponsePlot;
bool mIgnoreGuiEvents;
wxChoice* mAlgorithmCtrl;
wxChoice* mPreprocCtrl;
SliderTextCtrl* mAttackTimeCtrl;
SliderTextCtrl* mLookaheadTimeCtrl;
DECLARE_EVENT_TABLE()
};