audacia/src/effects/RealtimeEffectManager.cpp

624 lines
17 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
RealtimeEffectManager.cpp
Paul Licameli split from EffectManager.cpp
**********************************************************************/
#include "RealtimeEffectManager.h"
#include "audacity/EffectInterface.h"
#include <memory>
#include <atomic>
#include <wx/time.h>
class RealtimeEffectState
{
public:
explicit RealtimeEffectState( EffectClientInterface &effect );
EffectClientInterface &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:
EffectClientInterface &mEffect;
std::vector<int> mGroupProcessor;
int mCurrentProcessor;
std::atomic<int> mRealtimeSuspendCount{ 1 }; // Effects are initially suspended
};
RealtimeEffectManager & RealtimeEffectManager::Get()
{
static RealtimeEffectManager rem;
return rem;
}
RealtimeEffectManager::RealtimeEffectManager()
{
mRealtimeLock.Enter();
mRealtimeActive = false;
mRealtimeSuspended = true;
mRealtimeLatency = 0;
mRealtimeLock.Leave();
}
RealtimeEffectManager::~RealtimeEffectManager()
{
}
#if defined(EXPERIMENTAL_EFFECTS_RACK)
void RealtimeEffectManager::RealtimeSetEffects(const EffectArray & effects)
{
// Block RealtimeProcess()
RealtimeSuspend();
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 ) );
}
else {
// Preserve state for effect that remains in the chain
newStates.emplace_back( std::move( *found ) );
}
}
// 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
mStates.swap( newStates );
// Allow RealtimeProcess() to, well, process
RealtimeResume();
}
#endif
bool RealtimeEffectManager::RealtimeIsActive()
{
return mStates.size() != 0;
}
bool RealtimeEffectManager::RealtimeIsSuspended()
{
return mRealtimeSuspended;
}
void RealtimeEffectManager::RealtimeAddEffect(EffectClientInterface *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)
{
// Initialize realtime processing
effect->RealtimeInitialize();
// Add the required processors
for (size_t i = 0, cnt = mRealtimeChans.size(); i < cnt; i++)
{
state->RealtimeAddProcessor(i, mRealtimeChans[i], mRealtimeRates[i]);
}
}
// Allow RealtimeProcess() to, well, process
RealtimeResume();
}
void RealtimeEffectManager::RealtimeRemoveEffect(EffectClientInterface *effect)
{
// Block RealtimeProcess()
RealtimeSuspend();
if (mRealtimeActive)
{
// Cleanup realtime processing
effect->RealtimeFinalize();
}
// Remove from list of active effects
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)
mStates.erase(found);
// Allow RealtimeProcess() to, well, process
RealtimeResume();
}
void RealtimeEffectManager::RealtimeInitialize(double rate)
{
// The audio thread should not be running yet, but protect anyway
RealtimeSuspend();
// (Re)Set processor parameters
mRealtimeChans.clear();
mRealtimeRates.clear();
// RealtimeAdd/RemoveEffect() needs to know when we're active so it can
// initialize newly added effects
mRealtimeActive = true;
// Tell each effect to get ready for action
for (auto &state : mStates) {
state->GetEffect().SetSampleRate(rate);
state->GetEffect().RealtimeInitialize();
}
// Get things moving
RealtimeResume();
}
void RealtimeEffectManager::RealtimeAddProcessor(int group, unsigned chans, float rate)
{
for (auto &state : mStates)
state->RealtimeAddProcessor(group, chans, rate);
mRealtimeChans.push_back(chans);
mRealtimeRates.push_back(rate);
}
void RealtimeEffectManager::RealtimeFinalize()
{
// Make sure nothing is going on
RealtimeSuspend();
// It is now safe to clean up
mRealtimeLatency = 0;
// Tell each effect to clean up as well
for (auto &state : mStates)
state->GetEffect().RealtimeFinalize();
// Reset processor parameters
mRealtimeChans.clear();
mRealtimeRates.clear();
// No longer active
mRealtimeActive = false;
}
void RealtimeEffectManager::RealtimeSuspend()
{
mRealtimeLock.Enter();
// Already suspended...bail
if (mRealtimeSuspended)
{
mRealtimeLock.Leave();
return;
}
// Show that we aren't going to be doing anything
mRealtimeSuspended = true;
// And make sure the effects don't either
for (auto &state : mStates)
state->RealtimeSuspend();
mRealtimeLock.Leave();
}
void RealtimeEffectManager::RealtimeSuspendOne( EffectClientInterface &effect )
{
auto begin = mStates.begin(), end = mStates.end();
auto found = std::find_if( begin, end,
[&effect]( const decltype( mStates )::value_type &state ){
return state && &state->GetEffect() == &effect;
}
);
if ( found != end )
(*found)->RealtimeSuspend();
}
void RealtimeEffectManager::RealtimeResume()
{
mRealtimeLock.Enter();
// Already running...bail
if (!mRealtimeSuspended)
{
mRealtimeLock.Leave();
return;
}
// Tell the effects to get ready for more action
for (auto &state : mStates)
state->RealtimeResume();
// And we should too
mRealtimeSuspended = false;
mRealtimeLock.Leave();
}
void RealtimeEffectManager::RealtimeResumeOne( EffectClientInterface &effect )
{
auto begin = mStates.begin(), end = mStates.end();
auto found = std::find_if( begin, end,
[&effect]( const decltype( mStates )::value_type &state ){
return state && &state->GetEffect() == &effect;
}
);
if ( found != end )
(*found)->RealtimeResume();
}
//
// This will be called in a different thread than the main GUI thread.
//
void RealtimeEffectManager::RealtimeProcessStart()
{
// Protect ourselves from the main thread
mRealtimeLock.Enter();
// Can be suspended because of the audio stream being paused or because effects
// have been suspended.
if (!mRealtimeSuspended)
{
for (auto &state : mStates)
{
if (state->IsRealtimeActive())
state->GetEffect().RealtimeProcessStart();
}
}
mRealtimeLock.Leave();
}
//
// This will be called in a different thread than the main GUI thread.
//
size_t RealtimeEffectManager::RealtimeProcess(int group, unsigned chans, float **buffers, size_t numSamples)
{
// Protect ourselves from the main thread
mRealtimeLock.Enter();
// 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 || mStates.empty())
{
mRealtimeLock.Leave();
return numSamples;
}
// Remember when we started so we can calculate the amount of latency we
// are introducing
wxMilliClock_t start = wxGetUTCTimeMillis();
// Allocate the in/out buffer arrays
float **ibuf = (float **) alloca(chans * sizeof(float *));
float **obuf = (float **) alloca(chans * sizeof(float *));
// And populate the input with the buffers we've been given while allocating
// NEW output buffers
for (unsigned int i = 0; i < chans; i++)
{
ibuf[i] = buffers[i];
obuf[i] = (float *) alloca(numSamples * sizeof(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 &state : mStates)
{
if (state->IsRealtimeActive())
{
state->RealtimeProcess(group, chans, ibuf, obuf, numSamples);
called++;
}
for (unsigned int j = 0; j < chans; j++)
{
float *temp;
temp = ibuf[j];
ibuf[j] = obuf[j];
obuf[j] = temp;
}
}
// Once we're done, we might wind up with the last effect storing its results
// in the temporary buffers. If that's the case, we need to copy it over to
// the caller's buffers. This happens when the number of effects processed
// is odd.
if (called & 1)
{
for (unsigned int i = 0; i < chans; i++)
{
memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
}
}
// Remember the latency
mRealtimeLatency = (int) (wxGetUTCTimeMillis() - start).GetValue();
mRealtimeLock.Leave();
//
// This is wrong...needs to handle tails
//
return numSamples;
}
//
// This will be called in a different thread than the main GUI thread.
//
void RealtimeEffectManager::RealtimeProcessEnd()
{
// Protect ourselves from the main thread
mRealtimeLock.Enter();
// Can be suspended because of the audio stream being paused or because effects
// have been suspended.
if (!mRealtimeSuspended)
{
for (auto &state : mStates)
{
if (state->IsRealtimeActive())
state->GetEffect().RealtimeProcessEnd();
}
}
mRealtimeLock.Leave();
}
int RealtimeEffectManager::GetRealtimeLatency()
{
return mRealtimeLatency;
}
RealtimeEffectState::RealtimeEffectState( EffectClientInterface &effect )
: mEffect{ effect }
{
}
bool RealtimeEffectState::RealtimeSuspend()
{
auto result = mEffect.RealtimeSuspend();
if ( result ) {
mRealtimeSuspendCount++;
}
return result;
}
bool RealtimeEffectState::RealtimeResume()
{
auto result = mEffect.RealtimeResume();
if ( result ) {
mRealtimeSuspendCount--;
}
return result;
}
// RealtimeAddProcessor and RealtimeProcess use the same method of
// determining the current processor index, so updates to one should
// be reflected in the other.
bool RealtimeEffectState::RealtimeAddProcessor(int group, unsigned chans, float rate)
{
auto ichans = chans;
auto ochans = chans;
auto gchans = chans;
// Reset processor index
if (group == 0)
{
mCurrentProcessor = 0;
mGroupProcessor.clear();
}
// 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 accommodate the client's
// requirements, then we replicate the input channels until the
// client's needs are met.
if (ichans < numAudioIn)
{
// All input channels have been consumed
ichans = 0;
}
// Otherwise fulfill 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 >= numAudioIn)
{
gchans = numAudioIn;
ichans -= gchans;
}
// If we don't have enough output channels to accommodate 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 < numAudioOut)
{
// All output channels have been consumed
ochans = 0;
}
// Otherwise fulfill 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 >= numAudioOut)
{
ochans -= numAudioOut;
}
// Add a NEW processor
mEffect.RealtimeAddProcessor(gchans, rate);
// Bump to next processor
mCurrentProcessor++;
}
return true;
}
// 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 RealtimeEffectState::RealtimeProcess(int group,
unsigned chans,
float **inbuf,
float **outbuf,
size_t numSamples)
{
//
// The caller passes the number of channels to process and specifies
// the number of input and output buffers. There will always be the
// same number of output buffers as there are input buffers.
//
// Effects always require a certain number of input and output buffers,
// so if the number of channels we're currently processing are different
// than what the effect expects, then we use a few methods of satisfying
// the effects requirements.
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;
auto ochans = chans;
auto gchans = chans;
unsigned indx = 0;
unsigned ondx = 0;
int processor = mGroupProcessor[group];
// 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 accommodate the client's
// requirements, then we replicate the input channels until the
// client's needs are met.
if (ichans < numAudioIn)
{
for (size_t i = 0; i < numAudioIn; i++)
{
if (indx == ichans)
{
indx = 0;
}
clientIn[i] = inbuf[indx++];
}
// All input channels have been consumed
ichans = 0;
}
// Otherwise fulfill 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 >= numAudioIn)
{
gchans = 0;
for (size_t i = 0; i < numAudioIn; i++, ichans--, gchans++)
{
clientIn[i] = inbuf[indx++];
}
}
// If we don't have enough output channels to accommodate 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 < numAudioOut)
{
for (size_t i = 0; i < numAudioOut; i++)
{
if (i < ochans)
{
clientOut[i] = outbuf[i];
}
else
{
clientOut[i] = dummybuf;
}
}
// All output channels have been consumed
ochans = 0;
}
// Otherwise fulfill 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 >= numAudioOut)
{
for (size_t i = 0; i < numAudioOut; i++, ochans--)
{
clientOut[i] = outbuf[ondx++];
}
}
// Finally call the plugin to process the block
len = 0;
const auto blockSize = mEffect.GetBlockSize();
for (decltype(numSamples) block = 0; block < numSamples; block += blockSize)
{
auto cnt = std::min(numSamples - block, blockSize);
len += mEffect.RealtimeProcess(processor, clientIn, clientOut, cnt);
for (size_t i = 0 ; i < numAudioIn; i++)
{
clientIn[i] += cnt;
}
for (size_t i = 0 ; i < numAudioOut; i++)
{
clientOut[i] += cnt;
}
}
// Bump to next processor
processor++;
}
return len;
}
bool RealtimeEffectState::IsRealtimeActive()
{
return mRealtimeSuspendCount == 0;
}