audacia/src/effects/EffectManager.cpp

775 lines
21 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
EffectManager.cpp
Audacity(R) is copyright (c) 1999-2008 Audacity Team.
License: GPL v2. See License.txt.
**********************************************************************/
#include "../Audacity.h"
#include <wx/msgdlg.h>
#include <wx/stopwatch.h>
#include <wx/tokenzr.h>
#include "../Experimental.h"
#if defined(EXPERIMENTAL_EFFECTS_RACK)
#include "EffectRack.h"
#endif
#include "EffectManager.h"
// ============================================================================
//
// Create singleton and return reference
//
// (Thread-safe...no active threading during construction or after destruction)
// ============================================================================
EffectManager & EffectManager::Get()
{
static EffectManager em;
return em;
}
EffectManager::EffectManager()
{
#ifdef EFFECT_CATEGORIES
mCategories = new CategoryMap();
mRootCategories = new CategorySet();
mUnsorted = new EffectSet();
// Create effect category graph. These categories and relationships
// are taken from revision 2 of lv2.ttl, loaders for other plugin systems
// (such as LADSPA/LRDF) should map their categories to these ones when
// applicable. Individual LADSPA/LRDF and LV2 plugins can add new
// categories and make them subcategories of the existing ones, but not
// add subcategory relationships between these categories.
//
// We need some persistent, global identifiers for categories - LRDF
// and LV2 uses URI strings so we do that too. The URIs here are the
// same ones as in lv2.ttl. Category identifiers in other plugin systems
// must be mapped to URIs by their loaders.
#define LV2PREFIX "http://lv2plug.in/ns/lv2core#"
typedef EffectCategory* CatPtr;
CatPtr gen = AddCategory(wxT(LV2PREFIX) wxT("GeneratorPlugin"),
_("Generator"));
CatPtr inst = AddCategory(wxT(LV2PREFIX) wxT("InstrumentPlugin"),
/* i18n-hint: (noun).*/
_("Instrument"));
CatPtr osc = AddCategory(wxT(LV2PREFIX) wxT("OscillatorPlugin"),
_("Oscillator"));
CatPtr util = AddCategory(wxT(LV2PREFIX) wxT("UtilityPlugin"),
_("Utility"));
CatPtr conv = AddCategory(wxT(LV2PREFIX) wxT("ConverterPlugin"),
_("Converter"));
CatPtr anal = AddCategory(wxT(LV2PREFIX) wxT("AnalyserPlugin"),
_("Analyser"));
CatPtr mix = AddCategory(wxT(LV2PREFIX) wxT("MixerPlugin"),
_("Mixer"));
CatPtr sim = AddCategory(wxT(LV2PREFIX) wxT("SimulatorPlugin"),
_("Simulator"));
CatPtr del = AddCategory(wxT(LV2PREFIX) wxT("DelayPlugin"),
_("Delay"));
CatPtr mod = AddCategory(wxT(LV2PREFIX) wxT("ModulatorPlugin"),
_("Modulator"));
CatPtr rev = AddCategory(wxT(LV2PREFIX) wxT("ReverbPlugin"),
_("Reverb"));
CatPtr phas = AddCategory(wxT(LV2PREFIX) wxT("PhaserPlugin"),
_("Phaser"));
CatPtr flng = AddCategory(wxT(LV2PREFIX) wxT("FlangerPlugin"),
_("Flanger"));
CatPtr chor = AddCategory(wxT(LV2PREFIX) wxT("ChorusPlugin"),
_("Chorus"));
CatPtr flt = AddCategory(wxT(LV2PREFIX) wxT("FilterPlugin"),
_("Filter"));
CatPtr lp = AddCategory(wxT(LV2PREFIX) wxT("LowpassPlugin"),
_("Lowpass"));
CatPtr bp = AddCategory(wxT(LV2PREFIX) wxT("BandpassPlugin"),
_("Bandpass"));
CatPtr hp = AddCategory(wxT(LV2PREFIX) wxT("HighpassPlugin"),
_("Highpass"));
CatPtr comb = AddCategory(wxT(LV2PREFIX) wxT("CombPlugin"),
_("Comb"));
CatPtr alp = AddCategory(wxT(LV2PREFIX) wxT("AllpassPlugin"),
_("Allpass"));
CatPtr eq = AddCategory(wxT(LV2PREFIX) wxT("EQPlugin"),
_("Equaliser"));
CatPtr peq = AddCategory(wxT(LV2PREFIX) wxT("ParaEQPlugin"),
_("Parametric"));
CatPtr meq = AddCategory(wxT(LV2PREFIX) wxT("MultiEQPlugin"),
_("Multiband"));
CatPtr spec = AddCategory(wxT(LV2PREFIX) wxT("SpectralPlugin"),
_("Spectral Processor"));
CatPtr ptch = AddCategory(wxT(LV2PREFIX) wxT("PitchPlugin"),
_("Pitch Shifter"));
CatPtr amp = AddCategory(wxT(LV2PREFIX) wxT("AmplifierPlugin"),
_("Amplifier"));
CatPtr dist = AddCategory(wxT(LV2PREFIX) wxT("DistortionPlugin"),
_("Distortion"));
CatPtr shp = AddCategory(wxT(LV2PREFIX) wxT("WaveshaperPlugin"),
_("Waveshaper"));
CatPtr dyn = AddCategory(wxT(LV2PREFIX) wxT("DynamicsPlugin"),
_("Dynamics Processor"));
CatPtr cmp = AddCategory(wxT(LV2PREFIX) wxT("CompressorPlugin"),
_("Compressor"));
CatPtr exp = AddCategory(wxT(LV2PREFIX) wxT("ExpanderPlugin"),
_("Expander"));
CatPtr lim = AddCategory(wxT(LV2PREFIX) wxT("LimiterPlugin"),
_("Limiter"));
CatPtr gate = AddCategory(wxT(LV2PREFIX) wxT("GatePlugin"),
_("Gate"));
AddCategoryParent(inst, gen);
AddCategoryParent(osc, gen);
AddCategoryParent(conv, util);
AddCategoryParent(anal, util);
AddCategoryParent(mix, util);
AddCategoryParent(rev, sim);
AddCategoryParent(rev, del);
AddCategoryParent(phas, mod);
AddCategoryParent(flng, mod);
AddCategoryParent(chor, mod);
AddCategoryParent(lp, flt);
AddCategoryParent(bp, flt);
AddCategoryParent(hp, flt);
AddCategoryParent(comb, flt);
AddCategoryParent(alp, flt);
AddCategoryParent(eq, flt);
AddCategoryParent(peq, eq);
AddCategoryParent(meq, eq);
AddCategoryParent(ptch, spec);
AddCategoryParent(shp, dist);
AddCategoryParent(cmp, dyn);
AddCategoryParent(exp, dyn);
AddCategoryParent(lim, dyn);
AddCategoryParent(gate, dyn);
// We also add a couple of categories for internal use. These are not
// in lv2.ttl.
#define ATEAM "http://audacityteam.org/namespace#"
CatPtr nrm = AddCategory(wxT(ATEAM) wxT("NoiseRemoval"),
_("Noise Removal"));
CatPtr pnt = AddCategory(wxT(ATEAM) wxT("PitchAndTempo"),
_("Pitch and Tempo"));
CatPtr tim = AddCategory(wxT(ATEAM) wxT("TimelineChanger"),
_("Timeline Changer"));
CatPtr aTim = AddCategory(wxT(ATEAM) wxT("TimeAnalyser"),
_("Time"));
CatPtr onst = AddCategory(wxT(ATEAM) wxT("OnsetDetector"),
_("Onsets"));
AddCategoryParent(nrm, util);
AddCategoryParent(tim, util);
AddCategoryParent(aTim, anal);
AddCategoryParent(onst, aTim);
// We freeze the internal subcategory relations between the categories
// added so far so LADSPA/LRDF or other category systems don't ruin
// our hierarchy.
FreezeCategories();
#endif
#if defined(EXPERIMENTAL_REALTIME_EFFECTS)
mRealtimeLock.Enter();
mRealtimeEffects = NULL;
mRealtimeCount = 0;
mRealtimeActive = false;
mRealtimeSuspended = true;
mRealtimeLatency = 0;
mRealtimeLock.Leave();
#endif
#if defined(EXPERIMENTAL_EFFECTS_RACK)
mRack = NULL;
#endif
}
EffectManager::~EffectManager()
{
#ifdef EFFECT_CATEGORIES
CategoryMap::iterator i;
for (i = mCategories->begin(); i != mCategories->end(); ++i)
delete i->second;
delete mUnsorted;
delete mRootCategories;
delete mCategories;
#endif
#if defined(EXPERIMENTAL_REALTIME_EFFECTS)
if (mRealtimeEffects)
{
delete [] mRealtimeEffects;
}
#endif
#if defined(EXPERIMENTAL_EFFECTS_RACK)
// wxWidgets has already destroyed the rack since it was derived from wxFrame. So
// no need to delete it here.
#endif
EffectMap::iterator iter = mEffects.begin();
while (iter != mEffects.end())
{
delete iter->second;
iter++;
}
}
void EffectManager::RegisterEffect(IdentInterface *p, Effect *f, int NewFlags)
{
f->SetEffectID(mNumEffects++);
if( NewFlags != 0)
{
f->SetEffectFlags( NewFlags );
}
PluginManager::Get().RegisterEffectPlugin(p, f);
mEffects[f->GetID()] = f;
}
void EffectManager::RegisterEffect(Effect *f, int NewFlags)
{
f->SetEffectID(mNumEffects++);
if( NewFlags != 0)
{
f->SetEffectFlags( NewFlags );
}
// This will go away after all effects have been converted
mEffects[PluginManager::Get().RegisterLegacyEffectPlugin(f)] = f;
#ifdef EFFECT_CATEGORIES
// Add the effect in the right categories
std::set<wxString> catUris = f->GetEffectCategories();
bool oneValid = false;
std::set<wxString>::const_iterator iter;
for (iter = catUris.begin(); iter != catUris.end(); ++iter) {
EffectCategory* cat = LookupCategory(*iter);
if (cat != 0) {
cat->AddEffect(f);
oneValid = true;
}
}
if (!oneValid)
mUnsorted->insert(f);
#endif
}
void EffectManager::UnregisterEffects()
{
#ifdef EFFECT_CATEGORIES
mUnsorted->clear();
CategoryMap::iterator iter;
for (iter = mCategories->begin(); iter != mCategories->end(); ++iter)
iter->second->mEffects.clear();
#endif
}
bool EffectManager::DoEffect(const PluginID & ID,
wxWindow *parent,
int flags,
double projectRate,
TrackList *list,
TrackFactory *factory,
SelectedRegion *selectedRegion,
wxString params)
{
Effect *effect = GetEffect(ID);
if (!effect)
{
return false;
}
#if defined(EXPERIMENTAL_REALTIME_EFFECTS) && defined(EXPERIMENTAL_EFFECTS_RACK)
if (effect->SupportsRealtime())
{
GetRack()->Add(effect);
}
#endif
return effect->DoEffect(parent,
flags,
projectRate,
list,
factory,
selectedRegion,
params);
}
wxString EffectManager::GetEffectName(const PluginID & ID)
{
return PluginManager::Get().GetName(ID);
}
wxString EffectManager::GetEffectIdentifier(const PluginID & ID)
{
wxString name = (PluginManager::Get().GetName(ID));
// Get rid of leading and trailing white space
name.Trim(true).Trim(false);
if (name == wxEmptyString)
{
return name;
}
wxStringTokenizer st(name, wxT(" "));
wxString id;
// CamelCase the name
while (st.HasMoreTokens())
{
wxString tok = st.GetNextToken();
id += tok.Left(1).MakeUpper() + tok.Mid(1).MakeLower();
}
return id;
}
wxString EffectManager::GetEffectDescription(const PluginID & ID)
{
Effect *effect = GetEffect(ID);
if (effect)
{
return effect->GetEffectDescription();
}
return wxEmptyString;
}
bool EffectManager::SupportsAutomation(const PluginID & ID)
{
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
if (plug)
{
return plug->IsEffectAutomatable();
}
return false;
}
wxString EffectManager::GetEffectParameters(const PluginID & ID)
{
Effect *effect = GetEffect(ID);
if (effect)
{
wxString parms;
effect->GetAutomationParameters(parms);
return parms;
}
return wxEmptyString;
}
bool EffectManager::SetEffectParameters(const PluginID & ID, const wxString & params)
{
Effect *effect = GetEffect(ID);
if (effect)
{
return effect->SetAutomationParameters(params);
}
return false;
}
bool EffectManager::PromptUser(const PluginID & ID, wxWindow *parent)
{
Effect *effect = GetEffect(ID);
bool result = false;
if (effect)
{
result = effect->PromptUser(parent, true);
}
return result;
}
#if defined(EXPERIMENTAL_EFFECTS_RACK)
EffectRack *EffectManager::GetRack()
{
if (!mRack)
{
mRack = new EffectRack();
mRack->CenterOnParent();
}
return mRack;
}
void EffectManager::ShowRack()
{
GetRack()->Show(!GetRack()->IsShown());
}
#endif
#if defined(EXPERIMENTAL_REALTIME_EFFECTS)
void EffectManager::RealtimeSetEffects(const EffectArray & effects)
{
int newCount = (int) effects.GetCount();
Effect **newEffects = new Effect *[newCount];
for (int i = 0; i < newCount; i++)
{
newEffects[i] = effects[i];
}
// Block RealtimeProcess()
RealtimeSuspend();
// Tell any effects no longer in the chain to clean up
for (int i = 0; i < mRealtimeCount; i++)
{
Effect *e = mRealtimeEffects[i];
// Scan the new chain for the effect
for (int j = 0; j < newCount; j++)
{
// Found it so we're done
if (e == newEffects[j])
{
e = NULL;
break;
}
}
// Must not have been in the new chain, so tell it to cleanup
if (e && mRealtimeActive)
{
e->RealtimeFinalize();
}
}
// Tell any new effects to get ready
for (int i = 0; i < newCount; i++)
{
Effect *e = newEffects[i];
// Scan the old chain for the effect
for (int j = 0; j < mRealtimeCount; j++)
{
// Found it so tell effect to get ready
if (e == mRealtimeEffects[j])
{
e = NULL;
}
}
// Must not have been in the old chain, so tell it to initialize
if (e && mRealtimeActive)
{
e->RealtimeInitialize();
}
}
// Get rid of the old chain
if (mRealtimeEffects)
{
delete [] mRealtimeEffects;
}
// And install the new one
mRealtimeEffects = newEffects;
mRealtimeCount = newCount;
// Allow RealtimeProcess() to, well, process
RealtimeResume();
}
#endif
void EffectManager::RealtimeInitialize()
{
// No need to do anything if there are no effects
if (!mRealtimeCount)
{
return;
}
// The audio thread should not be running yet, but protect anyway
RealtimeSuspend();
// RealtimeSetEffects() 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 (int i = 0; i < mRealtimeCount; i++)
{
mRealtimeEffects[i]->RealtimeInitialize();
}
// Get things moving
RealtimeResume();
}
void EffectManager::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 (int i = 0; i < mRealtimeCount; i++)
{
mRealtimeEffects[i]->RealtimeFinalize();
}
mRealtimeActive = false;
}
void EffectManager::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 (int i = 0; i < mRealtimeCount; i++)
{
mRealtimeEffects[i]->RealtimeSuspend();
}
mRealtimeLock.Leave();
}
void EffectManager::RealtimeResume()
{
mRealtimeLock.Enter();
// Already running...bail
if (!mRealtimeSuspended)
{
mRealtimeLock.Leave();
return;
}
// Tell the effects to get ready for more action
for (int i = 0; i < mRealtimeCount; i++)
{
mRealtimeEffects[i]->RealtimeResume();
}
// And we should too
mRealtimeSuspended = false;
mRealtimeLock.Leave();
}
//
// This will be called in a different thread than the main GUI thread.
//
sampleCount EffectManager::RealtimeProcess(int group, int chans, float rate, float **buffers, sampleCount 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 || mRealtimeCount == 0)
{
mRealtimeLock.Leave();
return numSamples;
}
// Remember when we started so we can calculate the amount of latency we
// are introducing
wxMilliClock_t start = wxGetLocalTimeMillis();
// 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 (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
for (int i = 0; i < mRealtimeCount; i++)
{
mRealtimeEffects[i]->RealtimeProcess(group, chans, rate, ibuf, obuf, numSamples);
for (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 is odd.
if (mRealtimeCount & 1)
{
for (int i = 0; i < chans; i++)
{
memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
}
}
// Remember the latency
mRealtimeLatency = (int) (wxGetLocalTimeMillis() - start).GetValue();
mRealtimeLock.Leave();
//
// This is wrong...needs to handle tails
//
return numSamples;
}
int EffectManager::GetRealtimeLatency()
{
return mRealtimeLatency;
}
Effect *EffectManager::GetEffect(const PluginID & ID)
{
Effect *effect;
// TODO: This is temporary and should be redone when all effects are converted
if (mEffects.find(ID) == mEffects.end())
{
effect = new Effect();
if (effect)
{
// This will instantiate the effect client if it hasn't already been done
EffectClientInterface *client = dynamic_cast<EffectClientInterface *>(PluginManager::Get().GetInstance(ID));
if (client && effect->Startup(client))
{
effect->SetEffectID(mNumEffects++);
mEffects[ID] = effect;
return effect;
}
delete effect;
}
wxMessageBox(wxString::Format(_("Attempting to initialize the following effect failed:\n\n%s\n\nMore information may be available in Help->Show Log"),
PluginManager::Get().GetName(ID).c_str()),
_("Effect failed to initialize"));
return NULL;
}
return mEffects[ID];
}
const PluginID & EffectManager::GetEffectByIdentifier(const wxString & strTarget)
{
static PluginID empty;
if (strTarget == wxEmptyString) // set GetEffectIdentifier to wxT("") to not show an effect in Batch mode
{
return empty;
}
PluginManager & pm = PluginManager::Get();
const PluginDescriptor *plug = pm.GetFirstPlugin(PluginTypeEffect);
while (plug)
{
if (GetEffectIdentifier(plug->GetID()).IsSameAs(strTarget))
{
return plug->GetID();
}
plug = pm.GetNextPlugin(PluginTypeEffect);
}
return empty;;
}
#ifdef EFFECT_CATEGORIES
EffectCategory* EffectManager::AddCategory(const wxString& URI,
const wxString& name) {
CategoryMap::const_iterator iter = mCategories->find(URI);
if (iter != mCategories->end())
return iter->second;
EffectCategory* cat = new EffectCategory(URI, name);
mCategories->insert(std::make_pair(URI, cat));
mRootCategories->insert(cat);
return cat;
}
EffectCategory* EffectManager::LookupCategory(const wxString& URI) {
CategoryMap::const_iterator iter = mCategories->find(URI);
if (iter != mCategories->end())
return iter->second;
return 0;
}
bool EffectManager::AddCategoryParent(EffectCategory* child,
EffectCategory* parent) {
bool result = child->AddParent(parent);
if (!result)
return false;
CategorySet::iterator iter = mRootCategories->find(child);
if (iter != mRootCategories->end())
mRootCategories->erase(iter);
return true;
}
void EffectManager::FreezeCategories() {
CategoryMap::iterator iter;
for (iter = mCategories->begin(); iter != mCategories->end(); ++iter)
iter->second->FreezeParents();
}
const CategorySet& EffectManager::GetRootCategories() const {
return *mRootCategories;
}
EffectSet EffectManager::GetUnsortedEffects(int flags) const {
if (flags == ALL_EFFECTS)
return *mUnsorted;
EffectSet result;
EffectSet::const_iterator iter;
for (iter = mUnsorted->begin(); iter != mUnsorted->end(); ++iter) {
int g = (*iter)->GetEffectFlags();
if ((flags & g) == g)
result.insert(*iter);
}
return result;
}
#endif