1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 VSTEffect.cpp
6
7 Dominic Mazzoni
8
9 This class implements a VST Plug-in effect. The plug-in must be
10 loaded in a platform-specific way and passed into the constructor,
11 but from here this class handles the interfacing.
12
13 ********************************************************************//**
14
15 \class AEffect
16 \brief VST Effects class, conforming to VST layout.
17
18 *//********************************************************************/
19
20 //#define VST_DEBUG
21 //#define DEBUG_VST
22
23 // *******************************************************************
24 // WARNING: This is NOT 64-bit safe
25 // *******************************************************************
26
27
28 #include "VSTEffect.h"
29 #include "../../ModuleManager.h"
30 #include "SampleCount.h"
31
32 #include "../../widgets/ProgressDialog.h"
33
34 #if 0
35 #if defined(BUILDING_AUDACITY)
36 #include "../../PlatformCompatibility.h"
37
38 // Make the main function private
39 #else
40 #define USE_VST 1
41 #endif
42 #endif
43
44 #if USE_VST
45
46 #include <limits.h>
47 #include <stdio.h>
48
49 #include <wx/setup.h> // for wxUSE_* macros
50 #include <wx/dynlib.h>
51 #include <wx/app.h>
52 #include <wx/defs.h>
53 #include <wx/buffer.h>
54 #include <wx/busyinfo.h>
55 #include <wx/button.h>
56 #include <wx/combobox.h>
57 #include <wx/file.h>
58 #include <wx/filename.h>
59 #include <wx/imaglist.h>
60 #include <wx/listctrl.h>
61 #include <wx/log.h>
62 #include <wx/module.h>
63 #include <wx/process.h>
64 #include <wx/recguard.h>
65 #include <wx/sizer.h>
66 #include <wx/slider.h>
67 #include <wx/scrolwin.h>
68 #include <wx/sstream.h>
69 #include <wx/statbox.h>
70 #include <wx/stattext.h>
71 #include <wx/timer.h>
72 #include <wx/tokenzr.h>
73 #include <wx/utils.h>
74
75 #if defined(__WXMSW__)
76 #include <shlwapi.h>
77 #pragma comment(lib, "shlwapi")
78 #else
79 #include <dlfcn.h>
80 #endif
81
82 // TODO: Unfortunately we have some dependencies on Audacity provided
83 // dialogs, widgets and other stuff. This will need to be cleaned up.
84
85 #include "FileNames.h"
86 #include "PlatformCompatibility.h"
87 #include "../../SelectFile.h"
88 #include "../../ShuttleGui.h"
89 #include "../../effects/Effect.h"
90 #include "../../widgets/valnum.h"
91 #include "../../widgets/AudacityMessageBox.h"
92 #include "../../widgets/NumericTextCtrl.h"
93 #include "XMLFileReader.h"
94
95 #if wxUSE_ACCESSIBILITY
96 #include "../../widgets/WindowAccessible.h"
97 #endif
98
99 #include "ConfigInterface.h"
100
101 #include <cstring>
102
103 // Put this inclusion last. On Linux it makes some unfortunate pollution of
104 // preprocessor macro name space that interferes with other headers.
105 #if defined(__WXOSX__)
106 #include "VSTControlOSX.h"
107 #elif defined(__WXMSW__)
108 #include "VSTControlMSW.h"
109 #elif defined(__WXGTK__)
110 #include "VSTControlGTK.h"
111 #endif
112
reinterpretAsFloat(uint32_t x)113 static float reinterpretAsFloat(uint32_t x)
114 {
115 static_assert(sizeof(float) == sizeof(uint32_t), "Cannot reinterpret uint32_t to float since sizes are different.");
116 float f;
117 std::memcpy(&f, &x, sizeof(float));
118 return f;
119 }
120
reinterpretAsUint32(float f)121 static uint32_t reinterpretAsUint32(float f)
122 {
123 static_assert(sizeof(float) == sizeof(uint32_t), "Cannot reinterpret float to uint32_t since sizes are different.");
124
125 uint32_t x;
126 std::memcpy(&x, &f, sizeof(uint32_t));
127 return x;
128 }
129
130 // NOTE: To debug the subprocess, use wxLogDebug and, on Windows, Debugview
131 // from TechNet (Sysinternals).
132
133 // ============================================================================
134 //
135 // Module registration entry point
136 //
137 // This is the symbol that Audacity looks for when the module is built as a
138 // dynamic library.
139 //
140 // When the module is builtin to Audacity, we use the same function, but it is
141 // declared static so as not to clash with other builtin modules.
142 //
143 // ============================================================================
DECLARE_MODULE_ENTRY(AudacityModule)144 DECLARE_MODULE_ENTRY(AudacityModule)
145 {
146 // Create our effects module and register
147 // Trust the module manager not to leak this
148 return safenew VSTEffectsModule();
149 }
150
151 // ============================================================================
152 //
153 // Register this as a builtin module
154 //
155 // We also take advantage of the fact that wxModules are initialized before
156 // the wxApp::OnInit() method is called. We check to see if Audacity was
157 // executed to scan a VST effect in a different process.
158 //
159 // ============================================================================
160 DECLARE_BUILTIN_MODULE(VSTBuiltin);
161
162
163 ///////////////////////////////////////////////////////////////////////////////
164 ///
165 /// Auto created at program start up, this initialises VST.
166 ///
167 ///////////////////////////////////////////////////////////////////////////////
168 class VSTSubEntry final : public wxModule
169 {
170 public:
OnInit()171 bool OnInit()
172 {
173 // Have we been started to check a plugin?
174 if (wxTheApp && wxTheApp->argc == 3 && wxStrcmp(wxTheApp->argv[1], VSTCMDKEY) == 0)
175 {
176 // NOTE: This can really hide failures, which is what we want for those pesky
177 // VSTs that are bad or that our support isn't correct. But, it can also
178 // hide Audacity failures in the subprocess, so if you're having an unruley
179 // VST or odd Audacity failures, comment it out and you might get more info.
180 //wxHandleFatalExceptions();
181 VSTEffectsModule::Check(wxTheApp->argv[2]);
182
183 // Returning false causes default processing to display a message box, but we don't
184 // want that so disable logging.
185 wxLog::EnableLogging(false);
186
187 return false;
188 }
189
190 return true;
191 };
192
OnExit()193 void OnExit() {};
194
195 DECLARE_DYNAMIC_CLASS(VSTSubEntry)
196 };
197 IMPLEMENT_DYNAMIC_CLASS(VSTSubEntry, wxModule);
198
199 //----------------------------------------------------------------------------
200 // VSTSubProcess
201 //----------------------------------------------------------------------------
202 #define OUTPUTKEY wxT("<VSTLOADCHK>-")
203 enum InfoKeys
204 {
205 kKeySubIDs,
206 kKeyBegin,
207 kKeyName,
208 kKeyPath,
209 kKeyVendor,
210 kKeyVersion,
211 kKeyDescription,
212 kKeyEffectType,
213 kKeyInteractive,
214 kKeyAutomatable,
215 kKeyEnd
216 };
217
218
219 ///////////////////////////////////////////////////////////////////////////////
220 ///
221 /// Information about one VST effect.
222 ///
223 ///////////////////////////////////////////////////////////////////////////////
224 class VSTSubProcess final : public wxProcess,
225 public EffectDefinitionInterface
226 {
227 public:
VSTSubProcess()228 VSTSubProcess()
229 {
230 Redirect();
231 }
232
233 // EffectClientInterface implementation
234
GetPath()235 PluginPath GetPath() override
236 {
237 return mPath;
238 }
239
GetSymbol()240 ComponentInterfaceSymbol GetSymbol() override
241 {
242 return mName;
243 }
244
GetVendor()245 VendorSymbol GetVendor() override
246 {
247 return { mVendor };
248 }
249
GetVersion()250 wxString GetVersion() override
251 {
252 return mVersion;
253 }
254
GetDescription()255 TranslatableString GetDescription() override
256 {
257 return mDescription;
258 }
259
GetFamily()260 EffectFamilySymbol GetFamily() override
261 {
262 return VSTPLUGINTYPE;
263 }
264
GetType()265 EffectType GetType() override
266 {
267 return mType;
268 }
269
IsInteractive()270 bool IsInteractive() override
271 {
272 return mInteractive;
273 }
274
IsDefault()275 bool IsDefault() override
276 {
277 return false;
278 }
279
IsLegacy()280 bool IsLegacy() override
281 {
282 return false;
283 }
284
SupportsRealtime()285 bool SupportsRealtime() override
286 {
287 return mType == EffectTypeProcess;
288 }
289
SupportsAutomation()290 bool SupportsAutomation() override
291 {
292 return mAutomatable;
293 }
294
295 public:
296 wxString mPath;
297 wxString mName;
298 wxString mVendor;
299 wxString mVersion;
300 TranslatableString mDescription;
301 EffectType mType;
302 bool mInteractive;
303 bool mAutomatable;
304 };
305
306 // ============================================================================
307 //
308 // VSTEffectsModule
309 //
310 // ============================================================================
VSTEffectsModule()311 VSTEffectsModule::VSTEffectsModule()
312 {
313 }
314
~VSTEffectsModule()315 VSTEffectsModule::~VSTEffectsModule()
316 {
317 }
318
319 // ============================================================================
320 // ComponentInterface implementation
321 // ============================================================================
322
GetPath()323 PluginPath VSTEffectsModule::GetPath()
324 {
325 return {};
326 }
327
GetSymbol()328 ComponentInterfaceSymbol VSTEffectsModule::GetSymbol()
329 {
330 return XO("VST Effects");
331 }
332
GetVendor()333 VendorSymbol VSTEffectsModule::GetVendor()
334 {
335 return XO("The Audacity Team");
336 }
337
GetVersion()338 wxString VSTEffectsModule::GetVersion()
339 {
340 // This "may" be different if this were to be maintained as a separate DLL
341 return AUDACITY_VERSION_STRING;
342 }
343
GetDescription()344 TranslatableString VSTEffectsModule::GetDescription()
345 {
346 return XO("Adds the ability to use VST effects in Audacity.");
347 }
348
349 // ============================================================================
350 // ModuleInterface implementation
351 // ============================================================================
352
Initialize()353 bool VSTEffectsModule::Initialize()
354 {
355 // Nothing to do here
356 return true;
357 }
358
Terminate()359 void VSTEffectsModule::Terminate()
360 {
361 // Nothing to do here
362 return;
363 }
364
GetOptionalFamilySymbol()365 EffectFamilySymbol VSTEffectsModule::GetOptionalFamilySymbol()
366 {
367 #if USE_VST
368 return VSTPLUGINTYPE;
369 #else
370 return {};
371 #endif
372 }
373
GetFileExtensions()374 const FileExtensions &VSTEffectsModule::GetFileExtensions()
375 {
376 static FileExtensions result{{ _T("vst") }};
377 return result;
378 }
379
InstallPath()380 FilePath VSTEffectsModule::InstallPath()
381 {
382 // Not yet ready for VST drag-and-drop...
383 // return FileNames::PlugInDir();
384
385 return {};
386 }
387
AutoRegisterPlugins(PluginManagerInterface & WXUNUSED (pm))388 bool VSTEffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED(pm))
389 {
390 // We don't auto-register
391 return true;
392 }
393
FindPluginPaths(PluginManagerInterface & pm)394 PluginPaths VSTEffectsModule::FindPluginPaths(PluginManagerInterface & pm)
395 {
396 FilePaths pathList;
397 FilePaths files;
398
399 // Check for the VST_PATH environment variable
400 wxString vstpath = wxString::FromUTF8(getenv("VST_PATH"));
401 if (!vstpath.empty())
402 {
403 wxStringTokenizer tok(vstpath, wxPATH_SEP);
404 while (tok.HasMoreTokens())
405 {
406 pathList.push_back(tok.GetNextToken());
407 }
408 }
409
410 #if defined(__WXMAC__)
411 #define VSTPATH wxT("/Library/Audio/Plug-Ins/VST")
412
413 // Look in ~/Library/Audio/Plug-Ins/VST and /Library/Audio/Plug-Ins/VST
414 pathList.push_back(wxGetHomeDir() + wxFILE_SEP_PATH + VSTPATH);
415 pathList.push_back(VSTPATH);
416
417 // Recursively search all paths for Info.plist files. This will identify all
418 // bundles.
419 pm.FindFilesInPathList(wxT("Info.plist"), pathList, files, true);
420
421 // Remove the 'Contents/Info.plist' portion of the names
422 for (size_t i = 0; i < files.size(); i++)
423 {
424 files[i] = wxPathOnly(wxPathOnly(files[i]));
425 if (!files[i].EndsWith(wxT(".vst")))
426 {
427 files.erase( files.begin() + i-- );
428 }
429 }
430
431 #elif defined(__WXMSW__)
432
433 TCHAR dpath[MAX_PATH];
434 TCHAR tpath[MAX_PATH];
435 DWORD len;
436
437 // Try HKEY_CURRENT_USER registry key first
438 len = WXSIZEOF(tpath);
439 if (SHRegGetUSValue(wxT("Software\\VST"),
440 wxT("VSTPluginsPath"),
441 NULL,
442 tpath,
443 &len,
444 FALSE,
445 NULL,
446 0) == ERROR_SUCCESS)
447 {
448 tpath[len] = 0;
449 dpath[0] = 0;
450 ExpandEnvironmentStrings(tpath, dpath, WXSIZEOF(dpath));
451 pathList.push_back(dpath);
452 }
453
454 // Then try HKEY_LOCAL_MACHINE registry key
455 len = WXSIZEOF(tpath);
456 if (SHRegGetUSValue(wxT("Software\\VST"),
457 wxT("VSTPluginsPath"),
458 NULL,
459 tpath,
460 &len,
461 TRUE,
462 NULL,
463 0) == ERROR_SUCCESS)
464 {
465 tpath[len] = 0;
466 dpath[0] = 0;
467 ExpandEnvironmentStrings(tpath, dpath, WXSIZEOF(dpath));
468 pathList.push_back(dpath);
469 }
470
471 // Add the default path last
472 dpath[0] = 0;
473 ExpandEnvironmentStrings(wxT("%ProgramFiles%\\Steinberg\\VSTPlugins"),
474 dpath,
475 WXSIZEOF(dpath));
476 pathList.push_back(dpath);
477
478 // Recursively scan for all DLLs
479 pm.FindFilesInPathList(wxT("*.dll"), pathList, files, true);
480
481 #else
482
483 // Nothing specified in the VST_PATH environment variable...provide defaults
484 if (vstpath.empty())
485 {
486 // We add this "non-default" one
487 pathList.push_back(wxT(LIBDIR) wxT("/vst"));
488
489 // These are the defaults used by other hosts
490 pathList.push_back(wxT("/usr/lib/vst"));
491 pathList.push_back(wxT("/usr/local/lib/vst"));
492 pathList.push_back(wxGetHomeDir() + wxFILE_SEP_PATH + wxT(".vst"));
493 }
494
495 // Recursively scan for all shared objects
496 pm.FindFilesInPathList(wxT("*.so"), pathList, files, true);
497
498 #endif
499
500 return { files.begin(), files.end() };
501 }
502
DiscoverPluginsAtPath(const PluginPath & path,TranslatableString & errMsg,const RegistrationCallback & callback)503 unsigned VSTEffectsModule::DiscoverPluginsAtPath(
504 const PluginPath & path, TranslatableString &errMsg,
505 const RegistrationCallback &callback)
506 {
507 bool error = false;
508 unsigned nFound = 0;
509 errMsg = {};
510 // TODO: Fix this for external usage
511 const auto &cmdpath = PlatformCompatibility::GetExecutablePath();
512
513 wxString effectIDs = wxT("0;");
514 wxStringTokenizer effectTzr(effectIDs, wxT(";"));
515
516 Optional<ProgressDialog> progress{};
517 size_t idCnt = 0;
518 size_t idNdx = 0;
519
520 bool cont = true;
521
522 while (effectTzr.HasMoreTokens() && cont)
523 {
524 wxString effectID = effectTzr.GetNextToken();
525
526 wxString cmd;
527 cmd.Printf(wxT("\"%s\" %s \"%s;%s\""), cmdpath, VSTCMDKEY, path, effectID);
528
529 VSTSubProcess proc;
530 try
531 {
532 int flags = wxEXEC_SYNC | wxEXEC_NODISABLE;
533 #if defined(__WXMSW__)
534 flags += wxEXEC_NOHIDE;
535 #endif
536 wxExecute(cmd, flags, &proc);
537 }
538 catch (...)
539 {
540 wxLogMessage(wxT("VST plugin registration failed for %s\n"), path);
541 error = true;
542 }
543
544 wxString output;
545 wxStringOutputStream ss(&output);
546 proc.GetInputStream()->Read(ss);
547
548 int keycount = 0;
549 bool haveBegin = false;
550 wxStringTokenizer tzr(output, wxT("\n"));
551 while (tzr.HasMoreTokens())
552 {
553 wxString line = tzr.GetNextToken();
554
555 // Our output may follow any output the plugin may have written.
556 if (!line.StartsWith(OUTPUTKEY))
557 {
558 continue;
559 }
560
561 long key;
562 if (!line.Mid(wxStrlen(OUTPUTKEY)).BeforeFirst(wxT('=')).ToLong(&key))
563 {
564 continue;
565 }
566 wxString val = line.AfterFirst(wxT('=')).BeforeFirst(wxT('\r'));
567
568 switch (key)
569 {
570 case kKeySubIDs:
571 effectIDs = val;
572 effectTzr.Reinit(effectIDs);
573 idCnt = effectTzr.CountTokens();
574 if (idCnt > 3)
575 {
576 progress.emplace( XO("Scanning Shell VST"),
577 XO("Registering %d of %d: %-64.64s")
578 .Format( 0, idCnt, proc.GetSymbol().Translation())
579 /*
580 , wxPD_APP_MODAL |
581 wxPD_AUTO_HIDE |
582 wxPD_CAN_ABORT |
583 wxPD_ELAPSED_TIME |
584 wxPD_ESTIMATED_TIME |
585 wxPD_REMAINING_TIME
586 */
587 );
588 progress->Show();
589 }
590 break;
591
592 case kKeyBegin:
593 haveBegin = true;
594 keycount++;
595 break;
596
597 case kKeyName:
598 proc.mName = val;
599 keycount++;
600 break;
601
602 case kKeyPath:
603 proc.mPath = val;
604 keycount++;
605 break;
606
607 case kKeyVendor:
608 proc.mVendor = val;
609 keycount++;
610 break;
611
612 case kKeyVersion:
613 proc.mVersion = val;
614 keycount++;
615 break;
616
617 case kKeyDescription:
618 proc.mDescription = Verbatim( val );
619 keycount++;
620 break;
621
622 case kKeyEffectType:
623 long type;
624 val.ToLong(&type);
625 proc.mType = (EffectType) type;
626 keycount++;
627 break;
628
629 case kKeyInteractive:
630 proc.mInteractive = val == wxT("1");
631 keycount++;
632 break;
633
634 case kKeyAutomatable:
635 proc.mAutomatable = val == wxT("1");
636 keycount++;
637 break;
638
639 case kKeyEnd:
640 {
641 if (!haveBegin || ++keycount != kKeyEnd)
642 {
643 keycount = 0;
644 haveBegin = false;
645 continue;
646 }
647
648 bool skip = false;
649 if (progress)
650 {
651 idNdx++;
652 auto result = progress->Update((int)idNdx, (int)idCnt,
653 XO("Registering %d of %d: %-64.64s")
654 .Format( idNdx, idCnt, proc.GetSymbol().Translation() ));
655 cont = (result == ProgressResult::Success);
656 }
657
658 if (!skip && cont)
659 {
660 if (callback)
661 callback( this, &proc );
662 ++nFound;
663 }
664 }
665 break;
666
667 default:
668 keycount = 0;
669 haveBegin = false;
670 break;
671 }
672 }
673 }
674
675 if (error)
676 errMsg = XO("Could not load the library");
677
678 return nFound;
679 }
680
IsPluginValid(const PluginPath & path,bool bFast)681 bool VSTEffectsModule::IsPluginValid(const PluginPath & path, bool bFast)
682 {
683 if( bFast )
684 return true;
685 wxString realPath = path.BeforeFirst(wxT(';'));
686 return wxFileName::FileExists(realPath) || wxFileName::DirExists(realPath);
687 }
688
689 std::unique_ptr<ComponentInterface>
CreateInstance(const PluginPath & path)690 VSTEffectsModule::CreateInstance(const PluginPath & path)
691 {
692 // Acquires a resource for the application.
693 // For us, the ID is simply the path to the effect
694 return std::make_unique<VSTEffect>(path);
695 }
696
697 // ============================================================================
698 // ModuleEffectInterface implementation
699 // ============================================================================
700
701 // ============================================================================
702 // VSTEffectsModule implementation
703 // ============================================================================
704
705 // static
706 //
707 // Called from reinvokation of Audacity or DLL to check in a separate process
Check(const wxChar * path)708 void VSTEffectsModule::Check(const wxChar *path)
709 {
710 VSTEffect effect(path);
711 if (effect.SetHost(NULL))
712 {
713 auto effectIDs = effect.GetEffectIDs();
714 wxString out;
715
716 if (effectIDs.size() > 0)
717 {
718 wxString subids;
719
720 for (size_t i = 0, cnt = effectIDs.size(); i < cnt; i++)
721 {
722 subids += wxString::Format(wxT("%d;"), effectIDs[i]);
723 }
724
725 out = wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeySubIDs, subids.RemoveLast());
726 }
727 else
728 {
729 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyBegin, wxEmptyString);
730 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyPath, effect.GetPath());
731 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyName, effect.GetSymbol().Internal());
732 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyVendor,
733 effect.GetVendor().Internal());
734 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyVersion, effect.GetVersion());
735 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyDescription, effect.GetDescription().Translation());
736 out += wxString::Format(wxT("%s%d=%d\n"), OUTPUTKEY, kKeyEffectType, effect.GetType());
737 out += wxString::Format(wxT("%s%d=%d\n"), OUTPUTKEY, kKeyInteractive, effect.IsInteractive());
738 out += wxString::Format(wxT("%s%d=%d\n"), OUTPUTKEY, kKeyAutomatable, effect.SupportsAutomation());
739 out += wxString::Format(wxT("%s%d=%s\n"), OUTPUTKEY, kKeyEnd, wxEmptyString);
740 }
741
742 // We want to output info in one chunk to prevent output
743 // from the effect intermixing with the info
744 const wxCharBuffer buf = out.ToUTF8();
745 fwrite(buf, 1, strlen(buf), stdout);
746 fflush(stdout);
747 }
748 }
749
750 ///////////////////////////////////////////////////////////////////////////////
751 //
752 // Dialog for configuring latency, buffer size and graphics mode for a
753 // VST effect.
754 //
755 ///////////////////////////////////////////////////////////////////////////////
756 class VSTEffectOptionsDialog final : public wxDialogWrapper
757 {
758 public:
759 VSTEffectOptionsDialog(wxWindow * parent, EffectHostInterface *host);
760 virtual ~VSTEffectOptionsDialog();
761
762 void PopulateOrExchange(ShuttleGui & S);
763
764 void OnOk(wxCommandEvent & evt);
765
766 private:
767 EffectHostInterface *mHost;
768 int mBufferSize;
769 bool mUseLatency;
770 bool mUseGUI;
771
772 DECLARE_EVENT_TABLE()
773 };
774
BEGIN_EVENT_TABLE(VSTEffectOptionsDialog,wxDialogWrapper)775 BEGIN_EVENT_TABLE(VSTEffectOptionsDialog, wxDialogWrapper)
776 EVT_BUTTON(wxID_OK, VSTEffectOptionsDialog::OnOk)
777 END_EVENT_TABLE()
778
779 VSTEffectOptionsDialog::VSTEffectOptionsDialog(wxWindow * parent, EffectHostInterface *host)
780 : wxDialogWrapper(parent, wxID_ANY, XO("VST Effect Options"))
781 {
782 mHost = host;
783
784 mHost->GetSharedConfig(wxT("Options"), wxT("BufferSize"), mBufferSize, 8192);
785 mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
786 mHost->GetSharedConfig(wxT("Options"), wxT("UseGUI"), mUseGUI, true);
787
788 ShuttleGui S(this, eIsCreating);
789 PopulateOrExchange(S);
790 }
791
~VSTEffectOptionsDialog()792 VSTEffectOptionsDialog::~VSTEffectOptionsDialog()
793 {
794 }
795
PopulateOrExchange(ShuttleGui & S)796 void VSTEffectOptionsDialog::PopulateOrExchange(ShuttleGui & S)
797 {
798 S.SetBorder(5);
799 S.StartHorizontalLay(wxEXPAND, 1);
800 {
801 S.StartVerticalLay(false);
802 {
803 S.StartStatic(XO("Buffer Size"));
804 {
805 S.AddVariableText( XO(
806 "The buffer size controls the number of samples sent to the effect "
807 "on each iteration. Smaller values will cause slower processing and "
808 "some effects require 8192 samples or less to work properly. However "
809 "most effects can accept large buffers and using them will greatly "
810 "reduce processing time."),
811 false, 0, 650);
812
813 S.StartHorizontalLay(wxALIGN_LEFT);
814 {
815 wxTextCtrl *t;
816 t = S.Validator<IntegerValidator<int>>(
817 &mBufferSize, NumValidatorStyle::DEFAULT, 8, 1048576 * 1)
818 .MinSize( { 100, -1 } )
819 .TieNumericTextBox(XXO("&Buffer Size (8 to 1048576 samples):"),
820 mBufferSize,
821 12);
822 }
823 S.EndHorizontalLay();
824 }
825 S.EndStatic();
826
827 S.StartStatic(XO("Latency Compensation"));
828 {
829 S.AddVariableText( XO(
830 "As part of their processing, some VST effects must delay returning "
831 "audio to Audacity. When not compensating for this delay, you will "
832 "notice that small silences have been inserted into the audio. "
833 "Enabling this option will provide that compensation, but it may "
834 "not work for all VST effects."),
835 false, 0, 650);
836
837 S.StartHorizontalLay(wxALIGN_LEFT);
838 {
839 S.TieCheckBox(XXO("Enable &compensation"),
840 mUseLatency);
841 }
842 S.EndHorizontalLay();
843 }
844 S.EndStatic();
845
846 S.StartStatic(XO("Graphical Mode"));
847 {
848 S.AddVariableText( XO(
849 "Most VST effects have a graphical interface for setting parameter values."
850 " A basic text-only method is also available. "
851 " Reopen the effect for this to take effect."),
852 false, 0, 650);
853 S.TieCheckBox(XXO("Enable &graphical interface"),
854 mUseGUI);
855 }
856 S.EndStatic();
857 }
858 S.EndVerticalLay();
859 }
860 S.EndHorizontalLay();
861
862 S.AddStandardButtons();
863
864 Layout();
865 Fit();
866 Center();
867 }
868
OnOk(wxCommandEvent & WXUNUSED (evt))869 void VSTEffectOptionsDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
870 {
871 if (!Validate())
872 {
873 return;
874 }
875
876 ShuttleGui S(this, eIsGettingFromDialog);
877 PopulateOrExchange(S);
878
879 mHost->SetSharedConfig(wxT("Options"), wxT("BufferSize"), mBufferSize);
880 mHost->SetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency);
881 mHost->SetSharedConfig(wxT("Options"), wxT("UseGUI"), mUseGUI);
882
883 EndModal(wxID_OK);
884 }
885
886 ///////////////////////////////////////////////////////////////////////////////
887 ///
888 /// Wrapper for wxTimer that calls a VST effect at regular intervals.
889 ///
890 /// \todo should there be tests for no timer available?
891 ///
892 ///////////////////////////////////////////////////////////////////////////////
893 class VSTEffectTimer final : public wxTimer
894 {
895 public:
VSTEffectTimer(VSTEffect * effect)896 VSTEffectTimer(VSTEffect *effect)
897 : wxTimer(),
898 mEffect(effect)
899 {
900 }
901
~VSTEffectTimer()902 ~VSTEffectTimer()
903 {
904 }
905
Notify()906 void Notify()
907 {
908 mEffect->OnTimer();
909 }
910
911 private:
912 VSTEffect *mEffect;
913 };
914
915 ///////////////////////////////////////////////////////////////////////////////
916 //
917 // VSTEffect
918 //
919 ///////////////////////////////////////////////////////////////////////////////
920 enum
921 {
922 ID_Duration = 20000,
923 ID_Sliders = 21000,
924 };
925
926 DEFINE_LOCAL_EVENT_TYPE(EVT_SIZEWINDOW);
927 DEFINE_LOCAL_EVENT_TYPE(EVT_UPDATEDISPLAY);
928
929 BEGIN_EVENT_TABLE(VSTEffect, wxEvtHandler)
930 EVT_COMMAND_RANGE(ID_Sliders, ID_Sliders + 999, wxEVT_COMMAND_SLIDER_UPDATED, VSTEffect::OnSlider)
931
932 // Events from the audioMaster callback
933 EVT_COMMAND(wxID_ANY, EVT_SIZEWINDOW, VSTEffect::OnSizeWindow)
934 END_EVENT_TABLE()
935
936 // Needed to support shell plugins...sucks, but whatcha gonna do???
937 intptr_t VSTEffect::mCurrentEffectID;
938
939 typedef AEffect *(*vstPluginMain)(audioMasterCallback audioMaster);
940
AudioMaster(AEffect * effect,int32_t opcode,int32_t index,intptr_t value,void * ptr,float opt)941 intptr_t VSTEffect::AudioMaster(AEffect * effect,
942 int32_t opcode,
943 int32_t index,
944 intptr_t value,
945 void * ptr,
946 float opt)
947 {
948 VSTEffect *vst = (effect ? (VSTEffect *) effect->ptr2 : NULL);
949
950 // Handles operations during initialization...before VSTEffect has had a
951 // chance to set its instance pointer.
952 switch (opcode)
953 {
954 case audioMasterVersion:
955 return (intptr_t) 2400;
956
957 case audioMasterCurrentId:
958 return mCurrentEffectID;
959
960 case audioMasterGetVendorString:
961 strcpy((char *) ptr, "Audacity Team"); // Do not translate, max 64 + 1 for null terminator
962 return 1;
963
964 case audioMasterGetProductString:
965 strcpy((char *) ptr, "Audacity"); // Do not translate, max 64 + 1 for null terminator
966 return 1;
967
968 case audioMasterGetVendorVersion:
969 return (intptr_t) (AUDACITY_VERSION << 24 |
970 AUDACITY_RELEASE << 16 |
971 AUDACITY_REVISION << 8 |
972 AUDACITY_MODLEVEL);
973
974 // Some (older) effects depend on an effIdle call when requested. An
975 // example is the Antress Modern plugins which uses the call to update
976 // the editors display when the program (preset) changes.
977 case audioMasterNeedIdle:
978 if (vst)
979 {
980 vst->NeedIdle();
981 return 1;
982 }
983 return 0;
984
985 // We would normally get this if the effect editor is dipslayed and something "major"
986 // has changed (like a program change) instead of multiple automation calls.
987 // Since we don't do anything with the parameters while the editor is displayed,
988 // there's no need for us to do anything.
989 case audioMasterUpdateDisplay:
990 if (vst)
991 {
992 vst->UpdateDisplay();
993 return 1;
994 }
995 return 0;
996
997 // Return the current time info.
998 case audioMasterGetTime:
999 if (vst)
1000 {
1001 return (intptr_t) vst->GetTimeInfo();
1002 }
1003 return 0;
1004
1005 // Inputs, outputs, or initial delay has changed...all we care about is initial delay.
1006 case audioMasterIOChanged:
1007 if (vst)
1008 {
1009 vst->SetBufferDelay(effect->initialDelay);
1010 return 1;
1011 }
1012 return 0;
1013
1014 case audioMasterGetSampleRate:
1015 if (vst)
1016 {
1017 return (intptr_t) vst->GetSampleRate();
1018 }
1019 return 0;
1020
1021 case audioMasterIdle:
1022 wxYieldIfNeeded();
1023 return 1;
1024
1025 case audioMasterGetCurrentProcessLevel:
1026 if (vst)
1027 {
1028 return vst->GetProcessLevel();
1029 }
1030 return 0;
1031
1032 case audioMasterGetLanguage:
1033 return kVstLangEnglish;
1034
1035 // We always replace, never accumulate
1036 case audioMasterWillReplaceOrAccumulate:
1037 return 1;
1038
1039 // Resize the window to accommodate the effect size
1040 case audioMasterSizeWindow:
1041 if (vst)
1042 {
1043 vst->SizeWindow(index, value);
1044 }
1045 return 1;
1046
1047 case audioMasterCanDo:
1048 {
1049 char *s = (char *) ptr;
1050 if (strcmp(s, "acceptIOChanges") == 0 ||
1051 strcmp(s, "sendVstTimeInfo") == 0 ||
1052 strcmp(s, "startStopProcess") == 0 ||
1053 strcmp(s, "shellCategory") == 0 ||
1054 strcmp(s, "sizeWindow") == 0)
1055 {
1056 return 1;
1057 }
1058
1059 #if defined(VST_DEBUG)
1060 #if defined(__WXMSW__)
1061 wxLogDebug(wxT("VST canDo: %s"), wxString::FromAscii((char *)ptr));
1062 #else
1063 wxPrintf(wxT("VST canDo: %s\n"), wxString::FromAscii((char *)ptr));
1064 #endif
1065 #endif
1066
1067 return 0;
1068 }
1069
1070 case audioMasterBeginEdit:
1071 case audioMasterEndEdit:
1072 return 0;
1073
1074 case audioMasterAutomate:
1075 if (vst)
1076 {
1077 vst->Automate(index, opt);
1078 }
1079 return 0;
1080
1081 // We're always connected (sort of)
1082 case audioMasterPinConnected:
1083
1084 // We don't do MIDI yet
1085 case audioMasterWantMidi:
1086 case audioMasterProcessEvents:
1087
1088 // Don't need to see any messages about these
1089 return 0;
1090 }
1091
1092 #if defined(VST_DEBUG)
1093 #if defined(__WXMSW__)
1094 wxLogDebug(wxT("vst: %p opcode: %d index: %d value: %p ptr: %p opt: %f user: %p"),
1095 effect, (int) opcode, (int) index, (void *) value, ptr, opt, vst);
1096 #else
1097 wxPrintf(wxT("vst: %p opcode: %d index: %d value: %p ptr: %p opt: %f user: %p\n"),
1098 effect, (int) opcode, (int) index, (void *) value, ptr, opt, vst);
1099 #endif
1100 #endif
1101
1102 return 0;
1103 }
1104
1105 #if !defined(__WXMSW__)
operator ()(void * p) const1106 void VSTEffect::ModuleDeleter::operator() (void* p) const
1107 {
1108 if (p)
1109 dlclose(p);
1110 }
1111 #endif
1112
1113 #if defined(__WXMAC__)
operator ()(void * p) const1114 void VSTEffect::BundleDeleter::operator() (void* p) const
1115 {
1116 if (p)
1117 CFRelease(static_cast<CFBundleRef>(p));
1118 }
1119
reset()1120 void VSTEffect::ResourceHandle::reset()
1121 {
1122 if (mpHandle)
1123 CFBundleCloseBundleResourceMap(mpHandle, mNum);
1124 mpHandle = nullptr;
1125 mNum = 0;
1126 }
1127 #endif
1128
VSTEffect(const PluginPath & path,VSTEffect * master)1129 VSTEffect::VSTEffect(const PluginPath & path, VSTEffect *master)
1130 : mPath(path),
1131 mMaster(master)
1132 {
1133 mHost = NULL;
1134 mModule = NULL;
1135 mAEffect = NULL;
1136 mDialog = NULL;
1137
1138 mTimer = std::make_unique<VSTEffectTimer>(this);
1139 mTimerGuard = 0;
1140
1141 mInteractive = false;
1142 mAudioIns = 0;
1143 mAudioOuts = 0;
1144 mMidiIns = 0;
1145 mMidiOuts = 0;
1146 mSampleRate = 44100;
1147 mBlockSize = mUserBlockSize = 8192;
1148 mBufferDelay = 0;
1149 mProcessLevel = 1; // in GUI thread
1150 mHasPower = false;
1151 mWantsIdle = false;
1152 mWantsEditIdle = false;
1153 mUseLatency = true;
1154 mReady = false;
1155
1156 memset(&mTimeInfo, 0, sizeof(mTimeInfo));
1157 mTimeInfo.samplePos = 0.0;
1158 mTimeInfo.sampleRate = 44100.0; // this is a bogus value, but it's only for the display
1159 mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
1160 mTimeInfo.tempo = 120.0;
1161 mTimeInfo.timeSigNumerator = 4;
1162 mTimeInfo.timeSigDenominator = 4;
1163 mTimeInfo.flags = kVstTempoValid | kVstNanosValid;
1164
1165 // UI
1166
1167 mGui = false;
1168 mContainer = NULL;
1169
1170 // If we're a slave then go ahead a load immediately
1171 if (mMaster)
1172 {
1173 Load();
1174 }
1175 }
1176
~VSTEffect()1177 VSTEffect::~VSTEffect()
1178 {
1179 if (mDialog)
1180 {
1181 mDialog->Close();
1182 }
1183
1184 Unload();
1185 }
1186
1187 // ============================================================================
1188 // ComponentInterface Implementation
1189 // ============================================================================
1190
GetPath()1191 PluginPath VSTEffect::GetPath()
1192 {
1193 return mPath;
1194 }
1195
GetSymbol()1196 ComponentInterfaceSymbol VSTEffect::GetSymbol()
1197 {
1198 return mName;
1199 }
1200
GetVendor()1201 VendorSymbol VSTEffect::GetVendor()
1202 {
1203 return { mVendor };
1204 }
1205
GetVersion()1206 wxString VSTEffect::GetVersion()
1207 {
1208 wxString version;
1209
1210 bool skipping = true;
1211 for (int i = 0, s = 0; i < 4; i++, s += 8)
1212 {
1213 int dig = (mVersion >> s) & 0xff;
1214 if (dig != 0 || !skipping)
1215 {
1216 version += !skipping ? wxT(".") : wxT("");
1217 version += wxString::Format(wxT("%d"), dig);
1218 skipping = false;
1219 }
1220 }
1221
1222 return version;
1223 }
1224
GetDescription()1225 TranslatableString VSTEffect::GetDescription()
1226 {
1227 // VST does have a product string opcode and some effects return a short
1228 // description, but most do not or they just return the name again. So,
1229 // try to provide some sort of useful information.
1230 return XO("Audio In: %d, Audio Out: %d").Format( mAudioIns, mAudioOuts );
1231 }
1232
1233 // ============================================================================
1234 // EffectDefinitionInterface Implementation
1235 // ============================================================================
1236
GetType()1237 EffectType VSTEffect::GetType()
1238 {
1239 if (mAudioIns == 0 && mAudioOuts == 0 && mMidiIns == 0 && mMidiOuts == 0)
1240 {
1241 return EffectTypeTool;
1242 }
1243
1244 if (mAudioIns == 0 && mMidiIns == 0)
1245 {
1246 return EffectTypeGenerate;
1247 }
1248
1249 if (mAudioOuts == 0 && mMidiOuts == 0)
1250 {
1251 return EffectTypeAnalyze;
1252 }
1253
1254 return EffectTypeProcess;
1255 }
1256
1257
GetFamily()1258 EffectFamilySymbol VSTEffect::GetFamily()
1259 {
1260 return VSTPLUGINTYPE;
1261 }
1262
IsInteractive()1263 bool VSTEffect::IsInteractive()
1264 {
1265 return mInteractive;
1266 }
1267
IsDefault()1268 bool VSTEffect::IsDefault()
1269 {
1270 return false;
1271 }
1272
IsLegacy()1273 bool VSTEffect::IsLegacy()
1274 {
1275 return false;
1276 }
1277
SupportsRealtime()1278 bool VSTEffect::SupportsRealtime()
1279 {
1280 return GetType() == EffectTypeProcess;
1281 }
1282
SupportsAutomation()1283 bool VSTEffect::SupportsAutomation()
1284 {
1285 return mAutomatable;
1286 }
1287
1288 // ============================================================================
1289 // EffectClientInterface Implementation
1290 // ============================================================================
1291
SetHost(EffectHostInterface * host)1292 bool VSTEffect::SetHost(EffectHostInterface *host)
1293 {
1294 mHost = host;
1295
1296 if (!mAEffect)
1297 {
1298 Load();
1299 }
1300
1301 if (!mAEffect)
1302 {
1303 return false;
1304 }
1305
1306 // If we have a master then there's no need to load settings since the master will feed
1307 // us everything we need.
1308 if (mMaster)
1309 {
1310 return true;
1311 }
1312
1313 if (mHost)
1314 {
1315 int userBlockSize;
1316 mHost->GetSharedConfig(wxT("Options"), wxT("BufferSize"), userBlockSize, 8192);
1317 mUserBlockSize = std::max( 1, userBlockSize );
1318 mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
1319
1320 mBlockSize = mUserBlockSize;
1321
1322 bool haveDefaults;
1323 mHost->GetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false);
1324 if (!haveDefaults)
1325 {
1326 SaveParameters(mHost->GetFactoryDefaultsGroup());
1327 mHost->SetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), true);
1328 }
1329
1330 LoadParameters(mHost->GetCurrentSettingsGroup());
1331 }
1332
1333 return true;
1334 }
1335
GetAudioInCount()1336 unsigned VSTEffect::GetAudioInCount()
1337 {
1338 return mAudioIns;
1339 }
1340
GetAudioOutCount()1341 unsigned VSTEffect::GetAudioOutCount()
1342 {
1343 return mAudioOuts;
1344 }
1345
GetMidiInCount()1346 int VSTEffect::GetMidiInCount()
1347 {
1348 return mMidiIns;
1349 }
1350
GetMidiOutCount()1351 int VSTEffect::GetMidiOutCount()
1352 {
1353 return mMidiOuts;
1354 }
1355
SetBlockSize(size_t maxBlockSize)1356 size_t VSTEffect::SetBlockSize(size_t maxBlockSize)
1357 {
1358 mBlockSize = std::min( maxBlockSize, mUserBlockSize );
1359 return mBlockSize;
1360 }
1361
GetBlockSize() const1362 size_t VSTEffect::GetBlockSize() const
1363 {
1364 return mBlockSize;
1365 }
1366
SetSampleRate(double rate)1367 void VSTEffect::SetSampleRate(double rate)
1368 {
1369 mSampleRate = (float) rate;
1370 }
1371
GetLatency()1372 sampleCount VSTEffect::GetLatency()
1373 {
1374 if (mUseLatency)
1375 {
1376 // ??? Threading issue ???
1377 auto delay = mBufferDelay;
1378 mBufferDelay = 0;
1379 return delay;
1380 }
1381
1382 return 0;
1383 }
1384
GetTailSize()1385 size_t VSTEffect::GetTailSize()
1386 {
1387 return 0;
1388 }
1389
IsReady()1390 bool VSTEffect::IsReady()
1391 {
1392 return mReady;
1393 }
1394
ProcessInitialize(sampleCount WXUNUSED (totalLen),ChannelNames WXUNUSED (chanMap))1395 bool VSTEffect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
1396 {
1397 // Initialize time info
1398 memset(&mTimeInfo, 0, sizeof(mTimeInfo));
1399 mTimeInfo.sampleRate = mSampleRate;
1400 mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
1401 mTimeInfo.tempo = 120.0;
1402 mTimeInfo.timeSigNumerator = 4;
1403 mTimeInfo.timeSigDenominator = 4;
1404 mTimeInfo.flags = kVstTempoValid | kVstNanosValid | kVstTransportPlaying;
1405
1406 // Set processing parameters...power must be off for this
1407 callDispatcher(effSetSampleRate, 0, 0, NULL, mSampleRate);
1408 callDispatcher(effSetBlockSize, 0, mBlockSize, NULL, 0.0);
1409
1410 // Turn on the power
1411 PowerOn();
1412
1413 // Set the initial buffer delay
1414 SetBufferDelay(mAEffect->initialDelay);
1415
1416 mReady = true;
1417
1418 return true;
1419 }
1420
ProcessFinalize()1421 bool VSTEffect::ProcessFinalize()
1422 {
1423 mReady = false;
1424
1425 PowerOff();
1426
1427 return true;
1428 }
1429
ProcessBlock(float ** inBlock,float ** outBlock,size_t blockLen)1430 size_t VSTEffect::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
1431 {
1432 // Only call the effect if there's something to do...some do not like zero-length block
1433 if (blockLen)
1434 {
1435 // Go let the plugin moleste the samples
1436 callProcessReplacing(inBlock, outBlock, blockLen);
1437
1438 // And track the position
1439 mTimeInfo.samplePos += (double) blockLen;
1440 }
1441
1442 return blockLen;
1443 }
1444
GetChannelCount()1445 unsigned VSTEffect::GetChannelCount()
1446 {
1447 return mNumChannels;
1448 }
1449
SetChannelCount(unsigned numChannels)1450 void VSTEffect::SetChannelCount(unsigned numChannels)
1451 {
1452 mNumChannels = numChannels;
1453 }
1454
RealtimeInitialize()1455 bool VSTEffect::RealtimeInitialize()
1456 {
1457 mMasterIn.reinit( mAudioIns, mBlockSize, true );
1458 mMasterOut.reinit( mAudioOuts, mBlockSize );
1459
1460 return ProcessInitialize(0, NULL);
1461 }
1462
RealtimeAddProcessor(unsigned numChannels,float sampleRate)1463 bool VSTEffect::RealtimeAddProcessor(unsigned numChannels, float sampleRate)
1464 {
1465 mSlaves.push_back(std::make_unique<VSTEffect>(mPath, this));
1466 VSTEffect *const slave = mSlaves.back().get();
1467
1468 slave->SetBlockSize(mBlockSize);
1469 slave->SetChannelCount(numChannels);
1470 slave->SetSampleRate(sampleRate);
1471
1472 int clen = 0;
1473 if (mAEffect->flags & effFlagsProgramChunks)
1474 {
1475 void *chunk = NULL;
1476
1477 clen = (int) callDispatcher(effGetChunk, 1, 0, &chunk, 0.0); // get master's chunk, for the program only
1478 if (clen != 0)
1479 {
1480 slave->callSetChunk(true, clen, chunk); // copy state to slave, for the program only
1481 }
1482 }
1483
1484 if (clen == 0)
1485 {
1486 callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
1487
1488 for (int i = 0; i < mAEffect->numParams; i++)
1489 {
1490 slave->callSetParameter(i, callGetParameter(i));
1491 }
1492
1493 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
1494 }
1495
1496 return slave->ProcessInitialize(0, NULL);
1497 }
1498
RealtimeFinalize()1499 bool VSTEffect::RealtimeFinalize()
1500 {
1501 for (const auto &slave : mSlaves)
1502 slave->ProcessFinalize();
1503 mSlaves.clear();
1504
1505 mMasterIn.reset();
1506
1507 mMasterOut.reset();
1508
1509 return ProcessFinalize();
1510 }
1511
RealtimeSuspend()1512 bool VSTEffect::RealtimeSuspend()
1513 {
1514 PowerOff();
1515
1516 for (const auto &slave : mSlaves)
1517 slave->PowerOff();
1518
1519 return true;
1520 }
1521
RealtimeResume()1522 bool VSTEffect::RealtimeResume()
1523 {
1524 PowerOn();
1525
1526 for (const auto &slave : mSlaves)
1527 slave->PowerOn();
1528
1529 return true;
1530 }
1531
RealtimeProcessStart()1532 bool VSTEffect::RealtimeProcessStart()
1533 {
1534 for (unsigned int i = 0; i < mAudioIns; i++)
1535 memset(mMasterIn[i].get(), 0, mBlockSize * sizeof(float));
1536
1537 mNumSamples = 0;
1538
1539 return true;
1540 }
1541
RealtimeProcess(int group,float ** inbuf,float ** outbuf,size_t numSamples)1542 size_t VSTEffect::RealtimeProcess(int group, float **inbuf, float **outbuf, size_t numSamples)
1543 {
1544 wxASSERT(numSamples <= mBlockSize);
1545
1546 for (unsigned int c = 0; c < mAudioIns; c++)
1547 {
1548 for (decltype(numSamples) s = 0; s < numSamples; s++)
1549 {
1550 mMasterIn[c][s] += inbuf[c][s];
1551 }
1552 }
1553 mNumSamples = std::max(numSamples, mNumSamples);
1554
1555 return mSlaves[group]->ProcessBlock(inbuf, outbuf, numSamples);
1556 }
1557
RealtimeProcessEnd()1558 bool VSTEffect::RealtimeProcessEnd()
1559 {
1560 // These casts to float** should be safe...
1561 ProcessBlock(
1562 reinterpret_cast <float**> (mMasterIn.get()),
1563 reinterpret_cast <float**> (mMasterOut.get()),
1564 mNumSamples);
1565
1566 return true;
1567 }
1568
1569 ///
1570 /// Some history...
1571 ///
1572 /// Before we ran into the Antress plugin problem with buffer size limitations,
1573 /// (see below) we just had a plain old effect loop...get the input samples, pass
1574 /// them to the effect, save the output samples.
1575 ///
1576 /// But, the hack I put in to limit the buffer size to only 8k (normally 512k or so)
1577 /// severely impacted performance. So, Michael C. added some intermediate buffering
1578 /// that sped things up quite a bit and this is how things have worked for quite a
1579 /// while. It still didn't get the performance back to the pre-hack stage, but it
1580 /// was a definite benefit.
1581 ///
1582 /// History over...
1583 ///
1584 /// I've recently (May 2014) tried newer versions of the Antress effects and they
1585 /// no longer seem to have a problem with buffer size. So, I've made a bit of a
1586 /// compromise...I've made the buffer size user configurable. Should have done this
1587 /// from the beginning. I've left the default 8k, just in case, but now the user
1588 /// can set the buffering based on their specific setup and needs.
1589 ///
1590 /// And at the same time I added buffer delay compensation, which allows Audacity
1591 /// to account for latency introduced by some effects. This is based on information
1592 /// provided by the effect, so it will not work with all effects since they don't
1593 /// all provide the information (kn0ck0ut is one).
1594 ///
ShowInterface(wxWindow & parent,const EffectDialogFactory & factory,bool forceModal)1595 bool VSTEffect::ShowInterface(
1596 wxWindow &parent, const EffectDialogFactory &factory, bool forceModal)
1597 {
1598 if (mDialog)
1599 {
1600 if ( mDialog->Close(true) )
1601 mDialog = nullptr;
1602 return false;
1603 }
1604
1605 // mDialog is null
1606 auto cleanup = valueRestorer( mDialog );
1607
1608 // mProcessLevel = 1; // in GUI thread
1609
1610 // Set some defaults since some VSTs need them...these will be reset when
1611 // normal or realtime processing begins
1612 if (!IsReady())
1613 {
1614 mSampleRate = 44100;
1615 mBlockSize = 8192;
1616 ProcessInitialize(0, NULL);
1617 }
1618
1619 if ( factory )
1620 mDialog = factory(parent, mHost, this);
1621 if (!mDialog)
1622 {
1623 return false;
1624 }
1625 mDialog->CentreOnParent();
1626
1627 if (SupportsRealtime() && !forceModal)
1628 {
1629 mDialog->Show();
1630 cleanup.release();
1631
1632 return false;
1633 }
1634
1635 bool res = mDialog->ShowModal() != 0;
1636
1637 return res;
1638 }
1639
GetAutomationParameters(CommandParameters & parms)1640 bool VSTEffect::GetAutomationParameters(CommandParameters & parms)
1641 {
1642 for (int i = 0; i < mAEffect->numParams; i++)
1643 {
1644 wxString name = GetString(effGetParamName, i);
1645 if (name.empty())
1646 {
1647 name.Printf(wxT("parm_%d"), i);
1648 }
1649
1650 float value = callGetParameter(i);
1651 if (!parms.Write(name, value))
1652 {
1653 return false;
1654 }
1655 }
1656
1657 return true;
1658 }
1659
SetAutomationParameters(CommandParameters & parms)1660 bool VSTEffect::SetAutomationParameters(CommandParameters & parms)
1661 {
1662 callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
1663 for (int i = 0; i < mAEffect->numParams; i++)
1664 {
1665 wxString name = GetString(effGetParamName, i);
1666 if (name.empty())
1667 {
1668 name.Printf(wxT("parm_%d"), i);
1669 }
1670
1671 double d = 0.0;
1672 if (!parms.Read(name, &d))
1673 {
1674 return false;
1675 }
1676
1677 if (d >= -1.0 && d <= 1.0)
1678 {
1679 callSetParameter(i, d);
1680 for (const auto &slave : mSlaves)
1681 slave->callSetParameter(i, d);
1682 }
1683 }
1684 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
1685
1686 return true;
1687 }
1688
1689
LoadUserPreset(const RegistryPath & name)1690 bool VSTEffect::LoadUserPreset(const RegistryPath & name)
1691 {
1692 if (!LoadParameters(name))
1693 {
1694 return false;
1695 }
1696
1697 RefreshParameters();
1698
1699 return true;
1700 }
1701
SaveUserPreset(const RegistryPath & name)1702 bool VSTEffect::SaveUserPreset(const RegistryPath & name)
1703 {
1704 return SaveParameters(name);
1705 }
1706
GetFactoryPresets()1707 RegistryPaths VSTEffect::GetFactoryPresets()
1708 {
1709 RegistryPaths progs;
1710
1711 // Some plugins, like Guitar Rig 5, only report 128 programs while they have hundreds. While
1712 // I was able to come up with a hack in the Guitar Rig case to gather all of the program names
1713 // it would not let me set a program outside of the first 128.
1714 if (mVstVersion >= 2)
1715 {
1716 for (int i = 0; i < mAEffect->numPrograms; i++)
1717 {
1718 progs.push_back(GetString(effGetProgramNameIndexed, i));
1719 }
1720 }
1721
1722 return progs;
1723 }
1724
LoadFactoryPreset(int id)1725 bool VSTEffect::LoadFactoryPreset(int id)
1726 {
1727 callSetProgram(id);
1728
1729 RefreshParameters();
1730
1731 return true;
1732 }
1733
LoadFactoryDefaults()1734 bool VSTEffect::LoadFactoryDefaults()
1735 {
1736 if (!LoadParameters(mHost->GetFactoryDefaultsGroup()))
1737 {
1738 return false;
1739 }
1740
1741 RefreshParameters();
1742
1743 return true;
1744 }
1745
1746 // ============================================================================
1747 // EffectUIClientInterface implementation
1748 // ============================================================================
1749
SetHostUI(EffectUIHostInterface * host)1750 void VSTEffect::SetHostUI(EffectUIHostInterface *host)
1751 {
1752 mUIHost = host;
1753 }
1754
PopulateUI(ShuttleGui & S)1755 bool VSTEffect::PopulateUI(ShuttleGui &S)
1756 {
1757 auto parent = S.GetParent();
1758 mDialog = static_cast<wxDialog *>(wxGetTopLevelParent(parent));
1759 mParent = parent;
1760
1761 mParent->PushEventHandler(this);
1762
1763 // Determine if the VST editor is supposed to be used or not
1764 mHost->GetSharedConfig(wxT("Options"),
1765 wxT("UseGUI"),
1766 mGui,
1767 true);
1768 mGui = mAEffect->flags & effFlagsHasEditor ? mGui : false;
1769
1770 // Must use the GUI editor if parameters aren't provided
1771 if (mAEffect->numParams == 0)
1772 {
1773 mGui = true;
1774 }
1775
1776 // Build the appropriate dialog type
1777 if (mGui)
1778 {
1779 BuildFancy();
1780 }
1781 else
1782 {
1783 BuildPlain();
1784 }
1785
1786 return true;
1787 }
1788
IsGraphicalUI()1789 bool VSTEffect::IsGraphicalUI()
1790 {
1791 return mGui;
1792 }
1793
ValidateUI()1794 bool VSTEffect::ValidateUI()
1795 {
1796 if (!mParent->Validate() || !mParent->TransferDataFromWindow())
1797 {
1798 return false;
1799 }
1800
1801 if (GetType() == EffectTypeGenerate)
1802 {
1803 mHost->SetDuration(mDuration->GetValue());
1804 }
1805
1806 return true;
1807 }
1808
HideUI()1809 bool VSTEffect::HideUI()
1810 {
1811 return true;
1812 }
1813
CloseUI()1814 bool VSTEffect::CloseUI()
1815 {
1816 #ifdef __WXMAC__
1817 #ifdef __WX_EVTLOOP_BUSY_WAITING__
1818 wxEventLoop::SetBusyWaiting(false);
1819 #endif
1820 mControl->Close();
1821 #endif
1822
1823 mParent->RemoveEventHandler(this);
1824
1825 PowerOff();
1826
1827 NeedEditIdle(false);
1828
1829 RemoveHandler();
1830
1831 mNames.reset();
1832 mSliders.reset();
1833 mDisplays.reset();
1834 mLabels.reset();
1835
1836 mUIHost = NULL;
1837 mParent = NULL;
1838 mDialog = NULL;
1839
1840 return true;
1841 }
1842
CanExportPresets()1843 bool VSTEffect::CanExportPresets()
1844 {
1845 return true;
1846 }
1847
1848 // Throws exceptions rather than reporting errors.
ExportPresets()1849 void VSTEffect::ExportPresets()
1850 {
1851 wxString path;
1852
1853 // Ask the user for the real name
1854 //
1855 // Passing a valid parent will cause some effects dialogs to malfunction
1856 // upon returning from the SelectFile().
1857 path = SelectFile(FileNames::Operation::Presets,
1858 XO("Save VST Preset As:"),
1859 wxEmptyString,
1860 wxT("preset"),
1861 wxT("xml"),
1862 {
1863 { XO("Standard VST bank file"), { wxT("fxb") }, true },
1864 { XO("Standard VST program file"), { wxT("fxp") }, true },
1865 { XO("Audacity VST preset file"), { wxT("xml") }, true },
1866 },
1867 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
1868 NULL);
1869
1870 // User canceled...
1871 if (path.empty())
1872 {
1873 return;
1874 }
1875
1876 wxFileName fn(path);
1877 wxString ext = fn.GetExt();
1878 if (ext.CmpNoCase(wxT("fxb")) == 0)
1879 {
1880 SaveFXB(fn);
1881 }
1882 else if (ext.CmpNoCase(wxT("fxp")) == 0)
1883 {
1884 SaveFXP(fn);
1885 }
1886 else if (ext.CmpNoCase(wxT("xml")) == 0)
1887 {
1888 // may throw
1889 SaveXML(fn);
1890 }
1891 else
1892 {
1893 // This shouldn't happen, but complain anyway
1894 AudacityMessageBox(
1895 XO("Unrecognized file extension."),
1896 XO("Error Saving VST Presets"),
1897 wxOK | wxCENTRE,
1898 mParent);
1899
1900 return;
1901 }
1902 }
1903
1904 //
1905 // Load an "fxb", "fxp" or Audacuty "xml" file
1906 //
1907 // Based on work by Sven Giermann
1908 //
ImportPresets()1909 void VSTEffect::ImportPresets()
1910 {
1911 wxString path;
1912
1913 // Ask the user for the real name
1914 path = SelectFile(FileNames::Operation::Presets,
1915 XO("Load VST Preset:"),
1916 wxEmptyString,
1917 wxT("preset"),
1918 wxT("xml"),
1919 { {
1920 XO("VST preset files"),
1921 { wxT("fxb"), wxT("fxp"), wxT("xml") },
1922 true
1923 } },
1924 wxFD_OPEN | wxRESIZE_BORDER,
1925 mParent);
1926
1927 // User canceled...
1928 if (path.empty())
1929 {
1930 return;
1931 }
1932
1933 wxFileName fn(path);
1934 wxString ext = fn.GetExt();
1935 bool success = false;
1936 if (ext.CmpNoCase(wxT("fxb")) == 0)
1937 {
1938 success = LoadFXB(fn);
1939 }
1940 else if (ext.CmpNoCase(wxT("fxp")) == 0)
1941 {
1942 success = LoadFXP(fn);
1943 }
1944 else if (ext.CmpNoCase(wxT("xml")) == 0)
1945 {
1946 success = LoadXML(fn);
1947 }
1948 else
1949 {
1950 // This shouldn't happen, but complain anyway
1951 AudacityMessageBox(
1952 XO("Unrecognized file extension."),
1953 XO("Error Loading VST Presets"),
1954 wxOK | wxCENTRE,
1955 mParent);
1956
1957 return;
1958 }
1959
1960 if (!success)
1961 {
1962 AudacityMessageBox(
1963 XO("Unable to load presets file."),
1964 XO("Error Loading VST Presets"),
1965 wxOK | wxCENTRE,
1966 mParent);
1967
1968 return;
1969 }
1970
1971 RefreshParameters();
1972
1973 return;
1974 }
1975
HasOptions()1976 bool VSTEffect::HasOptions()
1977 {
1978 return true;
1979 }
1980
ShowOptions()1981 void VSTEffect::ShowOptions()
1982 {
1983 VSTEffectOptionsDialog dlg(mParent, mHost);
1984 if (dlg.ShowModal())
1985 {
1986 // Reinitialize configuration settings
1987 int userBlockSize;
1988 mHost->GetSharedConfig(wxT("Options"), wxT("BufferSize"), userBlockSize, 8192);
1989 mUserBlockSize = std::max( 1, userBlockSize );
1990 mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
1991 }
1992 }
1993
1994 // ============================================================================
1995 // VSTEffect implementation
1996 // ============================================================================
1997
Load()1998 bool VSTEffect::Load()
1999 {
2000 vstPluginMain pluginMain;
2001 bool success = false;
2002
2003 long effectID = 0;
2004 wxString realPath = mPath.BeforeFirst(wxT(';'));
2005 mPath.AfterFirst(wxT(';')).ToLong(&effectID);
2006 mCurrentEffectID = (intptr_t) effectID;
2007
2008 mModule = NULL;
2009 mAEffect = NULL;
2010
2011 #if defined(__WXMAC__)
2012 // Start clean
2013 mBundleRef.reset();
2014
2015 mResource = ResourceHandle{};
2016
2017 // Convert the path to a CFSTring
2018 wxCFStringRef path(realPath);
2019
2020 // Convert the path to a URL
2021 CFURLRef urlRef =
2022 CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
2023 path,
2024 kCFURLPOSIXPathStyle,
2025 true);
2026 if (urlRef == NULL)
2027 {
2028 return false;
2029 }
2030
2031 // Create the bundle using the URL
2032 BundleHandle bundleRef{ CFBundleCreate(kCFAllocatorDefault, urlRef) };
2033
2034 // Done with the URL
2035 CFRelease(urlRef);
2036
2037 // Bail if the bundle wasn't created
2038 if (!bundleRef)
2039 {
2040 return false;
2041 }
2042
2043 // Retrieve a reference to the executable
2044 CFURLRef exeRef = CFBundleCopyExecutableURL(bundleRef.get());
2045 if (!exeRef)
2046 return false;
2047
2048 // Convert back to path
2049 UInt8 exePath[PLATFORM_MAX_PATH];
2050 Boolean good = CFURLGetFileSystemRepresentation(exeRef, true, exePath, sizeof(exePath));
2051
2052 // Done with the executable reference
2053 CFRelease(exeRef);
2054
2055 // Bail if we couldn't resolve the executable path
2056 if (good == FALSE)
2057 return false;
2058
2059 // Attempt to open it
2060 mModule.reset((char*)dlopen((char *) exePath, RTLD_NOW | RTLD_LOCAL));
2061 if (!mModule)
2062 return false;
2063
2064 // Try to locate the NEW plugin entry point
2065 pluginMain = (vstPluginMain) dlsym(mModule.get(), "VSTPluginMain");
2066
2067 // If not found, try finding the old entry point
2068 if (pluginMain == NULL)
2069 {
2070 pluginMain = (vstPluginMain) dlsym(mModule.get(), "main_macho");
2071 }
2072
2073 // Must not be a VST plugin
2074 if (pluginMain == NULL)
2075 {
2076 mModule.reset();
2077 return false;
2078 }
2079
2080 // Need to keep the bundle reference around so we can map the
2081 // resources.
2082 mBundleRef = std::move(bundleRef);
2083
2084 // Open the resource map ... some plugins (like GRM Tools) need this.
2085 mResource = ResourceHandle{
2086 mBundleRef.get(), CFBundleOpenBundleResourceMap(mBundleRef.get())
2087 };
2088
2089 #elif defined(__WXMSW__)
2090
2091 {
2092 wxLogNull nolog;
2093
2094 // Try to load the library
2095 auto lib = std::make_unique<wxDynamicLibrary>(realPath);
2096 if (!lib)
2097 return false;
2098
2099 // Bail if it wasn't successful
2100 if (!lib->IsLoaded())
2101 return false;
2102
2103 // Try to find the entry point, while suppressing error messages
2104 pluginMain = (vstPluginMain) lib->GetSymbol(wxT("VSTPluginMain"));
2105 if (pluginMain == NULL)
2106 {
2107 pluginMain = (vstPluginMain) lib->GetSymbol(wxT("main"));
2108 if (pluginMain == NULL)
2109 return false;
2110 }
2111
2112 // Save the library reference
2113 mModule = std::move(lib);
2114 }
2115
2116 #else
2117
2118 // Attempt to load it
2119 //
2120 // Spent a few days trying to figure out why some VSTs where running okay and
2121 // others were hit or miss. The cause was that we export all of Audacity's
2122 // symbols and some of the loaded libraries were picking up Audacity's and
2123 // not their own.
2124 //
2125 // So far, I've only seen this issue on Linux, but we might just be getting
2126 // lucky on the Mac and Windows. The sooner we stop exporting everything
2127 // the better.
2128 //
2129 // To get around the problem, I just added the RTLD_DEEPBIND flag to the load
2130 // and that "basically" puts Audacity last when the loader needs to resolve
2131 // symbols.
2132 //
2133 // Once we define a proper external API, the flags can be removed.
2134 #ifndef RTLD_DEEPBIND
2135 #define RTLD_DEEPBIND 0
2136 #endif
2137 ModuleHandle lib {
2138 (char*) dlopen((const char *)wxString(realPath).ToUTF8(),
2139 RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND)
2140 };
2141 if (!lib)
2142 {
2143 return false;
2144 }
2145
2146 // Try to find the entry point, while suppressing error messages
2147 pluginMain = (vstPluginMain) dlsym(lib.get(), "VSTPluginMain");
2148 if (pluginMain == NULL)
2149 {
2150 pluginMain = (vstPluginMain) dlsym(lib.get(), "main");
2151 if (pluginMain == NULL)
2152 return false;
2153 }
2154
2155 // Save the library reference
2156 mModule = std::move(lib);
2157
2158 #endif
2159
2160 // Initialize the plugin
2161 try
2162 {
2163 mAEffect = pluginMain(VSTEffect::AudioMaster);
2164 }
2165 catch (...)
2166 {
2167 wxLogMessage(wxT("VST plugin initialization failed\n"));
2168 mAEffect = NULL;
2169 }
2170
2171 // Was it successful?
2172 if (mAEffect)
2173 {
2174 // Save a reference to ourselves
2175 //
2176 // Note: Some hosts use "user" and some use "ptr2/resvd2". It might
2177 // be worthwhile to check if user is NULL before using it and
2178 // then falling back to "ptr2/resvd2".
2179 mAEffect->ptr2 = this;
2180
2181 // Give the plugin an initial sample rate and blocksize
2182 callDispatcher(effSetSampleRate, 0, 0, NULL, 48000.0);
2183 callDispatcher(effSetBlockSize, 0, 512, NULL, 0);
2184
2185 // Ask the plugin to identify itself...might be needed for older plugins
2186 callDispatcher(effIdentify, 0, 0, NULL, 0);
2187
2188 // Open the plugin
2189 callDispatcher(effOpen, 0, 0, NULL, 0.0);
2190
2191 // Get the VST version the plugin understands
2192 mVstVersion = callDispatcher(effGetVstVersion, 0, 0, NULL, 0);
2193
2194 // Set it again in case plugin ignored it before the effOpen
2195 callDispatcher(effSetSampleRate, 0, 0, NULL, 48000.0);
2196 callDispatcher(effSetBlockSize, 0, 512, NULL, 0);
2197
2198 // Ensure that it looks like a plugin and can deal with ProcessReplacing
2199 // calls. Also exclude synths for now.
2200 if (mAEffect->magic == kEffectMagic &&
2201 !(mAEffect->flags & effFlagsIsSynth) &&
2202 mAEffect->flags & effFlagsCanReplacing)
2203 {
2204 if (mVstVersion >= 2)
2205 {
2206 mName = GetString(effGetEffectName);
2207 if (mName.length() == 0)
2208 {
2209 mName = GetString(effGetProductString);
2210 }
2211 }
2212 if (mName.length() == 0)
2213 {
2214 mName = wxFileName{realPath}.GetName();
2215 }
2216
2217 if (mVstVersion >= 2)
2218 {
2219 mVendor = GetString(effGetVendorString);
2220 mVersion = wxINT32_SWAP_ON_LE(callDispatcher(effGetVendorVersion, 0, 0, NULL, 0));
2221 }
2222 if (mVersion == 0)
2223 {
2224 mVersion = wxINT32_SWAP_ON_LE(mAEffect->version);
2225 }
2226
2227 if (mAEffect->flags & effFlagsHasEditor || mAEffect->numParams != 0)
2228 {
2229 mInteractive = true;
2230 }
2231
2232 mAudioIns = mAEffect->numInputs;
2233 mAudioOuts = mAEffect->numOutputs;
2234
2235 mMidiIns = 0;
2236 mMidiOuts = 0;
2237
2238 // Check to see if parameters can be automated. This isn't a guarantee
2239 // since it could be that the effect simply doesn't support the opcode.
2240 mAutomatable = false;
2241 for (int i = 0; i < mAEffect->numParams; i++)
2242 {
2243 if (callDispatcher(effCanBeAutomated, 0, i, NULL, 0.0))
2244 {
2245 mAutomatable = true;
2246 break;
2247 }
2248 }
2249
2250 // Make sure we start out with a valid program selection
2251 // I've found one plugin (SoundHack +morphfilter) that will
2252 // crash Audacity when saving the initial default parameters
2253 // with this.
2254 callSetProgram(0);
2255
2256 // Pretty confident that we're good to go
2257 success = true;
2258 }
2259 }
2260
2261 if (!success)
2262 {
2263 Unload();
2264 }
2265
2266 return success;
2267 }
2268
Unload()2269 void VSTEffect::Unload()
2270 {
2271 if (mDialog)
2272 {
2273 CloseUI();
2274 }
2275
2276 if (mAEffect)
2277 {
2278 // Turn the power off
2279 PowerOff();
2280
2281 // Finally, close the plugin
2282 callDispatcher(effClose, 0, 0, NULL, 0.0);
2283 mAEffect = NULL;
2284 }
2285
2286 if (mModule)
2287 {
2288 #if defined(__WXMAC__)
2289 mResource.reset();
2290 mBundleRef.reset();
2291 #endif
2292
2293 mModule.reset();
2294 mAEffect = NULL;
2295 }
2296 }
2297
GetEffectIDs()2298 std::vector<int> VSTEffect::GetEffectIDs()
2299 {
2300 std::vector<int> effectIDs;
2301
2302 // Are we a shell?
2303 if (mVstVersion >= 2 && (VstPlugCategory) callDispatcher(effGetPlugCategory, 0, 0, NULL, 0) == kPlugCategShell)
2304 {
2305 char name[64];
2306 int effectID;
2307
2308 effectID = (int) callDispatcher(effShellGetNextPlugin, 0, 0, &name, 0);
2309 while (effectID)
2310 {
2311 effectIDs.push_back(effectID);
2312 effectID = (int) callDispatcher(effShellGetNextPlugin, 0, 0, &name, 0);
2313 }
2314 }
2315
2316 return effectIDs;
2317 }
2318
LoadParameters(const RegistryPath & group)2319 bool VSTEffect::LoadParameters(const RegistryPath & group)
2320 {
2321 wxString value;
2322
2323 VstPatchChunkInfo info = {1, mAEffect->uniqueID, mAEffect->version, mAEffect->numParams, ""};
2324 mHost->GetPrivateConfig(group, wxT("UniqueID"), info.pluginUniqueID, info.pluginUniqueID);
2325 mHost->GetPrivateConfig(group, wxT("Version"), info.pluginVersion, info.pluginVersion);
2326 mHost->GetPrivateConfig(group, wxT("Elements"), info.numElements, info.numElements);
2327
2328 if ((info.pluginUniqueID != mAEffect->uniqueID) ||
2329 (info.pluginVersion != mAEffect->version) ||
2330 (info.numElements != mAEffect->numParams))
2331 {
2332 return false;
2333 }
2334
2335 if (mHost->GetPrivateConfig(group, wxT("Chunk"), value, wxEmptyString))
2336 {
2337 ArrayOf<char> buf{ value.length() / 4 * 3 };
2338
2339 int len = VSTEffect::b64decode(value, buf.get());
2340 if (len)
2341 {
2342 callSetChunk(true, len, buf.get(), &info);
2343 }
2344
2345 return true;
2346 }
2347
2348 wxString parms;
2349 if (!mHost->GetPrivateConfig(group, wxT("Parameters"), parms, wxEmptyString))
2350 {
2351 return false;
2352 }
2353
2354 CommandParameters eap;
2355 if (!eap.SetParameters(parms))
2356 {
2357 return false;
2358 }
2359
2360 return SetAutomationParameters(eap);
2361 }
2362
SaveParameters(const RegistryPath & group)2363 bool VSTEffect::SaveParameters(const RegistryPath & group)
2364 {
2365 mHost->SetPrivateConfig(group, wxT("UniqueID"), mAEffect->uniqueID);
2366 mHost->SetPrivateConfig(group, wxT("Version"), mAEffect->version);
2367 mHost->SetPrivateConfig(group, wxT("Elements"), mAEffect->numParams);
2368
2369 if (mAEffect->flags & effFlagsProgramChunks)
2370 {
2371 void *chunk = NULL;
2372 int clen = (int) callDispatcher(effGetChunk, 1, 0, &chunk, 0.0);
2373 if (clen <= 0)
2374 {
2375 return false;
2376 }
2377
2378 mHost->SetPrivateConfig(group, wxT("Chunk"), VSTEffect::b64encode(chunk, clen));
2379 return true;
2380 }
2381
2382 CommandParameters eap;
2383 if (!GetAutomationParameters(eap))
2384 {
2385 return false;
2386 }
2387
2388 wxString parms;
2389 if (!eap.GetParameters(parms))
2390 {
2391 return false;
2392 }
2393
2394 return mHost->SetPrivateConfig(group, wxT("Parameters"), parms);
2395 }
2396
OnTimer()2397 void VSTEffect::OnTimer()
2398 {
2399 wxRecursionGuard guard(mTimerGuard);
2400
2401 // Ignore it if we're recursing
2402 if (guard.IsInside())
2403 {
2404 return;
2405 }
2406
2407 if (mVstVersion >= 2 && mWantsIdle)
2408 {
2409 int ret = callDispatcher(effIdle, 0, 0, NULL, 0.0);
2410 if (!ret)
2411 {
2412 mWantsIdle = false;
2413 }
2414 }
2415
2416 if (mWantsEditIdle)
2417 {
2418 callDispatcher(effEditIdle, 0, 0, NULL, 0.0);
2419 }
2420 }
2421
NeedIdle()2422 void VSTEffect::NeedIdle()
2423 {
2424 mWantsIdle = true;
2425 mTimer->Start(100);
2426 }
2427
NeedEditIdle(bool state)2428 void VSTEffect::NeedEditIdle(bool state)
2429 {
2430 mWantsEditIdle = state;
2431 mTimer->Start(100);
2432 }
2433
GetTimeInfo()2434 VstTimeInfo *VSTEffect::GetTimeInfo()
2435 {
2436 mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
2437 return &mTimeInfo;
2438 }
2439
GetSampleRate()2440 float VSTEffect::GetSampleRate()
2441 {
2442 return mTimeInfo.sampleRate;
2443 }
2444
GetProcessLevel()2445 int VSTEffect::GetProcessLevel()
2446 {
2447 return mProcessLevel;
2448 }
2449
PowerOn()2450 void VSTEffect::PowerOn()
2451 {
2452 if (!mHasPower)
2453 {
2454 // Turn the power on
2455 callDispatcher(effMainsChanged, 0, 1, NULL, 0.0);
2456
2457 // Tell the effect we're going to start processing
2458 if (mVstVersion >= 2)
2459 {
2460 callDispatcher(effStartProcess, 0, 0, NULL, 0.0);
2461 }
2462
2463 // Set state
2464 mHasPower = true;
2465 }
2466 }
2467
PowerOff()2468 void VSTEffect::PowerOff()
2469 {
2470 if (mHasPower)
2471 {
2472 // Tell the effect we're going to stop processing
2473 if (mVstVersion >= 2)
2474 {
2475 callDispatcher(effStopProcess, 0, 0, NULL, 0.0);
2476 }
2477
2478 // Turn the power off
2479 callDispatcher(effMainsChanged, 0, 0, NULL, 0.0);
2480
2481 // Set state
2482 mHasPower = false;
2483 }
2484 }
2485
SizeWindow(int w,int h)2486 void VSTEffect::SizeWindow(int w, int h)
2487 {
2488 // Queue the event to make the resizes smoother
2489 if (mParent)
2490 {
2491 wxCommandEvent sw(EVT_SIZEWINDOW);
2492 sw.SetInt(w);
2493 sw.SetExtraLong(h);
2494 mParent->GetEventHandler()->AddPendingEvent(sw);
2495 }
2496
2497 return;
2498 }
2499
UpdateDisplay()2500 void VSTEffect::UpdateDisplay()
2501 {
2502 #if 0
2503 // Tell the dialog to refresh effect information
2504 if (mParent)
2505 {
2506 wxCommandEvent ud(EVT_UPDATEDISPLAY);
2507 mParent->GetEventHandler()->AddPendingEvent(ud);
2508 }
2509 #endif
2510 return;
2511 }
2512
Automate(int index,float value)2513 void VSTEffect::Automate(int index, float value)
2514 {
2515 // Just ignore it if we're a slave
2516 if (mMaster)
2517 {
2518 return;
2519 }
2520
2521 for (const auto &slave : mSlaves)
2522 slave->callSetParameter(index, value);
2523
2524 return;
2525 }
2526
SetBufferDelay(int samples)2527 void VSTEffect::SetBufferDelay(int samples)
2528 {
2529 // We do not support negative delay
2530 if (samples >= 0 && mUseLatency)
2531 {
2532 mBufferDelay = samples;
2533 }
2534
2535 return;
2536 }
2537
GetString(wxString & outstr,int opcode,int index)2538 int VSTEffect::GetString(wxString & outstr, int opcode, int index)
2539 {
2540 char buf[256];
2541
2542 memset(buf, 0, sizeof(buf));
2543
2544 callDispatcher(opcode, index, 0, buf, 0.0);
2545
2546 outstr = wxString::FromUTF8(buf);
2547
2548 return 0;
2549 }
2550
GetString(int opcode,int index)2551 wxString VSTEffect::GetString(int opcode, int index)
2552 {
2553 wxString str;
2554
2555 GetString(str, opcode, index);
2556
2557 return str;
2558 }
2559
SetString(int opcode,const wxString & str,int index)2560 void VSTEffect::SetString(int opcode, const wxString & str, int index)
2561 {
2562 char buf[256];
2563 strcpy(buf, str.Left(255).ToUTF8());
2564
2565 callDispatcher(opcode, index, 0, buf, 0.0);
2566 }
2567
callDispatcher(int opcode,int index,intptr_t value,void * ptr,float opt)2568 intptr_t VSTEffect::callDispatcher(int opcode,
2569 int index, intptr_t value, void *ptr, float opt)
2570 {
2571 // Needed since we might be in the dispatcher when the timer pops
2572 wxCRIT_SECT_LOCKER(locker, mDispatcherLock);
2573 return mAEffect->dispatcher(mAEffect, opcode, index, value, ptr, opt);
2574 }
2575
callProcessReplacing(float ** inputs,float ** outputs,int sampleframes)2576 void VSTEffect::callProcessReplacing(float **inputs,
2577 float **outputs, int sampleframes)
2578 {
2579 mAEffect->processReplacing(mAEffect, inputs, outputs, sampleframes);
2580 }
2581
callGetParameter(int index)2582 float VSTEffect::callGetParameter(int index)
2583 {
2584 return mAEffect->getParameter(mAEffect, index);
2585 }
2586
callSetParameter(int index,float value)2587 void VSTEffect::callSetParameter(int index, float value)
2588 {
2589 if (mVstVersion == 0 || callDispatcher(effCanBeAutomated, 0, index, NULL, 0.0))
2590 {
2591 mAEffect->setParameter(mAEffect, index, value);
2592
2593 for (const auto &slave : mSlaves)
2594 slave->callSetParameter(index, value);
2595 }
2596 }
2597
callSetProgram(int index)2598 void VSTEffect::callSetProgram(int index)
2599 {
2600 callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
2601
2602 callDispatcher(effSetProgram, 0, index, NULL, 0.0);
2603 for (const auto &slave : mSlaves)
2604 slave->callSetProgram(index);
2605
2606 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
2607 }
2608
callSetChunk(bool isPgm,int len,void * buf)2609 void VSTEffect::callSetChunk(bool isPgm, int len, void *buf)
2610 {
2611 VstPatchChunkInfo info;
2612
2613 memset(&info, 0, sizeof(info));
2614 info.version = 1;
2615 info.pluginUniqueID = mAEffect->uniqueID;
2616 info.pluginVersion = mAEffect->version;
2617 info.numElements = isPgm ? mAEffect->numParams : mAEffect->numPrograms;
2618
2619 callSetChunk(isPgm, len, buf, &info);
2620 }
2621
callSetChunk(bool isPgm,int len,void * buf,VstPatchChunkInfo * info)2622 void VSTEffect::callSetChunk(bool isPgm, int len, void *buf, VstPatchChunkInfo *info)
2623 {
2624 if (isPgm)
2625 {
2626 // Ask the effect if this is an acceptable program
2627 if (callDispatcher(effBeginLoadProgram, 0, 0, info, 0.0) == -1)
2628 {
2629 return;
2630 }
2631 }
2632 else
2633 {
2634 // Ask the effect if this is an acceptable bank
2635 if (callDispatcher(effBeginLoadBank, 0, 0, info, 0.0) == -1)
2636 {
2637 return;
2638 }
2639 }
2640
2641 callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
2642 callDispatcher(effSetChunk, isPgm ? 1 : 0, len, buf, 0.0);
2643 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
2644
2645 for (const auto &slave : mSlaves)
2646 slave->callSetChunk(isPgm, len, buf, info);
2647 }
2648
2649 ////////////////////////////////////////////////////////////////////////////////
2650 // Base64 en/decoding
2651 //
2652 // Original routines marked as public domain and found at:
2653 //
2654 // http://en.wikibooks.org/wiki/Algorithm_implementation/Miscellaneous/Base64
2655 //
2656 ////////////////////////////////////////////////////////////////////////////////
2657
2658 // Lookup table for encoding
2659 const static wxChar cset[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
2660 const static char padc = wxT('=');
2661
b64encode(const void * in,int len)2662 wxString VSTEffect::b64encode(const void *in, int len)
2663 {
2664 unsigned char *p = (unsigned char *) in;
2665 wxString out;
2666
2667 unsigned long temp;
2668 for (int i = 0; i < len / 3; i++)
2669 {
2670 temp = (*p++) << 16; //Convert to big endian
2671 temp += (*p++) << 8;
2672 temp += (*p++);
2673 out += cset[(temp & 0x00FC0000) >> 18];
2674 out += cset[(temp & 0x0003F000) >> 12];
2675 out += cset[(temp & 0x00000FC0) >> 6];
2676 out += cset[(temp & 0x0000003F)];
2677 }
2678
2679 switch (len % 3)
2680 {
2681 case 1:
2682 temp = (*p++) << 16; //Convert to big endian
2683 out += cset[(temp & 0x00FC0000) >> 18];
2684 out += cset[(temp & 0x0003F000) >> 12];
2685 out += padc;
2686 out += padc;
2687 break;
2688
2689 case 2:
2690 temp = (*p++) << 16; //Convert to big endian
2691 temp += (*p++) << 8;
2692 out += cset[(temp & 0x00FC0000) >> 18];
2693 out += cset[(temp & 0x0003F000) >> 12];
2694 out += cset[(temp & 0x00000FC0) >> 6];
2695 out += padc;
2696 break;
2697 }
2698
2699 return out;
2700 }
2701
b64decode(const wxString & in,void * out)2702 int VSTEffect::b64decode(const wxString &in, void *out)
2703 {
2704 int len = in.length();
2705 unsigned char *p = (unsigned char *) out;
2706
2707 if (len % 4) //Sanity check
2708 {
2709 return 0;
2710 }
2711
2712 int padding = 0;
2713 if (len)
2714 {
2715 if (in[len - 1] == padc)
2716 {
2717 padding++;
2718 }
2719
2720 if (in[len - 2] == padc)
2721 {
2722 padding++;
2723 }
2724 }
2725
2726 //const char *a = in.mb_str();
2727 //Setup a vector to hold the result
2728 unsigned long temp = 0; //Holds decoded quanta
2729 int i = 0;
2730 while (i < len)
2731 {
2732 for (int quantumPosition = 0; quantumPosition < 4; quantumPosition++)
2733 {
2734 unsigned char c = in[i];
2735 temp <<= 6;
2736
2737 if (c >= 0x41 && c <= 0x5A)
2738 {
2739 temp |= c - 0x41;
2740 }
2741 else if (c >= 0x61 && c <= 0x7A)
2742 {
2743 temp |= c - 0x47;
2744 }
2745 else if (c >= 0x30 && c <= 0x39)
2746 {
2747 temp |= c + 0x04;
2748 }
2749 else if (c == 0x2B)
2750 {
2751 temp |= 0x3E;
2752 }
2753 else if (c == 0x2F)
2754 {
2755 temp |= 0x3F;
2756 }
2757 else if (c == padc)
2758 {
2759 switch (len - i)
2760 {
2761 case 1: //One pad character
2762 *p++ = (temp >> 16) & 0x000000FF;
2763 *p++ = (temp >> 8) & 0x000000FF;
2764 return p - (unsigned char *) out;
2765 case 2: //Two pad characters
2766 *p++ = (temp >> 10) & 0x000000FF;
2767 return p - (unsigned char *) out;
2768 }
2769 }
2770 i++;
2771 }
2772 *p++ = (temp >> 16) & 0x000000FF;
2773 *p++ = (temp >> 8) & 0x000000FF;
2774 *p++ = temp & 0x000000FF;
2775 }
2776
2777 return p - (unsigned char *) out;
2778 }
2779
RemoveHandler()2780 void VSTEffect::RemoveHandler()
2781 {
2782 }
2783
OnSize(wxSizeEvent & evt)2784 static void OnSize(wxSizeEvent & evt)
2785 {
2786 evt.Skip();
2787
2788 // Once the parent dialog reaches its final size as indicated by
2789 // a non-default minimum size, we set the maximum size to match.
2790 // This is a bit of a hack to prevent VSTs GUI windows from resizing
2791 // there's no real reason to allow it. But, there should be a better
2792 // way of handling it.
2793 wxWindow *w = (wxWindow *) evt.GetEventObject();
2794 wxSize sz = w->GetMinSize();
2795
2796 if (sz != wxDefaultSize)
2797 {
2798 w->SetMaxSize(sz);
2799 }
2800 }
2801
BuildFancy()2802 void VSTEffect::BuildFancy()
2803 {
2804 // Turn the power on...some effects need this when the editor is open
2805 PowerOn();
2806
2807 auto control = Destroy_ptr<VSTControl>{ safenew VSTControl };
2808 if (!control)
2809 {
2810 return;
2811 }
2812
2813 if (!control->Create(mParent, this))
2814 {
2815 return;
2816 }
2817
2818 {
2819 auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
2820
2821 mainSizer->Add((mControl = control.release()), 0, wxALIGN_CENTER);
2822
2823 mParent->SetMinSize(wxDefaultSize);
2824 mParent->SetSizer(mainSizer.release());
2825 }
2826
2827 NeedEditIdle(true);
2828
2829 mDialog->Bind(wxEVT_SIZE, OnSize);
2830
2831 #ifdef __WXMAC__
2832 #ifdef __WX_EVTLOOP_BUSY_WAITING__
2833 wxEventLoop::SetBusyWaiting(true);
2834 #endif
2835 #endif
2836
2837 return;
2838 }
2839
BuildPlain()2840 void VSTEffect::BuildPlain()
2841 {
2842 wxASSERT(mParent); // To justify safenew
2843 wxScrolledWindow *const scroller = safenew wxScrolledWindow(mParent,
2844 wxID_ANY,
2845 wxDefaultPosition,
2846 wxDefaultSize,
2847 wxVSCROLL | wxTAB_TRAVERSAL);
2848
2849 {
2850 auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
2851
2852 // Try to give the window a sensible default/minimum size
2853 scroller->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3),
2854 mParent->GetSize().GetHeight() / 2));
2855 scroller->SetScrollRate(0, 20);
2856
2857 // This fools NVDA into not saying "Panel" when the dialog gets focus
2858 scroller->SetName(wxT("\a"));
2859 scroller->SetLabel(wxT("\a"));
2860
2861 mainSizer->Add(scroller, 1, wxEXPAND | wxALL, 5);
2862 mParent->SetSizer(mainSizer.release());
2863 }
2864
2865 mNames.reinit(static_cast<size_t>(mAEffect->numParams));
2866 mSliders.reinit(static_cast<size_t>(mAEffect->numParams));
2867 mDisplays.reinit(static_cast<size_t>(mAEffect->numParams));
2868 mLabels.reinit(static_cast<size_t>(mAEffect->numParams));
2869
2870 {
2871 auto paramSizer = std::make_unique<wxStaticBoxSizer>(wxVERTICAL, scroller, _("Effect Settings"));
2872
2873 {
2874 auto gridSizer = std::make_unique<wxFlexGridSizer>(4, 0, 0);
2875 gridSizer->AddGrowableCol(1);
2876
2877 // Add the duration control for generators
2878 if (GetType() == EffectTypeGenerate)
2879 {
2880 wxControl *item = safenew wxStaticText(scroller, 0, _("Duration:"));
2881 gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
2882 mDuration = safenew
2883 NumericTextCtrl(scroller, ID_Duration,
2884 NumericConverter::TIME,
2885 mHost->GetDurationFormat(),
2886 mHost->GetDuration(),
2887 mSampleRate,
2888 NumericTextCtrl::Options{}
2889 .AutoPos(true));
2890 mDuration->SetName( XO("Duration") );
2891 gridSizer->Add(mDuration, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
2892 gridSizer->Add(1, 1, 0);
2893 gridSizer->Add(1, 1, 0);
2894 }
2895
2896 // Find the longest parameter name.
2897 int namew = 0;
2898 int w;
2899 int h;
2900 for (int i = 0; i < mAEffect->numParams; i++)
2901 {
2902 wxString text = GetString(effGetParamName, i);
2903
2904 if (text.Right(1) != wxT(':'))
2905 {
2906 text += wxT(':');
2907 }
2908
2909 scroller->GetTextExtent(text, &w, &h);
2910 if (w > namew)
2911 {
2912 namew = w;
2913 }
2914 }
2915
2916 scroller->GetTextExtent(wxT("HHHHHHHH"), &w, &h);
2917
2918 for (int i = 0; i < mAEffect->numParams; i++)
2919 {
2920 mNames[i] = safenew wxStaticText(scroller,
2921 wxID_ANY,
2922 wxEmptyString,
2923 wxDefaultPosition,
2924 wxSize(namew, -1),
2925 wxALIGN_RIGHT | wxST_NO_AUTORESIZE);
2926 gridSizer->Add(mNames[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
2927
2928 mSliders[i] = safenew wxSliderWrapper(scroller,
2929 ID_Sliders + i,
2930 0,
2931 0,
2932 1000,
2933 wxDefaultPosition,
2934 wxSize(200, -1));
2935 gridSizer->Add(mSliders[i], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5);
2936 #if wxUSE_ACCESSIBILITY
2937 // so that name can be set on a standard control
2938 mSliders[i]->SetAccessible(safenew WindowAccessible(mSliders[i]));
2939 #endif
2940
2941 mDisplays[i] = safenew wxStaticText(scroller,
2942 wxID_ANY,
2943 wxEmptyString,
2944 wxDefaultPosition,
2945 wxSize(w, -1),
2946 wxALIGN_RIGHT | wxST_NO_AUTORESIZE);
2947 gridSizer->Add(mDisplays[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
2948
2949 mLabels[i] = safenew wxStaticText(scroller,
2950 wxID_ANY,
2951 wxEmptyString,
2952 wxDefaultPosition,
2953 wxSize(w, -1),
2954 wxALIGN_LEFT | wxST_NO_AUTORESIZE);
2955 gridSizer->Add(mLabels[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5);
2956 }
2957
2958 paramSizer->Add(gridSizer.release(), 1, wxEXPAND | wxALL, 5);
2959 }
2960 scroller->SetSizer(paramSizer.release());
2961 }
2962
2963 RefreshParameters();
2964
2965 mSliders[0]->SetFocus();
2966 }
2967
RefreshParameters(int skip)2968 void VSTEffect::RefreshParameters(int skip)
2969 {
2970 if (!mNames)
2971 {
2972 return;
2973 }
2974
2975 for (int i = 0; i < mAEffect->numParams; i++)
2976 {
2977 wxString text = GetString(effGetParamName, i);
2978
2979 text = text.Trim(true).Trim(false);
2980
2981 wxString name = text;
2982
2983 if (text.Right(1) != wxT(':'))
2984 {
2985 text += wxT(':');
2986 }
2987 mNames[i]->SetLabel(text);
2988
2989 // For some parameters types like on/off, setting the slider value has
2990 // a side effect that causes it to only move when the parameter changes
2991 // from off to on. However, this prevents changing the value using the
2992 // keyboard, so we skip the active slider if any.
2993 if (i != skip)
2994 {
2995 mSliders[i]->SetValue(callGetParameter(i) * 1000);
2996 }
2997 name = text;
2998
2999 text = GetString(effGetParamDisplay, i);
3000 if (text.empty())
3001 {
3002 text.Printf(wxT("%.5g"),callGetParameter(i));
3003 }
3004 mDisplays[i]->SetLabel(wxString::Format(wxT("%8s"), text));
3005 name += wxT(' ') + text;
3006
3007 text = GetString(effGetParamDisplay, i);
3008 if (!text.empty())
3009 {
3010 text.Printf(wxT("%-8s"), GetString(effGetParamLabel, i));
3011 mLabels[i]->SetLabel(wxString::Format(wxT("%8s"), text));
3012 name += wxT(' ') + text;
3013 }
3014
3015 mSliders[i]->SetName(name);
3016 }
3017 }
3018
OnSizeWindow(wxCommandEvent & evt)3019 void VSTEffect::OnSizeWindow(wxCommandEvent & evt)
3020 {
3021 if (!mControl)
3022 {
3023 return;
3024 }
3025
3026 mControl->SetMinSize(wxSize(evt.GetInt(), (int) evt.GetExtraLong()));
3027 mControl->SetSize(wxSize(evt.GetInt(), (int) evt.GetExtraLong()));
3028
3029 // DO NOT CHANGE THE ORDER OF THESE
3030 //
3031 // Guitar Rig (and possibly others) Cocoa VSTs can resize too large
3032 // if the bounds are unlimited.
3033 mDialog->SetMinSize(wxDefaultSize);
3034 mDialog->SetMaxSize(wxDefaultSize);
3035 mDialog->Layout();
3036 mDialog->SetMinSize(mDialog->GetBestSize());
3037 mDialog->SetMaxSize(mDialog->GetBestSize());
3038 mDialog->Fit();
3039 }
3040
OnSlider(wxCommandEvent & evt)3041 void VSTEffect::OnSlider(wxCommandEvent & evt)
3042 {
3043 wxSlider *s = (wxSlider *) evt.GetEventObject();
3044 int i = s->GetId() - ID_Sliders;
3045
3046 callSetParameter(i, s->GetValue() / 1000.0);
3047
3048 RefreshParameters(i);
3049 }
3050
LoadFXB(const wxFileName & fn)3051 bool VSTEffect::LoadFXB(const wxFileName & fn)
3052 {
3053 bool ret = false;
3054
3055 // Try to open the file...will be closed automatically when method returns
3056 wxFFile f(fn.GetFullPath(), wxT("rb"));
3057 if (!f.IsOpened())
3058 {
3059 return false;
3060 }
3061
3062 // Allocate memory for the contents
3063 ArrayOf<unsigned char> data{ size_t(f.Length()) };
3064 if (!data)
3065 {
3066 AudacityMessageBox(
3067 XO("Unable to allocate memory when loading presets file."),
3068 XO("Error Loading VST Presets"),
3069 wxOK | wxCENTRE,
3070 mParent);
3071 return false;
3072 }
3073 unsigned char *bptr = data.get();
3074
3075 do
3076 {
3077 // Read in the whole file
3078 ssize_t len = f.Read((void *) bptr, f.Length());
3079 if (f.Error())
3080 {
3081 AudacityMessageBox(
3082 XO("Unable to read presets file."),
3083 XO("Error Loading VST Presets"),
3084 wxOK | wxCENTRE,
3085 mParent);
3086 break;
3087 }
3088
3089 // Most references to the data are via an "int" array
3090 int32_t *iptr = (int32_t *) bptr;
3091
3092 // Verify that we have at least enough for the header
3093 if (len < 156)
3094 {
3095 break;
3096 }
3097
3098 // Verify that we probably have an FX file
3099 if (wxINT32_SWAP_ON_LE(iptr[0]) != CCONST('C', 'c', 'n', 'K'))
3100 {
3101 break;
3102 }
3103
3104 // Ignore the size...sometimes it's there, other times it's zero
3105
3106 // Get the version and verify
3107 int version = wxINT32_SWAP_ON_LE(iptr[3]);
3108 if (version != 1 && version != 2)
3109 {
3110 break;
3111 }
3112
3113 VstPatchChunkInfo info =
3114 {
3115 1,
3116 wxINT32_SWAP_ON_LE(iptr[4]),
3117 wxINT32_SWAP_ON_LE(iptr[5]),
3118 wxINT32_SWAP_ON_LE(iptr[6]),
3119 ""
3120 };
3121
3122 // Ensure this program looks to belong to the current plugin
3123 if ((info.pluginUniqueID != mAEffect->uniqueID) &&
3124 (info.pluginVersion != mAEffect->version) &&
3125 (info.numElements != mAEffect->numPrograms))
3126 {
3127 break;
3128 }
3129
3130 // Get the number of programs
3131 int numProgs = info.numElements;
3132
3133 // Get the current program index
3134 int curProg = 0;
3135 if (version >= 2)
3136 {
3137 curProg = wxINT32_SWAP_ON_LE(iptr[7]);
3138 if (curProg < 0 || curProg >= numProgs)
3139 {
3140 break;
3141 }
3142 }
3143
3144 // Is it a bank of programs?
3145 if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'x', 'B', 'k'))
3146 {
3147 // Drop the header
3148 bptr += 156;
3149 len -= 156;
3150
3151 unsigned char *tempPtr = bptr;
3152 ssize_t tempLen = len;
3153
3154 // Validate all of the programs
3155 for (int i = 0; i < numProgs; i++)
3156 {
3157 if (!LoadFXProgram(&tempPtr, tempLen, i, true))
3158 {
3159 break;
3160 }
3161 }
3162
3163 // Ask the effect if this is an acceptable bank
3164 if (callDispatcher(effBeginLoadBank, 0, 0, &info, 0.0) == -1)
3165 {
3166 return false;
3167 }
3168
3169 // Start loading the individual programs
3170 for (int i = 0; i < numProgs; i++)
3171 {
3172 ret = LoadFXProgram(&bptr, len, i, false);
3173 }
3174 }
3175 // Or maybe a bank chunk?
3176 else if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'B', 'C', 'h'))
3177 {
3178 // Can't load programs chunks if the plugin doesn't support it
3179 if (!(mAEffect->flags & effFlagsProgramChunks))
3180 {
3181 break;
3182 }
3183
3184 // Verify that we have enough to grab the chunk size
3185 if (len < 160)
3186 {
3187 break;
3188 }
3189
3190 // Get the chunk size
3191 int size = wxINT32_SWAP_ON_LE(iptr[39]);
3192
3193 // We finally know the full length of the program
3194 int proglen = 160 + size;
3195
3196 // Verify that we have enough for the entire program
3197 if (len < proglen)
3198 {
3199 break;
3200 }
3201
3202 // Set the entire bank in one shot
3203 callSetChunk(false, size, &iptr[40], &info);
3204
3205 // Success
3206 ret = true;
3207 }
3208 // Unrecognizable type
3209 else
3210 {
3211 break;
3212 }
3213
3214 // Set the active program
3215 if (ret && version >= 2)
3216 {
3217 callSetProgram(curProg);
3218 }
3219 } while (false);
3220
3221 return ret;
3222 }
3223
LoadFXP(const wxFileName & fn)3224 bool VSTEffect::LoadFXP(const wxFileName & fn)
3225 {
3226 bool ret = false;
3227
3228 // Try to open the file...will be closed automatically when method returns
3229 wxFFile f(fn.GetFullPath(), wxT("rb"));
3230 if (!f.IsOpened())
3231 {
3232 return false;
3233 }
3234
3235 // Allocate memory for the contents
3236 ArrayOf<unsigned char> data{ size_t(f.Length()) };
3237 if (!data)
3238 {
3239 AudacityMessageBox(
3240 XO("Unable to allocate memory when loading presets file."),
3241 XO("Error Loading VST Presets"),
3242 wxOK | wxCENTRE,
3243 mParent);
3244 return false;
3245 }
3246 unsigned char *bptr = data.get();
3247
3248 do
3249 {
3250 // Read in the whole file
3251 ssize_t len = f.Read((void *) bptr, f.Length());
3252 if (f.Error())
3253 {
3254 AudacityMessageBox(
3255 XO("Unable to read presets file."),
3256 XO("Error Loading VST Presets"),
3257 wxOK | wxCENTRE,
3258 mParent);
3259 break;
3260 }
3261
3262 // Get (or default) currently selected program
3263 int i = 0; //mProgram->GetCurrentSelection();
3264 if (i < 0)
3265 {
3266 i = 0; // default to first program
3267 }
3268
3269 // Go verify and set the program
3270 ret = LoadFXProgram(&bptr, len, i, false);
3271 } while (false);
3272
3273 return ret;
3274 }
3275
LoadFXProgram(unsigned char ** bptr,ssize_t & len,int index,bool dryrun)3276 bool VSTEffect::LoadFXProgram(unsigned char **bptr, ssize_t & len, int index, bool dryrun)
3277 {
3278 // Most references to the data are via an "int" array
3279 int32_t *iptr = (int32_t *) *bptr;
3280
3281 // Verify that we have at least enough for a program without parameters
3282 if (len < 28)
3283 {
3284 return false;
3285 }
3286
3287 // Verify that we probably have an FX file
3288 if (wxINT32_SWAP_ON_LE(iptr[0]) != CCONST('C', 'c', 'n', 'K'))
3289 {
3290 return false;
3291 }
3292
3293 // Ignore the size...sometimes it's there, other times it's zero
3294
3295 // Get the version and verify
3296 #if defined(IS_THIS_AN_FXP_ARTIFICAL_LIMITATION)
3297 int version = wxINT32_SWAP_ON_LE(iptr[3]);
3298 if (version != 1)
3299 {
3300 return false;
3301 }
3302 #endif
3303
3304 VstPatchChunkInfo info =
3305 {
3306 1,
3307 wxINT32_SWAP_ON_LE(iptr[4]),
3308 wxINT32_SWAP_ON_LE(iptr[5]),
3309 wxINT32_SWAP_ON_LE(iptr[6]),
3310 ""
3311 };
3312
3313 // Ensure this program looks to belong to the current plugin
3314 if ((info.pluginUniqueID != mAEffect->uniqueID) &&
3315 (info.pluginVersion != mAEffect->version) &&
3316 (info.numElements != mAEffect->numParams))
3317 {
3318 return false;
3319 }
3320
3321 // Get the number of parameters
3322 int numParams = info.numElements;
3323
3324 // At this point, we have to have enough to include the program name as well
3325 if (len < 56)
3326 {
3327 return false;
3328 }
3329
3330 // Get the program name
3331 wxString progName(wxString::From8BitData((char *)&iptr[7]));
3332
3333 // Might be a regular program
3334 if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'x', 'C', 'k'))
3335 {
3336 // We finally know the full length of the program
3337 int proglen = 56 + (numParams * sizeof(float));
3338
3339 // Verify that we have enough for all of the parameter values
3340 if (len < proglen)
3341 {
3342 return false;
3343 }
3344
3345 // Validate all of the parameter values
3346 for (int i = 0; i < numParams; i++)
3347 {
3348 uint32_t ival = wxUINT32_SWAP_ON_LE(iptr[14 + i]);
3349 float val = reinterpretAsFloat(ival);
3350 if (val < 0.0 || val > 1.0)
3351 {
3352 return false;
3353 }
3354 }
3355
3356 // They look okay...time to start changing things
3357 if (!dryrun)
3358 {
3359 // Ask the effect if this is an acceptable program
3360 if (callDispatcher(effBeginLoadProgram, 0, 0, &info, 0.0) == -1)
3361 {
3362 return false;
3363 }
3364
3365 // Load all of the parameters
3366 callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
3367 for (int i = 0; i < numParams; i++)
3368 {
3369 wxUint32 val = wxUINT32_SWAP_ON_LE(iptr[14 + i]);
3370 callSetParameter(i, reinterpretAsFloat(val));
3371 }
3372 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
3373 }
3374
3375 // Update in case we're loading an "FxBk" format bank file
3376 *bptr += proglen;
3377 len -= proglen;
3378 }
3379 // Maybe we have a program chunk
3380 else if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'P', 'C', 'h'))
3381 {
3382 // Can't load programs chunks if the plugin doesn't support it
3383 if (!(mAEffect->flags & effFlagsProgramChunks))
3384 {
3385 return false;
3386 }
3387
3388 // Verify that we have enough to grab the chunk size
3389 if (len < 60)
3390 {
3391 return false;
3392 }
3393
3394 // Get the chunk size
3395 int size = wxINT32_SWAP_ON_LE(iptr[14]);
3396
3397 // We finally know the full length of the program
3398 int proglen = 60 + size;
3399
3400 // Verify that we have enough for the entire program
3401 if (len < proglen)
3402 {
3403 return false;
3404 }
3405
3406 // Set the entire program in one shot
3407 if (!dryrun)
3408 {
3409 callSetChunk(true, size, &iptr[15], &info);
3410 }
3411
3412 // Update in case we're loading an "FxBk" format bank file
3413 *bptr += proglen;
3414 len -= proglen;
3415 }
3416 else
3417 {
3418 // Unknown type
3419 return false;
3420 }
3421
3422 if (!dryrun)
3423 {
3424 SetString(effSetProgramName, wxString(progName), index);
3425 }
3426
3427 return true;
3428 }
3429
LoadXML(const wxFileName & fn)3430 bool VSTEffect::LoadXML(const wxFileName & fn)
3431 {
3432 mInChunk = false;
3433 mInSet = false;
3434
3435 // default to read as XML file
3436 // Load the program
3437 XMLFileReader reader;
3438 bool ok = reader.Parse(this, fn.GetFullPath());
3439
3440 // Something went wrong with the file, clean up
3441 if (mInSet)
3442 {
3443 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
3444
3445 mInSet = false;
3446 }
3447
3448 if (!ok)
3449 {
3450 // Inform user of load failure
3451 AudacityMessageBox(
3452 reader.GetErrorStr(),
3453 XO("Error Loading VST Presets"),
3454 wxOK | wxCENTRE,
3455 mParent);
3456 return false;
3457 }
3458
3459 return true;
3460 }
3461
SaveFXB(const wxFileName & fn)3462 void VSTEffect::SaveFXB(const wxFileName & fn)
3463 {
3464 // Create/Open the file
3465 const wxString fullPath{fn.GetFullPath()};
3466 wxFFile f(fullPath, wxT("wb"));
3467 if (!f.IsOpened())
3468 {
3469 AudacityMessageBox(
3470 XO("Could not open file: \"%s\"").Format( fullPath ),
3471 XO("Error Saving VST Presets"),
3472 wxOK | wxCENTRE,
3473 mParent);
3474 return;
3475 }
3476
3477 wxMemoryBuffer buf;
3478 wxInt32 subType;
3479 void *chunkPtr = nullptr;
3480 int chunkSize = 0;
3481 int dataSize = 148;
3482 wxInt32 tab[8];
3483 int curProg = 0 ; //mProgram->GetCurrentSelection();
3484
3485 if (mAEffect->flags & effFlagsProgramChunks)
3486 {
3487 subType = CCONST('F', 'B', 'C', 'h');
3488
3489 chunkSize = callDispatcher(effGetChunk, 0, 0, &chunkPtr, 0.0);
3490 dataSize += 4 + chunkSize;
3491 }
3492 else
3493 {
3494 subType = CCONST('F', 'x', 'B', 'k');
3495
3496 for (int i = 0; i < mAEffect->numPrograms; i++)
3497 {
3498 SaveFXProgram(buf, i);
3499 }
3500
3501 dataSize += buf.GetDataLen();
3502 }
3503
3504 tab[0] = wxINT32_SWAP_ON_LE(CCONST('C', 'c', 'n', 'K'));
3505 tab[1] = wxINT32_SWAP_ON_LE(dataSize);
3506 tab[2] = wxINT32_SWAP_ON_LE(subType);
3507 tab[3] = wxINT32_SWAP_ON_LE(curProg >= 0 ? 2 : 1);
3508 tab[4] = wxINT32_SWAP_ON_LE(mAEffect->uniqueID);
3509 tab[5] = wxINT32_SWAP_ON_LE(mAEffect->version);
3510 tab[6] = wxINT32_SWAP_ON_LE(mAEffect->numPrograms);
3511 tab[7] = wxINT32_SWAP_ON_LE(curProg >= 0 ? curProg : 0);
3512
3513 f.Write(tab, sizeof(tab));
3514 if (!f.Error())
3515 {
3516 char padding[124];
3517 memset(padding, 0, sizeof(padding));
3518 f.Write(padding, sizeof(padding));
3519
3520 if (!f.Error())
3521 {
3522 if (mAEffect->flags & effFlagsProgramChunks)
3523 {
3524 wxInt32 size = wxINT32_SWAP_ON_LE(chunkSize);
3525 f.Write(&size, sizeof(size));
3526 f.Write(chunkPtr, chunkSize);
3527 }
3528 else
3529 {
3530 f.Write(buf.GetData(), buf.GetDataLen());
3531 }
3532 }
3533 }
3534
3535 if (f.Error())
3536 {
3537 AudacityMessageBox(
3538 XO("Error writing to file: \"%s\"").Format( fullPath ),
3539 XO("Error Saving VST Presets"),
3540 wxOK | wxCENTRE,
3541 mParent);
3542 }
3543
3544 f.Close();
3545
3546 return;
3547 }
3548
SaveFXP(const wxFileName & fn)3549 void VSTEffect::SaveFXP(const wxFileName & fn)
3550 {
3551 // Create/Open the file
3552 const wxString fullPath{ fn.GetFullPath() };
3553 wxFFile f(fullPath, wxT("wb"));
3554 if (!f.IsOpened())
3555 {
3556 AudacityMessageBox(
3557 XO("Could not open file: \"%s\"").Format( fullPath ),
3558 XO("Error Saving VST Presets"),
3559 wxOK | wxCENTRE,
3560 mParent);
3561 return;
3562 }
3563
3564 wxMemoryBuffer buf;
3565
3566 int ndx = callDispatcher(effGetProgram, 0, 0, NULL, 0.0);
3567 SaveFXProgram(buf, ndx);
3568
3569 f.Write(buf.GetData(), buf.GetDataLen());
3570 if (f.Error())
3571 {
3572 AudacityMessageBox(
3573 XO("Error writing to file: \"%s\"").Format( fullPath ),
3574 XO("Error Saving VST Presets"),
3575 wxOK | wxCENTRE,
3576 mParent);
3577 }
3578
3579 f.Close();
3580
3581 return;
3582 }
3583
SaveFXProgram(wxMemoryBuffer & buf,int index)3584 void VSTEffect::SaveFXProgram(wxMemoryBuffer & buf, int index)
3585 {
3586 wxInt32 subType;
3587 void *chunkPtr;
3588 int chunkSize;
3589 int dataSize = 48;
3590 char progName[28];
3591 wxInt32 tab[7];
3592
3593 callDispatcher(effGetProgramNameIndexed, index, 0, &progName, 0.0);
3594 progName[27] = '\0';
3595 chunkSize = strlen(progName);
3596 memset(&progName[chunkSize], 0, sizeof(progName) - chunkSize);
3597
3598 if (mAEffect->flags & effFlagsProgramChunks)
3599 {
3600 subType = CCONST('F', 'P', 'C', 'h');
3601
3602 chunkSize = callDispatcher(effGetChunk, 1, 0, &chunkPtr, 0.0);
3603 dataSize += 4 + chunkSize;
3604 }
3605 else
3606 {
3607 subType = CCONST('F', 'x', 'C', 'k');
3608
3609 dataSize += (mAEffect->numParams << 2);
3610 }
3611
3612 tab[0] = wxINT32_SWAP_ON_LE(CCONST('C', 'c', 'n', 'K'));
3613 tab[1] = wxINT32_SWAP_ON_LE(dataSize);
3614 tab[2] = wxINT32_SWAP_ON_LE(subType);
3615 tab[3] = wxINT32_SWAP_ON_LE(1);
3616 tab[4] = wxINT32_SWAP_ON_LE(mAEffect->uniqueID);
3617 tab[5] = wxINT32_SWAP_ON_LE(mAEffect->version);
3618 tab[6] = wxINT32_SWAP_ON_LE(mAEffect->numParams);
3619
3620 buf.AppendData(tab, sizeof(tab));
3621 buf.AppendData(progName, sizeof(progName));
3622
3623 if (mAEffect->flags & effFlagsProgramChunks)
3624 {
3625 wxInt32 size = wxINT32_SWAP_ON_LE(chunkSize);
3626 buf.AppendData(&size, sizeof(size));
3627 buf.AppendData(chunkPtr, chunkSize);
3628 }
3629 else
3630 {
3631 for (int i = 0; i < mAEffect->numParams; i++)
3632 {
3633 float val = callGetParameter(i);
3634 wxUint32 ival = wxUINT32_SWAP_ON_LE(reinterpretAsUint32(val));
3635 buf.AppendData(&ival, sizeof(ival));
3636 }
3637 }
3638
3639 return;
3640 }
3641
3642 // Throws exceptions rather than giving error return.
SaveXML(const wxFileName & fn)3643 void VSTEffect::SaveXML(const wxFileName & fn)
3644 // may throw
3645 {
3646 XMLFileWriter xmlFile{ fn.GetFullPath(), XO("Error Saving Effect Presets") };
3647
3648 xmlFile.StartTag(wxT("vstprogrampersistence"));
3649 xmlFile.WriteAttr(wxT("version"), wxT("2"));
3650
3651 xmlFile.StartTag(wxT("effect"));
3652 // Use internal name only in persistent information
3653 xmlFile.WriteAttr(wxT("name"), GetSymbol().Internal());
3654 xmlFile.WriteAttr(wxT("uniqueID"), mAEffect->uniqueID);
3655 xmlFile.WriteAttr(wxT("version"), mAEffect->version);
3656 xmlFile.WriteAttr(wxT("numParams"), mAEffect->numParams);
3657
3658 xmlFile.StartTag(wxT("program"));
3659 xmlFile.WriteAttr(wxT("name"), wxEmptyString); //mProgram->GetValue());
3660
3661 int clen = 0;
3662 if (mAEffect->flags & effFlagsProgramChunks)
3663 {
3664 void *chunk = NULL;
3665
3666 clen = (int) callDispatcher(effGetChunk, 1, 0, &chunk, 0.0);
3667 if (clen != 0)
3668 {
3669 xmlFile.StartTag(wxT("chunk"));
3670 xmlFile.WriteSubTree(VSTEffect::b64encode(chunk, clen) + wxT('\n'));
3671 xmlFile.EndTag(wxT("chunk"));
3672 }
3673 }
3674
3675 if (clen == 0)
3676 {
3677 for (int i = 0; i < mAEffect->numParams; i++)
3678 {
3679 xmlFile.StartTag(wxT("param"));
3680
3681 xmlFile.WriteAttr(wxT("index"), i);
3682 xmlFile.WriteAttr(wxT("name"),
3683 GetString(effGetParamName, i));
3684 xmlFile.WriteAttr(wxT("value"),
3685 wxString::Format(wxT("%f"),
3686 callGetParameter(i)));
3687
3688 xmlFile.EndTag(wxT("param"));
3689 }
3690 }
3691
3692 xmlFile.EndTag(wxT("program"));
3693
3694 xmlFile.EndTag(wxT("effect"));
3695
3696 xmlFile.EndTag(wxT("vstprogrampersistence"));
3697
3698 xmlFile.Commit();
3699 }
3700
HandleXMLTag(const std::string_view & tag,const AttributesList & attrs)3701 bool VSTEffect::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
3702 {
3703 if (tag == "vstprogrampersistence")
3704 {
3705 for (auto pair : attrs)
3706 {
3707 auto attr = pair.first;
3708 auto value = pair.second;
3709
3710 if (attr == "version")
3711 {
3712 if (!value.TryGet(mXMLVersion))
3713 {
3714 return false;
3715 }
3716
3717 if (mXMLVersion < 1 || mXMLVersion > 2)
3718 {
3719 return false;
3720 }
3721 }
3722 else
3723 {
3724 return false;
3725 }
3726 }
3727
3728 return true;
3729 }
3730
3731 if (tag == "effect")
3732 {
3733 memset(&mXMLInfo, 0, sizeof(mXMLInfo));
3734 mXMLInfo.version = 1;
3735 mXMLInfo.pluginUniqueID = mAEffect->uniqueID;
3736 mXMLInfo.pluginVersion = mAEffect->version;
3737 mXMLInfo.numElements = mAEffect->numParams;
3738
3739 for (auto pair : attrs)
3740 {
3741 auto attr = pair.first;
3742 auto value = pair.second;
3743
3744 if (attr == "name")
3745 {
3746 wxString strValue = value.ToWString();
3747
3748 if (strValue != GetSymbol().Internal())
3749 {
3750 auto msg = XO("This parameter file was saved from %s. Continue?")
3751 .Format( strValue );
3752 int result = AudacityMessageBox(
3753 msg,
3754 XO("Confirm"),
3755 wxYES_NO,
3756 mParent );
3757 if (result == wxNO)
3758 {
3759 return false;
3760 }
3761 }
3762 }
3763 else if (attr == "version")
3764 {
3765 long version;
3766 if (!value.TryGet(version))
3767 {
3768 return false;
3769 }
3770
3771 mXMLInfo.pluginVersion = (int) version;
3772 }
3773 else if (mXMLVersion > 1 && attr == "uniqueID")
3774 {
3775 long uniqueID;
3776 if (!value.TryGet(uniqueID))
3777 {
3778 return false;
3779 }
3780
3781 mXMLInfo.pluginUniqueID = (int) uniqueID;
3782 }
3783 else if (mXMLVersion > 1 && attr == "numParams")
3784 {
3785 long numParams;
3786 if (!value.TryGet(numParams))
3787 {
3788 return false;
3789 }
3790
3791 mXMLInfo.numElements = (int) numParams;
3792 }
3793 else
3794 {
3795 return false;
3796 }
3797 }
3798
3799 return true;
3800 }
3801
3802 if (tag == "program")
3803 {
3804 for (auto pair : attrs)
3805 {
3806 auto attr = pair.first;
3807 auto value = pair.second;
3808
3809 if (attr == "name")
3810 {
3811 const wxString strValue = value.ToWString();
3812
3813 if (strValue.length() > 24)
3814 {
3815 return false;
3816 }
3817
3818 int ndx = 0; //mProgram->GetCurrentSelection();
3819 if (ndx == wxNOT_FOUND)
3820 {
3821 ndx = 0;
3822 }
3823
3824 SetString(effSetProgramName, strValue, ndx);
3825 }
3826 else
3827 {
3828 return false;
3829 }
3830 }
3831
3832 mInChunk = false;
3833
3834 if (callDispatcher(effBeginLoadProgram, 0, 0, &mXMLInfo, 0.0) == -1)
3835 {
3836 return false;
3837 }
3838
3839 callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
3840
3841 mInSet = true;
3842
3843 return true;
3844 }
3845
3846 if (tag == "param")
3847 {
3848 long ndx = -1;
3849 double val = -1.0;
3850
3851 for (auto pair : attrs)
3852 {
3853 auto attr = pair.first;
3854 auto value = pair.second;
3855
3856 if (attr == "index")
3857 {
3858 if (!value.TryGet(ndx))
3859 {
3860 return false;
3861 }
3862
3863 if (ndx < 0 || ndx >= mAEffect->numParams)
3864 {
3865 // Could be a different version of the effect...probably should
3866 // tell the user
3867 return false;
3868 }
3869 }
3870 // "name" attribute is ignored for params
3871 /* else if (attr == "name")
3872 {
3873
3874 // Nothing to do with it for now
3875 }*/
3876 else if (attr == "value")
3877 {
3878 if (!value.TryGet(val))
3879 {
3880 return false;
3881 }
3882
3883 if (val < 0.0 || val > 1.0)
3884 {
3885 return false;
3886 }
3887 }
3888 }
3889
3890 if (ndx == -1 || val == -1.0)
3891 {
3892 return false;
3893 }
3894
3895 callSetParameter(ndx, val);
3896
3897 return true;
3898 }
3899
3900 if (tag == "chunk")
3901 {
3902 mInChunk = true;
3903 return true;
3904 }
3905
3906 return false;
3907 }
3908
HandleXMLEndTag(const std::string_view & tag)3909 void VSTEffect::HandleXMLEndTag(const std::string_view& tag)
3910 {
3911 if (tag == "chunk")
3912 {
3913 if (mChunk.length())
3914 {
3915 ArrayOf<char> buf{ mChunk.length() / 4 * 3 };
3916
3917 int len = VSTEffect::b64decode(mChunk, buf.get());
3918 if (len)
3919 {
3920 callSetChunk(true, len, buf.get(), &mXMLInfo);
3921 }
3922
3923 mChunk.clear();
3924 }
3925 mInChunk = false;
3926 }
3927
3928 if (tag == "program")
3929 {
3930 if (mInSet)
3931 {
3932 callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
3933
3934 mInSet = false;
3935 }
3936 }
3937 }
3938
HandleXMLContent(const std::string_view & content)3939 void VSTEffect::HandleXMLContent(const std::string_view& content)
3940 {
3941 if (mInChunk)
3942 {
3943 mChunk += wxString(std::string(content)).Trim(true).Trim(false);
3944 }
3945 }
3946
HandleXMLChild(const std::string_view & tag)3947 XMLTagHandler *VSTEffect::HandleXMLChild(const std::string_view& tag)
3948 {
3949 if (tag == "vstprogrampersistence")
3950 {
3951 return this;
3952 }
3953
3954 if (tag == "effect")
3955 {
3956 return this;
3957 }
3958
3959 if (tag == "program")
3960 {
3961 return this;
3962 }
3963
3964 if (tag == "param")
3965 {
3966 return this;
3967 }
3968
3969 if (tag == "chunk")
3970 {
3971 return this;
3972 }
3973
3974 return NULL;
3975 }
3976
3977 #endif // USE_VST
3978