diff --git a/src/effects/Compressor2.cpp b/src/effects/Compressor2.cpp index f4916d364..9a2bf5bdf 100644 --- a/src/effects/Compressor2.cpp +++ b/src/effects/Compressor2.cpp @@ -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 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(&mAlgorithm) + mAlgorithmCtrl = S.Validator(&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(&mCompressBy) + mPreprocCtrl = S.Validator(&mCompressBy) .AddChoice(XO("Compress based on:"), Msgids(kCompressByStrings, nBy), mCompressBy); - ctrl->SetMinSize(box_size); + mPreprocCtrl->SetMinSize(box_size); S.Validator(&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 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); +} diff --git a/src/effects/Compressor2.h b/src/effects/Compressor2.h index 9e223075d..5506e5899 100644 --- a/src/effects/Compressor2.h +++ b/src/effects/Compressor2.h @@ -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 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 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 mPreproc; std::unique_ptr 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() };