1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-12-24 17:41:13 +01:00

Migrating the remaining effects

This brings the builtin, LV2, and VAMP effects inline with the
Audio Units, LADSPA, and VST effects.  All effects now share
a common UI.

This gives all effects (though not implemented for all):

User and factory preset capability
Preset import/export capability
Shared or private configuration options

Builtin effects can now be migrated to RTP, depending on algorithm.
LV2 effects now support graphical interfaces if the plugin supplies one.
Nyquist prompt enhanced to provide some features of the Nyquist Workbench.

It may not look like it, but this was a LOT of work, so trust me, there
WILL be problems and everything effect related should be suspect.  Keep
a sharp eye (or two) open.
This commit is contained in:
Leland Lucius
2015-04-16 22:53:42 -05:00
parent 40e6bcc56a
commit 8fbfa460c4
140 changed files with 17288 additions and 20367 deletions

View File

@@ -178,28 +178,10 @@ AudacityProjectCommandFunctor::AudacityProjectCommandFunctor(AudacityProject *pr
mPluginID = pluginID;
}
#if defined(EFFECT_CATEGORIES)
AudacityProjectCommandFunctor::AudacityProjectCommandFunctor(AudacityProject *project,
audCommandListFunction commandFunction,
wxArrayInt explicitIndices)
{
mProject = project;
mCommandFunction = NULL;
mCommandKeyFunction = NULL;
mCommandListFunction = commandFunction;
mCommandPluginFunction = NULL;
mExplicitIndices = explicitIndices;
}
#endif
void AudacityProjectCommandFunctor::operator()(int index, const wxEvent * evt)
{
if (mCommandPluginFunction)
(mProject->*(mCommandPluginFunction)) (mPluginID);
#if defined(EFFECT_CATEGORIES)
else if (mCommandListFunction && mExplicitIndices.GetCount() > 0)
(mProject->*(mCommandListFunction)) (mExplicitIndices[index]);
#endif
(mProject->*(mCommandPluginFunction)) (mPluginID, AudacityProject::OnEffectFlags::kNone);
else if (mCommandListFunction)
(mProject->*(mCommandListFunction)) (index);
else if (mCommandKeyFunction)
@@ -992,44 +974,10 @@ void AudacityProject::CreateMenusAndCommands()
c->BeginMenu(_("&Generate"));
c->SetDefaultFlags(AudioIONotBusyFlag, AudioIONotBusyFlag);
#ifndef EFFECT_CATEGORIES
PopulateEffectsMenu(c,
EffectTypeGenerate,
AudioIONotBusyFlag,
AudioIONotBusyFlag);
#else
int flags;
flags = INSERT_EFFECT | BUILTIN_EFFECT | PLUGIN_EFFECT;
EffectCategory* ac =
em.LookupCategory(wxT("http://lv2plug.in/ns/lv2core#GeneratorPlugin"));
CategorySet roots = ac->GetSubCategories();
EffectSet generators = ac->GetEffects();
EffectSet topLevel = CreateEffectSubmenus(c, roots, flags, 0);
std::copy(generators.begin(), generators.end(),
std::insert_iterator<EffectSet>(topLevel, topLevel.begin()));
AddEffectsToMenu(c, topLevel);
// Add all uncategorised effects in a special submenu
EffectSet unsorted =
em.GetUnsortedEffects(flags);
if (unsorted.size() > 0) {
c->AddSeparator();
c->BeginSubMenu(_("Unsorted"));
names.Clear();
indices.Clear();
EffectSet::const_iterator iter;
for (iter = unsorted.begin(); iter != unsorted.end(); ++iter) {
names.Add((*iter)->GetEffectName());
indices.Add((*iter)->GetEffectID());
}
c->AddItemList(wxT("Generate"), names,
FNI(OnProcessAny, indices), true);
c->EndSubMenu();
}
#endif // EFFECT_CATEGORIES
c->EndMenu();
@@ -1040,7 +988,7 @@ void AudacityProject::CreateMenusAndCommands()
c->BeginMenu(_("Effe&ct"));
wxString buildMenuLabel;
if (mLastEffectType != 0) {
if (!mLastEffect.IsEmpty()) {
buildMenuLabel.Printf(_("Repeat %s"),
EffectManager::Get().GetEffectName(mLastEffect).c_str());
}
@@ -1053,40 +1001,10 @@ void AudacityProject::CreateMenusAndCommands()
c->AddSeparator();
// this is really ugly but we need to keep all the old code to get any
// effects at all in the menu when EFFECT_CATEGORIES is undefined
#ifndef EFFECT_CATEGORIES
PopulateEffectsMenu(c,
EffectTypeProcess,
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
TracksExistFlag | IsRealtimeNotActiveFlag);
#else
int flags = PROCESS_EFFECT | BUILTIN_EFFECT | PLUGIN_EFFECT | ADVANCED_EFFECT;
// The categories form a DAG, so we start at the roots (the categories
// without incoming links)
CategorySet roots = em.GetRootCategories();
EffectSet topLevel = CreateEffectSubmenus(c, roots, flags, 0);
AddEffectsToMenu(c, topLevel);
// Add all uncategorised effects in a special submenu
EffectSet unsorted =
em.GetUnsortedEffects(flags);
if (unsorted.size() > 0) {
c->AddSeparator();
c->BeginSubMenu(_("Unsorted"));
names.Clear();
indices.Clear();
EffectSet::const_iterator iter;
for (iter = unsorted.begin(); iter != unsorted.end(); ++iter) {
names.Add((*iter)->GetEffectName());
indices.Add((*iter)->GetEffectID());
}
c->AddItemList(wxT("Effect"), names, FNI(OnProcessAny, indices), true);
c->EndSubMenu();
}
#endif
c->EndMenu();
@@ -1103,42 +1021,10 @@ void AudacityProject::CreateMenusAndCommands()
AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag,
AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag);
#ifndef EFFECT_CATEGORIES
PopulateEffectsMenu(c,
EffectTypeAnalyze,
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
TracksExistFlag | IsRealtimeNotActiveFlag);
#else
flags = ANALYZE_EFFECT | BUILTIN_EFFECT | PLUGIN_EFFECT;
EffectCategory* ac =
em.LookupCategory(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin"));
CategorySet roots = ac->GetSubCategories();
EffectSet analyzers = ac->GetEffects();
EffectSet topLevel = CreateEffectSubmenus(c, roots, flags, 0);
std::copy(analyzers.begin(), analyzers.end(),
std::insert_iterator<EffectSet>(topLevel, topLevel.begin()));
AddEffectsToMenu(c, topLevel);
// Add all uncategorised effects in a special submenu
EffectSet unsorted =
em.GetUnsortedEffects(flags);
if (unsorted.size() > 0) {
c->AddSeparator();
c->BeginSubMenu(_("Unsorted"));
names.Clear();
indices.Clear();
EffectSet::const_iterator iter;
for (iter = unsorted.begin(); iter != unsorted.end(); ++iter) {
names.Add((*iter)->GetEffectName());
indices.Add((*iter)->GetEffectID());
}
c->AddItemList(wxT("Analyze"), names,
FNI(OnProcessAny, indices), true);
c->EndSubMenu();
}
#endif // EFFECT_CATEGORIES
c->EndMenu();
@@ -1381,7 +1267,7 @@ void AudacityProject::PopulateEffectsMenu(CommandManager* c,
AddEffectMenuItems(c, defplugs, batchflags, realflags, true);
if (optplugs.GetCount())
if (defplugs.GetCount() && optplugs.GetCount())
{
c->AddSeparator();
}
@@ -1421,13 +1307,6 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c,
wxString name = plug->GetName();
// This goes away once everything has been converted to new format
wxString stripped;
if (name.EndsWith(wxT("..."), &stripped))
{
name = stripped;
}
if (plug->IsEffectInteractive())
{
name += wxT("...");
@@ -1492,13 +1371,6 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c,
wxString name = plug->GetName();
// This goes away once everything has been converted to new format
wxString stripped;
if (name.EndsWith(wxT("..."), &stripped))
{
name = stripped;
}
if (plug->IsEffectInteractive())
{
name += wxT("...");
@@ -1635,73 +1507,6 @@ void AudacityProject::AddEffectMenuItemGroup(CommandManager *c,
return;
}
#ifdef EFFECT_CATEGORIES
EffectSet AudacityProject::CreateEffectSubmenus(CommandManager* c,
const CategorySet& categories,
int flags,
unsigned submenuThreshold) {
EffectSet topLevel;
CategorySet::const_iterator iter;
for (iter = categories.begin(); iter != categories.end(); ++iter) {
EffectSet effects = (*iter)->GetAllEffects(flags);
// If the subgraph for this category only contains a single effect,
// add it directly in this menu
if (effects.size() <= submenuThreshold)
topLevel.insert(effects.begin(), effects.end());
// If there are more than one effect, add a submenu for the category
else if (effects.size() > 0) {
EffectSet directEffects = (*iter)->GetEffects(flags);
CategorySet subCategories = (*iter)->GetSubCategories();
CategorySet nonEmptySubCategories;
CategorySet::const_iterator sci;
for (sci = subCategories.begin(); sci != subCategories.end(); ++sci) {
if ((*sci)->GetAllEffects(flags).size() > 0)
nonEmptySubCategories.insert(*sci);
}
// If there are no direct effects and only one subcategory,
// add the contents of that subcategory directly in this menu.
if (directEffects.size() == 0 && nonEmptySubCategories.size() == 1) {
EffectSet a = CreateEffectSubmenus(c, nonEmptySubCategories,
flags);
topLevel.insert(a.begin(), a.end());
}
// Else, add submenus for the subcategories
else {
c->BeginSubMenu((*iter)->GetName());
EffectSet a = CreateEffectSubmenus(c, subCategories, flags);
a.insert(directEffects.begin(), directEffects.end());
AddEffectsToMenu(c, a);
c->EndSubMenu();
}
}
}
return topLevel;
}
void AudacityProject::AddEffectsToMenu(CommandManager* c,
const EffectSet& effects) {
wxArrayString names;
wxArrayInt indices;
EffectSet::const_iterator iter;
for (iter = effects.begin(); iter != effects.end(); ++iter) {
names.Add((*iter)->GetEffectName());
indices.Add((*iter)->GetEffectID());
}
c->AddItemList(wxT("Effects"), names, FNI(OnProcessAny, indices), true);
}
#endif
void AudacityProject::CreateRecentFilesMenu(CommandManager *c)
{
// Recent Files and Recent Projects menus
@@ -1907,7 +1712,7 @@ wxUint32 AudacityProject::GetUpdateFlags()
if (mUndoManager.UnsavedChanges())
flags |= UnsavedChangesFlag;
if (!mLastEffect.empty())
if (!mLastEffect.IsEmpty())
flags |= HasLastEffectFlag;
if (mUndoManager.UndoAvailable())
@@ -1948,10 +1753,8 @@ wxUint32 AudacityProject::GetUpdateFlags()
else
flags |= IsNotSyncLockedFlag;
#if defined(EXPERIMENTAL_REALTIME_EFFECTS)
if (!EffectManager::Get().RealtimeIsActive())
flags |= IsRealtimeNotActiveFlag;
#endif
return flags;
}
@@ -3258,23 +3061,23 @@ void AudacityProject::OnZeroCrossing()
/// OnEffect() takes a PluginID and has the EffectManager execute the assocated effect.
///
/// At the moment flags are used only to indicate
/// whether to prompt for parameters or whether to
/// use the most recently stored values.
///
/// At some point we should change to specifying a
/// parameter source - one of:
/// + Prompt
/// + Use previous values
/// + Parse from a string that is passed in
///
/// DanH: I've added the third option as a temporary measure. I think this
/// should eventually be done by having effects as Command objects.
bool AudacityProject::OnEffect(int type,
const PluginID & ID,
wxString params,
bool saveState)
/// At the moment flags are used only to indicate whether to prompt for parameters and
/// whether to save the state to history.
bool AudacityProject::OnEffect(const PluginID & ID, int flags)
{
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
wxASSERT(plug != NULL);
EffectType type = plug->GetEffectType();
// Make sure there's no activity since the effect is about to be applied
// to the project's tracks. Mainly for Apply during RTP, but also used
// for batch commands
if (flags & OnEffectFlags::kConfigured)
{
OnStop();
SelectAllIfNone();
}
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
TrackListIterator iter(mTracks);
@@ -3295,7 +3098,7 @@ bool AudacityProject::OnEffect(int type,
if (count == 0) {
// No tracks were selected...
if (type & INSERT_EFFECT) {
if (type == EffectTypeGenerate) {
// Create a new track for the generated audio...
newTrack = mTrackFactory->NewWaveTrack();
mTracks->Add(newTrack);
@@ -3309,154 +3112,77 @@ bool AudacityProject::OnEffect(int type,
EffectManager & em = EffectManager::Get();
if (em.DoEffect(ID, this, type, mRate, mTracks, mTrackFactory,
&mViewInfo.selectedRegion, params))
{
if (saveState)
{
wxString longDesc = em.GetEffectDescription(ID);
wxString shortDesc = em.GetEffectName(ID);
bool success = em.DoEffect(ID, this, mRate,
mTracks, mTrackFactory,
&mViewInfo.selectedRegion,
!(flags & OnEffectFlags::kConfigured));
if (shortDesc.Length() > 3 && shortDesc.Right(3)==wxT("..."))
shortDesc = shortDesc.Left(shortDesc.Length()-3);
PushState(longDesc, shortDesc);
// Only remember a successful effect, don't rmemeber insert,
// or analyze effects.
if ((type & (INSERT_EFFECT | ANALYZE_EFFECT))==0) {
mLastEffect = ID;
mLastEffectType = type;
wxString lastEffectDesc;
/* i18n-hint: %s will be the name of the effect which will be
* repeated if this menu item is chosen */
lastEffectDesc.Printf(_("Repeat %s"), shortDesc.c_str());
mCommandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc);
}
}
//STM:
//The following automatically re-zooms after sound was generated.
// IMO, it was disorienting, removing to try out without re-fitting
//mchinen:12/14/08 reapplying for generate effects
if ( type & INSERT_EFFECT)
{
if (count == 0 || (clean && mViewInfo.selectedRegion.t0() == 0.0))
OnZoomFit();
// mTrackPanel->Refresh(false);
}
RedrawProject();
if (focus != NULL) {
focus->SetFocus();
}
mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack());
mTrackPanel->Refresh(false);
} else {
if (!success) {
if (newTrack) {
mTracks->Remove(newTrack);
delete newTrack;
mTrackPanel->Refresh(false);
}
#if defined(EXPERIMENTAL_REALTIME_EFFECTS)
// For now, we're limiting realtime preview to a single effect, so
// make sure the menus reflect that fact that one may have just been
// opened.
UpdateMenus(false);
#endif
return false;
}
if (!(flags & OnEffectFlags::kSkipState))
{
wxString shortDesc = em.GetEffectName(ID);
wxString longDesc = em.GetEffectDescription(ID);
PushState(longDesc, shortDesc);
// Only remember a successful effect, don't rmemeber insert,
// or analyze effects.
if (type == EffectTypeProcess) {
mLastEffect = ID;
wxString lastEffectDesc;
/* i18n-hint: %s will be the name of the effect which will be
* repeated if this menu item is chosen */
lastEffectDesc.Printf(_("Repeat %s"), shortDesc.c_str());
mCommandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc);
}
}
//STM:
//The following automatically re-zooms after sound was generated.
// IMO, it was disorienting, removing to try out without re-fitting
//mchinen:12/14/08 reapplying for generate effects
if (type == EffectTypeGenerate)
{
if (count == 0 || (clean && mViewInfo.selectedRegion.t0() == 0.0))
OnZoomFit();
// mTrackPanel->Refresh(false);
}
RedrawProject();
if (focus != NULL) {
focus->SetFocus();
}
mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack());
mTrackPanel->Refresh(false);
return true;
}
void AudacityProject::OnEffect(const PluginID & pluginID)
{
PluginManager & pm = PluginManager::Get();
const PluginDescriptor *plug = pm.GetPlugin(pluginID);
int type;
switch (plug->GetEffectType())
{
case EffectTypeGenerate:
type = INSERT_EFFECT;
break;
case EffectTypeProcess:
type = PROCESS_EFFECT;
break;
case EffectTypeAnalyze:
type = ANALYZE_EFFECT;
break;
case EffectTypeNone:
type = 0;
break;
}
type |= plug->IsEffectDefault() ? BUILTIN_EFFECT : PLUGIN_EFFECT;
OnEffect(type, pluginID);
}
// Warning...complete hackage ahead
void AudacityProject::OnEffect(const PluginID & pluginID, bool configured)
{
PluginManager & pm = PluginManager::Get();
const PluginDescriptor *plug = pm.GetPlugin(pluginID);
int type;
switch (plug->GetEffectType())
{
case EffectTypeGenerate:
type = INSERT_EFFECT;
break;
case EffectTypeProcess:
type = PROCESS_EFFECT;
break;
case EffectTypeAnalyze:
type = ANALYZE_EFFECT;
break;
case EffectTypeNone:
type = 0;
break;
}
type |= plug->IsEffectDefault() ? BUILTIN_EFFECT : PLUGIN_EFFECT;
type |= configured ? CONFIGURED_EFFECT : 0;
OnStop();
SelectAllIfNone();
OnEffect(type, pluginID);
}
void AudacityProject::OnRepeatLastEffect(int WXUNUSED(index))
{
if (!mLastEffect.empty()) {
// Setting the CONFIGURED_EFFECT bit prevents
// prompting for parameters.
OnEffect(mLastEffectType | CONFIGURED_EFFECT, mLastEffect);
if (!mLastEffect.IsEmpty())
{
OnEffect(mLastEffect, OnEffectFlags::kConfigured);
}
}
#ifdef EFFECT_CATEGORIES
void AudacityProject::OnProcessAny(int index)
{
Effect* e = EffectManager::Get().GetEffect(index);
OnEffect(ALL_EFFECTS, e);
}
#endif
void AudacityProject::OnStereoToMono(int WXUNUSED(index))
{
OnEffect(ALL_EFFECTS,
EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")));
OnEffect(EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")),
OnEffectFlags::kConfigured);
}
//
@@ -6359,53 +6085,6 @@ void AudacityProject::OnEditChains()
dlg.ShowModal();
}
wxString AudacityProject::BuildCleanFileName(wxString fileName, wxString extension)
{
wxFileName newFileName(fileName);
wxString justName = newFileName.GetName();
wxString pathName = newFileName.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
if (justName == wxT("")) {
wxDateTime now = wxDateTime::Now();
int year = now.GetYear();
wxDateTime::Month month = now.GetMonth();
wxString monthName = now.GetMonthName(month);
int dom = now.GetDay();
int hour = now.GetHour();
int minute = now.GetMinute();
int second = now.GetSecond();
justName = wxString::Format(wxT("%d-%s-%02d-%02d-%02d-%02d"),
year, monthName.c_str(), dom, hour, minute, second);
// SetName(cleanedFileName);
// bool isStereo;
// double endTime = project->mTracks->GetEndTime();
// double startTime = 0.0;
//OnSelectAll();
pathName = gPrefs->Read(wxT("/DefaultOpenPath"), ::wxGetCwd());
::wxMessageBox(wxString::Format(wxT("Export recording to %s\n/cleaned/%s%s"),
pathName.c_str(), justName.c_str(), extension.c_str()),
wxT("Export recording"),
wxOK | wxCENTRE);
pathName += wxT("/");
}
wxString cleanedName = pathName;
cleanedName += wxT("cleaned");
bool flag = ::wxFileName::FileExists(cleanedName);
if (flag == true) {
::wxMessageBox(wxT("Cannot create directory 'cleaned'. \nFile already exists that is not a directory"));
return wxT("");
}
::wxFileName::Mkdir(cleanedName, 0777, wxPATH_MKDIR_FULL); // make sure it exists
cleanedName += wxT("/");
cleanedName += justName;
cleanedName += extension;
wxGetApp().AddFileToHistory(cleanedName);
return cleanedName;
}
void AudacityProject::OnRemoveTracks()
{
TrackListIterator iter(mTracks);