1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-16 08:37:42 +02:00

Move fields out of Effect, into new class RealtimeEffectState...

... and simplify, using a std::atomic instead of a critical section.

(But did this before, and does this now, correctly synchronize across threads?
I defer that question.)
This commit is contained in:
Paul Licameli 2019-06-23 19:23:18 -04:00
parent 0f62046313
commit 867e6a8d9e
4 changed files with 137 additions and 139 deletions

View File

@ -146,10 +146,6 @@ Effect::Effect()
mNumGroups = 0;
mProgress = NULL;
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount = 1; // Effects are initially suspended
mRealtimeSuspendLock.Leave();
mUIParent = NULL;
mUIDialog = NULL;
@ -476,46 +472,41 @@ bool Effect::RealtimeFinalize()
return false;
}
RealtimeEffectState::RealtimeEffectState( Effect &effect )
: mEffect{ effect }
{
}
bool RealtimeEffectState::RealtimeSuspend()
{
auto result = mEffect.RealtimeSuspend();
if ( result ) {
mRealtimeSuspendCount++;
}
return result;
}
bool Effect::RealtimeSuspend()
{
if (mClient)
{
if (mClient->RealtimeSuspend())
{
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount++;
mRealtimeSuspendLock.Leave();
return true;
}
return false;
}
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount++;
mRealtimeSuspendLock.Leave();
return mClient->RealtimeSuspend();
return true;
}
bool RealtimeEffectState::RealtimeResume()
{
auto result = mEffect.RealtimeResume();
if ( result ) {
mRealtimeSuspendCount--;
}
return result;
}
bool Effect::RealtimeResume()
{
if (mClient)
{
if (mClient->RealtimeResume())
{
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount--;
mRealtimeSuspendLock.Leave();
return true;
}
return false;
}
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount--;
mRealtimeSuspendLock.Leave();
return mClient->RealtimeResume();
return true;
}
@ -2306,7 +2297,7 @@ double Effect::CalcPreviewInputLength(double previewLength)
// RealtimeAddProcessor and RealtimeProcess use the same method of
// determining the current processor index, so updates to one should
// be reflected in the other.
bool Effect::RealtimeAddProcessor(int group, unsigned chans, float rate)
bool RealtimeEffectState::RealtimeAddProcessor(int group, unsigned chans, float rate)
{
auto ichans = chans;
auto ochans = chans;
@ -2322,13 +2313,16 @@ bool Effect::RealtimeAddProcessor(int group, unsigned chans, float rate)
// Remember the processor starting index
mGroupProcessor.push_back(mCurrentProcessor);
const auto numAudioIn = mEffect.GetAudioInCount();
const auto numAudioOut = mEffect.GetAudioOutCount();
// Call the client until we run out of input or output channels
while (ichans > 0 && ochans > 0)
{
// If we don't have enough input channels to accomodate the client's
// requirements, then we replicate the input channels until the
// client's needs are met.
if (ichans < mNumAudioIn)
if (ichans < numAudioIn)
{
// All input channels have been consumed
ichans = 0;
@ -2336,16 +2330,16 @@ bool Effect::RealtimeAddProcessor(int group, unsigned chans, float rate)
// Otherwise fullfil the client's needs with as many input channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ichans >= mNumAudioIn)
else if (ichans >= numAudioIn)
{
gchans = mNumAudioIn;
gchans = numAudioIn;
ichans -= gchans;
}
// If we don't have enough output channels to accomodate the client's
// requirements, then we provide all of the output channels and fulfill
// the client's needs with dummy buffers. These will just get tossed.
if (ochans < mNumAudioOut)
if (ochans < numAudioOut)
{
// All output channels have been consumed
ochans = 0;
@ -2353,13 +2347,13 @@ bool Effect::RealtimeAddProcessor(int group, unsigned chans, float rate)
// Otherwise fullfil the client's needs with as many output channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ochans >= mNumAudioOut)
else if (ochans >= numAudioOut)
{
ochans -= mNumAudioOut;
ochans -= numAudioOut;
}
// Add a NEW processor
RealtimeAddProcessor(gchans, rate);
mEffect.RealtimeAddProcessor(gchans, rate);
// Bump to next processor
mCurrentProcessor++;
@ -2371,7 +2365,7 @@ bool Effect::RealtimeAddProcessor(int group, unsigned chans, float rate)
// RealtimeAddProcessor and RealtimeProcess use the same method of
// determining the current processor group, so updates to one should
// be reflected in the other.
size_t Effect::RealtimeProcess(int group,
size_t RealtimeEffectState::RealtimeProcess(int group,
unsigned chans,
float **inbuf,
float **outbuf,
@ -2386,8 +2380,11 @@ size_t Effect::RealtimeProcess(int group,
// so if the number of channels we're curently processing are different
// than what the effect expects, then we use a few methods of satisfying
// the effects requirements.
float **clientIn = (float **) alloca(mNumAudioIn * sizeof(float *));
float **clientOut = (float **) alloca(mNumAudioOut * sizeof(float *));
const auto numAudioIn = mEffect.GetAudioInCount();
const auto numAudioOut = mEffect.GetAudioOutCount();
float **clientIn = (float **) alloca(numAudioIn * sizeof(float *));
float **clientOut = (float **) alloca(numAudioOut * sizeof(float *));
float *dummybuf = (float *) alloca(numSamples * sizeof(float));
decltype(numSamples) len = 0;
auto ichans = chans;
@ -2404,9 +2401,9 @@ size_t Effect::RealtimeProcess(int group,
// If we don't have enough input channels to accomodate the client's
// requirements, then we replicate the input channels until the
// client's needs are met.
if (ichans < mNumAudioIn)
if (ichans < numAudioIn)
{
for (size_t i = 0; i < mNumAudioIn; i++)
for (size_t i = 0; i < numAudioIn; i++)
{
if (indx == ichans)
{
@ -2421,10 +2418,10 @@ size_t Effect::RealtimeProcess(int group,
// Otherwise fullfil the client's needs with as many input channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ichans >= mNumAudioIn)
else if (ichans >= numAudioIn)
{
gchans = 0;
for (size_t i = 0; i < mNumAudioIn; i++, ichans--, gchans++)
for (size_t i = 0; i < numAudioIn; i++, ichans--, gchans++)
{
clientIn[i] = inbuf[indx++];
}
@ -2433,9 +2430,9 @@ size_t Effect::RealtimeProcess(int group,
// If we don't have enough output channels to accomodate the client's
// requirements, then we provide all of the output channels and fulfill
// the client's needs with dummy buffers. These will just get tossed.
if (ochans < mNumAudioOut)
if (ochans < numAudioOut)
{
for (size_t i = 0; i < mNumAudioOut; i++)
for (size_t i = 0; i < numAudioOut; i++)
{
if (i < ochans)
{
@ -2453,9 +2450,9 @@ size_t Effect::RealtimeProcess(int group,
// Otherwise fullfil the client's needs with as many output channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ochans >= mNumAudioOut)
else if (ochans >= numAudioOut)
{
for (size_t i = 0; i < mNumAudioOut; i++, ochans--)
for (size_t i = 0; i < numAudioOut; i++, ochans--)
{
clientOut[i] = outbuf[ondx++];
}
@ -2463,17 +2460,18 @@ size_t Effect::RealtimeProcess(int group,
// Finally call the plugin to process the block
len = 0;
for (decltype(numSamples) block = 0; block < numSamples; block += mBlockSize)
const auto blockSize = mEffect.GetBlockSize();
for (decltype(numSamples) block = 0; block < numSamples; block += blockSize)
{
auto cnt = std::min(numSamples - block, mBlockSize);
len += RealtimeProcess(processor, clientIn, clientOut, cnt);
auto cnt = std::min(numSamples - block, blockSize);
len += mEffect.RealtimeProcess(processor, clientIn, clientOut, cnt);
for (size_t i = 0 ; i < mNumAudioIn; i++)
for (size_t i = 0 ; i < numAudioIn; i++)
{
clientIn[i] += cnt;
}
for (size_t i = 0 ; i < mNumAudioOut; i++)
for (size_t i = 0 ; i < numAudioOut; i++)
{
clientOut[i] += cnt;
}
@ -2486,7 +2484,7 @@ size_t Effect::RealtimeProcess(int group,
return len;
}
bool Effect::IsRealtimeActive()
bool RealtimeEffectState::IsRealtimeActive()
{
return mRealtimeSuspendCount == 0;
}

View File

@ -16,6 +16,7 @@
#include "../Experimental.h"
#include <atomic>
#include <set>
#include <wx/defs.h>
@ -262,15 +263,6 @@ class AUDACITY_DLL_API Effect /* not final */ : public wxEvtHandler,
bool Delegate( Effect &delegate, wxWindow *parent, bool shouldPrompt);
// Realtime Effect Processing
/* not virtual */ bool RealtimeAddProcessor(int group, unsigned chans, float rate);
/* not virtual */ size_t RealtimeProcess(int group,
unsigned chans,
float **inbuf,
float **outbuf,
size_t numSamples);
/* not virtual */ bool IsRealtimeActive();
virtual bool IsHidden();
// Nonvirtual
@ -544,12 +536,6 @@ private:
size_t mBlockSize;
unsigned mNumChannels;
std::vector<int> mGroupProcessor;
int mCurrentProcessor;
wxCriticalSection mRealtimeSuspendLock;
int mRealtimeSuspendCount;
const static wxString kUserPresetIdent;
const static wxString kFactoryPresetIdent;
const static wxString kCurrentSettingsIdent;
@ -561,6 +547,29 @@ private:
friend class EffectPresetsDialog;
};
class RealtimeEffectState
{
public:
explicit RealtimeEffectState( Effect &effect );
Effect &GetEffect() const { return mEffect; }
bool RealtimeSuspend();
bool RealtimeResume();
bool RealtimeAddProcessor(int group, unsigned chans, float rate);
size_t RealtimeProcess(int group,
unsigned chans, float **inbuf, float **outbuf, size_t numSamples);
bool IsRealtimeActive();
private:
Effect &mEffect;
std::vector<int> mGroupProcessor;
int mCurrentProcessor;
std::atomic<int> mRealtimeSuspendCount{ 1 }; // Effects are initially suspended
};
// FIXME:
// FIXME: Remove this once all effects are using the NEW dialog

View File

@ -42,51 +42,35 @@ void RealtimeEffectManager::RealtimeSetEffects(const EffectArray & effects)
// Block RealtimeProcess()
RealtimeSuspend();
// Tell any effects no longer in the chain to clean up
for (auto e: mRealtimeEffects)
{
// Scan the NEW chain for the effect
for (auto e1: effects)
{
// Found it so we're done
if (e == e1)
{
e = NULL;
break;
decltype( mStates ) newStates;
auto begin = mStates.begin(), end = mStates.end();
for ( auto pEffect : effects ) {
auto found = std::find_if( begin, end,
[=]( const decltype( mStates )::value_type &state ){
return state && &state->GetEffect() == pEffect;
}
);
if ( found == end ) {
// Tell New effect to get ready
pEffect->RealtimeInitialize();
newStates.emplace_back(
std::make_unique< RealtimeEffectState >( *pEffect ) );
}
// Must not have been in the NEW chain, so tell it to cleanup
if (e && mRealtimeActive)
{
e->RealtimeFinalize();
else {
// Preserve state for effect that remains in the chain
newStates.emplace_back( std::move( *found ) );
}
}
// Tell any NEW effects to get ready
for (auto e : effects)
{
// Scan the old chain for the effect
for (auto e1 : mRealtimeEffects)
{
// Found it so tell effect to get ready
if (e == e1)
{
e = NULL;
break;
}
}
// Must not have been in the old chain, so tell it to initialize
if (e && mRealtimeActive)
{
e->RealtimeInitialize();
}
// Remaining states that were not moved need to clean up
for ( auto &state : mStates ) {
if ( state )
state->GetEffect().RealtimeFinalize();
}
// Get rid of the old chain
// And install the NEW one
mRealtimeEffects = effects;
mStates.swap( newStates );
// Allow RealtimeProcess() to, well, process
RealtimeResume();
@ -95,7 +79,7 @@ void RealtimeEffectManager::RealtimeSetEffects(const EffectArray & effects)
bool RealtimeEffectManager::RealtimeIsActive()
{
return mRealtimeEffects.size() != 0;
return mStates.size() != 0;
}
bool RealtimeEffectManager::RealtimeIsSuspended()
@ -108,6 +92,10 @@ void RealtimeEffectManager::RealtimeAddEffect(Effect *effect)
// Block RealtimeProcess()
RealtimeSuspend();
// Add to list of active effects
mStates.emplace_back( std::make_unique< RealtimeEffectState >( *effect ) );
auto &state = mStates.back();
// Initialize effect if realtime is already active
if (mRealtimeActive)
{
@ -117,12 +105,10 @@ void RealtimeEffectManager::RealtimeAddEffect(Effect *effect)
// Add the required processors
for (size_t i = 0, cnt = mRealtimeChans.size(); i < cnt; i++)
{
effect->RealtimeAddProcessor(i, mRealtimeChans[i], mRealtimeRates[i]);
state->RealtimeAddProcessor(i, mRealtimeChans[i], mRealtimeRates[i]);
}
}
// Add to list of active effects
mRealtimeEffects.push_back(effect);
// Allow RealtimeProcess() to, well, process
RealtimeResume();
@ -140,10 +126,14 @@ void RealtimeEffectManager::RealtimeRemoveEffect(Effect *effect)
}
// Remove from list of active effects
auto end = mRealtimeEffects.end();
auto found = std::find(mRealtimeEffects.begin(), end, effect);
auto end = mStates.end();
auto found = std::find_if( mStates.begin(), end,
[&](const decltype(mStates)::value_type &state){
return &state->GetEffect() == effect;
}
);
if (found != end)
mRealtimeEffects.erase(found);
mStates.erase(found);
// Allow RealtimeProcess() to, well, process
RealtimeResume();
@ -163,9 +153,9 @@ void RealtimeEffectManager::RealtimeInitialize(double rate)
mRealtimeActive = true;
// Tell each effect to get ready for action
for (auto e : mRealtimeEffects) {
e->SetSampleRate(rate);
e->RealtimeInitialize();
for (auto &state : mStates) {
state->GetEffect().SetSampleRate(rate);
state->GetEffect().RealtimeInitialize();
}
// Get things moving
@ -174,8 +164,8 @@ void RealtimeEffectManager::RealtimeInitialize(double rate)
void RealtimeEffectManager::RealtimeAddProcessor(int group, unsigned chans, float rate)
{
for (auto e : mRealtimeEffects)
e->RealtimeAddProcessor(group, chans, rate);
for (auto &state : mStates)
state->RealtimeAddProcessor(group, chans, rate);
mRealtimeChans.push_back(chans);
mRealtimeRates.push_back(rate);
@ -190,8 +180,8 @@ void RealtimeEffectManager::RealtimeFinalize()
mRealtimeLatency = 0;
// Tell each effect to clean up as well
for (auto e : mRealtimeEffects)
e->RealtimeFinalize();
for (auto &state : mStates)
state->GetEffect().RealtimeFinalize();
// Reset processor parameters
mRealtimeChans.clear();
@ -216,8 +206,8 @@ void RealtimeEffectManager::RealtimeSuspend()
mRealtimeSuspended = true;
// And make sure the effects don't either
for (auto e : mRealtimeEffects)
e->RealtimeSuspend();
for (auto &state : mStates)
state->RealtimeSuspend();
mRealtimeLock.Leave();
}
@ -234,8 +224,8 @@ void RealtimeEffectManager::RealtimeResume()
}
// Tell the effects to get ready for more action
for (auto e : mRealtimeEffects)
e->RealtimeResume();
for (auto &state : mStates)
state->RealtimeResume();
// And we should too
mRealtimeSuspended = false;
@ -255,10 +245,10 @@ void RealtimeEffectManager::RealtimeProcessStart()
// have been suspended.
if (!mRealtimeSuspended)
{
for (auto e : mRealtimeEffects)
for (auto &state : mStates)
{
if (e->IsRealtimeActive())
e->RealtimeProcessStart();
if (state->IsRealtimeActive())
state->GetEffect().RealtimeProcessStart();
}
}
@ -275,7 +265,7 @@ size_t RealtimeEffectManager::RealtimeProcess(int group, unsigned chans, float *
// Can be suspended because of the audio stream being paused or because effects
// have been suspended, so allow the samples to pass as-is.
if (mRealtimeSuspended || mRealtimeEffects.empty())
if (mRealtimeSuspended || mStates.empty())
{
mRealtimeLock.Leave();
return numSamples;
@ -300,11 +290,11 @@ size_t RealtimeEffectManager::RealtimeProcess(int group, unsigned chans, float *
// Now call each effect in the chain while swapping buffer pointers to feed the
// output of one effect as the input to the next effect
size_t called = 0;
for (auto e : mRealtimeEffects)
for (auto &state : mStates)
{
if (e->IsRealtimeActive())
if (state->IsRealtimeActive())
{
e->RealtimeProcess(group, chans, ibuf, obuf, numSamples);
state->RealtimeProcess(group, chans, ibuf, obuf, numSamples);
called++;
}
@ -352,10 +342,10 @@ void RealtimeEffectManager::RealtimeProcessEnd()
// have been suspended.
if (!mRealtimeSuspended)
{
for (auto e : mRealtimeEffects)
for (auto &state : mStates)
{
if (e->IsRealtimeActive())
e->RealtimeProcessEnd();
if (state->IsRealtimeActive())
state->GetEffect().RealtimeProcessEnd();
}
}

View File

@ -17,6 +17,7 @@
class Effect;
using EffectArray = std::vector <Effect*> ;
class RealtimeEffectState;
class AUDACITY_DLL_API RealtimeEffectManager final
{
@ -46,7 +47,7 @@ private:
~RealtimeEffectManager();
wxCriticalSection mRealtimeLock;
EffectArray mRealtimeEffects;
std::vector< std::unique_ptr<RealtimeEffectState> > mStates;
int mRealtimeLatency;
bool mRealtimeSuspended;
bool mRealtimeActive;