Audacity 3.2.0
VST3Wrapper.cpp
Go to the documentation of this file.
1#include "VST3Wrapper.h"
2#include <stdexcept>
3#include <optional>
4#include <map>
5
6#include "EffectInterface.h"
7
9
10#include <pluginterfaces/vst/ivsteditcontroller.h>
11#include <pluginterfaces/vst/ivstparameterchanges.h>
12#include <wx/dir.h>
13#include <wx/tokenzr.h>
14
15#include "AudacityException.h"
16#include "ConfigInterface.h"
17#include "FileException.h"
18#include "memorystream.h"
19#include "MemoryX.h"
20#include "VST3Utils.h"
22
23namespace
24{
25
26// define some shared registry keys
27constexpr auto processorStateKey = wxT("ProcessorState");
28constexpr auto controllerStateKey = wxT("ControllerState");
29constexpr auto parametersKey = wxT("Parameters");
30
32{
34 Steinberg::Vst::ParamID rootUnitProgramChangeParameterID { Steinberg::Vst::kNoParamId };
35 Steinberg::Vst::ProgramListID rootUnitProgramListID { Steinberg::Vst::kNoProgramListId };
36 Steinberg::int32 rootUnitProgramCount { 0 };
37};
38
39std::map<std::string, VST3PluginCache> sVST3PluginCache;
40
41VST3PluginCache* GetCache(const VST3::UID& effectUid)
42{
43 const auto key = effectUid.toString();
44 auto it = sVST3PluginCache.find(key);
45 if(it != sVST3PluginCache.end())
46 return &it->second;
47 return nullptr;
48}
49
50VST3PluginCache& CreateCache(const VST3::UID& effectUid)
51{
52 const auto key = effectUid.toString();
53 assert(sVST3PluginCache.find(key) == sVST3PluginCache.end());
54 const auto result = sVST3PluginCache.insert(std::pair { key, VST3PluginCache {} });
55 return result.first->second;
56}
57
58Steinberg::Vst::SpeakerArrangement GetBusArragementForChannels(
59 int32_t channelsCount, Steinberg::Vst::SpeakerArrangement defaultArragment)
60{
61 if (channelsCount == 1)
62 return defaultArragment;
63
64 return Steinberg::Vst::SpeakerArr::kStereo;
65}
66
68{
70 std::map<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue> parameterChanges;
71
73 std::optional<wxString> processorState;
75 std::optional<wxString> controllerState;
76};
77
78std::map<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue> ParametersFromString(const wxString& str)
79{
80 std::map<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue> result;
81 wxStringTokenizer tokenizer(str, ";");
82 while(tokenizer.HasMoreTokens())
83 {
84 auto token = tokenizer.GetNextToken();
85
86 const auto split = token.Find('=');
87 if(split == wxNOT_FOUND)
88 continue;
89
90 unsigned long id;
91 double value;
92 if(!token.Left(split).ToULong(&id) ||
93 !token.Right(token.Length() - split - 1).ToDouble(&value))
94 continue;
95
96 result[id] = value;
97 }
98 return result;
99}
100
101wxString ParametersToString(const std::map<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue>& params)
102{
103 wxString result;
104 for(auto& p : params)
105 result.Append(wxString::Format(
106 "%lu=%f;", static_cast<unsigned long>(p.first), p.second));
107 return result;
108}
109
111{
112 auto vst3settings = settings.cast<VST3EffectSettings>();
113 assert(vst3settings);
114 return *vst3settings;
115}
116
118{
119 auto vst3settings = settings.cast<VST3EffectSettings>();
120 assert(vst3settings);
121 return *vst3settings;
122}
123
124
125//Activates main audio input/output buses and disables others (event, audio aux)
126bool ActivateMainAudioBuses(Steinberg::Vst::IComponent& component)
127{
128 using namespace Steinberg;
129
130 constexpr int32 MaxChannelsPerAudioBus = 2;
131
132 std::vector<Vst::SpeakerArrangement> defaultInputSpeakerArrangements;
133 std::vector<Vst::SpeakerArrangement> defaultOutputSpeakerArrangements;
134
135 const auto processor = FUnknownPtr<Vst::IAudioProcessor>(&component);
136
137 for(int i = 0, count = component.getBusCount(Vst::kAudio, Vst::kInput); i < count; ++i)
138 {
139 Vst::BusInfo busInfo {};
140 Vst::SpeakerArrangement arrangement {0ull};
141
142 component.getBusInfo(Vst::kAudio, Vst::kInput, i, busInfo);
143
144 Vst::SpeakerArrangement defaultArragement {};
145 processor->getBusArrangement(Vst::kInput, i, defaultArragement);
146
147 arrangement =
148 busInfo.busType == Vst::kMain
149 ? GetBusArragementForChannels(busInfo.channelCount, defaultArragement)
150 : defaultArragement;
151
152 component.activateBus(Vst::kAudio, Vst::kInput, i, busInfo.busType == Vst::kMain);
153 defaultInputSpeakerArrangements.push_back(arrangement);
154 }
155 for(int i = 0, count = component.getBusCount(Vst::kAudio, Vst::kOutput); i < count; ++i)
156 {
157 Vst::BusInfo busInfo {};
158 Vst::SpeakerArrangement arrangement {0ull};
159
160 component.getBusInfo(Vst::kAudio, Vst::kOutput, i, busInfo);
161
162 Vst::SpeakerArrangement defaultArragement {};
163 processor->getBusArrangement(Vst::kOutput, i, defaultArragement);
164
165 arrangement =
166 busInfo.busType == Vst::kMain
167 ? GetBusArragementForChannels(busInfo.channelCount, defaultArragement)
168 : defaultArragement;
169
170 component.activateBus(Vst::kAudio, Vst::kOutput, i, busInfo.busType == Vst::kMain);
171 defaultOutputSpeakerArrangements.push_back(arrangement);
172 }
173 for(int i = 0, count = component.getBusCount(Vst::kEvent, Vst::kInput); i < count; ++i)
174 component.activateBus(Vst::kEvent, Vst::kInput, i, 0);
175 for(int i = 0, count = component.getBusCount(Vst::kEvent, Vst::kOutput); i < count; ++i)
176 component.activateBus(Vst::kEvent, Vst::kOutput, i, 0);
177
178 auto result = processor->setBusArrangements(
179 defaultInputSpeakerArrangements.empty() ? nullptr : defaultInputSpeakerArrangements.data(), defaultInputSpeakerArrangements.size(),
180 defaultOutputSpeakerArrangements.empty() ? nullptr : defaultOutputSpeakerArrangements.data(), defaultOutputSpeakerArrangements.size()
181 );
182
183 return result == kResultOk;
184}
185
186//The component should be disabled
187bool SetupProcessing(Steinberg::Vst::IComponent& component, Steinberg::Vst::ProcessSetup& setup)
188{
189 using namespace Steinberg;
190 auto processor = FUnknownPtr<Vst::IAudioProcessor>(&component);
191
192 if(processor->setupProcessing(setup) == kResultOk)
193 {
194 //We don't (yet) support custom input/output channel configuration
195 //on the host side. No support for event bus. Use default bus and
196 //channel configuration
197 return ActivateMainAudioBuses(component);
198 }
199 return false;
200}
201
202class InputParameterChanges final : public Steinberg::Vst::IParameterChanges
203{
204 const Steinberg::int32 mParameterCount;
206public:
207
208 InputParameterChanges(const std::vector<std::pair<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue>>& values, SingleInputParameterValue* queues)
209 : mParameterQueues(queues), mParameterCount(values.size())
210 {
211 FUNKNOWN_CTOR
212
213 int queueIndex{0};
214 for(auto& p : values)
215 queues[queueIndex++].Set(p.first, p.second);
216 }
217
219 {
220 FUNKNOWN_DTOR;
221 }
222
223 Steinberg::Vst::IParamValueQueue* PLUGIN_API
224 addParameterData(const Steinberg::Vst::ParamID& id, Steinberg::int32& index) override
225 {
226 return nullptr;
227 }
228
229 Steinberg::int32 PLUGIN_API getParameterCount() override
230 {
231 return mParameterCount;
232 }
233 Steinberg::Vst::IParamValueQueue* PLUGIN_API getParameterData(Steinberg::int32 index) override
234 {
235 return &mParameterQueues[index];
236 }
237
239};
240
241IMPLEMENT_FUNKNOWN_METHODS(InputParameterChanges, Steinberg::Vst::IParameterChanges,
242 Steinberg::Vst::IParameterChanges::iid);
243
244class ComponentHandler : public Steinberg::Vst::IComponentHandler
245{
247 EffectSettingsAccess* mAccess{nullptr};
248 //Used to prevent calls from non-UI thread
250
251 EffectSettings* mStateChangeSettings {nullptr};
252 std::map<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue> mParametersCache;
253 std::map<Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue> mCurrentParamValues;
254
255public:
256
258 : mThreadId(std::this_thread::get_id()), mWrapper(wrapper)
259 {
260 FUNKNOWN_CTOR;
261 }
262 virtual ~ComponentHandler() { FUNKNOWN_DTOR; }
263
265 {
266 const auto paramsCount = mWrapper.mEditController->getParameterCount();
267
268 for (int i = 0; i < paramsCount; ++i)
269 {
270 using Steinberg::Vst::ParameterInfo;
271 ParameterInfo info {};
272 mWrapper.mEditController->getParameterInfo(i, info);
273
274 if ((info.flags & ParameterInfo::kIsReadOnly) != 0)
275 continue;
276
277 mCurrentParamValues[info.id] =
278 mWrapper.mEditController->getParamNormalized(info.id);
279 }
280 }
281
282 void SetAccess(EffectSettingsAccess* access) { mAccess = access; }
283
284 EffectSettingsAccess* GetAccess() { return mAccess; }
285
287 {
288 mStateChangeSettings = &settings;
289 }
290
292 {
293 assert(mStateChangeSettings != nullptr);
294 FlushCache(*mStateChangeSettings);
295 mStateChangeSettings = nullptr;
296 }
297
299 {
300 if(mParametersCache.empty())
301 return;
302
303 auto& vst3settings = GetSettings(settings);
304 for(auto& p : mParametersCache)
305 vst3settings.parameterChanges[p.first] = p.second;
306 mParametersCache.clear();
307 }
308
310 {
311 mParametersCache.clear();
312 }
313
315 Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized)
316 {
317 auto it = mCurrentParamValues.find(id);
318
319 if (it == mCurrentParamValues.end())
320 return;
321
322 // Tolerance to avoid unnecessary updates
323 // Waves plugins constantly update several parameters
324 // with very small changes in the values without any
325 // user input
326 constexpr auto epsilon = Steinberg::Vst::ParamValue(1e-5);
327
328 if (std::abs(it->second - valueNormalized) < epsilon)
329 return;
330
331 it->second = valueNormalized;
332
333 if (mStateChangeSettings == nullptr && mWrapper.ParamChangedHandler)
334 mWrapper.ParamChangedHandler(id, valueNormalized);
335 }
336
337 Steinberg::tresult PLUGIN_API beginEdit(Steinberg::Vst::ParamID id) override { return Steinberg::kResultOk; }
338
339 Steinberg::tresult PLUGIN_API performEdit(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) override
340 {
341 if(std::this_thread::get_id() != mThreadId)
342 return Steinberg::kResultFalse;
343
344 NotifyParamChange(id, valueNormalized);
345
346 if(mStateChangeSettings != nullptr || !mWrapper.IsActive())
347 // Collecting edit callbacks from the plug-in, in response to changes
348 // of complete state, while doing FetchSettings() (which may be delayed...)
349 mParametersCache[id] = valueNormalized;
350 else if(mAccess)
351 {
352 mAccess->ModifySettings([&](EffectSettings& settings)
353 {
354 auto& vst3settings = GetSettings(settings);
355 vst3settings.parameterChanges[id] = valueNormalized;
356 return nullptr;
357 });
358 }
359
360 return Steinberg::kResultOk;
361 }
362
363 Steinberg::tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override { return Steinberg::kResultOk; }
364
365 Steinberg::tresult PLUGIN_API restartComponent(Steinberg::int32 flags) override { return Steinberg::kNotImplemented; }
366
367 DECLARE_FUNKNOWN_METHODS
368};
369
370IMPLEMENT_FUNKNOWN_METHODS(ComponentHandler, Steinberg::Vst::IComponentHandler, Steinberg::Vst::IComponentHandler::iid)
371
372}
373
374
375void SingleInputParameterValue::Set(Steinberg::Vst::ParamID id, const Steinberg::Vst::ParamValue value)
376{
378 mValue = value;
379}
380
381Steinberg::tresult SingleInputParameterValue::addPoint(Steinberg::int32 sampleOffset, Steinberg::Vst::ParamValue value,
382 Steinberg::int32& index)
383{
384 return Steinberg::kResultFalse;
385}
386
388{
389 return mParameterId;
390}
391
392Steinberg::tresult SingleInputParameterValue::getPoint(Steinberg::int32 index, Steinberg::int32& sampleOffset,
393 Steinberg::Vst::ParamValue& value)
394{
395 sampleOffset = 0;
396 value = mValue;
397 return Steinberg::kResultOk;
398}
399
401{
402 return 1;
403}
404
405IMPLEMENT_FUNKNOWN_METHODS(SingleInputParameterValue, Steinberg::Vst::IParamValueQueue, Steinberg::Vst::IParamValueQueue::iid);
406
407VST3Wrapper::VST3Wrapper(VST3::Hosting::Module& module, const VST3::Hosting::ClassInfo& effectClassInfo)
408 : mModule{ module }
409 , mEffectClassInfo { effectClassInfo }
410{
411 using namespace Steinberg;
412
413 const auto& pluginFactory = module.getFactory();
414
415 auto effectComponent = pluginFactory.createInstance<Vst::IComponent>(effectClassInfo.ID());
416 if(!effectComponent)
417 throw std::runtime_error("Cannot create VST3 effect component");
418 if(effectComponent->initialize(&AudacityVst3HostApplication::Get()) != kResultOk)
419 throw std::runtime_error("Cannot initialize VST3 effect component");
420
421 auto audioProcessor = FUnknownPtr<Vst::IAudioProcessor>(effectComponent);
422 if(!audioProcessor)
423 //It's stated that "This interface must always be supported by audio processing plug-ins."
424 throw std::runtime_error("VST3 plugin does not provide audio processor interface");
425
426 if(audioProcessor->canProcessSampleSize(Vst::kSample32) != kResultTrue)
427 throw std::runtime_error("32-bit sample size not supported");
428
429 mEffectComponent = effectComponent;
430 mAudioProcessor = audioProcessor;
431
432 auto editController = FUnknownPtr<Vst::IEditController>(mEffectComponent);
433 if(editController.get() == nullptr)
434 {
435 TUID controllerCID;
436
437 if (mEffectComponent->getControllerClassId (controllerCID) == kResultTrue)
438 editController = pluginFactory.createInstance<Vst::IEditController>(VST3::UID(controllerCID));
439 }
440
441 if(editController.get() == nullptr)
442 throw std::runtime_error("Failed to instantiate edit controller");
443
444 mEditController = editController;
445
447
448 mComponentHandler = owned(safenew ComponentHandler(*this));
449 mEditController->setComponentHandler(mComponentHandler);
450
451 const auto componentConnectionPoint = FUnknownPtr<Vst::IConnectionPoint>{ mEffectComponent };
452 const auto controllerConnectionPoint = FUnknownPtr<Vst::IConnectionPoint>{ mEditController };
453
454 if (componentConnectionPoint && controllerConnectionPoint)
455 {
456 mComponentConnectionProxy = owned(safenew internal::ConnectionProxy(componentConnectionPoint));
457 mControllerConnectionProxy = owned(safenew internal::ConnectionProxy(controllerConnectionPoint));
458
459 mComponentConnectionProxy->connect(controllerConnectionPoint);
460 mControllerConnectionProxy->connect(componentConnectionPoint);
461 }
462
463 if(GetCache(mEffectClassInfo.ID()) == nullptr)
464 {
465 //First time instantiation...
466 auto& cache = CreateCache(mEffectClassInfo.ID());
467
468 //Find root unit program change parameter ID (if any)
469 for(int32 parameterIndex = 0, parametersCount = mEditController->getParameterCount();
470 parameterIndex < parametersCount;
471 ++parameterIndex)
472 {
473 Vst::ParameterInfo parameterInfo {};
474 if(mEditController->getParameterInfo(parameterIndex, parameterInfo) != kResultOk)
475 continue;
476
477 if(parameterInfo.unitId != Vst::kRootUnitId)
478 continue;
479 if((parameterInfo.flags & Vst::ParameterInfo::kIsProgramChange) == 0)
480 continue;
481
482 auto unitInfoProvider = FUnknownPtr<Vst::IUnitInfo>(mEditController);
483 if(!unitInfoProvider)
484 //Sanity check...
485 break;
486
487 for(int32 unitIndex = 0, unitCount = unitInfoProvider->getUnitCount();
488 unitIndex < unitCount;
489 ++unitIndex)
490 {
491 Vst::UnitInfo unitInfo{};
492 if(unitInfoProvider->getUnitInfo(unitIndex, unitInfo) != kResultOk)
493 continue;
494
495 if(unitInfo.id != Vst::kRootUnitId)
496 continue;
497
498 for(int32 programListIndex = 0, programListCount = unitInfoProvider->getProgramListCount();
499 programListIndex < programListCount;
500 ++programListIndex)
501 {
502 Vst::ProgramListInfo programListInfo{};
503 if(unitInfoProvider->getProgramListInfo(programListIndex, programListInfo) != kResultOk)
504 continue;
505
506 if(programListInfo.id != unitInfo.programListId)//unitInfo.id == kRootUnitId
507 continue;
508
509 cache.rootUnitProgramListID = programListInfo.id;
510 cache.rootUnitProgramCount = programListInfo.programCount;
511 break;
512 }
513
514 if(cache.rootUnitProgramListID == Vst::kNoProgramListId)
515 cache.rootUnitProgramCount = parameterInfo.stepCount + 1;
516
517 cache.rootUnitProgramChangeParameterID = parameterInfo.id;
518
519 break;
520 }
521
522 break;
523 }
524 }
525}
526
528{
529 using namespace Steinberg;
530
532 mComponentConnectionProxy->disconnect(FUnknownPtr<Vst::IConnectionPoint>(mEditController));
534 mControllerConnectionProxy->disconnect(FUnknownPtr<Vst::IConnectionPoint>(mEffectComponent));
535
537 {
538 mEditController->setComponentHandler(nullptr);
539 mEditController->terminate();
540 }
542 mEffectComponent->terminate();
543}
544
546{
547 using namespace Steinberg;
548
549 //Preinitialize with some default values in case if parameters
550 //flush happens before processing initialized
551 mSetup.maxSamplesPerBlock = 512;
552 mSetup.processMode = Vst::kOffline;
553 mSetup.symbolicSampleSize = Vst::kSample32;
554 mSetup.sampleRate = 44100.0;
555
557 throw std::runtime_error("bus configuration not supported");
558
559 mParameterQueues = std::make_unique<SingleInputParameterValue[]>(mEditController->getParameterCount());
560 mParameters.reserve(mEditController->getParameterCount());
561
562 Steinberg::MemoryStream stateStream;
563 if(mEffectComponent->getState(&stateStream) == kResultOk)
564 {
565 int64 unused;
566 stateStream.seek(0, IBStream::kIBSeekSet, &unused);
567 mEditController->setComponentState(&stateStream);
568 }
569
570 {
571 auto cache = GetCache(mEffectClassInfo.ID());
572 if(!cache->defaultSettings.has_value())
573 {
574 cache->defaultSettings = MakeSettings();
575 StoreSettings(cache->defaultSettings);
576 }
577 }
578
579 static_cast<ComponentHandler*>(mComponentHandler.get())
580 ->LoadCurrentParamValues();
581}
582
583const VST3::Hosting::ClassInfo& VST3Wrapper::GetEffectClassInfo() const
584{
585 return mEffectClassInfo;
586}
587
588bool VST3Wrapper::IsActive() const noexcept
589{
590 return mActive;
591}
592
594{
595 //TODO: perform version check
596 {
597 auto componentHandler = static_cast<ComponentHandler*>(mComponentHandler.get());
598 componentHandler->ResetCache();
599 componentHandler->BeginStateChange(settings);
600 auto cleanup = finally([&] { componentHandler->EndStateChange(); });
601
602 //Restore state
603 const auto* vst3settings = &GetSettings(settings);
604 if(!vst3settings->processorState.has_value())
606
607 if(vst3settings->processorState.has_value())
608 {
609 auto processorState = PresetsBufferStream::fromString(*vst3settings->processorState);
610 processorState->seek(0, Steinberg::IBStream::kIBSeekSet);
611 if(mEffectComponent->setState(processorState) == Steinberg::kResultOk)
612 {
613 processorState->seek(0, Steinberg::IBStream::kIBSeekSet);
614 if(mEditController->setComponentState(processorState) == Steinberg::kResultOk)
615 {
616 if(vst3settings->controllerState.has_value())
617 {
618 auto controllerState = PresetsBufferStream::fromString(*vst3settings->controllerState);
619 controllerState->seek(0, Steinberg::IBStream::kIBSeekSet);
620 mEditController->setState(controllerState);
621 }
622 }
623 }
624 }
625 }
626 //restore parameters if present
627 auto& vst3setting = GetSettings(settings);
628 for(auto& p : vst3setting.parameterChanges)
629 mEditController->setParamNormalized(p.first, p.second);
630}
631
633{
634 using namespace Steinberg;
635
636 VST3EffectSettings vst3settings;
637
638 {
639 PresetsBufferStream processorState;
640 if(mEffectComponent->getState(&processorState) == kResultOk)
641 vst3settings.processorState = processorState.toString();
642 }
643 {
644 PresetsBufferStream controllerState;
645 if(mEditController->getState(&controllerState) == kResultOk)
646 vst3settings.controllerState = controllerState.toString();
647 }
648
649 std::swap(vst3settings, GetSettings(settings));
650}
651
652void VST3Wrapper::LoadPreset(const wxString& presetId)
653{
654 using namespace Steinberg;
655
656 auto cache = GetCache(mEffectClassInfo.ID());
657
658 if(cache->rootUnitProgramChangeParameterID != Vst::kNoParamId &&
659 cache->rootUnitProgramCount > 0)
660 {
661 Vst::UnitID unitId;
662 int32 programIndex;
663 if(VST3Utils::ParseFactoryPresetID(presetId, unitId, programIndex) &&
664 programIndex >= 0 && programIndex < cache->rootUnitProgramCount)
665 {
666 auto paramValueNormalized = static_cast<Vst::ParamValue>(programIndex);
667 if(cache->rootUnitProgramCount > 1)
668 paramValueNormalized /= static_cast<Vst::ParamValue>(cache->rootUnitProgramCount - 1);
669
670 mEditController->setParamNormalized(
671 cache->rootUnitProgramChangeParameterID,
672 paramValueNormalized);
673
675 {
676 mComponentHandler->beginEdit(cache->rootUnitProgramChangeParameterID);
677 auto commit = finally([&] { mComponentHandler->endEdit(cache->rootUnitProgramChangeParameterID); });
678 mComponentHandler->performEdit(
679 cache->rootUnitProgramChangeParameterID,
680 paramValueNormalized);
681 }
682
683 return;
684 }
685 }
686
687 auto fileStream = owned(Vst::FileStream::open(presetId.c_str(), "rb"));
688 if(!fileStream)
690
691 if(!LoadPresetFromStream(fileStream))
692 {
695 XO("Unable to apply VST3 preset file %s").Format(presetId),
696 XO("Error"));
697 }
698}
699
700void VST3Wrapper::SavePresetToFile(const wxString& filepath) const
701{
702 using namespace Steinberg;
703
704 auto fileStream = owned(Vst::FileStream::open(filepath.c_str(), "wb"));
705 if(!fileStream)
707
708 if(!SavePresetToStream(fileStream))
711 XO("Failed to save VST3 preset to file"),
712 XO("Error"));
713}
714
715
716bool VST3Wrapper::LoadPresetFromStream(Steinberg::IBStream* fileStream)
717{
718 using namespace Steinberg;
719
720 return Vst::PresetFile::loadPreset
721 (
722 fileStream,
723 FUID::fromTUID(mEffectClassInfo.ID().data()),
724 mEffectComponent.get(),
725 mEditController.get()
726 );
727}
728
729
730bool VST3Wrapper::SavePresetToStream(Steinberg::IBStream* fileStream) const
731{
732 using namespace Steinberg;
733
734 return Vst::PresetFile::savePreset
735 (
736 fileStream,
737 FUID::fromTUID(mEffectClassInfo.ID().data()),
738 mEffectComponent.get(),
739
740 mEditController.get()
741 );
742}
743
744bool VST3Wrapper::Initialize(EffectSettings& settings, Steinberg::Vst::SampleRate sampleRate, Steinberg::int32 processMode, Steinberg::int32 maxSamplesPerBlock)
745{
746 using namespace Steinberg;
747
748 Vst::ProcessSetup setup = {
749 processMode,
750 Vst::kSample32,
751 maxSamplesPerBlock,
753 };
754
755 if(!SetupProcessing(*mEffectComponent.get(), setup))
756 return false;
757
758 mSetup = setup;
759
761
762 if(mEffectComponent->setActive(true) == kResultOk)
763 {
764 if(mAudioProcessor->setProcessing(true) != kResultFalse)
765 {
766 mProcessContext.state = Vst::ProcessContext::kPlaying;
767 mProcessContext.sampleRate = sampleRate;
768
769 mActive = true;
771 //make zero-flush, to make sure parameters are delivered to the processor...
772 Process(nullptr, nullptr, 0);
774 return true;
775 }
776 }
777 return false;
778}
779
781{
782 //Could be FlushParameters but processor is already configured
783 //If no calls to process were performed deliver changes here
784 mProcessContext.state = 0;
785 if(settings != nullptr)
786 {
788 Process(nullptr, nullptr, 0);
789 }
790 mAudioProcessor->setProcessing(false);
791 mEffectComponent->setActive(false);
792 mActive = false;
793
794 if(settings != nullptr)
796}
797
799{
801}
802
804{
805 const auto& vst3settings = GetSettings(settings);
806 for(auto& p : vst3settings.parameterChanges)
807 {
808 auto it = std::find_if(mParameters.begin(), mParameters.end(), [&p](const auto& v) { return v.first == p.first; });
809 if(it != mParameters.end())
810 it->second = p.second;
811 else
812 mParameters.push_back(p);
813 }
814}
815
816//Used as a workaround for issue #2555: some plugins do not accept changes
817//via IEditController::setParamNormalized, but seem to read current
818//parameter values directly from the DSP model.
819//
820//When processing is disabled this call helps synchronize internal state of the
821//IEditController, so that next time UI is opened it displays correct values.
822//
823//As a side effect subsequent call to IAudioProcessor::process may flush
824//plugin internal buffers
826{
827 if(!mActive)
828 {
829 auto componentHandler = static_cast<ComponentHandler*>(mComponentHandler.get());
830 componentHandler->FlushCache(settings);
831
832 const auto doProcessing = !GetSettings(settings).parameterChanges.empty();
833
834 if(hasChanges != nullptr)
835 *hasChanges = doProcessing;
836
837 if(!doProcessing)
838 return;
839
841 mActive = true;
842 if(mEffectComponent->setActive(true) == Steinberg::kResultOk)
843 {
845 if(mAudioProcessor->setProcessing(true) != Steinberg::kResultFalse)
846 {
847 mProcessContext.sampleRate = mSetup.sampleRate;
848 mProcessContext.state = 0;
849 Process(nullptr, nullptr, 0);
850 mAudioProcessor->setProcessing(false);
851 }
852 }
853 mEffectComponent->setActive(false);
854 mActive = false;
855 }
856 else if(hasChanges != nullptr)
857 *hasChanges = false;
858}
859
860size_t VST3Wrapper::Process(const float* const* inBlock, float* const* outBlock, size_t blockLen)
861{
862 using namespace Steinberg;
863
864 InputParameterChanges inputParameterChanges(mParameters, mParameterQueues.get());
865 mParameters.clear();
866
867 Vst::ProcessData data;
868 data.processMode = mSetup.processMode;
869 data.symbolicSampleSize = mSetup.symbolicSampleSize;
870 data.inputParameterChanges = &inputParameterChanges;
871 data.processContext = &mProcessContext;
872
873 static_assert(std::numeric_limits<decltype(blockLen)>::max()
874 >= std::numeric_limits<decltype(data.numSamples)>::max());
875
876 data.numSamples = static_cast<decltype(data.numSamples)>(std::min(
877 blockLen,
878 static_cast<decltype(blockLen)>(mSetup.maxSamplesPerBlock)
879 ));
880
881 data.numInputs = inBlock == nullptr ? 0 : mEffectComponent->getBusCount(Vst::kAudio, Vst::kInput);
882 data.numOutputs = outBlock == nullptr ? 0 : mEffectComponent->getBusCount(Vst::kAudio, Vst::kOutput);
883
884 if(data.numInputs > 0)
885 {
886 int inputBlocksOffset {0};
887
888 data.inputs = static_cast<Vst::AudioBusBuffers*>(
889 alloca(sizeof(Vst::AudioBusBuffers) * data.numInputs));
890
891 for(int busIndex = 0; busIndex < data.numInputs; ++busIndex)
892 {
893 Vst::BusInfo busInfo { };
894 if(mEffectComponent->getBusInfo(Vst::kAudio, Vst::kInput, busIndex, busInfo) != kResultOk)
895 {
896 return 0;
897 }
898 if(busInfo.busType == Vst::kMain)
899 {
900 data.inputs[busIndex].numChannels = busInfo.channelCount;
901 data.inputs[busIndex].channelBuffers32 = const_cast<float**>(inBlock + inputBlocksOffset);
902 inputBlocksOffset += busInfo.channelCount;
903 }
904 else
905 {
906 //aux is not yet supported
907 data.inputs[busIndex].numChannels = 0;
908 data.inputs[busIndex].channelBuffers32 = nullptr;
909 }
910 data.inputs[busIndex].silenceFlags = 0UL;
911 }
912 }
913 if(data.numOutputs > 0)
914 {
915 int outputBlocksOffset {0};
916
917 data.outputs = static_cast<Vst::AudioBusBuffers*>(
918 alloca(sizeof(Vst::AudioBusBuffers) * data.numOutputs));
919 for(int busIndex = 0; busIndex < data.numOutputs; ++busIndex)
920 {
921 Vst::BusInfo busInfo { };
922 if(mEffectComponent->getBusInfo(Vst::kAudio, Vst::kOutput, busIndex, busInfo) != kResultOk)
923 {
924 return 0;
925 }
926 if(busInfo.busType == Vst::kMain)
927 {
928 data.outputs[busIndex].numChannels = busInfo.channelCount;
929 data.outputs[busIndex].channelBuffers32 = const_cast<float**>(outBlock + outputBlocksOffset);
930 outputBlocksOffset += busInfo.channelCount;
931 }
932 else
933 {
934 //aux is not yet supported
935 data.outputs[busIndex].numChannels = 0;
936 data.outputs[busIndex].channelBuffers32 = nullptr;
937 }
938 data.outputs[busIndex].silenceFlags = 0UL;
939 }
940 }
941
942 const auto processResult = mAudioProcessor->process(data);
943
944 return processResult == kResultOk ?
945 data.numSamples : 0;
946}
947
948
950{
951 mAudioProcessor->setProcessing(false);
952}
953
955{
956 mAudioProcessor->setProcessing(true);
957}
958
960{
961 static_cast<ComponentHandler*>(mComponentHandler.get())->SetAccess(&access);
962}
963
965{
966 auto componentHandler = static_cast<ComponentHandler*>(mComponentHandler.get());
967 componentHandler->SetAccess(nullptr);
968}
969
970std::vector<VST3Wrapper::FactoryPresetDesc> VST3Wrapper::FindFactoryPresets() const
971{
972 using namespace Steinberg;
973
974 wxArrayString paths;
975 wxDir::GetAllFiles(VST3Utils::GetFactoryPresetsPath(mEffectClassInfo), &paths);
976
977 std::vector<FactoryPresetDesc> result;
978 for(auto& path : paths)
979 {
980 wxFileName filename(path);
981 result.push_back({ filename.GetFullPath(), filename.GetName() });
982 }
983
984 auto cache = GetCache(mEffectClassInfo.ID());
985 if(cache->rootUnitProgramChangeParameterID != Vst::kNoParamId &&
986 cache->rootUnitProgramCount > 0)
987 {
988 Vst::String128 programName;
989 if(cache->rootUnitProgramListID != Vst::kNoProgramListId)
990 {
991 auto unitInfoProvider = FUnknownPtr<Vst::IUnitInfo>(mEditController);
992
993 for(int32 programIndex = 0, programCount = cache->rootUnitProgramCount;
994 programIndex < programCount;
995 ++programIndex)
996 {
997 if(unitInfoProvider->getProgramName(cache->rootUnitProgramListID, programIndex, programName) != kResultOk)
998 programName[0] = 0;
999 auto presetDisplayName = VST3Utils::ToWxString(programName);
1000 if(presetDisplayName.empty())
1001 presetDisplayName = wxString::Format("Unit %03d - %04d", Vst::kRootUnitId, programIndex);
1002
1003 result.push_back({
1004 VST3Utils::MakeFactoryPresetID(Vst::kRootUnitId, programIndex),
1005 presetDisplayName
1006 });
1007 }
1008 }
1009 else
1010 {
1011 for(int32_t programIndex = 0; programIndex < cache->rootUnitProgramCount; ++programIndex)
1012 {
1013 auto normalizedValue = mEditController->plainParamToNormalized(cache->rootUnitProgramChangeParameterID, programIndex);
1014 if(mEditController->getParamStringByValue(cache->rootUnitProgramChangeParameterID, normalizedValue, programName) == kResultOk)
1015 {
1016 result.push_back({
1017 VST3Utils::MakeFactoryPresetID(Vst::kRootUnitId, programIndex),
1018 VST3Utils::ToWxString(programName)
1019 });
1020 }
1021 }
1022 }
1023 }
1024
1025 return result;
1026}
1027
1028
1029Steinberg::int32 VST3Wrapper::GetLatencySamples() const
1030{
1031 return mAudioProcessor->getLatencySamples();
1032}
1033
1035{
1036 return EffectSettings::Make<VST3EffectSettings>();
1037}
1038
1040{
1041 VST3EffectSettings vst3settings;
1042
1043 if(parms.HasEntry(processorStateKey))
1044 {
1045 vst3settings.processorState = parms.Read(processorStateKey);
1046 if(parms.HasEntry(controllerStateKey))
1047 vst3settings.controllerState = parms.Read(controllerStateKey);
1048 }
1049 if(parms.HasEntry(parametersKey))
1050 vst3settings.parameterChanges = ParametersFromString(parms.Read(parametersKey));
1051
1052 std::swap(vst3settings, GetSettings(settings));
1053}
1054
1056{
1057 const auto& vst3settings = GetSettings(settings);
1058
1059 if(vst3settings.processorState.has_value())
1060 parms.Write(processorStateKey, *vst3settings.processorState);
1061 if(vst3settings.controllerState.has_value())
1062 parms.Write(controllerStateKey, *vst3settings.controllerState);
1063 if(!vst3settings.parameterChanges.empty())
1064 parms.Write(parametersKey, ParametersToString(vst3settings.parameterChanges));
1065}
1066
1069{
1070 VST3EffectSettings vst3settings;
1071
1072 wxString processorStateStr;
1073 if(GetConfig(effect, PluginSettings::Private, name, processorStateKey, processorStateStr, wxEmptyString))
1074 {
1075 vst3settings.processorState = processorStateStr;
1076 wxString controllerStateStr;
1077 if(GetConfig(effect, PluginSettings::Private, name, controllerStateKey, controllerStateStr, wxEmptyString))
1078 vst3settings.controllerState = controllerStateStr;
1079 }
1080 wxString parametersStr;
1081 if(GetConfig(effect, PluginSettings::Private, name, parametersKey, parametersStr, wxEmptyString))
1082 vst3settings.parameterChanges = ParametersFromString(parametersStr);
1083
1084 std::swap(vst3settings, GetSettings(settings));
1085 return { nullptr };
1086}
1087
1089{
1090 using namespace Steinberg;
1091
1092 const auto& vst3settings = GetSettings(settings);
1093 if(vst3settings.processorState.has_value())
1094 {
1095 SetConfig(effect, PluginSettings::Private, name, processorStateKey, *vst3settings.processorState);
1096 if(vst3settings.controllerState.has_value())
1097 SetConfig(effect, PluginSettings::Private, name, controllerStateKey, *vst3settings.controllerState);
1098 }
1099 if(!vst3settings.parameterChanges.empty())
1100 SetConfig(effect, PluginSettings::Private, name, parametersKey, ParametersToString(vst3settings.parameterChanges));
1101}
1102
1104{
1105 auto& from = GetSettings(*const_cast<EffectSettings*>(&src));
1106 auto& to = GetSettings(dst);
1107
1108 //Don't allocate in worker
1109 std::swap(from.parameterChanges, to.parameterChanges);
1110}
wxT("CloseDown"))
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
@ BadEnvironment
Indicates problems with environment, such as a full disk.
int min(int a, int b)
#define str(a)
EffectDistortionSettings params
std::optional< std::unique_ptr< EffectSettingsAccess::Message > > OptionalMessage
const wxChar * values
MessageBoxException for failures of file operations.
XO("Cut/Copy/Paste")
wxString RegistryPath
Definition: Identifier.h:218
#define safenew
Definition: MemoryX.h:10
static const AudacityProject::AttachedObjects::RegisteredFactory key
wxString name
Definition: TagsEditor.cpp:166
static Settings & settings()
Definition: TrackInfo.cpp:51
IMPLEMENT_FUNKNOWN_METHODS(SingleInputParameterValue, Steinberg::Vst::IParamValueQueue, Steinberg::Vst::IParamValueQueue::iid)
int id
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the SettingsVis...
virtual bool HasEntry(const wxString &strName) const override
EffectDefinitionInterface is a ComponentInterface that adds some basic read-only information about ef...
Thrown for failure of file or database operations in deeply nested places.
Definition: FileException.h:19
Abstract base class used in importing a file.
static Steinberg::IPtr< PresetsBufferStream > fromString(const wxString &str)
Definition: VST3Utils.cpp:153
wxString toString() const
Definition: VST3Utils.cpp:167
A MessageBoxException that shows a given, unvarying string.
Steinberg::int32 PLUGIN_API getPointCount() override
void Set(Steinberg::Vst::ParamID id, const Steinberg::Vst::ParamValue value)
Steinberg::Vst::ParamValue mValue
Definition: VST3Wrapper.h:33
Steinberg::Vst::ParamID PLUGIN_API getParameterId() override
Steinberg::tresult PLUGIN_API getPoint(Steinberg::int32 index, Steinberg::int32 &sampleOffset, Steinberg::Vst::ParamValue &value) override
Steinberg::tresult PLUGIN_API addPoint(Steinberg::int32 sampleOffset, Steinberg::Vst::ParamValue value, Steinberg::int32 &index) override
Steinberg::Vst::ParamID mParameterId
Definition: VST3Wrapper.h:32
tresult PLUGIN_API seek(int64 pos, int32 mode, int64 *result) SMTG_OVERRIDE
static bool ParseFactoryPresetID(const wxString &presetId, Steinberg::Vst::UnitID &unitId, Steinberg::int32 &programIndex)
Definition: VST3Utils.cpp:131
static wxString GetFactoryPresetsPath(const VST3::Hosting::ClassInfo &effectClassInfo)
Definition: VST3Utils.cpp:145
static wxString MakeFactoryPresetID(Steinberg::Vst::UnitID unitId, Steinberg::int32 programIndex)
Definition: VST3Utils.cpp:124
static wxString ToWxString(const Steinberg::Vst::TChar *str)
Definition: VST3Utils.cpp:93
void SuspendProcessing()
void BeginParameterEdit(EffectSettingsAccess &access)
const VST3::Hosting::ClassInfo & GetEffectClassInfo() const
size_t Process(const float *const *inBlock, float *const *outBlock, size_t blockLen)
void ResumeProcessing()
static EffectSettings MakeSettings()
Steinberg::IPtr< Steinberg::Vst::IConnectionPoint > mControllerConnectionProxy
Definition: VST3Wrapper.h:72
void LoadPreset(const wxString &presetId)
void ProcessBlockStart(const EffectSettings &settings)
Prepares effect to process next block with changes written to the settings object.
Steinberg::IPtr< Steinberg::Vst::IEditController > mEditController
Definition: VST3Wrapper.h:70
Steinberg::IPtr< Steinberg::Vst::IComponent > mEffectComponent
Definition: VST3Wrapper.h:69
static void SaveUserPreset(const EffectDefinitionInterface &effect, const RegistryPath &name, const EffectSettings &settings)
Steinberg::IPtr< Steinberg::Vst::IComponentHandler > mComponentHandler
Definition: VST3Wrapper.h:73
Steinberg::Vst::ProcessContext mProcessContext
Definition: VST3Wrapper.h:162
Steinberg::int32 GetLatencySamples() const
void FetchSettings(EffectSettings &)
Fetch state from settings object, may change internal runtime data.
static void SaveSettings(const EffectSettings &settings, CommandParameters &parms)
const VST3::Hosting::ClassInfo & mEffectClassInfo
Definition: VST3Wrapper.h:58
std::unique_ptr< SingleInputParameterValue[]> mParameterQueues
Definition: VST3Wrapper.h:160
std::function< void(Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue)> ParamChangedHandler
Definition: VST3Wrapper.h:143
std::vector< FactoryPresetDesc > FindFactoryPresets() const
bool SavePresetToStream(Steinberg::IBStream *fileStream) const
static void CopySettingsContents(const EffectSettings &src, EffectSettings &dst)
bool IsActive() const noexcept
void InitializeComponents()
Should be called once before almost any other method call.
bool Initialize(EffectSettings &settings, Steinberg::Vst::SampleRate sampleRate, Steinberg::int32 processMode, Steinberg::int32 maxSamplesPerBlock)
Initializes effect for processing using settings.
void SavePresetToFile(const wxString &filepath) const
void StoreSettings(EffectSettings &) const
Saves current state inside settings object, clears all runtime data.
static void LoadSettings(const CommandParameters &parms, EffectSettings &settings)
Steinberg::IPtr< Steinberg::Vst::IConnectionPoint > mComponentConnectionProxy
Definition: VST3Wrapper.h:71
Steinberg::IPtr< Steinberg::Vst::IAudioProcessor > mAudioProcessor
Definition: VST3Wrapper.h:67
void ConsumeChanges(const EffectSettings &settings)
void EndParameterEdit()
Steinberg::Vst::ProcessSetup mSetup
Definition: VST3Wrapper.h:68
std::vector< std::pair< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > > mParameters
Definition: VST3Wrapper.h:155
void FlushParameters(EffectSettings &settings, bool *hasChanges=nullptr)
void Finalize(EffectSettings *settings)
VST3Wrapper(VST3::Hosting::Module &module, const VST3::Hosting::ClassInfo &effectClassInfo)
bool LoadPresetFromStream(Steinberg::IBStream *fileStream)
static OptionalMessage LoadUserPreset(const EffectDefinitionInterface &effect, const RegistryPath &name, EffectSettings &settings)
std::map< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > mParametersCache
Steinberg::tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override
void SetAccess(EffectSettingsAccess *access)
std::map< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > mCurrentParamValues
void NotifyParamChange(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized)
Steinberg::tresult PLUGIN_API beginEdit(Steinberg::Vst::ParamID id) override
Steinberg::tresult PLUGIN_API performEdit(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) override
Steinberg::tresult PLUGIN_API restartComponent(Steinberg::int32 flags) override
InputParameterChanges(const std::vector< std::pair< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > > &values, SingleInputParameterValue *queues)
Steinberg::Vst::IParamValueQueue *PLUGIN_API addParameterData(const Steinberg::Vst::ParamID &id, Steinberg::int32 &index) override
Steinberg::int32 PLUGIN_API getParameterCount() override
Steinberg::Vst::IParamValueQueue *PLUGIN_API getParameterData(Steinberg::int32 index) override
Host's proxy object between connection points.
bool SetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, const Value &value)
bool GetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, Value &var, const Value &defval)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:634
bool ActivateMainAudioBuses(Steinberg::Vst::IComponent &component)
std::map< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > ParametersFromString(const wxString &str)
Definition: VST3Wrapper.cpp:78
Steinberg::Vst::SpeakerArrangement GetBusArragementForChannels(int32_t channelsCount, Steinberg::Vst::SpeakerArrangement defaultArragment)
Definition: VST3Wrapper.cpp:58
VST3PluginCache & CreateCache(const VST3::UID &effectUid)
Definition: VST3Wrapper.cpp:50
bool SetupProcessing(Steinberg::Vst::IComponent &component, Steinberg::Vst::ProcessSetup &setup)
VST3PluginCache * GetCache(const VST3::UID &effectUid)
Definition: VST3Wrapper.cpp:41
std::map< std::string, VST3PluginCache > sVST3PluginCache
Definition: VST3Wrapper.cpp:39
const VST3EffectSettings & GetSettings(const EffectSettings &settings)
wxString ParametersToString(const std::map< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > &params)
STL namespace.
Externalized state of a plug-in.
std::optional< wxString > controllerState
Holds the last known controller state, rarely updates (usually only on UI or preset change)
Definition: VST3Wrapper.cpp:75
std::map< Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue > parameterChanges
Holds the parameter that has been changed since last processing pass.
Definition: VST3Wrapper.cpp:70
std::optional< wxString > processorState
Holds the last known processor/component state, rarely updates (usually only on UI or preset change)
Definition: VST3Wrapper.cpp:73