1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   EffectManager.cpp
6 
7   Audacity(R) is copyright (c) 1999-2008 Audacity Team.
8   License: GPL v2.  See License.txt.
9 
10 ******************************************************************//**
11 
12 \class EffectManager
13 \brief EffectManager is the class that handles effects and effect categories.
14 
15 It maintains a graph of effect categories and subcategories,
16 registers and unregisters effects and can return filtered lists of
17 effects.
18 
19 *//*******************************************************************/
20 
21 
22 #include "EffectManager.h"
23 
24 #include "Effect.h"
25 
26 #include <algorithm>
27 #include <wx/tokenzr.h>
28 
29 #include "../widgets/AudacityMessageBox.h"
30 
31 #include "../ShuttleGetDefinition.h"
32 #include "../commands/CommandContext.h"
33 #include "../commands/AudacityCommand.h"
34 #include "../PluginManager.h"
35 
36 
37 /*******************************************************************************
38 Creates a singleton and returns reference
39 
40  (Thread-safe...no active threading during construction or after destruction)
41 *******************************************************************************/
Get()42 EffectManager & EffectManager::Get()
43 {
44    static EffectManager em;
45    return em;
46 }
47 
EffectManager()48 EffectManager::EffectManager()
49 {
50    mSkipStateFlag = false;
51 }
52 
~EffectManager()53 EffectManager::~EffectManager()
54 {
55 }
56 
57 // Here solely for the purpose of Nyquist Workbench until
58 // a better solution is devised.
RegisterEffect(std::unique_ptr<Effect> uEffect)59 const PluginID & EffectManager::RegisterEffect(std::unique_ptr<Effect> uEffect)
60 {
61    auto pEffect = uEffect.get();
62    const PluginID & ID =
63       PluginManager::Get().RegisterPlugin(std::move(uEffect), PluginTypeEffect);
64    mEffects[ID] = pEffect;
65    return ID;
66 }
67 
68 // Here solely for the purpose of Nyquist Workbench until
69 // a better solution is devised.
UnregisterEffect(const PluginID & ID)70 void EffectManager::UnregisterEffect(const PluginID & ID)
71 {
72    PluginID id = ID;
73    PluginManager::Get().UnregisterPlugin(id);
74    mEffects.erase(id);
75 }
76 
DoAudacityCommand(const PluginID & ID,const CommandContext & context,wxWindow * parent,bool shouldPrompt)77 bool EffectManager::DoAudacityCommand(const PluginID & ID,
78                              const CommandContext &context,
79                              wxWindow *parent,
80                              bool shouldPrompt /* = true */)
81 
82 {
83    this->SetSkipStateFlag(false);
84    AudacityCommand *command = GetAudacityCommand(ID);
85 
86    if (!command)
87    {
88       return false;
89    }
90 
91    bool res = command->DoAudacityCommand(parent, context, shouldPrompt);
92 
93    return res;
94 }
95 
GetCommandSymbol(const PluginID & ID)96 ComponentInterfaceSymbol EffectManager::GetCommandSymbol(const PluginID & ID)
97 {
98    return PluginManager::Get().GetSymbol(ID);
99 }
100 
GetCommandName(const PluginID & ID)101 TranslatableString EffectManager::GetCommandName(const PluginID & ID)
102 {
103    return GetCommandSymbol(ID).Msgid();
104 }
105 
GetEffectFamilyName(const PluginID & ID)106 TranslatableString EffectManager::GetEffectFamilyName(const PluginID & ID)
107 {
108    auto effect = GetEffect(ID);
109    if (effect)
110       return effect->GetFamily().Msgid();
111    return {};
112 }
113 
GetVendorName(const PluginID & ID)114 TranslatableString EffectManager::GetVendorName(const PluginID & ID)
115 {
116    auto effect = GetEffect(ID);
117    if (effect)
118       return effect->GetVendor().Msgid();
119    return {};
120 }
121 
GetCommandIdentifier(const PluginID & ID)122 CommandID EffectManager::GetCommandIdentifier(const PluginID & ID)
123 {
124    wxString name = PluginManager::Get().GetSymbol(ID).Internal();
125    return Effect::GetSquashedName(name);
126 }
127 
GetCommandDescription(const PluginID & ID)128 TranslatableString EffectManager::GetCommandDescription(const PluginID & ID)
129 {
130    if (GetEffect(ID))
131       return XO("Applied effect: %s").Format( GetCommandName(ID) );
132    if (GetAudacityCommand(ID))
133       return XO("Applied command: %s").Format( GetCommandName(ID) );
134 
135    return {};
136 }
137 
GetCommandUrl(const PluginID & ID)138 ManualPageID EffectManager::GetCommandUrl(const PluginID & ID)
139 {
140    Effect* pEff = GetEffect(ID);
141    if( pEff )
142       return pEff->ManualPage();
143    AudacityCommand * pCom = GetAudacityCommand(ID);
144    if( pCom )
145       return pCom->ManualPage();
146 
147    return wxEmptyString;
148 }
149 
GetCommandTip(const PluginID & ID)150 TranslatableString EffectManager::GetCommandTip(const PluginID & ID)
151 {
152    Effect* pEff = GetEffect(ID);
153    if( pEff )
154       return pEff->GetDescription();
155    AudacityCommand * pCom = GetAudacityCommand(ID);
156    if( pCom )
157       return pCom->GetDescription();
158 
159    return {};
160 }
161 
162 
GetCommandDefinition(const PluginID & ID,const CommandContext & context,int flags)163 void EffectManager::GetCommandDefinition(const PluginID & ID, const CommandContext & context, int flags)
164 {
165    ComponentInterface *command;
166    command = GetEffect(ID);
167    if( !command )
168       command = GetAudacityCommand( ID );
169    if( !command )
170       return;
171 
172    ShuttleParams NullShuttle;
173    // Test if it defines any parameters at all.
174    bool bHasParams = command->DefineParams( NullShuttle );
175    if( (flags ==0) && !bHasParams )
176       return;
177 
178    // This is capturing the output context into the shuttle.
179    ShuttleGetDefinition S(  *context.pOutput.get()->mStatusTarget.get() );
180    S.StartStruct();
181    // using GET to expose a CommandID to the user!
182    // Macro command details are one place that we do expose Identifier
183    // to (more sophisticated) users
184    S.AddItem( GetCommandIdentifier( ID ).GET(), "id" );
185    S.AddItem( GetCommandName( ID ).Translation(), "name" );
186    if( bHasParams ){
187       S.StartField( "params" );
188       S.StartArray();
189       command->DefineParams( S );
190       S.EndArray();
191       S.EndField();
192    }
193    // use GET() to expose some details to macro programming users
194    S.AddItem( GetCommandUrl( ID ).GET(), "url" );
195    // The tip is a translated string!
196    S.AddItem( GetCommandTip( ID ).Translation(), "tip" );
197    S.EndStruct();
198 }
199 
200 
201 
IsHidden(const PluginID & ID)202 bool EffectManager::IsHidden(const PluginID & ID)
203 {
204    Effect *effect = GetEffect(ID);
205 
206    if (effect)
207    {
208       return effect->IsHidden();
209    }
210 
211    return false;
212 }
213 
SetSkipStateFlag(bool flag)214 void EffectManager::SetSkipStateFlag(bool flag)
215 {
216    mSkipStateFlag = flag;
217 }
218 
GetSkipStateFlag()219 bool EffectManager::GetSkipStateFlag()
220 {
221    return mSkipStateFlag;
222 }
223 
SupportsAutomation(const PluginID & ID)224 bool EffectManager::SupportsAutomation(const PluginID & ID)
225 {
226    const PluginDescriptor *plug =  PluginManager::Get().GetPlugin(ID);
227    if (plug)
228    {
229       return plug->IsEffectAutomatable();
230    }
231 
232    return false;
233 }
234 
GetEffectParameters(const PluginID & ID)235 wxString EffectManager::GetEffectParameters(const PluginID & ID)
236 {
237    Effect *effect = GetEffect(ID);
238 
239    if (effect)
240    {
241       wxString parms;
242 
243       effect->GetAutomationParameters(parms);
244 
245       // Some effects don't have automatable parameters and will not return
246       // anything, so try to get the active preset (current or factory).
247       if (parms.empty())
248       {
249          parms = GetDefaultPreset(ID);
250       }
251 
252       return parms;
253    }
254 
255    AudacityCommand *command = GetAudacityCommand(ID);
256 
257    if (command)
258    {
259       wxString parms;
260 
261       command->GetAutomationParameters(parms);
262 
263       // Some effects don't have automatable parameters and will not return
264       // anything, so try to get the active preset (current or factory).
265       if (parms.empty())
266       {
267          parms = GetDefaultPreset(ID);
268       }
269 
270       return parms;
271    }
272    return wxEmptyString;
273 }
274 
SetEffectParameters(const PluginID & ID,const wxString & params)275 bool EffectManager::SetEffectParameters(const PluginID & ID, const wxString & params)
276 {
277    Effect *effect = GetEffect(ID);
278 
279    if (effect)
280    {
281       CommandParameters eap(params);
282 
283       if (eap.HasEntry(wxT("Use Preset")))
284       {
285          return effect->SetAutomationParameters(eap.Read(wxT("Use Preset")));
286       }
287 
288       return effect->SetAutomationParameters(params);
289    }
290    AudacityCommand *command = GetAudacityCommand(ID);
291 
292    if (command)
293    {
294       // Set defaults (if not initialised) before setting values.
295       command->Init();
296       CommandParameters eap(params);
297 
298       if (eap.HasEntry(wxT("Use Preset")))
299       {
300          return command->SetAutomationParameters(eap.Read(wxT("Use Preset")));
301       }
302 
303       return command->SetAutomationParameters(params);
304    }
305    return false;
306 }
307 
PromptUser(const PluginID & ID,const EffectClientInterface::EffectDialogFactory & factory,wxWindow & parent)308 bool EffectManager::PromptUser(
309    const PluginID & ID,
310    const EffectClientInterface::EffectDialogFactory &factory, wxWindow &parent)
311 {
312    bool result = false;
313    Effect *effect = GetEffect(ID);
314 
315    if (effect)
316    {
317       result = effect->ShowInterface(
318          parent, factory, effect->IsBatchProcessing() );
319       return result;
320    }
321 
322    AudacityCommand *command = GetAudacityCommand(ID);
323 
324    if (command)
325    {
326       result = command->PromptUser(&parent);
327       return result;
328    }
329 
330    return result;
331 }
332 
HasPresets(const PluginID & ID)333 bool EffectManager::HasPresets(const PluginID & ID)
334 {
335    Effect *effect = GetEffect(ID);
336 
337    if (!effect)
338    {
339       return false;
340    }
341 
342    return effect->GetUserPresets().size() > 0 ||
343           effect->GetFactoryPresets().size() > 0 ||
344           effect->HasCurrentSettings() ||
345           effect->HasFactoryDefaults();
346 }
347 
348 #include <wx/choice.h>
349 #include <wx/listbox.h>
350 #include "../ShuttleGui.h"
351 
352 namespace {
353 
354 ///////////////////////////////////////////////////////////////////////////////
355 //
356 // EffectPresetsDialog
357 //
358 ///////////////////////////////////////////////////////////////////////////////
359 
360 class EffectPresetsDialog final : public wxDialogWrapper
361 {
362 public:
363    EffectPresetsDialog(wxWindow *parent, Effect *effect);
364    virtual ~EffectPresetsDialog();
365 
366    wxString GetSelected() const;
367    void SetSelected(const wxString & parms);
368 
369 private:
370    void SetPrefix(const TranslatableString & type, const wxString & prefix);
371    void UpdateUI();
372 
373    void OnType(wxCommandEvent & evt);
374    void OnOk(wxCommandEvent & evt);
375    void OnCancel(wxCommandEvent & evt);
376 
377 private:
378    wxChoice *mType;
379    wxListBox *mPresets;
380 
381    RegistryPaths mFactoryPresets;
382    RegistryPaths mUserPresets;
383    wxString mSelection;
384 
385    DECLARE_EVENT_TABLE()
386 };
387 
388 enum
389 {
390    ID_Type = 10000
391 };
392 
BEGIN_EVENT_TABLE(EffectPresetsDialog,wxDialogWrapper)393 BEGIN_EVENT_TABLE(EffectPresetsDialog, wxDialogWrapper)
394    EVT_CHOICE(ID_Type, EffectPresetsDialog::OnType)
395    EVT_LISTBOX_DCLICK(wxID_ANY, EffectPresetsDialog::OnOk)
396    EVT_BUTTON(wxID_OK, EffectPresetsDialog::OnOk)
397    EVT_BUTTON(wxID_CANCEL, EffectPresetsDialog::OnCancel)
398 END_EVENT_TABLE()
399 
400 EffectPresetsDialog::EffectPresetsDialog(wxWindow *parent, Effect *effect)
401 :  wxDialogWrapper(parent, wxID_ANY, XO("Select Preset"))
402 {
403    ShuttleGui S(this, eIsCreating);
404    S.StartVerticalLay();
405    {
406       S.StartTwoColumn();
407       S.SetStretchyCol(1);
408       {
409          S.AddPrompt(XXO("Type:"));
410          mType = S.Id(ID_Type).AddChoice( {}, {}, 0 );
411 
412          S.AddPrompt(XXO("&Preset:"));
413          mPresets = S
414             .Style( wxLB_SINGLE | wxLB_NEEDED_SB )
415             .AddListBox( {} );
416       }
417       S.EndTwoColumn();
418 
419       S.AddStandardButtons();
420    }
421    S.EndVerticalLay();
422 
423    mUserPresets = effect->GetUserPresets();
424    mFactoryPresets = effect->GetFactoryPresets();
425 
426    if (mUserPresets.size() > 0)
427    {
428       mType->Append(_("User Presets"));
429    }
430 
431    if (mFactoryPresets.size() > 0)
432    {
433       mType->Append(_("Factory Presets"));
434    }
435 
436    if (effect->HasCurrentSettings())
437    {
438       mType->Append(_("Current Settings"));
439    }
440 
441    if (effect->HasFactoryDefaults())
442    {
443       mType->Append(_("Factory Defaults"));
444    }
445 
446    UpdateUI();
447 }
448 
~EffectPresetsDialog()449 EffectPresetsDialog::~EffectPresetsDialog()
450 {
451 }
452 
GetSelected() const453 wxString EffectPresetsDialog::GetSelected() const
454 {
455    return mSelection;
456 }
457 
SetSelected(const wxString & parms)458 void EffectPresetsDialog::SetSelected(const wxString & parms)
459 {
460    wxString preset = parms;
461    if (preset.StartsWith(Effect::kUserPresetIdent))
462    {
463       preset.Replace(Effect::kUserPresetIdent, wxEmptyString, false);
464       SetPrefix(XO("User Presets"), preset);
465    }
466    else if (preset.StartsWith(Effect::kFactoryPresetIdent))
467    {
468       preset.Replace(Effect::kFactoryPresetIdent, wxEmptyString, false);
469       SetPrefix(XO("Factory Presets"), preset);
470    }
471    else if (preset.StartsWith(Effect::kCurrentSettingsIdent))
472    {
473       SetPrefix(XO("Current Settings"), wxEmptyString);
474    }
475    else if (preset.StartsWith(Effect::kFactoryDefaultsIdent))
476    {
477       SetPrefix(XO("Factory Defaults"), wxEmptyString);
478    }
479 }
480 
SetPrefix(const TranslatableString & type,const wxString & prefix)481 void EffectPresetsDialog::SetPrefix(
482    const TranslatableString & type, const wxString & prefix)
483 {
484    mType->SetStringSelection(type.Translation());
485 
486    if (type == XO("User Presets"))
487    {
488       mPresets->Clear();
489       for (const auto &preset : mUserPresets)
490          mPresets->Append(preset);
491       mPresets->Enable(true);
492       mPresets->SetStringSelection(prefix);
493       if (mPresets->GetSelection() == wxNOT_FOUND)
494       {
495          mPresets->SetSelection(0);
496       }
497       mSelection = Effect::kUserPresetIdent + mPresets->GetStringSelection();
498    }
499    else if (type == XO("Factory Presets"))
500    {
501       mPresets->Clear();
502       for (size_t i = 0, cnt = mFactoryPresets.size(); i < cnt; i++)
503       {
504          auto label = mFactoryPresets[i];
505          if (label.empty())
506          {
507             label = _("None");
508          }
509          mPresets->Append(label);
510       }
511       mPresets->Enable(true);
512       mPresets->SetStringSelection(prefix);
513       if (mPresets->GetSelection() == wxNOT_FOUND)
514       {
515          mPresets->SetSelection(0);
516       }
517       mSelection = Effect::kFactoryPresetIdent + mPresets->GetStringSelection();
518    }
519    else if (type == XO("Current Settings"))
520    {
521       mPresets->Clear();
522       mPresets->Enable(false);
523       mSelection = Effect::kCurrentSettingsIdent;
524    }
525    else if (type == XO("Factory Defaults"))
526    {
527       mPresets->Clear();
528       mPresets->Enable(false);
529       mSelection = Effect::kFactoryDefaultsIdent;
530    }
531 }
532 
UpdateUI()533 void EffectPresetsDialog::UpdateUI()
534 {
535    int selected = mType->GetSelection();
536    if (selected == wxNOT_FOUND)
537    {
538       selected = 0;
539       mType->SetSelection(selected);
540    }
541    wxString type = mType->GetString(selected);
542 
543    if (type == _("User Presets"))
544    {
545       selected = mPresets->GetSelection();
546       if (selected == wxNOT_FOUND)
547       {
548          selected = 0;
549       }
550 
551       mPresets->Clear();
552       for (const auto &preset : mUserPresets)
553          mPresets->Append(preset);
554       mPresets->Enable(true);
555       mPresets->SetSelection(selected);
556       mSelection = Effect::kUserPresetIdent + mPresets->GetString(selected);
557    }
558    else if (type == _("Factory Presets"))
559    {
560       selected = mPresets->GetSelection();
561       if (selected == wxNOT_FOUND)
562       {
563          selected = 0;
564       }
565 
566       mPresets->Clear();
567       for (size_t i = 0, cnt = mFactoryPresets.size(); i < cnt; i++)
568       {
569          auto label = mFactoryPresets[i];
570          if (label.empty())
571          {
572             label = _("None");
573          }
574          mPresets->Append(label);
575       }
576       mPresets->Enable(true);
577       mPresets->SetSelection(selected);
578       mSelection = Effect::kFactoryPresetIdent + mPresets->GetString(selected);
579    }
580    else if (type == _("Current Settings"))
581    {
582       mPresets->Clear();
583       mPresets->Enable(false);
584       mSelection = Effect::kCurrentSettingsIdent;
585    }
586    else if (type == _("Factory Defaults"))
587    {
588       mPresets->Clear();
589       mPresets->Enable(false);
590       mSelection = Effect::kFactoryDefaultsIdent;
591    }
592 }
593 
OnType(wxCommandEvent & WXUNUSED (evt))594 void EffectPresetsDialog::OnType(wxCommandEvent & WXUNUSED(evt))
595 {
596    UpdateUI();
597 }
598 
OnOk(wxCommandEvent & WXUNUSED (evt))599 void EffectPresetsDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
600 {
601    UpdateUI();
602 
603    EndModal(true);
604 }
605 
OnCancel(wxCommandEvent & WXUNUSED (evt))606 void EffectPresetsDialog::OnCancel(wxCommandEvent & WXUNUSED(evt))
607 {
608    mSelection = wxEmptyString;
609 
610    EndModal(false);
611 }
612 
613 }
614 
GetPreset(const PluginID & ID,const wxString & params,wxWindow * parent)615 wxString EffectManager::GetPreset(const PluginID & ID, const wxString & params, wxWindow * parent)
616 {
617    Effect *effect = GetEffect(ID);
618 
619    if (!effect)
620    {
621       return wxEmptyString;
622    }
623 
624    CommandParameters eap(params);
625 
626    wxString preset;
627    if (eap.HasEntry(wxT("Use Preset")))
628    {
629       preset = eap.Read(wxT("Use Preset"));
630    }
631 
632    {
633       EffectPresetsDialog dlg(parent, effect);
634       dlg.Layout();
635       dlg.Fit();
636       dlg.SetSize(dlg.GetMinSize());
637       dlg.CenterOnParent();
638       dlg.SetSelected(preset);
639 
640       if (dlg.ShowModal())
641          preset = dlg.GetSelected();
642       else
643          preset = wxEmptyString;
644    }
645 
646    if (preset.empty())
647    {
648       return preset;
649    }
650 
651    // This cleans a config "file" backed by a string in memory.
652    eap.DeleteAll();
653 
654    eap.Write(wxT("Use Preset"), preset);
655    eap.GetParameters(preset);
656 
657    return preset;
658 }
659 
GetDefaultPreset(const PluginID & ID)660 wxString EffectManager::GetDefaultPreset(const PluginID & ID)
661 {
662    Effect *effect = GetEffect(ID);
663 
664    if (!effect)
665    {
666       return wxEmptyString;
667    }
668 
669    wxString preset;
670    if (effect->HasCurrentSettings())
671    {
672       preset = Effect::kCurrentSettingsIdent;
673    }
674    else if (effect->HasFactoryDefaults())
675    {
676       preset = Effect::kFactoryDefaultsIdent;
677    }
678 
679    if (!preset.empty())
680    {
681       CommandParameters eap;
682 
683       eap.Write(wxT("Use Preset"), preset);
684       eap.GetParameters(preset);
685    }
686 
687    return preset;
688 }
689 
SetBatchProcessing(const PluginID & ID,bool start)690 void EffectManager::SetBatchProcessing(const PluginID & ID, bool start)
691 {
692    Effect *effect = GetEffect(ID);
693    if (effect)
694    {
695       effect->SetBatchProcessing(start);
696       return;
697    }
698 
699    AudacityCommand *command = GetAudacityCommand(ID);
700    if (command)
701    {
702       command->SetBatchProcessing(start);
703       return;
704    }
705 
706 }
707 
GetEffect(const PluginID & ID)708 Effect *EffectManager::GetEffect(const PluginID & ID)
709 {
710    // Must have a "valid" ID
711    if (ID.empty())
712    {
713       return NULL;
714    }
715 
716    // If it is actually a command then refuse it (as an effect).
717    if( mCommands.find( ID ) != mCommands.end() )
718       return NULL;
719 
720    // TODO: This is temporary and should be redone when all effects are converted
721    if (mEffects.find(ID) == mEffects.end())
722    {
723       // This will instantiate the effect client if it hasn't already been done
724       EffectDefinitionInterface *ident = dynamic_cast<EffectDefinitionInterface *>(PluginManager::Get().GetInstance(ID));
725       if (ident && ident->IsLegacy())
726       {
727          auto effect = dynamic_cast<Effect *>(ident);
728          if (effect && effect->Startup(NULL))
729          {
730             mEffects[ID] = effect;
731             return effect;
732          }
733       }
734 
735       auto effect = std::make_shared<Effect>(); // TODO: use make_unique and store in std::unordered_map
736       if (effect)
737       {
738          EffectClientInterface *client = dynamic_cast<EffectClientInterface *>(ident);
739          if (client && effect->Startup(client))
740          {
741             auto pEffect = effect.get();
742             mEffects[ID] = pEffect;
743             mHostEffects[ID] = std::move(effect);
744             return pEffect;
745          }
746       }
747 
748       auto command = dynamic_cast<AudacityCommand *>(PluginManager::Get().GetInstance(ID));
749       if( !command )
750          AudacityMessageBox(
751             XO(
752 "Attempting to initialize the following effect failed:\n\n%s\n\nMore information may be available in 'Help > Diagnostics > Show Log'")
753                .Format( GetCommandName(ID) ),
754             XO("Effect failed to initialize"));
755 
756       return NULL;
757    }
758 
759    return mEffects[ID];
760 }
761 
GetAudacityCommand(const PluginID & ID)762 AudacityCommand *EffectManager::GetAudacityCommand(const PluginID & ID)
763 {
764    // Must have a "valid" ID
765    if (ID.empty())
766    {
767       return NULL;
768    }
769 
770    // TODO: This is temporary and should be redone when all effects are converted
771    if (mCommands.find(ID) == mCommands.end())
772    {
773 
774       // This will instantiate the effect client if it hasn't already been done
775       auto command = dynamic_cast<AudacityCommand *>(PluginManager::Get().GetInstance(ID));
776       if (command )//&& command->Startup(NULL))
777       {
778          command->Init();
779          mCommands[ID] = command;
780          return command;
781       }
782 
783          /*
784       if (ident && ident->IsLegacy())
785       {
786          auto command = dynamic_cast<AudacityCommand *>(ident);
787          if (commandt && command->Startup(NULL))
788          {
789             mCommands[ID] = command;
790             return command;
791          }
792       }
793 
794 
795       auto command = std::make_shared<AudacityCommand>(); // TODO: use make_unique and store in std::unordered_map
796       if (command)
797       {
798          AudacityCommand *client = dynamic_cast<AudacityCommand *>(ident);
799          if (client && command->Startup(client))
800          {
801             auto pCommand = command.get();
802             mEffects[ID] = pCommand;
803             mHostEffects[ID] = std::move(effect);
804             return pEffect;
805          }
806       }
807 */
808       AudacityMessageBox(
809          XO(
810 "Attempting to initialize the following command failed:\n\n%s\n\nMore information may be available in 'Help > Diagnostics > Show Log'")
811             .Format( GetCommandName(ID) ),
812          XO("Command failed to initialize"));
813 
814       return NULL;
815    }
816 
817    return mCommands[ID];
818 }
819 
820 
GetEffectByIdentifier(const CommandID & strTarget)821 const PluginID & EffectManager::GetEffectByIdentifier(const CommandID & strTarget)
822 {
823    static PluginID empty;
824    if (strTarget.empty()) // set GetCommandIdentifier to wxT("") to not show an effect in Batch mode
825    {
826       return empty;
827    }
828 
829    PluginManager & pm = PluginManager::Get();
830    // Effects OR Generic commands...
831    for (auto &plug
832         : pm.PluginsOfType(PluginTypeEffect | PluginTypeAudacityCommand)) {
833       auto &ID = plug.GetID();
834       if (GetCommandIdentifier(ID) == strTarget)
835          return ID;
836    }
837    return empty;
838 }
839 
840