JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_VST3PluginFormat.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26#if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD)
27
28#include "juce_VST3Headers.h"
29#include "juce_VST3Common.h"
30#include "juce_ARACommon.h"
31
32#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
33#include <ARA_API/ARAVST3.h>
34
35namespace ARA
36{
37DEF_CLASS_IID (IMainFactory)
38DEF_CLASS_IID (IPlugInEntryPoint)
39DEF_CLASS_IID (IPlugInEntryPoint2)
40}
41#endif
42
43namespace juce
44{
45
46// UB Sanitizer doesn't necessarily have instrumentation for loaded plugins, so
47// it won't recognize the dynamic types of pointers to the plugin's interfaces.
49
50using namespace Steinberg;
51
52//==============================================================================
53#ifndef JUCE_VST3_DEBUGGING
54 #define JUCE_VST3_DEBUGGING 0
55#endif
56
57#if JUCE_VST3_DEBUGGING
58 #define VST3_DBG(a) Logger::writeToLog (a);
59#else
60 #define VST3_DBG(a)
61#endif
62
63#if JUCE_DEBUG
64static int warnOnFailure (int result) noexcept
65{
66 const char* message = "Unknown result!";
67
68 switch (result)
69 {
70 case kResultOk: return result;
71 case kNotImplemented: message = "kNotImplemented"; break;
72 case kNoInterface: message = "kNoInterface"; break;
73 case kResultFalse: message = "kResultFalse"; break;
74 case kInvalidArgument: message = "kInvalidArgument"; break;
75 case kInternalError: message = "kInternalError"; break;
76 case kNotInitialized: message = "kNotInitialized"; break;
77 case kOutOfMemory: message = "kOutOfMemory"; break;
78 default: break;
79 }
80
81 DBG (message);
82 return result;
83}
84
85static int warnOnFailureIfImplemented (int result) noexcept
86{
87 if (result != kResultOk && result != kNotImplemented)
88 return warnOnFailure (result);
89
90 return result;
91}
92#else
93 #define warnOnFailure(x) x
94 #define warnOnFailureIfImplemented(x) x
95#endif
96
97enum class MediaKind { audio, event };
98
99static Vst::MediaType toVstType (MediaKind x) { return x == MediaKind::audio ? Vst::kAudio : Vst::kEvent; }
100static Vst::BusDirection toVstType (Direction x) { return x == Direction::input ? Vst::kInput : Vst::kOutput; }
101
102static std::vector<Vst::ParamID> getAllParamIDs (Vst::IEditController& controller)
103{
105
106 auto count = controller.getParameterCount();
107
108 for (decltype (count) i = 0; i < count; ++i)
109 {
110 Vst::ParameterInfo info{};
111 controller.getParameterInfo (i, info);
112 result.push_back (info.id);
113 }
114
115 return result;
116}
117
118//==============================================================================
119/* Allows parameter updates to be queued up without blocking,
120 and automatically dispatches these updates on the main thread.
121*/
122class EditControllerParameterDispatcher final : private Timer
123{
124public:
125 ~EditControllerParameterDispatcher() override { stopTimer(); }
126
127 void push (Steinberg::int32 index, float value)
128 {
129 if (controller == nullptr)
130 return;
131
132 if (MessageManager::getInstance()->isThisTheMessageThread())
133 controller->setParamNormalized (cache.getParamID (index), value);
134 else
135 cache.set (index, value);
136 }
137
138 void start (Vst::IEditController& controllerIn)
139 {
140 controller = &controllerIn;
142 startTimerHz (60);
143 }
144
145 void flush()
146 {
147 cache.ifSet ([this] (Steinberg::int32 index, float value)
148 {
149 controller->setParamNormalized (cache.getParamID (index), value);
150 });
151 }
152
153private:
154 void timerCallback() override
155 {
156 flush();
157 }
158
159 CachedParamValues cache;
160 Vst::IEditController* controller = nullptr;
161};
162
163//==============================================================================
164static std::array<uint32, 4> getNormalisedTUID (const TUID& tuid) noexcept
165{
166 const FUID fuid { tuid };
167 return { { fuid.getLong1(), fuid.getLong2(), fuid.getLong3(), fuid.getLong4() } };
168}
169
170template <typename Range>
171static int getHashForRange (Range&& range) noexcept
172{
173 uint32 value = 0;
174
175 for (const auto& item : range)
176 value = (value * 31) + (uint32) item;
177
178 return (int) value;
179}
180
181template <typename ObjectType>
182static void fillDescriptionWith (PluginDescription& description, ObjectType& object)
183{
184 description.version = toString (object.version).trim();
185 description.category = toString (object.subCategories).trim();
186
187 if (description.manufacturerName.trim().isEmpty())
188 description.manufacturerName = toString (object.vendor).trim();
189}
190
192{
194
195 const auto araMainFactoryClassNames = [&]
196 {
198
199 #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
200 for (const auto& c : info.classes)
201 if (c.category == kARAMainFactoryClass)
202 factories.insert (CharPointer_UTF8 (c.name.c_str()));
203 #endif
204
205 return factories;
206 }();
207
208 for (const auto& c : info.classes)
209 {
210 if (c.category != kVstAudioEffectClass)
211 continue;
212
213 PluginDescription description;
214
215 description.fileOrIdentifier = pluginFile.getFullPathName();
216 description.lastFileModTime = pluginFile.getLastModificationTime();
217 description.lastInfoUpdateTime = Time::getCurrentTime();
218 description.manufacturerName = CharPointer_UTF8 (info.factoryInfo.vendor.c_str());
219 description.name = CharPointer_UTF8 (c.name.c_str());
220 description.descriptiveName = CharPointer_UTF8 (c.name.c_str());
221 description.pluginFormatName = "VST3";
222 description.numInputChannels = 0;
223 description.numOutputChannels = 0;
224 description.hasARAExtension = araMainFactoryClassNames.find (description.name) != araMainFactoryClassNames.end();
225 description.version = CharPointer_UTF8 (c.version.c_str());
226
227 const auto uid = VST3::UID::fromString (c.cid);
228
229 if (! uid)
230 continue;
231
232 description.deprecatedUid = getHashForRange (uid->data());
233 description.uniqueId = getHashForRange (getNormalisedTUID (uid->data()));
234
235 StringArray categories;
236
237 for (const auto& category : c.subCategories)
238 categories.add (CharPointer_UTF8 (category.c_str()));
239
240 description.category = categories.joinIntoString ("|");
241
242 description.isInstrument = std::any_of (c.subCategories.begin(),
243 c.subCategories.end(),
244 [] (const auto& subcategory) { return subcategory == "Instrument"; });
245
246 result.push_back (description);
247 }
248
249 return result;
250}
251
252static void createPluginDescription (PluginDescription& description,
253 const File& pluginFile, const String& company, const String& name,
254 const PClassInfo& info, PClassInfo2* info2, PClassInfoW* infoW,
255 int numInputs, int numOutputs)
256{
257 description.fileOrIdentifier = pluginFile.getFullPathName();
258 description.lastFileModTime = pluginFile.getLastModificationTime();
259 description.lastInfoUpdateTime = Time::getCurrentTime();
260 description.manufacturerName = company;
261 description.name = name;
262 description.descriptiveName = name;
263 description.pluginFormatName = "VST3";
264 description.numInputChannels = numInputs;
265 description.numOutputChannels = numOutputs;
266
267 description.deprecatedUid = getHashForRange (info.cid);
268 description.uniqueId = getHashForRange (getNormalisedTUID (info.cid));
269
270 if (infoW != nullptr) fillDescriptionWith (description, *infoW);
271 else if (info2 != nullptr) fillDescriptionWith (description, *info2);
272
273 if (description.category.isEmpty())
274 description.category = toString (info.category).trim();
275
276 description.isInstrument = description.category.containsIgnoreCase ("Instrument"); // This seems to be the only way to find that out! ARGH!
277}
278
279static int getNumSingleDirectionBusesFor (Vst::IComponent* component,
281 Direction direction)
282{
283 jassert (component != nullptr);
285 return (int) component->getBusCount (toVstType (kind), toVstType (direction));
286}
287
289static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, Direction busDirection)
290{
291 jassert (component != nullptr);
293
294 const auto direction = toVstType (busDirection);
295 const Steinberg::int32 numBuses = component->getBusCount (Vst::kAudio, direction);
296
297 int numChannels = 0;
298
299 for (Steinberg::int32 i = numBuses; --i >= 0;)
300 {
301 Vst::BusInfo busInfo;
302 warnOnFailure (component->getBusInfo (Vst::kAudio, direction, i, busInfo));
303 numChannels += ((busInfo.flags & Vst::BusInfo::kDefaultActive) != 0 ? (int) busInfo.channelCount : 0);
304 }
305
306 return numChannels;
307}
308
309static void setStateForAllEventBuses (Vst::IComponent* component,
310 bool state,
311 Direction busDirection)
312{
313 jassert (component != nullptr);
315
316 const auto direction = toVstType (busDirection);
317 const Steinberg::int32 numBuses = component->getBusCount (Vst::kEvent, direction);
318
319 for (Steinberg::int32 i = numBuses; --i >= 0;)
320 warnOnFailure (component->activateBus (Vst::kEvent, direction, i, state));
321}
322
323//==============================================================================
324static void toProcessContext (Vst::ProcessContext& context,
325 AudioPlayHead* playHead,
326 double sampleRate)
327{
328 jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK
329
330 using namespace Vst;
331
332 zerostruct (context);
333 context.sampleRate = sampleRate;
334
335 const auto position = playHead != nullptr ? playHead->getPosition()
336 : nullopt;
337
338 if (position.hasValue())
339 {
340 if (const auto timeInSamples = position->getTimeInSamples())
341 context.projectTimeSamples = *timeInSamples;
342 else
343 jassertfalse; // The time in samples *must* be valid.
344
345 if (const auto tempo = position->getBpm())
346 {
347 context.state |= ProcessContext::kTempoValid;
348 context.tempo = *tempo;
349 }
350
351 if (const auto loop = position->getLoopPoints())
352 {
353 context.state |= ProcessContext::kCycleValid;
354 context.cycleStartMusic = loop->ppqStart;
355 context.cycleEndMusic = loop->ppqEnd;
356 }
357
358 if (const auto sig = position->getTimeSignature())
359 {
360 context.state |= ProcessContext::kTimeSigValid;
361 context.timeSigNumerator = sig->numerator;
362 context.timeSigDenominator = sig->denominator;
363 }
364
365 if (const auto pos = position->getPpqPosition())
366 {
367 context.state |= ProcessContext::kProjectTimeMusicValid;
368 context.projectTimeMusic = *pos;
369 }
370
371 if (const auto barStart = position->getPpqPositionOfLastBarStart())
372 {
373 context.state |= ProcessContext::kBarPositionValid;
374 context.barPositionMusic = *barStart;
375 }
376
377 if (const auto frameRate = position->getFrameRate())
378 {
379 if (const auto offset = position->getEditOriginTime())
380 {
381 context.state |= ProcessContext::kSmpteValid;
382 context.smpteOffsetSubframes = (Steinberg::int32) (80.0 * *offset * frameRate->getEffectiveRate());
383 context.frameRate.framesPerSecond = (Steinberg::uint32) frameRate->getBaseRate();
384 context.frameRate.flags = (Steinberg::uint32) ((frameRate->isDrop() ? FrameRate::kDropRate : 0)
385 | (frameRate->isPullDown() ? FrameRate::kPullDownRate : 0));
386 }
387 }
388
389 if (const auto hostTime = position->getHostTimeNs())
390 {
391 context.state |= ProcessContext::kSystemTimeValid;
392 context.systemTime = (int64_t) *hostTime;
393 jassert (context.systemTime >= 0);
394 }
395
396 if (position->getIsPlaying()) context.state |= ProcessContext::kPlaying;
397 if (position->getIsRecording()) context.state |= ProcessContext::kRecording;
398 if (position->getIsLooping()) context.state |= ProcessContext::kCycleActive;
399 }
400}
401
402//==============================================================================
404
405struct VST3HostContext final : public Vst::IComponentHandler, // From VST V3.0.0
406 public Vst::IComponentHandler2, // From VST V3.1.0 (a very well named class, of course!)
407 public Vst::IComponentHandler3, // From VST V3.5.0 (also very well named!)
408 public Vst::IContextMenuTarget,
409 public Vst::IHostApplication,
410 public Vst::IUnitHandler,
411 private ComponentRestarter::Listener
412{
414 {
416 }
417
418 ~VST3HostContext() override = default;
419
421
422 FUnknown* getFUnknown() { return static_cast<Vst::IComponentHandler*> (this); }
423
424 static bool hasFlag (Steinberg::int32 source, Steinberg::int32 flag) noexcept
425 {
426 return (source & flag) == flag;
427 }
428
429 //==============================================================================
430 tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override;
431 tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override;
432 tresult PLUGIN_API endEdit (Vst::ParamID paramID) override;
433
434 tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override;
435 tresult PLUGIN_API setDirty (TBool) override;
436
437 //==============================================================================
438 tresult PLUGIN_API requestOpenEditor ([[maybe_unused]] FIDString name) override
439 {
440 // This request cannot currently be surfaced in the JUCE public API
441 return kResultFalse;
442 }
443
444 tresult PLUGIN_API startGroupEdit() override
445 {
447 return kResultFalse;
448 }
449
450 tresult PLUGIN_API finishGroupEdit() override
451 {
453 return kResultFalse;
454 }
455
456 void setPlugin (VST3PluginInstance* instance)
457 {
458 jassert (plugin == nullptr);
459 plugin = instance;
460 }
461
462 //==============================================================================
463 struct ContextMenu final : public Vst::IContextMenu
464 {
465 ContextMenu (VST3PluginInstance& pluginInstance) : owner (pluginInstance) {}
466 virtual ~ContextMenu() {}
467
468 JUCE_DECLARE_VST3_COM_REF_METHODS
469 JUCE_DECLARE_VST3_COM_QUERY_METHODS
470
471 Steinberg::int32 PLUGIN_API getItemCount() override { return (Steinberg::int32) items.size(); }
472
473 tresult PLUGIN_API addItem (const Item& item, IContextMenuTarget* target) override
474 {
475 jassert (target != nullptr);
476
477 ItemAndTarget newItem;
478 newItem.item = item;
479 newItem.target = addVSTComSmartPtrOwner (target);
480
481 items.add (newItem);
482 return kResultOk;
483 }
484
485 tresult PLUGIN_API removeItem (const Item& toRemove, IContextMenuTarget* target) override
486 {
487 for (int i = items.size(); --i >= 0;)
488 {
489 auto& item = items.getReference (i);
490
491 if (item.item.tag == toRemove.tag && item.target.get() == target)
492 items.remove (i);
493 }
494
495 return kResultOk;
496 }
497
498 tresult PLUGIN_API getItem (Steinberg::int32 tag, Item& result, IContextMenuTarget** target) override
499 {
500 for (int i = 0; i < items.size(); ++i)
501 {
502 auto& item = items.getReference (i);
503
504 if (item.item.tag == tag)
505 {
506 result = item.item;
507
508 if (target != nullptr)
509 *target = item.target.get();
510
511 return kResultTrue;
512 }
513 }
514
515 zerostruct (result);
516 return kResultFalse;
517 }
518
519 tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override;
520
521 #if ! JUCE_MODAL_LOOPS_PERMITTED
522 static void menuFinished (int modalResult, VSTComSmartPtr<ContextMenu> menu) { menu->handleResult (modalResult); }
523 #endif
524
525 private:
526 enum { zeroTagReplacement = 0x7fffffff };
527
528 Atomic<int> refCount;
529 VST3PluginInstance& owner;
530
531 struct ItemAndTarget
532 {
533 Item item;
534 VSTComSmartPtr<IContextMenuTarget> target;
535 };
536
537 Array<ItemAndTarget> items;
538
539 void handleResult (int result)
540 {
541 if (result == 0)
542 return;
543
544 if (result == zeroTagReplacement)
545 result = 0;
546
547 for (int i = 0; i < items.size(); ++i)
548 {
549 auto& item = items.getReference (i);
550
551 if ((int) item.item.tag == result)
552 {
553 if (item.target != nullptr)
554 item.target->executeMenuItem ((Steinberg::int32) result);
555
556 break;
557 }
558 }
559 }
560
562 };
563
564 Vst::IContextMenu* PLUGIN_API createContextMenu (IPlugView*, const Vst::ParamID*) override
565 {
566 if (plugin == nullptr)
567 return nullptr;
568
569 auto* result = new ContextMenu (*plugin);
570 result->addRef();
571 return result;
572 }
573
574 tresult PLUGIN_API executeMenuItem (Steinberg::int32) override
575 {
577 return kResultFalse;
578 }
579
580 //==============================================================================
581 tresult PLUGIN_API getName (Vst::String128 name) override
582 {
583 Steinberg::String str (appName.toUTF8());
584 str.copyTo (name, 0, 127);
585 return kResultOk;
586 }
587
588 tresult PLUGIN_API createInstance (TUID cid, TUID iid, void** obj) override
589 {
590 *obj = nullptr;
591
592 if (! doUIDsMatch (cid, iid))
593 {
595 return kInvalidArgument;
596 }
597
598 if (doUIDsMatch (cid, Vst::IMessage::iid) && doUIDsMatch (iid, Vst::IMessage::iid))
599 {
600 *obj = new Message;
601 return kResultOk;
602 }
603
604 if (doUIDsMatch (cid, Vst::IAttributeList::iid) && doUIDsMatch (iid, Vst::IAttributeList::iid))
605 {
606 *obj = new AttributeList;
607 return kResultOk;
608 }
609
611 return kNotImplemented;
612 }
613
614 //==============================================================================
615 tresult PLUGIN_API notifyUnitSelection (Vst::UnitID) override
616 {
618 return kResultFalse;
619 }
620
621 tresult PLUGIN_API notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) override;
622
623 //==============================================================================
624 tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override
625 {
626 return testForMultiple (*this,
627 iid,
635 }
636
637private:
638 //==============================================================================
639 VST3PluginInstance* plugin = nullptr;
640 Atomic<int> refCount;
641 String appName;
642
644
645 void restartComponentOnMessageThread (int32 flags) override;
646
647 //==============================================================================
648 class Attribute
649 {
650 public:
651 using Int = Steinberg::int64;
652 using Float = double;
653 using String = std::vector<Vst::TChar>;
654 using Binary = std::vector<char>;
655
656 explicit Attribute (Int x) noexcept { constructFrom (std::move (x)); }
657 explicit Attribute (Float x) noexcept { constructFrom (std::move (x)); }
658 explicit Attribute (String x) noexcept { constructFrom (std::move (x)); }
659 explicit Attribute (Binary x) noexcept { constructFrom (std::move (x)); }
660
661 Attribute (Attribute&& other) noexcept
662 {
663 moveFrom (std::move (other));
664 }
665
666 Attribute& operator= (Attribute&& other) noexcept
667 {
668 reset();
669 moveFrom (std::move (other));
670 return *this;
671 }
672
673 ~Attribute() noexcept
674 {
675 reset();
676 }
677
678 tresult getInt (Steinberg::int64& result) const
679 {
680 if (kind != Kind::tagInt)
681 return kResultFalse;
682
683 result = storage.storedInt;
684 return kResultTrue;
685 }
686
687 tresult getFloat (double& result) const
688 {
689 if (kind != Kind::tagFloat)
690 return kResultFalse;
691
692 result = storage.storedFloat;
693 return kResultTrue;
694 }
695
696 tresult getString (Vst::TChar* data, Steinberg::uint32 numBytes) const
697 {
698 if (kind != Kind::tagString)
699 return kResultFalse;
700
701 std::memcpy (data,
702 storage.storedString.data(),
703 jmin (sizeof (Vst::TChar) * storage.storedString.size(), (size_t) numBytes));
704 return kResultTrue;
705 }
706
707 tresult getBinary (const void*& data, Steinberg::uint32& numBytes) const
708 {
709 if (kind != Kind::tagBinary)
710 return kResultFalse;
711
712 data = storage.storedBinary.data();
713 numBytes = (Steinberg::uint32) storage.storedBinary.size();
714 return kResultTrue;
715 }
716
717 private:
718 void constructFrom (Int x) noexcept { kind = Kind::tagInt; new (&storage.storedInt) Int (std::move (x)); }
719 void constructFrom (Float x) noexcept { kind = Kind::tagFloat; new (&storage.storedFloat) Float (std::move (x)); }
720 void constructFrom (String x) noexcept { kind = Kind::tagString; new (&storage.storedString) String (std::move (x)); }
721 void constructFrom (Binary x) noexcept { kind = Kind::tagBinary; new (&storage.storedBinary) Binary (std::move (x)); }
722
723 void reset() noexcept
724 {
725 switch (kind)
726 {
727 case Kind::tagInt: break;
728 case Kind::tagFloat: break;
729 case Kind::tagString: storage.storedString.~vector(); break;
730 case Kind::tagBinary: storage.storedBinary.~vector(); break;
731 }
732 }
733
734 void moveFrom (Attribute&& other) noexcept
735 {
736 switch (other.kind)
737 {
738 case Kind::tagInt: constructFrom (std::move (other.storage.storedInt)); break;
739 case Kind::tagFloat: constructFrom (std::move (other.storage.storedFloat)); break;
740 case Kind::tagString: constructFrom (std::move (other.storage.storedString)); break;
741 case Kind::tagBinary: constructFrom (std::move (other.storage.storedBinary)); break;
742 }
743 }
744
745 enum class Kind { tagInt, tagFloat, tagString, tagBinary };
746
747 union Storage
748 {
749 Storage() {}
750 ~Storage() {}
751
752 Steinberg::int64 storedInt;
753 double storedFloat;
754 std::vector<Vst::TChar> storedString;
755 std::vector<char> storedBinary;
756 };
757
758 Storage storage;
759 Kind kind;
760
762 };
763
764 //==============================================================================
765 class AttributeList final : public Vst::IAttributeList
766 {
767 public:
768 AttributeList() = default;
769 virtual ~AttributeList() = default;
770
771 JUCE_DECLARE_VST3_COM_REF_METHODS
772 JUCE_DECLARE_VST3_COM_QUERY_METHODS
773
774 //==============================================================================
775 tresult PLUGIN_API setInt (AttrID attr, Steinberg::int64 value) override
776 {
777 return set (attr, value);
778 }
779
780 tresult PLUGIN_API setFloat (AttrID attr, double value) override
781 {
782 return set (attr, value);
783 }
784
785 tresult PLUGIN_API setString (AttrID attr, const Vst::TChar* string) override
786 {
787 return set (attr, std::vector<Vst::TChar> (string, string + 1 + tstrlen (string)));
788 }
789
790 tresult PLUGIN_API setBinary (AttrID attr, const void* data, Steinberg::uint32 size) override
791 {
792 const auto* ptr = static_cast<const char*> (data);
793 return set (attr, std::vector<char> (ptr, ptr + size));
794 }
795
796 tresult PLUGIN_API getInt (AttrID attr, Steinberg::int64& result) override
797 {
798 return get (attr, [&] (const auto& x) { return x.getInt (result); });
799 }
800
801 tresult PLUGIN_API getFloat (AttrID attr, double& result) override
802 {
803 return get (attr, [&] (const auto& x) { return x.getFloat (result); });
804 }
805
806 tresult PLUGIN_API getString (AttrID attr, Vst::TChar* result, Steinberg::uint32 length) override
807 {
808 return get (attr, [&] (const auto& x) { return x.getString (result, length); });
809 }
810
811 tresult PLUGIN_API getBinary (AttrID attr, const void*& data, Steinberg::uint32& size) override
812 {
813 return get (attr, [&] (const auto& x) { return x.getBinary (data, size); });
814 }
815
816 private:
817 template <typename Value>
818 tresult set (AttrID attr, Value&& value)
819 {
820 if (attr == nullptr)
821 return kInvalidArgument;
822
823 const auto iter = attributes.find (attr);
824
825 if (iter != attributes.end())
826 iter->second = Attribute (std::forward<Value> (value));
827 else
828 attributes.emplace (attr, Attribute (std::forward<Value> (value)));
829
830 return kResultTrue;
831 }
832
833 template <typename Visitor>
834 tresult get (AttrID attr, Visitor&& visitor)
835 {
836 if (attr == nullptr)
837 return kInvalidArgument;
838
839 const auto iter = attributes.find (attr);
840
841 if (iter == attributes.cend())
842 return kResultFalse;
843
844 return visitor (iter->second);
845 }
846
848 Atomic<int> refCount { 1 };
849
851 };
852
853 struct Message final : public Vst::IMessage
854 {
855 Message() = default;
856 virtual ~Message() = default;
857
858 JUCE_DECLARE_VST3_COM_REF_METHODS
859 JUCE_DECLARE_VST3_COM_QUERY_METHODS
860
861 FIDString PLUGIN_API getMessageID() override { return messageId.toRawUTF8(); }
862 void PLUGIN_API setMessageID (FIDString id) override { messageId = toString (id); }
863 Vst::IAttributeList* PLUGIN_API getAttributes() override { return &attributeList; }
864
865 private:
866 AttributeList attributeList;
867 String messageId;
868 Atomic<int> refCount { 1 };
869
871 };
872
873 VSTComSmartPtr<AttributeList> attributeList;
874
876};
877
878//==============================================================================
880{
881 static std::vector<PluginDescription> tryLoadFast (const File& file, const File& moduleinfo)
882 {
883 if (! moduleinfo.existsAsFile())
884 return {};
885
886 MemoryBlock mb;
887
888 if (! moduleinfo.loadFileAsData (mb))
889 return {};
890
891 const std::string_view blockAsStringView (static_cast<const char*> (mb.getData()), mb.getSize());
893
894 if (! parsed)
895 return {};
896
897 return createPluginDescriptions (file, *parsed);
898 }
899
901 {
902 const auto moduleinfoNewLocation = file.getChildFile ("Contents").getChildFile ("Resources").getChildFile ("moduleinfo.json");
903
904 if (const auto loaded = tryLoadFast (file, moduleinfoNewLocation); ! loaded.empty())
905 return loaded;
906
907 return tryLoadFast (file, file.getChildFile ("Contents").getChildFile ("moduleinfo.json"));
908 }
909
911 IPluginFactory& factory,
912 const File& file)
913 {
915
916 StringArray foundNames;
917 PFactoryInfo factoryInfo;
918 factory.getFactoryInfo (&factoryInfo);
919 auto companyName = toString (factoryInfo.vendor).trim();
920
921 auto numClasses = factory.countClasses();
922
923 // Every ARA::IMainFactory must have a matching Steinberg::IComponent.
924 // The match is determined by the two classes having the same name.
926
927 #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
928 for (Steinberg::int32 i = 0; i < numClasses; ++i)
929 {
930 PClassInfo info;
931 factory.getClassInfo (i, &info);
932 if (std::strcmp (info.category, kARAMainFactoryClass) == 0)
934 }
935 #endif
936
937 for (Steinberg::int32 i = 0; i < numClasses; ++i)
938 {
939 PClassInfo info;
940 factory.getClassInfo (i, &info);
941
942 if (std::strcmp (info.category, kVstAudioEffectClass) != 0)
943 continue;
944
945 const String name (toString (info.name).trim());
946
947 if (foundNames.contains (name, true))
948 continue;
949
952
953 {
956
957 if (pf2.loadFrom (&factory))
958 {
959 info2.reset (new PClassInfo2());
960 pf2->getClassInfo2 (i, info2.get());
961 }
962
963 if (pf3.loadFrom (&factory))
964 {
965 infoW.reset (new PClassInfoW());
966 pf3->getClassInfoUnicode (i, infoW.get());
967 }
968 }
969
970 foundNames.add (name);
971
972 PluginDescription desc;
973
974 {
976
977 if (component.loadFrom (&factory, info.cid))
978 {
979 if (component->initialize (host.getFUnknown()) == kResultOk)
980 {
981 auto numInputs = getNumSingleDirectionChannelsFor (component.get(), Direction::input);
982 auto numOutputs = getNumSingleDirectionChannelsFor (component.get(), Direction::output);
983
984 createPluginDescription (desc, file, companyName, name,
985 info, info2.get(), infoW.get(), numInputs, numOutputs);
986
987 component->terminate();
988 }
989 else
990 {
992 }
993 }
994 else
995 {
997 }
998 }
999
1000 if (araMainFactoryClassNames.find (name) != araMainFactoryClassNames.end())
1001 desc.hasARAExtension = true;
1002
1003 if (desc.uniqueId != 0)
1004 result.push_back (desc);
1005 }
1006
1007 return result;
1008 }
1009};
1010
1011//==============================================================================
1012struct DLLHandle
1013{
1014 DLLHandle (const File& fileToOpen)
1016 {
1017 open();
1018 }
1019
1020 ~DLLHandle()
1021 {
1022 #if JUCE_MAC
1023 if (bundleRef != nullptr)
1024 #endif
1025 {
1026 if (factory != nullptr)
1027 factory->release();
1028
1029 using ExitModuleFn = bool (PLUGIN_API*)();
1030
1031 if (auto* exitFn = (ExitModuleFn) getFunction (exitFnName))
1032 exitFn();
1033
1034 #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD
1035 library.close();
1036 #endif
1037 }
1038 }
1039
1040 //==============================================================================
1044 IPluginFactory* JUCE_CALLTYPE getPluginFactory()
1045 {
1046 if (factory == nullptr)
1047 if (auto* proc = (GetFactoryProc) getFunction (factoryFnName))
1048 factory = proc();
1049
1050 // The plugin NEEDS to provide a factory to be able to be called a VST3!
1051 // Most likely you are trying to load a 32-bit VST3 from a 64-bit host
1052 // or vice versa.
1053 jassert (factory != nullptr);
1054 return factory;
1055 }
1056
1057 void* getFunction (const char* functionName)
1058 {
1059 #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD
1060 return library.getFunction (functionName);
1061 #elif JUCE_MAC
1062 if (bundleRef == nullptr)
1063 return nullptr;
1064
1065 CFUniquePtr<CFStringRef> name (String (functionName).toCFString());
1066 return CFBundleGetFunctionPointerForName (bundleRef.get(), name.get());
1067 #endif
1068 }
1069
1070 File getFile() const noexcept { return dllFile; }
1071
1072private:
1073 File dllFile;
1074 IPluginFactory* factory = nullptr;
1075
1076 static constexpr const char* factoryFnName = "GetPluginFactory";
1077
1078 #if JUCE_WINDOWS
1079 static constexpr const char* entryFnName = "InitDll";
1080 static constexpr const char* exitFnName = "ExitDll";
1081
1082 using EntryProc = bool (PLUGIN_API*)();
1083 #elif JUCE_LINUX || JUCE_BSD
1084 static constexpr const char* entryFnName = "ModuleEntry";
1085 static constexpr const char* exitFnName = "ModuleExit";
1086
1087 using EntryProc = bool (PLUGIN_API*) (void*);
1088 #elif JUCE_MAC
1089 static constexpr const char* entryFnName = "bundleEntry";
1090 static constexpr const char* exitFnName = "bundleExit";
1091
1092 using EntryProc = bool (*) (CFBundleRef);
1093 #endif
1094
1095 //==============================================================================
1096 #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD
1097 DynamicLibrary library;
1098
1099 bool open()
1100 {
1101 if (library.open (dllFile.getFullPathName()))
1102 {
1103 if (auto* proc = (EntryProc) getFunction (entryFnName))
1104 {
1105 #if JUCE_WINDOWS
1106 if (proc())
1107 #else
1108 if (proc (library.getNativeHandle()))
1109 #endif
1110 return true;
1111 }
1112 else
1113 {
1114 // this is required for some plug-ins which don't export the dll entry point function
1115 return true;
1116 }
1117
1118 library.close();
1119 }
1120
1121 return false;
1122 }
1123 #elif JUCE_MAC
1125
1126 bool open()
1127 {
1128 auto* utf8 = dllFile.getFullPathName().toRawUTF8();
1129
1131 (const UInt8*) utf8,
1133 dllFile.isDirectory())))
1134 {
1135 bundleRef.reset (CFBundleCreate (kCFAllocatorDefault, url.get()));
1136
1137 if (bundleRef != nullptr)
1138 {
1140
1141 if (CFBundleLoadExecutableAndReturnError (bundleRef.get(), &error.object))
1142 if (auto* proc = (EntryProc) getFunction (entryFnName))
1143 if (proc (bundleRef.get()))
1144 return true;
1145
1146 if (error.object != nullptr)
1149
1150 bundleRef = nullptr;
1151 }
1152 }
1153
1154 return false;
1155 }
1156 #endif
1157
1158 //==============================================================================
1160};
1161
1162struct DLLHandleCache final : public DeletedAtShutdown
1163{
1164 DLLHandleCache() = default;
1165 ~DLLHandleCache() override { clearSingletonInstance(); }
1166
1168
1170 {
1171 #if JUCE_LINUX || JUCE_BSD
1172 File file (getDLLFileFromBundle (modulePath));
1173 #else
1174 File file (modulePath);
1175 #endif
1176
1177 auto it = std::find_if (openHandles.begin(), openHandles.end(),
1178 [&] (const std::unique_ptr<DLLHandle>& handle)
1179 {
1180 return file == handle->getFile();
1181 });
1182
1183 if (it != openHandles.end())
1184 return *it->get();
1185
1186 openHandles.push_back (std::make_unique<DLLHandle> (file));
1187 return *openHandles.back().get();
1188 }
1189
1190private:
1191 #if JUCE_LINUX || JUCE_BSD
1192 File getDLLFileFromBundle (const String& bundlePath) const
1193 {
1194 auto machineName = []() -> String
1195 {
1196 struct utsname unameData;
1197 auto res = uname (&unameData);
1198
1199 if (res != 0)
1200 return {};
1201
1202 return unameData.machine;
1203 }();
1204
1205 File file (bundlePath);
1206
1207 return file.getChildFile ("Contents")
1208 .getChildFile (machineName + "-linux")
1209 .getChildFile (file.getFileNameWithoutExtension() + ".so");
1210 }
1211 #endif
1212
1214
1215 //==============================================================================
1217};
1218
1219
1221
1222//==============================================================================
1223#if JUCE_LINUX || JUCE_BSD
1224
1225class RunLoop final : public Steinberg::Linux::IRunLoop
1226{
1227public:
1228 RunLoop() = default;
1229
1230 ~RunLoop()
1231 {
1232 for (const auto& h : eventHandlerMap)
1233 LinuxEventLoop::unregisterFdCallback (h.first);
1234 }
1235
1236 //==============================================================================
1237 tresult PLUGIN_API registerEventHandler (Linux::IEventHandler* handler,
1238 Linux::FileDescriptor fd) override
1239 {
1240 if (handler == nullptr)
1241 return kInvalidArgument;
1242
1243 auto& handlers = eventHandlerMap[fd];
1244
1245 if (handlers.empty())
1246 {
1247 LinuxEventLoop::registerFdCallback (fd, [this] (int descriptor)
1248 {
1249 for (auto* h : eventHandlerMap[descriptor])
1250 h->onFDIsSet (descriptor);
1251
1252 return true;
1253 });
1254 }
1255
1256 handlers.push_back (handler);
1257
1258 return kResultTrue;
1259 }
1260
1261 tresult PLUGIN_API unregisterEventHandler (Linux::IEventHandler* handler) override
1262 {
1263 if (handler == nullptr)
1264 return kInvalidArgument;
1265
1266 for (auto iter = eventHandlerMap.begin(), end = eventHandlerMap.end(); iter != end;)
1267 {
1268 auto& handlers = iter->second;
1269
1271
1273 {
1274 handlers.erase (handlersIter);
1275
1276 if (handlers.empty())
1277 {
1278 LinuxEventLoop::unregisterFdCallback (iter->first);
1279 iter = eventHandlerMap.erase (iter);
1280 continue;
1281 }
1282 }
1283
1284 ++iter;
1285 }
1286
1287 return kResultTrue;
1288 }
1289
1290 //==============================================================================
1291 tresult PLUGIN_API registerTimer (Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds) override
1292 {
1293 if (handler == nullptr || milliseconds <= 0)
1294 return kInvalidArgument;
1295
1296 timerCallers.emplace_back (handler, (int) milliseconds);
1297 return kResultTrue;
1298 }
1299
1300 tresult PLUGIN_API unregisterTimer (Linux::ITimerHandler* handler) override
1301 {
1302 auto iter = std::find (timerCallers.begin(), timerCallers.end(), handler);
1303
1304 if (iter == timerCallers.end())
1305 return kInvalidArgument;
1306
1307 timerCallers.erase (iter);
1308 return kResultTrue;
1309 }
1310
1311 //==============================================================================
1312 uint32 PLUGIN_API addRef() override { return 1000; }
1313 uint32 PLUGIN_API release() override { return 1000; }
1314 tresult PLUGIN_API queryInterface (const TUID, void**) override { return kNoInterface; }
1315
1316private:
1317 //==============================================================================
1318 struct TimerCaller final : private Timer
1319 {
1320 TimerCaller (Linux::ITimerHandler* h, int interval) : handler (h) { startTimer (interval); }
1321 ~TimerCaller() override { stopTimer(); }
1322
1323 void timerCallback() override { handler->onTimer(); }
1324
1325 bool operator== (Linux::ITimerHandler* other) const noexcept { return handler == other; }
1326
1327 Linux::ITimerHandler* handler = nullptr;
1328 };
1329
1332
1333 //==============================================================================
1336};
1337
1338#endif
1339
1340//==============================================================================
1341struct VST3ModuleHandle final : public ReferenceCountedObject
1342{
1343 explicit VST3ModuleHandle (const File& pluginFile, const PluginDescription& pluginDesc)
1344 : file (pluginFile)
1345 {
1346 if (open (pluginDesc))
1347 {
1348 isOpen = true;
1349 getActiveModules().add (this);
1350 }
1351 }
1352
1354 {
1355 if (isOpen)
1356 getActiveModules().removeFirstMatchingValue (this);
1357 }
1358
1359 //==============================================================================
1361
1362 static VST3ModuleHandle::Ptr findOrCreateModule (const File& file,
1363 const PluginDescription& description)
1364 {
1365 for (auto* module : getActiveModules())
1366 {
1367 // VST3s are basically shells, you must therefore check their name along with their file:
1368 if (module->file == file && module->name == description.name)
1369 return module;
1370 }
1371
1372 VST3ModuleHandle::Ptr modulePtr (new VST3ModuleHandle (file, description));
1373
1374 if (! modulePtr->isOpen)
1375 modulePtr = nullptr;
1376
1377 return modulePtr;
1378 }
1379
1380 //==============================================================================
1381 IPluginFactory* getPluginFactory()
1382 {
1383 return DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory();
1384 }
1385
1386 File getFile() const noexcept { return file; }
1387 String getName() const noexcept { return name; }
1388
1389private:
1390 //==============================================================================
1392 {
1394 return activeModules;
1395 }
1396
1397 //==============================================================================
1398 bool open (const PluginDescription& description)
1399 {
1400 auto pluginFactory = addVSTComSmartPtrOwner (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory());
1401
1402 if (pluginFactory != nullptr)
1403 {
1404 auto numClasses = pluginFactory->countClasses();
1405
1406 for (Steinberg::int32 i = 0; i < numClasses; ++i)
1407 {
1408 PClassInfo info;
1409 pluginFactory->getClassInfo (i, &info);
1410
1411 if (std::strcmp (info.category, kVstAudioEffectClass) != 0)
1412 continue;
1413
1414 const auto uniqueId = getHashForRange (getNormalisedTUID (info.cid));
1415 const auto deprecatedUid = getHashForRange (info.cid);
1416
1417 if (toString (info.name).trim() == description.name
1418 && (uniqueId == description.uniqueId || deprecatedUid == description.deprecatedUid))
1419 {
1420 name = description.name;
1421 return true;
1422 }
1423 }
1424 }
1425
1426 return false;
1427 }
1428
1429 File file;
1430 String name;
1431 bool isOpen = false;
1432
1433 //==============================================================================
1435};
1436
1437template <typename Type, size_t N>
1438static int compareWithString (Type (&charArray)[N], const String& str)
1439{
1440 return std::strncmp (str.toRawUTF8(),
1441 charArray,
1442 std::min (str.getNumBytesAsUTF8(), (size_t) numElementsInArray (charArray)));
1443}
1444
1445template <typename Callback>
1446static void forEachARAFactory ([[maybe_unused]] IPluginFactory* pluginFactory, [[maybe_unused]] Callback&& cb)
1447{
1448 #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
1449 const auto numClasses = pluginFactory->countClasses();
1450 for (Steinberg::int32 i = 0; i < numClasses; ++i)
1451 {
1452 PClassInfo info;
1453 pluginFactory->getClassInfo (i, &info);
1454
1455 if (std::strcmp (info.category, kARAMainFactoryClass) == 0)
1456 {
1457 const bool keepGoing = cb (info);
1458 if (! keepGoing)
1459 break;
1460 }
1461 }
1462 #endif
1463}
1464
1466 [[maybe_unused]] const String& pluginName)
1467{
1469
1470 #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
1472 [&pluginFactory, &pluginName, &factory] (const auto& pcClassInfo)
1473 {
1474 if (compareWithString (pcClassInfo.name, pluginName) == 0)
1475 {
1476 ARA::IMainFactory* source;
1477 if (pluginFactory->createInstance (pcClassInfo.cid, ARA::IMainFactory::iid, (void**) &source)
1478 == Steinberg::kResultOk)
1479 {
1480 factory = getOrCreateARAFactory (source->getFactory(),
1481 [source]() { source->release(); });
1482 return false;
1483 }
1484 jassert (source == nullptr);
1485 }
1486
1487 return true;
1488 });
1489 #endif
1490
1491 return factory;
1492}
1493
1494static std::shared_ptr<const ARA::ARAFactory> getARAFactory (VST3ModuleHandle& module)
1495{
1496 auto* pluginFactory = module.getPluginFactory();
1497 return getARAFactory (pluginFactory, module.getName());
1498}
1499
1500//==============================================================================
1501struct VST3PluginWindow final : public AudioProcessorEditor,
1502 private ComponentMovementWatcher,
1503 private ComponentBoundsConstrainer,
1504 private IPlugFrame
1505{
1506 VST3PluginWindow (AudioPluginInstance* owner, VSTComSmartPtr<IPlugView> pluginView)
1507 : AudioProcessorEditor (owner),
1508 ComponentMovementWatcher (this),
1509 view (pluginView)
1510 #if JUCE_MAC
1511 , embeddedComponent (*owner)
1512 #endif
1513 {
1514 setSize (10, 10);
1515 setOpaque (true);
1516 setVisible (true);
1517 setConstrainer (this);
1518
1519 warnOnFailure (view->setFrame (this));
1520 view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface);
1521
1522 setContentScaleFactor();
1523 resizeToFit();
1524
1525 setResizable (view->canResize() == kResultTrue, false);
1526 }
1527
1528 ~VST3PluginWindow() override
1529 {
1530 if (scaleInterface != nullptr)
1531 scaleInterface->release();
1532
1533 #if JUCE_LINUX || JUCE_BSD
1534 embeddedComponent.removeClient();
1535 #endif
1536
1537 if (attachedCalled)
1538 warnOnFailure (view->removed());
1539
1540 warnOnFailure (view->setFrame (nullptr));
1541
1542 processor.editorBeingDeleted (this);
1543
1544 #if JUCE_MAC
1545 embeddedComponent.setView (nullptr);
1546 #endif
1547
1548 view = nullptr;
1549 }
1550
1551 #if JUCE_LINUX || JUCE_BSD
1552 Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID queryIid, void** obj) override
1553 {
1554 if (doUIDsMatch (queryIid, Steinberg::Linux::IRunLoop::iid))
1555 {
1556 *obj = &runLoop.get();
1557 return kResultTrue;
1558 }
1559
1561 *obj = nullptr;
1562
1563 return Steinberg::kNotImplemented;
1564 }
1565 #else
1566 JUCE_DECLARE_VST3_COM_QUERY_METHODS
1567 #endif
1568
1569 JUCE_DECLARE_VST3_COM_REF_METHODS
1570
1571 void paint (Graphics& g) override
1572 {
1573 g.fillAll (Colours::black);
1574 }
1575
1576 void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override
1577 {
1578 view->onWheel (wheel.deltaY);
1579 }
1580
1581 void focusGained (FocusChangeType) override { view->onFocus (true); }
1582 void focusLost (FocusChangeType) override { view->onFocus (false); }
1583
1587 bool keyStateChanged (bool /*isKeyDown*/) override { return true; }
1588 bool keyPressed (const KeyPress& /*key*/) override { return true; }
1589
1590private:
1591 void checkBounds (Rectangle<int>& bounds,
1592 const Rectangle<int>&,
1593 const Rectangle<int>&,
1594 bool,
1595 bool,
1596 bool,
1597 bool) override
1598 {
1599 auto rect = componentToVST3Rect (bounds);
1600 view->checkSizeConstraint (&rect);
1601 bounds = vst3ToComponentRect (rect);
1602 }
1603
1604 //==============================================================================
1605 void componentPeerChanged() override {}
1606
1607 /* Convert from the component's coordinate system to the hosted VST3's coordinate system. */
1608 ViewRect componentToVST3Rect (Rectangle<int> r) const
1609 {
1610 const auto physical = localAreaToGlobal (r) * nativeScaleFactor * getDesktopScaleFactor();
1611 return { 0, 0, physical.getWidth(), physical.getHeight() };
1612 }
1613
1614 /* Convert from the hosted VST3's coordinate system to the component's coordinate system. */
1615 Rectangle<int> vst3ToComponentRect (const ViewRect& vr) const
1616 {
1617 return getLocalArea (nullptr, Rectangle<int> { vr.right, vr.bottom } / (nativeScaleFactor * getDesktopScaleFactor()));
1618 }
1619
1620 void componentMovedOrResized (bool, bool wasResized) override
1621 {
1622 if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr)
1623 return;
1624
1625 if (view->canResize() == kResultTrue)
1626 {
1627 auto rect = componentToVST3Rect (getLocalBounds());
1628 view->checkSizeConstraint (&rect);
1629
1630 {
1631 const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true);
1632
1633 const auto logicalSize = vst3ToComponentRect (rect);
1634 setSize (logicalSize.getWidth(), logicalSize.getHeight());
1635 }
1636
1637 embeddedComponent.setBounds (getLocalBounds());
1638
1639 view->onSize (&rect);
1640 }
1641 else
1642 {
1643 ViewRect rect;
1644 warnOnFailure (view->getSize (&rect));
1645
1646 resizeWithRect (embeddedComponent, rect);
1647 }
1648
1649 // Some plugins don't update their cursor correctly when mousing out the window
1650 Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
1651 }
1652
1653 using ComponentMovementWatcher::componentMovedOrResized;
1654
1655 void componentVisibilityChanged() override
1656 {
1657 attachPluginWindow();
1658 resizeToFit();
1659 componentMovedOrResized (true, true);
1660 }
1661
1662 using ComponentMovementWatcher::componentVisibilityChanged;
1663
1664 void resizeToFit()
1665 {
1666 ViewRect rect;
1667 warnOnFailure (view->getSize (&rect));
1668 resizeWithRect (*this, rect);
1669 }
1670
1671 tresult PLUGIN_API resizeView (IPlugView* incomingView, ViewRect* newSize) override
1672 {
1673 const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true);
1674
1675 if (incomingView != nullptr && newSize != nullptr && incomingView == view.get())
1676 {
1677 const auto oldPhysicalSize = componentToVST3Rect (getLocalBounds());
1678 const auto logicalSize = vst3ToComponentRect (*newSize);
1679 setSize (logicalSize.getWidth(), logicalSize.getHeight());
1680 embeddedComponent.setSize (logicalSize.getWidth(), logicalSize.getHeight());
1681
1682 #if JUCE_WINDOWS
1683 embeddedComponent.updateHWNDBounds();
1684 #elif JUCE_LINUX || JUCE_BSD
1685 embeddedComponent.updateEmbeddedBounds();
1686 #endif
1687
1688 // According to the VST3 Workflow Diagrams, a resizeView from the plugin should
1689 // always trigger a response from the host which confirms the new size.
1690 auto currentPhysicalSize = componentToVST3Rect (getLocalBounds());
1691
1692 if (currentPhysicalSize.getWidth() != oldPhysicalSize.getWidth()
1693 || currentPhysicalSize.getHeight() != oldPhysicalSize.getHeight()
1694 || ! isInOnSize)
1695 {
1696 // Guard against plug-ins immediately calling resizeView() with the same size
1697 const ScopedValueSetter<bool> inOnSizeSetter (isInOnSize, true);
1698 view->onSize (&currentPhysicalSize);
1699 }
1700
1701 return kResultTrue;
1702 }
1703
1705 return kInvalidArgument;
1706 }
1707
1708 //==============================================================================
1709 void resizeWithRect (Component& comp, const ViewRect& rect) const
1710 {
1711 const auto logicalSize = vst3ToComponentRect (rect);
1712 comp.setSize (jmax (10, logicalSize.getWidth()),
1713 jmax (10, logicalSize.getHeight()));
1714 }
1715
1716 void attachPluginWindow()
1717 {
1718 if (pluginHandle == HandleFormat{})
1719 {
1720 #if JUCE_WINDOWS
1721 pluginHandle = static_cast<HWND> (embeddedComponent.getHWND());
1722 #endif
1723
1724 embeddedComponent.setBounds (getLocalBounds());
1725 addAndMakeVisible (embeddedComponent);
1726
1727 #if JUCE_MAC
1728 pluginHandle = (HandleFormat) embeddedComponent.getView();
1729 #elif JUCE_LINUX || JUCE_BSD
1730 pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID();
1731 #endif
1732
1733 if (pluginHandle == HandleFormat{})
1734 {
1736 return;
1737 }
1738
1739 [[maybe_unused]] const auto attachedResult = view->attached ((void*) pluginHandle, defaultVST3WindowType);
1740 [[maybe_unused]] const auto warning = warnOnFailure (attachedResult);
1741
1742 if (attachedResult == kResultOk)
1743 attachedCalled = true;
1744
1745 updatePluginScale();
1746 }
1747 }
1748
1749 void updatePluginScale()
1750 {
1751 if (scaleInterface != nullptr)
1752 setContentScaleFactor();
1753 else
1754 resizeToFit();
1755 }
1756
1757 void setContentScaleFactor()
1758 {
1759 if (scaleInterface != nullptr)
1760 {
1761 [[maybe_unused]] const auto result = scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getEffectiveScale());
1762
1763 #if ! JUCE_MAC
1764 [[maybe_unused]] const auto warning = warnOnFailure (result);
1765 #endif
1766 }
1767 }
1768
1769 void setScaleFactor (float s) override
1770 {
1771 userScaleFactor = s;
1772 setContentScaleFactor();
1773 resizeToFit();
1774 }
1775
1776 float getEffectiveScale() const
1777 {
1778 return nativeScaleFactor * userScaleFactor;
1779 }
1780
1781 //==============================================================================
1782 Atomic<int> refCount { 1 };
1783 VSTComSmartPtr<IPlugView> view;
1784
1785 #if JUCE_WINDOWS
1786 using HandleFormat = HWND;
1787
1788 struct ViewComponent final : public HWNDComponent
1789 {
1790 ViewComponent()
1791 {
1792 setOpaque (true);
1793 inner.addToDesktop (0);
1794
1795 if (auto* peer = inner.getPeer())
1796 setHWND (peer->getNativeHandle());
1797 }
1798
1799 void paint (Graphics& g) override { g.fillAll (Colours::black); }
1800
1801 private:
1802 struct Inner final : public Component
1803 {
1804 Inner() { setOpaque (true); }
1805 void paint (Graphics& g) override { g.fillAll (Colours::black); }
1806 };
1807
1808 Inner inner;
1809 };
1810
1811 ViewComponent embeddedComponent;
1812 #elif JUCE_MAC
1813 NSViewComponentWithParent embeddedComponent;
1814 using HandleFormat = NSView*;
1815 #elif JUCE_LINUX || JUCE_BSD
1816 SharedResourcePointer<RunLoop> runLoop;
1817 XEmbedComponent embeddedComponent { true, false };
1818 using HandleFormat = Window;
1819 #else
1820 Component embeddedComponent;
1821 using HandleFormat = void*;
1822 #endif
1823
1824 HandleFormat pluginHandle = {};
1825 bool recursiveResize = false, isInOnSize = false, attachedCalled = false;
1826
1827 Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr;
1828 float nativeScaleFactor = 1.0f;
1829 float userScaleFactor = 1.0f;
1830
1831 struct ScaleNotifierCallback
1832 {
1833 VST3PluginWindow& window;
1834
1835 void operator() (float platformScale) const
1836 {
1837 MessageManager::callAsync ([ref = Component::SafePointer<VST3PluginWindow> (&window), platformScale]
1838 {
1839 if (auto* r = ref.getComponent())
1840 {
1841 r->nativeScaleFactor = platformScale;
1842 r->setContentScaleFactor();
1843 r->resizeToFit();
1844
1845 #if JUCE_WINDOWS
1846 r->embeddedComponent.updateHWNDBounds();
1847 #elif JUCE_LINUX || JUCE_BSD
1848 r->embeddedComponent.updateEmbeddedBounds();
1849 #endif
1850 }
1851 });
1852 }
1853 };
1854
1855 NativeScaleFactorNotifier scaleNotifier { this, ScaleNotifierCallback { *this } };
1856
1857 //==============================================================================
1859};
1860
1861JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods
1862
1863//==============================================================================
1864static bool hasARAExtension (IPluginFactory* pluginFactory, const String& pluginClassName)
1865{
1866 bool result = false;
1867
1868 forEachARAFactory (pluginFactory,
1869 [&pluginClassName, &result] (const auto& pcClassInfo)
1870 {
1871 if (compareWithString (pcClassInfo.name, pluginClassName) == 0)
1872 {
1873 result = true;
1874
1875 return false;
1876 }
1877
1878 return true;
1879 });
1880
1881 return result;
1882}
1883
1884//==============================================================================
1885struct VST3ComponentHolder
1886{
1887 VST3ComponentHolder (const VST3ModuleHandle::Ptr& m) : module (m)
1888 {
1889 host = addVSTComSmartPtrOwner (new VST3HostContext());
1890 }
1891
1892 ~VST3ComponentHolder()
1893 {
1894 terminate();
1895 }
1896
1897 bool isIComponentAlsoIEditController() const
1898 {
1899 if (component == nullptr)
1900 {
1902 return false;
1903 }
1904
1905 return VSTComSmartPtr<Vst::IEditController>().loadFrom (component.get());
1906 }
1907
1908 bool fetchController (VSTComSmartPtr<Vst::IEditController>& editController)
1909 {
1910 if (! isComponentInitialised && ! initialise())
1911 return false;
1912
1913 editController.loadFrom (component.get());
1914
1915 // Get the IEditController:
1916 TUID controllerCID = { 0 };
1917
1918 if (editController == nullptr
1919 && component->getControllerClassId (controllerCID) == kResultTrue
1920 && FUID (controllerCID).isValid())
1921 {
1922 editController.loadFrom (factory.get(), controllerCID);
1923 }
1924
1925 if (editController == nullptr)
1926 {
1927 // Try finding the IEditController the long way around:
1928 auto numClasses = factory->countClasses();
1929
1930 for (Steinberg::int32 i = 0; i < numClasses; ++i)
1931 {
1932 PClassInfo classInfo;
1933 factory->getClassInfo (i, &classInfo);
1934
1935 if (std::strcmp (classInfo.category, kVstComponentControllerClass) == 0)
1936 editController.loadFrom (factory.get(), classInfo.cid);
1937 }
1938 }
1939
1940 return (editController != nullptr);
1941 }
1942
1943 //==============================================================================
1944 void fillInPluginDescription (PluginDescription& description) const
1945 {
1946 jassert (module != nullptr && isComponentInitialised);
1947
1948 PFactoryInfo factoryInfo;
1949 factory->getFactoryInfo (&factoryInfo);
1950
1951 auto classIdx = getClassIndex (module->getName());
1952
1953 if (classIdx >= 0)
1954 {
1955 PClassInfo info;
1956 [[maybe_unused]] bool success = (factory->getClassInfo (classIdx, &info) == kResultOk);
1957 jassert (success);
1958
1959 VSTComSmartPtr<IPluginFactory2> pf2;
1960 VSTComSmartPtr<IPluginFactory3> pf3;
1961
1964
1965 if (pf2.loadFrom (factory.get()))
1966 {
1967 info2.reset (new PClassInfo2());
1968 pf2->getClassInfo2 (classIdx, info2.get());
1969 }
1970 else
1971 {
1972 info2.reset();
1973 }
1974
1975 if (pf3.loadFrom (factory.get()))
1976 {
1977 pf3->setHostContext (host->getFUnknown());
1978 infoW.reset (new PClassInfoW());
1979 pf3->getClassInfoUnicode (classIdx, infoW.get());
1980 }
1981 else
1982 {
1983 infoW.reset();
1984 }
1985
1986 Vst::BusInfo bus;
1987 int totalNumInputChannels = 0, totalNumOutputChannels = 0;
1988
1989 int n = component->getBusCount (Vst::kAudio, Vst::kInput);
1990 for (int i = 0; i < n; ++i)
1991 if (component->getBusInfo (Vst::kAudio, Vst::kInput, i, bus) == kResultOk)
1992 totalNumInputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0);
1993
1994 n = component->getBusCount (Vst::kAudio, Vst::kOutput);
1995 for (int i = 0; i < n; ++i)
1996 if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk)
1997 totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0);
1998
1999 createPluginDescription (description, module->getFile(),
2000 factoryInfo.vendor, module->getName(),
2001 info, info2.get(), infoW.get(),
2002 totalNumInputChannels,
2003 totalNumOutputChannels);
2004
2005 description.hasARAExtension = hasARAExtension (factory.get(), description.name);
2006
2007 return;
2008 }
2009
2011 }
2012
2013 //==============================================================================
2014 bool initialise()
2015 {
2016 if (isComponentInitialised)
2017 return true;
2018
2019 // It's highly advisable to create your plugins using the message thread.
2020 // The VST3 spec requires that many of the functions called during
2021 // initialisation are only called from the message thread.
2023
2024 factory = addVSTComSmartPtrOwner (module->getPluginFactory());
2025
2026 int classIdx;
2027 if ((classIdx = getClassIndex (module->getName())) < 0)
2028 return false;
2029
2030 PClassInfo info;
2031 if (factory->getClassInfo (classIdx, &info) != kResultOk)
2032 return false;
2033
2034 if (! component.loadFrom (factory.get(), info.cid) || component == nullptr)
2035 return false;
2036
2037 cidOfComponent = FUID (info.cid);
2038
2039 if (warnOnFailure (component->initialize (host->getFUnknown())) != kResultOk)
2040 return false;
2041
2042 isComponentInitialised = true;
2043
2044 return true;
2045 }
2046
2047 void terminate()
2048 {
2049 if (isComponentInitialised)
2050 {
2051 component->terminate();
2052 isComponentInitialised = false;
2053 }
2054
2055 component = nullptr;
2056 }
2057
2058 //==============================================================================
2059 int getClassIndex (const String& className) const
2060 {
2061 PClassInfo info;
2062 const Steinberg::int32 numClasses = factory->countClasses();
2063
2064 for (Steinberg::int32 j = 0; j < numClasses; ++j)
2065 if (factory->getClassInfo (j, &info) == kResultOk
2066 && std::strcmp (info.category, kVstAudioEffectClass) == 0
2067 && toString (info.name).trim() == className)
2068 return j;
2069
2070 return -1;
2071 }
2072
2073 //==============================================================================
2074 VST3ModuleHandle::Ptr module;
2075 VSTComSmartPtr<IPluginFactory> factory;
2076 VSTComSmartPtr<VST3HostContext> host;
2077 VSTComSmartPtr<Vst::IComponent> component;
2078 FUID cidOfComponent;
2079
2080 bool isComponentInitialised = false;
2081};
2082
2083//==============================================================================
2084/* A queue which can store up to one element.
2085
2086 This is more memory-efficient than storing large vectors of
2087 parameter changes that we'll just throw away.
2088*/
2089class ParamValueQueue final : public Vst::IParamValueQueue
2090{
2091public:
2092 ParamValueQueue (Vst::ParamID idIn, Steinberg::int32 parameterIndexIn)
2093 : paramId (idIn), parameterIndex (parameterIndexIn) {}
2094
2095 virtual ~ParamValueQueue() = default;
2096
2097 JUCE_DECLARE_VST3_COM_REF_METHODS
2098 JUCE_DECLARE_VST3_COM_QUERY_METHODS
2099
2100 Vst::ParamID PLUGIN_API getParameterId() override { return paramId; }
2101
2102 Steinberg::int32 getParameterIndex() const noexcept { return parameterIndex; }
2103
2104 Steinberg::int32 PLUGIN_API getPointCount() override { return size; }
2105
2106 tresult PLUGIN_API getPoint (Steinberg::int32 index,
2107 Steinberg::int32& sampleOffset,
2108 Vst::ParamValue& value) override
2109 {
2110 if (! isPositiveAndBelow (index, size))
2111 return kResultFalse;
2112
2113 sampleOffset = 0;
2114 value = cachedValue;
2115
2116 return kResultTrue;
2117 }
2118
2119 tresult PLUGIN_API addPoint (Steinberg::int32,
2120 Vst::ParamValue value,
2121 Steinberg::int32& index) override
2122 {
2123 index = size++;
2124 set ((float) value);
2125
2126 return kResultTrue;
2127 }
2128
2129 void set (float valueIn)
2130 {
2131 cachedValue = valueIn;
2132 size = 1;
2133 }
2134
2135 void clear() { size = 0; }
2136
2137 float get() const noexcept
2138 {
2139 jassert (size > 0);
2140 return cachedValue;
2141 }
2142
2143private:
2144 const Vst::ParamID paramId;
2145 const Steinberg::int32 parameterIndex;
2146 float cachedValue;
2147 Steinberg::int32 size = 0;
2148 Atomic<int> refCount;
2149};
2150
2151//==============================================================================
2152/* An implementation of IParameterChanges with some important characteristics:
2153 - Lookup by index is O(1)
2154 - Lookup by paramID is also O(1)
2155 - addParameterData never allocates, as long you pass a paramID already passed to initialise
2156*/
2157class ParameterChanges final : public Vst::IParameterChanges
2158{
2159 static constexpr Steinberg::int32 notInVector = -1;
2160
2161 struct Entry
2162 {
2163 explicit Entry (std::unique_ptr<ParamValueQueue> queue) : ptr (addVSTComSmartPtrOwner (queue.release())) {}
2164
2165 VSTComSmartPtr<ParamValueQueue> ptr;
2166 Steinberg::int32 index = notInVector;
2167 };
2168
2170 using Queues = std::vector<Entry*>;
2171
2172public:
2173 virtual ~ParameterChanges() = default;
2174
2175 JUCE_DECLARE_VST3_COM_REF_METHODS
2176 JUCE_DECLARE_VST3_COM_QUERY_METHODS
2177
2178 Steinberg::int32 PLUGIN_API getParameterCount() override
2179 {
2180 return (Steinberg::int32) queues.size();
2181 }
2182
2183 ParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32 index) override
2184 {
2185 if (isPositiveAndBelow (index, queues.size()))
2186 {
2187 auto& entry = queues[(size_t) index];
2188 // If this fails, our container has become internally inconsistent
2189 jassert (entry->index == index);
2190 return entry->ptr.get();
2191 }
2192
2193 return nullptr;
2194 }
2195
2196 ParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID& id,
2197 Steinberg::int32& index) override
2198 {
2199 const auto it = map.find (id);
2200
2201 if (it == map.end())
2202 return nullptr;
2203
2204 auto& result = it->second;
2205
2206 if (result.index == notInVector)
2207 {
2208 result.index = (Steinberg::int32) queues.size();
2209 queues.push_back (&result);
2210 }
2211
2212 index = result.index;
2213 return result.ptr.get();
2214 }
2215
2216 void set (Vst::ParamID id, float value)
2217 {
2218 Steinberg::int32 indexOut = notInVector;
2219
2220 if (auto* queue = addParameterData (id, indexOut))
2221 queue->set (value);
2222 }
2223
2224 void clear()
2225 {
2226 for (auto* item : queues)
2227 item->index = notInVector;
2228
2229 queues.clear();
2230 }
2231
2232 void initialise (const std::vector<Vst::ParamID>& idsIn)
2233 {
2234 for (const auto [index, id] : enumerate (idsIn))
2235 map.emplace (id, Entry { std::make_unique<ParamValueQueue> (id, (Steinberg::int32) index) });
2236
2237 queues.reserve (map.size());
2238 queues.clear();
2239 }
2240
2241 template <typename Callback>
2242 void forEach (Callback&& callback) const
2243 {
2244 for (const auto* item : queues)
2245 {
2246 auto* ptr = item->ptr.get();
2247 callback (ptr->getParameterIndex(), ptr->getParameterId(), ptr->get());
2248 }
2249 }
2250
2251private:
2252 Map map;
2253 Queues queues;
2254 Atomic<int> refCount;
2255};
2256
2257//==============================================================================
2258class VST3PluginInstance final : public AudioPluginInstance
2259{
2260public:
2261 //==============================================================================
2262 struct VST3Parameter final : public Parameter
2263 {
2264 VST3Parameter (VST3PluginInstance& parent,
2265 Steinberg::int32 vstParameterIndex,
2266 Steinberg::Vst::ParamID parameterID,
2267 bool parameterIsAutomatable)
2268 : pluginInstance (parent),
2269 vstParamIndex (vstParameterIndex),
2270 paramID (parameterID),
2271 automatable (parameterIsAutomatable)
2272 {
2273 }
2274
2275 float getValue() const override
2276 {
2277 return pluginInstance.cachedParamValues.get (vstParamIndex);
2278 }
2279
2280 /* The 'normal' setValue call, which will update both the processor and editor.
2281 */
2282 void setValue (float newValue) override
2283 {
2284 pluginInstance.cachedParamValues.set (vstParamIndex, newValue);
2285 pluginInstance.parameterDispatcher.push (vstParamIndex, newValue);
2286 }
2287
2288 /* If we're syncing the editor to the processor, the processor won't need to
2289 be notified about the parameter updates, so we can avoid flagging the
2290 change when updating the float cache.
2291 */
2292 void setValueWithoutUpdatingProcessor (float newValue)
2293 {
2294 if (! exactlyEqual (pluginInstance.cachedParamValues.exchangeWithoutNotifying (vstParamIndex, newValue), newValue))
2295 sendValueChangedMessageToListeners (newValue);
2296 }
2297
2298 String getText (float value, int maximumLength) const override
2299 {
2300 MessageManagerLock lock;
2301
2302 if (pluginInstance.editController != nullptr)
2303 {
2304 Vst::String128 result;
2305
2306 if (pluginInstance.editController->getParamStringByValue (paramID, value, result) == kResultOk)
2307 return toString (result).substring (0, maximumLength);
2308 }
2309
2310 return Parameter::getText (value, maximumLength);
2311 }
2312
2313 float getValueForText (const String& text) const override
2314 {
2315 MessageManagerLock lock;
2316
2317 if (pluginInstance.editController != nullptr)
2318 {
2319 Vst::ParamValue result;
2320
2321 if (pluginInstance.editController->getParamValueByString (paramID, toString (text), result) == kResultOk)
2322 return (float) result;
2323 }
2324
2325 return Parameter::getValueForText (text);
2326 }
2327
2328 float getDefaultValue() const override
2329 {
2330 return (float) getParameterInfo().defaultNormalizedValue;
2331 }
2332
2333 String getName (int /*maximumStringLength*/) const override
2334 {
2335 return toString (getParameterInfo().title);
2336 }
2337
2338 String getLabel() const override
2339 {
2340 return toString (getParameterInfo().units);
2341 }
2342
2343 bool isAutomatable() const override
2344 {
2345 return automatable;
2346 }
2347
2348 bool isDiscrete() const override
2349 {
2350 return discrete;
2351 }
2352
2353 int getNumSteps() const override
2354 {
2355 return numSteps;
2356 }
2357
2358 StringArray getAllValueStrings() const override
2359 {
2360 return {};
2361 }
2362
2363 String getParameterID() const override
2364 {
2365 return String (paramID);
2366 }
2367
2368 Steinberg::Vst::ParamID getParamID() const noexcept { return paramID; }
2369
2370 private:
2371 Vst::ParameterInfo getParameterInfo() const
2372 {
2373 return pluginInstance.getParameterInfoForIndex (vstParamIndex);
2374 }
2375
2376 VST3PluginInstance& pluginInstance;
2377 const Steinberg::int32 vstParamIndex;
2378 const Steinberg::Vst::ParamID paramID;
2379 const bool automatable;
2380 const int numSteps = [&]
2381 {
2382 auto stepCount = getParameterInfo().stepCount;
2383 return stepCount == 0 ? AudioProcessor::getDefaultNumParameterSteps()
2384 : stepCount + 1;
2385 }();
2386 const bool discrete = getNumSteps() != AudioProcessor::getDefaultNumParameterSteps();
2387 };
2388
2389 //==============================================================================
2390 explicit VST3PluginInstance (std::unique_ptr<VST3ComponentHolder> componentHolder)
2391 : AudioPluginInstance (getBusProperties (componentHolder->component)),
2392 holder (std::move (componentHolder))
2393 {
2394 jassert (holder->isComponentInitialised);
2395 holder->host->setPlugin (this);
2396 }
2397
2398 ~VST3PluginInstance() override
2399 {
2400 callOnMessageThread ([this] { cleanup(); });
2401 }
2402
2403 void cleanup()
2404 {
2405 jassert (getActiveEditor() == nullptr); // You must delete any editors before deleting the plugin instance!
2406
2407 releaseResources();
2408
2409 if (editControllerConnection != nullptr && componentConnection != nullptr)
2410 {
2411 editControllerConnection->disconnect (componentConnection.get());
2412 componentConnection->disconnect (editControllerConnection.get());
2413 }
2414
2415 editController->setComponentHandler (nullptr);
2416
2417 if (isControllerInitialised && ! holder->isIComponentAlsoIEditController())
2418 editController->terminate();
2419
2420 holder->terminate();
2421
2422 componentConnection = nullptr;
2423 editControllerConnection = nullptr;
2424 unitData = nullptr;
2425 unitInfo = nullptr;
2426 programListData = nullptr;
2427 componentHandler2 = nullptr;
2428 componentHandler = nullptr;
2429 processor = nullptr;
2430 midiMapping = nullptr;
2431 editController2 = nullptr;
2432 editController = nullptr;
2433 }
2434
2435 //==============================================================================
2436 bool initialise()
2437 {
2438 // It's highly advisable to create your plugins using the message thread.
2439 // The VST3 spec requires that many of the functions called during
2440 // initialisation are only called from the message thread.
2442
2443 if (! holder->initialise())
2444 return false;
2445
2446 if (! (isControllerInitialised || holder->fetchController (editController)))
2447 return false;
2448
2449 // If the IComponent and IEditController are the same, we will have
2450 // already initialized the object at this point and should avoid doing so again.
2451 if (! holder->isIComponentAlsoIEditController())
2452 editController->initialize (holder->host->getFUnknown());
2453
2454 isControllerInitialised = true;
2455 editController->setComponentHandler (holder->host.get());
2456 grabInformationObjects();
2457 interconnectComponentAndController();
2458
2459 auto configureParameters = [this]
2460 {
2461 initialiseParameterList();
2462 synchroniseStates();
2463 syncProgramNames();
2464 };
2465
2466 configureParameters();
2467 setupIO();
2468
2469 // Some plug-ins don't present their parameters until after the IO has been
2470 // configured, so we need to jump though all these hoops again
2471 if (getParameters().isEmpty() && editController->getParameterCount() > 0)
2472 configureParameters();
2473
2474 updateMidiMappings();
2475
2476 parameterDispatcher.start (*editController);
2477
2478 return true;
2479 }
2480
2481 void getExtensions (ExtensionsVisitor& visitor) const override
2482 {
2483 struct Extensions final : public ExtensionsVisitor::VST3Client,
2484 public ExtensionsVisitor::ARAClient
2485 {
2486 explicit Extensions (const VST3PluginInstance* instanceIn) : instance (instanceIn) {}
2487
2488 Steinberg::Vst::IComponent* getIComponentPtr() const noexcept override { return instance->holder->component.get(); }
2489
2490 MemoryBlock getPreset() const override { return instance->getStateForPresetFile(); }
2491
2492 bool setPreset (const MemoryBlock& rawData) const override
2493 {
2494 return instance->setStateFromPresetFile (rawData);
2495 }
2496
2497 void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const noexcept override
2498 {
2499 cb (ARAFactoryWrapper { ::juce::getARAFactory (*(instance->holder->module)) });
2500 }
2501
2502 const VST3PluginInstance* instance = nullptr;
2503 };
2504
2505 Extensions extensions { this };
2506 visitor.visitVST3Client (extensions);
2507
2508 if (::juce::getARAFactory (*(holder->module)))
2509 {
2510 visitor.visitARAClient (extensions);
2511 }
2512 }
2513
2514 void* getPlatformSpecificData() override { return holder->component.get(); }
2515
2516 void updateMidiMappings()
2517 {
2518 // MIDI mappings will always be updated on the main thread, but we need to ensure
2519 // that we're not simultaneously reading them on the audio thread.
2520 const SpinLock::ScopedLockType processLock (processMutex);
2521
2522 if (midiMapping != nullptr)
2523 storedMidiMapping.storeMappings (*midiMapping);
2524 }
2525
2526 //==============================================================================
2527 const String getName() const override
2528 {
2529 auto& module = holder->module;
2530 return module != nullptr ? module->getName() : String();
2531 }
2532
2533 std::vector<Vst::SpeakerArrangement> getActualArrangements (bool isInput) const
2534 {
2536
2537 const auto numBuses = getBusCount (isInput);
2538
2539 for (auto i = 0; i < numBuses; ++i)
2540 result.push_back (getArrangementForBus (processor.get(), isInput, i));
2541
2542 return result;
2543 }
2544
2545 std::optional<std::vector<Vst::SpeakerArrangement>> busLayoutsToArrangements (bool isInput) const
2546 {
2548
2549 const auto numBuses = getBusCount (isInput);
2550
2551 for (auto i = 0; i < numBuses; ++i)
2552 {
2553 if (const auto arr = getVst3SpeakerArrangement (getBus (isInput, i)->getLastEnabledLayout()))
2554 result.push_back (*arr);
2555 else
2556 return {};
2557 }
2558
2559 return result;
2560 }
2561
2562 void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override
2563 {
2564 // The VST3 spec requires that IComponent::setupProcessing() is called on the message
2565 // thread. If you call it from a different thread, some plugins may break.
2567 MessageManagerLock lock;
2568
2569 const SpinLock::ScopedLockType processLock (processMutex);
2570
2571 // Avoid redundantly calling things like setActive, which can be a heavy-duty call for some plugins:
2572 if (isActive
2573 && approximatelyEqual (getSampleRate(), newSampleRate)
2574 && getBlockSize() == estimatedSamplesPerBlock)
2575 return;
2576
2577 using namespace Vst;
2578
2579 // If the plugin has already been activated (prepareToPlay has been called twice without
2580 // a matching releaseResources call) deactivate it so that the speaker layout and bus
2581 // activation can be updated safely.
2582 deactivate();
2583
2584 ProcessSetup setup;
2585 setup.symbolicSampleSize = isUsingDoublePrecision() ? kSample64 : kSample32;
2586 setup.maxSamplesPerBlock = estimatedSamplesPerBlock;
2587 setup.sampleRate = newSampleRate;
2588 setup.processMode = isNonRealtime() ? kOffline : kRealtime;
2589
2590 warnOnFailure (processor->setupProcessing (setup));
2591
2592 holder->initialise();
2593
2594 auto inArrangements = busLayoutsToArrangements (true) .value_or (std::vector<SpeakerArrangement>{});
2595 auto outArrangements = busLayoutsToArrangements (false).value_or (std::vector<SpeakerArrangement>{});
2596
2597 // Some plug-ins will crash if you pass a nullptr to setBusArrangements!
2598 SpeakerArrangement nullArrangement = {};
2599 auto* inData = inArrangements .empty() ? &nullArrangement : inArrangements .data();
2600 auto* outData = outArrangements.empty() ? &nullArrangement : outArrangements.data();
2601
2602 warnOnFailure (processor->setBusArrangements (inData, static_cast<int32> (inArrangements .size()),
2603 outData, static_cast<int32> (outArrangements.size())));
2604
2605 const auto inArrActual = getActualArrangements (true);
2606 const auto outArrActual = getActualArrangements (false);
2607
2608 jassert (inArrActual == inArrangements && outArrActual == outArrangements);
2609
2610 // Needed for having the same sample rate in processBlock(); some plugins need this!
2611 setRateAndBufferSizeDetails (newSampleRate, estimatedSamplesPerBlock);
2612
2613 auto numInputBuses = getBusCount (true);
2614 auto numOutputBuses = getBusCount (false);
2615
2616 for (int i = 0; i < numInputBuses; ++i)
2617 warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kInput, i, getBus (true, i)->isEnabled() ? 1 : 0));
2618
2619 for (int i = 0; i < numOutputBuses; ++i)
2620 warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kOutput, i, getBus (false, i)->isEnabled() ? 1 : 0));
2621
2622 setLatencySamples (jmax (0, (int) processor->getLatencySamples()));
2623
2624 inputBusMap .prepare (createChannelMappings (true));
2625 outputBusMap.prepare (createChannelMappings (false));
2626
2627 setStateForAllMidiBuses (true);
2628
2629 warnOnFailure (holder->component->setActive (true));
2630 warnOnFailureIfImplemented (processor->setProcessing (true));
2631
2632 isActive = true;
2633 }
2634
2635 void releaseResources() override
2636 {
2637 const SpinLock::ScopedLockType lock (processMutex);
2638 deactivate();
2639 }
2640
2641 bool supportsDoublePrecisionProcessing() const override
2642 {
2643 return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue);
2644 }
2645
2646 //==============================================================================
2647 /* Important: It is strongly recommended to use this function if you need to
2648 find the JUCE parameter corresponding to a particular IEditController
2649 parameter.
2650
2651 Note that a parameter at a given index in the IEditController does not
2652 necessarily correspond to the parameter at the same index in
2653 AudioProcessor::getParameters().
2654 */
2655 VST3Parameter* getParameterForID (Vst::ParamID paramID) const
2656 {
2657 const auto it = idToParamMap.find (paramID);
2658 return it != idToParamMap.end() ? it->second : nullptr;
2659 }
2660
2661 //==============================================================================
2662 void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
2663 {
2664 jassert (! isUsingDoublePrecision());
2665
2666 const SpinLock::ScopedLockType processLock (processMutex);
2667
2668 if (isActive && processor != nullptr)
2669 processAudio (buffer, midiMessages, Vst::kSample32, false);
2670 }
2671
2672 void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
2673 {
2674 jassert (isUsingDoublePrecision());
2675
2676 const SpinLock::ScopedLockType processLock (processMutex);
2677
2678 if (isActive && processor != nullptr)
2679 processAudio (buffer, midiMessages, Vst::kSample64, false);
2680 }
2681
2682 void processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
2683 {
2684 jassert (! isUsingDoublePrecision());
2685
2686 const SpinLock::ScopedLockType processLock (processMutex);
2687
2688 if (bypassParam != nullptr)
2689 {
2690 if (isActive && processor != nullptr)
2691 processAudio (buffer, midiMessages, Vst::kSample32, true);
2692 }
2693 else
2694 {
2695 AudioProcessor::processBlockBypassed (buffer, midiMessages);
2696 }
2697 }
2698
2699 void processBlockBypassed (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
2700 {
2701 jassert (isUsingDoublePrecision());
2702
2703 const SpinLock::ScopedLockType processLock (processMutex);
2704
2705 if (bypassParam != nullptr)
2706 {
2707 if (isActive && processor != nullptr)
2708 processAudio (buffer, midiMessages, Vst::kSample64, true);
2709 }
2710 else
2711 {
2712 AudioProcessor::processBlockBypassed (buffer, midiMessages);
2713 }
2714 }
2715
2716 //==============================================================================
2717 template <typename FloatType>
2718 void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages,
2719 Vst::SymbolicSampleSizes sampleSize, bool isProcessBlockBypassedCall)
2720 {
2721 using namespace Vst;
2722 auto numSamples = buffer.getNumSamples();
2723
2724 auto numInputAudioBuses = getBusCount (true);
2725 auto numOutputAudioBuses = getBusCount (false);
2726
2727 updateBypass (isProcessBlockBypassedCall);
2728
2729 ProcessData data;
2730 data.processMode = isNonRealtime() ? kOffline : kRealtime;
2731 data.symbolicSampleSize = sampleSize;
2732 data.numInputs = numInputAudioBuses;
2733 data.numOutputs = numOutputAudioBuses;
2734 data.inputParameterChanges = inputParameterChanges.get();
2735 data.outputParameterChanges = outputParameterChanges.get();
2736 data.numSamples = (Steinberg::int32) numSamples;
2737
2738 updateTimingInformation (data, getSampleRate());
2739
2740 for (int i = getTotalNumInputChannels(); i < buffer.getNumChannels(); ++i)
2741 buffer.clear (i, 0, numSamples);
2742
2743 inputParameterChanges->clear();
2744 outputParameterChanges->clear();
2745
2746 associateWith (data, buffer);
2747 associateWith (data, midiMessages);
2748
2749 cachedParamValues.ifSet ([&] (Steinberg::int32 index, float value)
2750 {
2751 inputParameterChanges->set (cachedParamValues.getParamID (index), value);
2752 });
2753
2754 processor->process (data);
2755
2756 outputParameterChanges->forEach ([&] (Steinberg::int32 vstParamIndex, Vst::ParamID id, float value)
2757 {
2758 // Send the parameter value from the processor to the editor
2759 parameterDispatcher.push (vstParamIndex, value);
2760
2761 // Update the host's parameter value
2762 if (auto* param = getParameterForID (id))
2763 param->setValueWithoutUpdatingProcessor (value);
2764 });
2765
2766 midiMessages.clear();
2767 MidiEventList::toMidiBuffer (midiMessages, *midiOutputs);
2768 }
2769
2770 //==============================================================================
2771 bool canAddBus (bool) const override { return false; }
2772 bool canRemoveBus (bool) const override { return false; }
2773
2774 bool isBusesLayoutSupported (const BusesLayout& layouts) const override
2775 {
2776 const SpinLock::ScopedLockType processLock (processMutex);
2777
2778 // if the processor is not active, we ask the underlying plug-in if the
2779 // layout is actually supported
2780 if (! isActive)
2781 return canApplyBusesLayout (layouts);
2782
2783 // not much we can do to check the layout while the audio processor is running
2784 // Let's at least check if it is a VST3 compatible layout
2785 for (const auto isInput : { true, false })
2786 {
2787 auto n = getBusCount (isInput);
2788
2789 for (int i = 0; i < n; ++i)
2790 if (getChannelLayoutOfBus (isInput, i).isDiscreteLayout())
2791 return false;
2792 }
2793
2794 return true;
2795 }
2796
2797 bool syncBusLayouts (const BusesLayout& layouts) const
2798 {
2799 for (const auto isInput : { true, false })
2800 {
2801 auto n = getBusCount (isInput);
2802 const Vst::BusDirection vstDir = (isInput ? Vst::kInput : Vst::kOutput);
2803
2804 for (int busIdx = 0; busIdx < n; ++busIdx)
2805 {
2806 const bool isEnabled = (! layouts.getChannelSet (isInput, busIdx).isDisabled());
2807
2808 if (holder->component->activateBus (Vst::kAudio, vstDir, busIdx, (isEnabled ? 1 : 0)) != kResultOk)
2809 return false;
2810 }
2811 }
2812
2813 const auto getPotentialArrangements = [&] (bool isInput) -> std::optional<std::vector<Vst::SpeakerArrangement>>
2814 {
2816
2817 for (int i = 0; i < layouts.getBuses (isInput).size(); ++i)
2818 {
2819 const auto& requested = layouts.getChannelSet (isInput, i);
2820
2821 if (const auto arr = getVst3SpeakerArrangement (requested.isDisabled() ? getBus (isInput, i)->getLastEnabledLayout() : requested))
2822 result.push_back (*arr);
2823 else
2824 return {};
2825 }
2826
2827 return result;
2828 };
2829
2830 auto inArrangements = getPotentialArrangements (true);
2831 auto outArrangements = getPotentialArrangements (false);
2832
2833 if (! inArrangements.has_value() || ! outArrangements.has_value())
2834 {
2835 // This bus layout can't be represented as a VST3 speaker arrangement
2836 return false;
2837 }
2838
2839 auto& inputArrangements = *inArrangements;
2840 auto& outputArrangements = *outArrangements;
2841
2842 // Some plug-ins will crash if you pass a nullptr to setBusArrangements!
2843 Vst::SpeakerArrangement nullArrangement = {};
2844 auto* inputArrangementData = inputArrangements .empty() ? &nullArrangement : inputArrangements .data();
2845 auto* outputArrangementData = outputArrangements.empty() ? &nullArrangement : outputArrangements.data();
2846
2847 if (processor->setBusArrangements (inputArrangementData, static_cast<int32> (inputArrangements .size()),
2848 outputArrangementData, static_cast<int32> (outputArrangements.size())) != kResultTrue)
2849 return false;
2850
2851 // check if the layout matches the request
2852 const auto inArrActual = getActualArrangements (true);
2853 const auto outArrActual = getActualArrangements (false);
2854
2855 return (inArrActual == inputArrangements && outArrActual == outputArrangements);
2856 }
2857
2858 bool canApplyBusesLayout (const BusesLayout& layouts) const override
2859 {
2860 // someone tried to change the layout while the AudioProcessor is running
2861 // call releaseResources first!
2862 jassert (! isActive);
2863
2864 const auto previousLayout = getBusesLayout();
2865 const auto result = syncBusLayouts (layouts);
2866 syncBusLayouts (previousLayout);
2867 return result;
2868 }
2869
2870 //==============================================================================
2871 void updateTrackProperties (const TrackProperties& properties) override
2872 {
2873 if (trackInfoListener != nullptr)
2874 {
2875 auto l = addVSTComSmartPtrOwner (new TrackPropertiesAttributeList (properties));
2876 trackInfoListener->setChannelContextInfos (l.get());
2877 }
2878 }
2879
2880 struct TrackPropertiesAttributeList final : public Vst::IAttributeList
2881 {
2882 TrackPropertiesAttributeList (const TrackProperties& properties) : props (properties) {}
2883 virtual ~TrackPropertiesAttributeList() {}
2884
2885 JUCE_DECLARE_VST3_COM_REF_METHODS
2886
2887 tresult PLUGIN_API queryInterface (const TUID queryIid, void** obj) override
2888 {
2889 return testForMultiple (*this,
2890 queryIid,
2891 UniqueBase<Vst::IAttributeList>{},
2892 SharedBase<FUnknown, Vst::IAttributeList>{}).extract (obj);
2893 }
2894
2895 tresult PLUGIN_API setInt (AttrID, Steinberg::int64) override { return kOutOfMemory; }
2896 tresult PLUGIN_API setFloat (AttrID, double) override { return kOutOfMemory; }
2897 tresult PLUGIN_API setString (AttrID, const Vst::TChar*) override { return kOutOfMemory; }
2898 tresult PLUGIN_API setBinary (AttrID, const void*, Steinberg::uint32) override { return kOutOfMemory; }
2899 tresult PLUGIN_API getFloat (AttrID, double&) override { return kResultFalse; }
2900 tresult PLUGIN_API getBinary (AttrID, const void*&, Steinberg::uint32&) override { return kResultFalse; }
2901
2902 tresult PLUGIN_API getString (AttrID id, Vst::TChar* string, Steinberg::uint32 size) override
2903 {
2904 if (! std::strcmp (id, Vst::ChannelContext::kChannelNameKey))
2905 {
2906 Steinberg::String str (props.name.toRawUTF8());
2907 str.copyTo (string, 0, (Steinberg::int32) jmin (size, (Steinberg::uint32) std::numeric_limits<Steinberg::int32>::max()));
2908
2909 return kResultTrue;
2910 }
2911
2912 return kResultFalse;
2913 }
2914
2915 tresult PLUGIN_API getInt (AttrID id, Steinberg::int64& value) override
2916 {
2917 if (! std::strcmp (Vst::ChannelContext::kChannelNameLengthKey, id)) value = props.name.length();
2918 else if (! std::strcmp (Vst::ChannelContext::kChannelColorKey, id)) value = static_cast<Steinberg::int64> (props.colour.getARGB());
2919 else return kResultFalse;
2920
2921 return kResultTrue;
2922 }
2923
2924 Atomic<int> refCount;
2925 TrackProperties props;
2926
2927 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrackPropertiesAttributeList)
2928 };
2929
2930 //==============================================================================
2931 String getChannelName (int channelIndex, Direction direction) const
2932 {
2933 auto numBuses = getNumSingleDirectionBusesFor (holder->component.get(), MediaKind::audio, direction);
2934
2935 int numCountedChannels = 0;
2936
2937 for (int i = 0; i < numBuses; ++i)
2938 {
2939 auto busInfo = getBusInfo (MediaKind::audio, direction, i);
2940
2941 numCountedChannels += busInfo.channelCount;
2942
2943 if (channelIndex < numCountedChannels)
2944 return toString (busInfo.name);
2945 }
2946
2947 return {};
2948 }
2949
2950 const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, Direction::input); }
2951 const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, Direction::output); }
2952
2953 bool isInputChannelStereoPair (int channelIndex) const override
2954 {
2955 int busIdx;
2956 return getOffsetInBusBufferForAbsoluteChannelIndex (true, channelIndex, busIdx) >= 0
2957 && getBusInfo (MediaKind::audio, Direction::input, busIdx).channelCount == 2;
2958 }
2959
2960 bool isOutputChannelStereoPair (int channelIndex) const override
2961 {
2962 int busIdx;
2963 return getOffsetInBusBufferForAbsoluteChannelIndex (false, channelIndex, busIdx) >= 0
2964 && getBusInfo (MediaKind::audio, Direction::output, busIdx).channelCount == 2;
2965 }
2966
2967 bool acceptsMidi() const override { return hasMidiInput; }
2968 bool producesMidi() const override { return hasMidiOutput; }
2969
2970 //==============================================================================
2971 AudioProcessorParameter* getBypassParameter() const override { return bypassParam; }
2972
2973 //==============================================================================
2975 double getTailLengthSeconds() const override
2976 {
2977 if (processor != nullptr)
2978 {
2979 auto sampleRate = getSampleRate();
2980
2981 if (sampleRate > 0.0)
2982 {
2983 auto tailSamples = processor->getTailSamples();
2984
2985 if (tailSamples == Vst::kInfiniteTail)
2987
2988 return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate;
2989 }
2990 }
2991
2992 return 0.0;
2993 }
2994
2995 //==============================================================================
2996 AudioProcessorEditor* createEditor() override
2997 {
2998 if (auto view = becomeVSTComSmartPtrOwner (tryCreatingView()))
2999 return new VST3PluginWindow (this, view);
3000
3001 return nullptr;
3002 }
3003
3004 bool hasEditor() const override
3005 {
3006 // (if possible, avoid creating a second instance of the editor, because that crashes some plugins)
3007 if (getActiveEditor() != nullptr)
3008 return true;
3009
3010 auto view = becomeVSTComSmartPtrOwner (tryCreatingView());
3011 return view != nullptr;
3012 }
3013
3014 //==============================================================================
3015 int getNumPrograms() override { return programNames.size(); }
3016 const String getProgramName (int index) override { return index >= 0 ? programNames[index] : String(); }
3017 void changeProgramName (int, const String&) override {}
3018
3019 int getCurrentProgram() override
3020 {
3021 if (programNames.size() > 0 && editController != nullptr)
3022 if (auto* param = getParameterForID (programParameterID))
3023 return jmax (0, roundToInt (param->getValue() * (float) (programNames.size() - 1)));
3024
3025 return 0;
3026 }
3027
3028 void setCurrentProgram (int program) override
3029 {
3030 if (programNames.size() > 0 && editController != nullptr)
3031 {
3032 auto value = static_cast<Vst::ParamValue> (program) / static_cast<Vst::ParamValue> (jmax (1, programNames.size() - 1));
3033
3034 if (auto* param = getParameterForID (programParameterID))
3035 param->setValueNotifyingHost ((float) value);
3036 }
3037 }
3038
3039 //==============================================================================
3040 void reset() override
3041 {
3042 const SpinLock::ScopedLockType lock (processMutex);
3043
3044 if (holder->component != nullptr && processor != nullptr)
3045 {
3046 processor->setProcessing (false);
3047 holder->component->setActive (false);
3048
3049 holder->component->setActive (true);
3050 processor->setProcessing (true);
3051 }
3052 }
3053
3054 //==============================================================================
3055 void getStateInformation (MemoryBlock& destData) override
3056 {
3057 // The VST3 plugin format requires that get/set state calls are made
3058 // from the message thread.
3059 // We'll lock the message manager here as a safety precaution, but some
3060 // plugins may still misbehave!
3061
3063 MessageManagerLock lock;
3064
3065 parameterDispatcher.flush();
3066
3067 XmlElement state ("VST3PluginState");
3068
3069 appendStateFrom (state, holder->component, "IComponent");
3070 appendStateFrom (state, editController, "IEditController");
3071
3072 AudioProcessor::copyXmlToBinary (state, destData);
3073 }
3074
3075 void setStateInformation (const void* data, int sizeInBytes) override
3076 {
3077 // The VST3 plugin format requires that get/set state calls are made
3078 // from the message thread.
3079 // We'll lock the message manager here as a safety precaution, but some
3080 // plugins may still misbehave!
3081
3083 MessageManagerLock lock;
3084
3085 parameterDispatcher.flush();
3086
3087 if (auto head = AudioProcessor::getXmlFromBinary (data, sizeInBytes))
3088 {
3089 auto componentStream (createMemoryStreamForState (*head, "IComponent"));
3090
3091 if (componentStream != nullptr && holder->component != nullptr)
3092 holder->component->setState (componentStream.get());
3093
3094 if (editController != nullptr)
3095 {
3096 if (componentStream != nullptr)
3097 {
3098 Steinberg::int64 result;
3099 componentStream->seek (0, IBStream::kIBSeekSet, &result);
3100 setComponentStateAndResetParameters (*componentStream);
3101 }
3102
3103 auto controllerStream (createMemoryStreamForState (*head, "IEditController"));
3104
3105 if (controllerStream != nullptr)
3106 editController->setState (controllerStream.get());
3107 }
3108 }
3109 }
3110
3111 void setComponentStateAndResetParameters (Steinberg::MemoryStream& stream)
3112 {
3113 jassert (editController != nullptr);
3114
3115 warnOnFailureIfImplemented (editController->setComponentState (&stream));
3116 resetParameters();
3117 }
3118
3119 void resetParameters()
3120 {
3121 for (auto* parameter : getParameters())
3122 {
3123 auto* vst3Param = static_cast<VST3Parameter*> (parameter);
3124 const auto value = (float) editController->getParamNormalized (vst3Param->getParamID());
3125 vst3Param->setValueWithoutUpdatingProcessor (value);
3126 }
3127 }
3128
3129 MemoryBlock getStateForPresetFile() const
3130 {
3131 auto memoryStream = becomeVSTComSmartPtrOwner (new Steinberg::MemoryStream());
3132
3133 if (memoryStream == nullptr || holder->component == nullptr)
3134 return {};
3135
3136 const auto saved = Steinberg::Vst::PresetFile::savePreset (memoryStream.get(),
3137 holder->cidOfComponent,
3138 holder->component.get(),
3139 editController.get());
3140
3141 if (saved)
3142 return { memoryStream->getData(), static_cast<size_t> (memoryStream->getSize()) };
3143
3144 return {};
3145 }
3146
3147 bool setStateFromPresetFile (const MemoryBlock& rawData) const
3148 {
3149 auto rawDataCopy = rawData;
3150 auto memoryStream = becomeVSTComSmartPtrOwner (new Steinberg::MemoryStream (rawDataCopy.getData(), (int) rawDataCopy.getSize()));
3151
3152 if (memoryStream == nullptr || holder->component == nullptr)
3153 return false;
3154
3155 return Steinberg::Vst::PresetFile::loadPreset (memoryStream.get(), holder->cidOfComponent,
3156 holder->component.get(), editController.get(), nullptr);
3157 }
3158
3159 //==============================================================================
3160 void fillInPluginDescription (PluginDescription& description) const override
3161 {
3162 holder->fillInPluginDescription (description);
3163 }
3164
3166 void getCurrentProgramStateInformation (MemoryBlock& destData) override
3167 {
3168 destData.setSize (0, true);
3169 }
3170
3172 void setCurrentProgramStateInformation ([[maybe_unused]] const void* data,
3173 [[maybe_unused]] int sizeInBytes) override
3174 {
3175 }
3176
3177private:
3178 void deactivate()
3179 {
3180 if (! isActive)
3181 return;
3182
3183 isActive = false;
3184
3185 if (processor != nullptr)
3186 warnOnFailureIfImplemented (processor->setProcessing (false));
3187
3188 if (holder->component != nullptr)
3189 warnOnFailure (holder->component->setActive (false));
3190
3191 setStateForAllMidiBuses (false);
3192 }
3193
3194 //==============================================================================
3195 #if JUCE_LINUX || JUCE_BSD
3196 SharedResourcePointer<RunLoop> runLoop;
3197 #endif
3198
3200
3201 friend VST3HostContext;
3202
3203 // Information objects:
3204 String company;
3208
3209 // Rudimentary interfaces:
3210 VSTComSmartPtr<Vst::IEditController> editController;
3211 VSTComSmartPtr<Vst::IEditController2> editController2;
3212 VSTComSmartPtr<Vst::IMidiMapping> midiMapping;
3213 VSTComSmartPtr<Vst::IAudioProcessor> processor;
3214 VSTComSmartPtr<Vst::IComponentHandler> componentHandler;
3215 VSTComSmartPtr<Vst::IComponentHandler2> componentHandler2;
3216 VSTComSmartPtr<Vst::IUnitInfo> unitInfo;
3217 VSTComSmartPtr<Vst::IUnitData> unitData;
3218 VSTComSmartPtr<Vst::IProgramListData> programListData;
3219 VSTComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection;
3220 VSTComSmartPtr<Vst::ChannelContext::IInfoListener> trackInfoListener;
3221
3226 HostBufferMapper inputBusMap, outputBusMap;
3227
3228 StringArray programNames;
3229 Vst::ParamID programParameterID = (Vst::ParamID) -1;
3230
3232 EditControllerParameterDispatcher parameterDispatcher;
3233 StoredMidiMapping storedMidiMapping;
3234
3235 /* The plugin may request a restart during playback, which may in turn
3236 attempt to call functions such as setProcessing and setActive. It is an
3237 error to call these functions simultaneously with
3238 IAudioProcessor::process, so we use this mutex to ensure that this
3239 scenario is impossible.
3240 */
3241 SpinLock processMutex;
3242
3243 //==============================================================================
3244 template <typename Type>
3245 static void appendStateFrom (XmlElement& head, VSTComSmartPtr<Type>& object, const String& identifier)
3246 {
3247 if (object != nullptr)
3248 {
3250
3251 const auto result = object->getState (&stream);
3252
3253 if (result == kResultTrue)
3254 {
3255 MemoryBlock info (stream.getData(), (size_t) stream.getSize());
3256 head.createNewChildElement (identifier)->addTextElement (info.toBase64Encoding());
3257 }
3258 }
3259 }
3260
3261 static VSTComSmartPtr<Steinberg::MemoryStream> createMemoryStreamForState (XmlElement& head, StringRef identifier)
3262 {
3263 if (auto* state = head.getChildByName (identifier))
3264 {
3265 MemoryBlock mem;
3266
3267 if (mem.fromBase64Encoding (state->getAllSubText()))
3268 {
3269 auto stream = becomeVSTComSmartPtrOwner (new Steinberg::MemoryStream());
3270 stream->setSize ((TSize) mem.getSize());
3271 mem.copyTo (stream->getData(), 0, mem.getSize());
3272 return stream;
3273 }
3274 }
3275
3276 return {};
3277 }
3278
3279 CachedParamValues cachedParamValues;
3280 VSTComSmartPtr<ParameterChanges> inputParameterChanges = addVSTComSmartPtrOwner (new ParameterChanges);
3281 VSTComSmartPtr<ParameterChanges> outputParameterChanges = addVSTComSmartPtrOwner (new ParameterChanges);
3282 VSTComSmartPtr<MidiEventList> midiInputs = addVSTComSmartPtrOwner (new MidiEventList);
3283 VSTComSmartPtr<MidiEventList> midiOutputs = addVSTComSmartPtrOwner (new MidiEventList);
3284 Vst::ProcessContext timingInfo; //< Only use this in processBlock()!
3285 bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false;
3286 const bool hasMidiInput = getNumSingleDirectionBusesFor (holder->component.get(), MediaKind::event, Direction::input) > 0,
3287 hasMidiOutput = getNumSingleDirectionBusesFor (holder->component.get(), MediaKind::event, Direction::output) > 0;
3288 VST3Parameter* bypassParam = nullptr;
3289
3290 //==============================================================================
3292 void interconnectComponentAndController()
3293 {
3294 componentConnection.loadFrom (holder->component.get());
3295 editControllerConnection.loadFrom (editController.get());
3296
3297 if (componentConnection != nullptr && editControllerConnection != nullptr)
3298 {
3299 warnOnFailure (componentConnection->connect (editControllerConnection.get()));
3300 warnOnFailure (editControllerConnection->connect (componentConnection.get()));
3301 }
3302 }
3303
3304 void initialiseParameterList()
3305 {
3306 AudioProcessorParameterGroup newParameterTree;
3307
3308 // We're going to add parameter groups to the tree recursively in the same order as the
3309 // first parameters contained within them.
3312 groupMap[Vst::kRootUnitId] = &newParameterTree;
3313
3314 if (unitInfo != nullptr)
3315 {
3316 const auto numUnits = unitInfo->getUnitCount();
3317
3318 for (int i = 1; i < numUnits; ++i)
3319 {
3320 Vst::UnitInfo ui{};
3321 unitInfo->getUnitInfo (i, ui);
3322 infoMap[ui.id] = std::move (ui);
3323 }
3324 }
3325
3326 {
3327 auto allIds = getAllParamIDs (*editController);
3328 inputParameterChanges ->initialise (allIds);
3329 outputParameterChanges->initialise (allIds);
3330 cachedParamValues = CachedParamValues { std::move (allIds) };
3331 }
3332
3333 for (int i = 0; i < editController->getParameterCount(); ++i)
3334 {
3335 auto paramInfo = getParameterInfoForIndex (i);
3336 auto* param = new VST3Parameter (*this,
3337 i,
3338 paramInfo.id,
3339 (paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0);
3340
3341 if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0)
3342 bypassParam = param;
3343
3344 std::function<AudioProcessorParameterGroup* (Vst::UnitID)> findOrCreateGroup;
3345 findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID)
3346 {
3347 auto existingGroup = groupMap.find (groupID);
3348
3349 if (existingGroup != groupMap.end())
3350 return existingGroup->second;
3351
3352 auto groupInfo = infoMap.find (groupID);
3353
3354 if (groupInfo == infoMap.end())
3355 return groupMap[Vst::kRootUnitId];
3356
3357 auto* group = new AudioProcessorParameterGroup (String (groupInfo->first),
3358 toString (groupInfo->second.name),
3359 {});
3360 groupMap[groupInfo->first] = group;
3361
3362 auto* parentGroup = findOrCreateGroup (groupInfo->second.parentUnitId);
3363 parentGroup->addChild (std::unique_ptr<AudioProcessorParameterGroup> (group));
3364
3365 return group;
3366 };
3367
3368 auto* group = findOrCreateGroup (paramInfo.unitId);
3369 group->addChild (std::unique_ptr<AudioProcessorParameter> (param));
3370 }
3371
3372 setHostedParameterTree (std::move (newParameterTree));
3373
3374 idToParamMap = [this]
3375 {
3377
3378 for (auto* parameter : getParameters())
3379 {
3380 auto* vst3Param = static_cast<VST3Parameter*> (parameter);
3381 result.emplace (vst3Param->getParamID(), vst3Param);
3382 }
3383
3384 return result;
3385 }();
3386 }
3387
3388 void synchroniseStates()
3389 {
3391
3392 if (holder->component->getState (&stream) == kResultTrue)
3393 if (stream.seek (0, Steinberg::IBStream::kIBSeekSet, nullptr) == kResultTrue)
3394 setComponentStateAndResetParameters (stream);
3395 }
3396
3397 void grabInformationObjects()
3398 {
3399 processor.loadFrom (holder->component.get());
3400 unitInfo.loadFrom (holder->component.get());
3401 programListData.loadFrom (holder->component.get());
3402 unitData.loadFrom (holder->component.get());
3403 editController2.loadFrom (holder->component.get());
3404 midiMapping.loadFrom (holder->component.get());
3405 componentHandler.loadFrom (holder->component.get());
3406 componentHandler2.loadFrom (holder->component.get());
3407 trackInfoListener.loadFrom (holder->component.get());
3408
3409 if (processor == nullptr) processor.loadFrom (editController.get());
3410 if (unitInfo == nullptr) unitInfo.loadFrom (editController.get());
3411 if (programListData == nullptr) programListData.loadFrom (editController.get());
3412 if (unitData == nullptr) unitData.loadFrom (editController.get());
3413 if (editController2 == nullptr) editController2.loadFrom (editController.get());
3414 if (midiMapping == nullptr) midiMapping.loadFrom (editController.get());
3415 if (componentHandler == nullptr) componentHandler.loadFrom (editController.get());
3416 if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController.get());
3417 if (trackInfoListener == nullptr) trackInfoListener.loadFrom (editController.get());
3418 }
3419
3420 void setStateForAllMidiBuses (bool newState)
3421 {
3422 setStateForAllEventBuses (holder->component.get(), newState, Direction::input);
3423 setStateForAllEventBuses (holder->component.get(), newState, Direction::output);
3424 }
3425
3426 std::vector<ChannelMapping> createChannelMappings (bool isInput) const
3427 {
3429 result.reserve ((size_t) getBusCount (isInput));
3430
3431 for (auto i = 0; i < getBusCount (isInput); ++i)
3432 result.emplace_back (*getBus (isInput, i));
3433
3434 return result;
3435 }
3436
3437 void setupIO()
3438 {
3439 setStateForAllMidiBuses (true);
3440
3441 Vst::ProcessSetup setup;
3442 setup.symbolicSampleSize = Vst::kSample32;
3443 setup.maxSamplesPerBlock = 1024;
3444 setup.sampleRate = 44100.0;
3445 setup.processMode = Vst::kRealtime;
3446
3447 warnOnFailure (processor->setupProcessing (setup));
3448
3449 inputBusMap .prepare (createChannelMappings (true));
3450 outputBusMap.prepare (createChannelMappings (false));
3451 setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock);
3452 }
3453
3454 static AudioProcessor::BusesProperties getBusProperties (VSTComSmartPtr<Vst::IComponent>& component)
3455 {
3456 AudioProcessor::BusesProperties busProperties;
3457 VSTComSmartPtr<Vst::IAudioProcessor> processor;
3458 processor.loadFrom (component.get());
3459
3460 for (const auto isInput : { true, false })
3461 {
3462 const Vst::BusDirection dir = (isInput ? Vst::kInput : Vst::kOutput);
3463 const int numBuses = component->getBusCount (Vst::kAudio, dir);
3464
3465 for (int i = 0; i < numBuses; ++i)
3466 {
3467 Vst::BusInfo info;
3468
3469 if (component->getBusInfo (Vst::kAudio, dir, (Steinberg::int32) i, info) != kResultOk)
3470 continue;
3471
3472 AudioChannelSet layout = (info.channelCount == 0 ? AudioChannelSet::disabled()
3473 : AudioChannelSet::discreteChannels (info.channelCount));
3474
3475 Vst::SpeakerArrangement arr;
3476 if (processor != nullptr && processor->getBusArrangement (dir, i, arr) == kResultOk)
3477 if (const auto set = getChannelSetForSpeakerArrangement (arr))
3478 layout = *set;
3479
3480 busProperties.addBus (isInput, toString (info.name), layout,
3481 (info.flags & Vst::BusInfo::kDefaultActive) != 0);
3482 }
3483 }
3484
3485 return busProperties;
3486 }
3487
3488 //==============================================================================
3489 Vst::BusInfo getBusInfo (MediaKind kind, Direction direction, int index = 0) const
3490 {
3491 Vst::BusInfo busInfo;
3492 busInfo.mediaType = toVstType (kind);
3493 busInfo.direction = toVstType (direction);
3494 busInfo.channelCount = 0;
3495
3496 holder->component->getBusInfo (busInfo.mediaType, busInfo.direction,
3497 (Steinberg::int32) index, busInfo);
3498 return busInfo;
3499 }
3500
3501 //==============================================================================
3502 void updateBypass (bool processBlockBypassedCalled)
3503 {
3504 // to remain backward compatible, the logic needs to be the following:
3505 // - if processBlockBypassed was called then definitely bypass the VST3
3506 // - if processBlock was called then only un-bypass the VST3 if the previous
3507 // call was processBlockBypassed, otherwise do nothing
3508 if (processBlockBypassedCalled)
3509 {
3510 if (bypassParam != nullptr && (approximatelyEqual (bypassParam->getValue(), 0.0f) || ! lastProcessBlockCallWasBypass))
3511 bypassParam->setValue (1.0f);
3512 }
3513 else
3514 {
3515 if (lastProcessBlockCallWasBypass && bypassParam != nullptr)
3516 bypassParam->setValue (0.0f);
3517
3518 }
3519
3520 lastProcessBlockCallWasBypass = processBlockBypassedCalled;
3521 }
3522
3523 //==============================================================================
3525 IPlugView* tryCreatingView() const
3526 {
3528
3529 IPlugView* v = editController->createView (Vst::ViewType::kEditor);
3530
3531 if (v == nullptr) v = editController->createView (nullptr);
3532 if (v == nullptr) editController->queryInterface (IPlugView::iid, (void**) &v);
3533
3534 return v;
3535 }
3536
3537 //==============================================================================
3538 template <typename FloatType>
3539 void associateWith (Vst::ProcessData& destination, AudioBuffer<FloatType>& buffer)
3540 {
3541 destination.inputs = inputBusMap .getVst3LayoutForJuceBuffer (buffer);
3542 destination.outputs = outputBusMap.getVst3LayoutForJuceBuffer (buffer);
3543 }
3544
3545 void associateWith (Vst::ProcessData& destination, MidiBuffer& midiBuffer)
3546 {
3547 midiInputs->clear();
3548 midiOutputs->clear();
3549
3550 if (acceptsMidi())
3551 {
3552 MidiEventList::hostToPluginEventList (*midiInputs,
3553 midiBuffer,
3554 storedMidiMapping,
3555 [this] (const auto controlID, const auto paramValue)
3556 {
3557 if (auto* param = this->getParameterForID (controlID))
3558 param->setValueNotifyingHost ((float) paramValue);
3559 });
3560 }
3561
3562 destination.inputEvents = midiInputs.get();
3563 destination.outputEvents = midiOutputs.get();
3564 }
3565
3566 void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate)
3567 {
3568 toProcessContext (timingInfo, getPlayHead(), processSampleRate);
3569 destination.processContext = &timingInfo;
3570 }
3571
3572 Vst::ParameterInfo getParameterInfoForIndex (Steinberg::int32 index) const
3573 {
3574 Vst::ParameterInfo paramInfo{};
3575
3576 if (editController != nullptr)
3577 editController->getParameterInfo ((int32) index, paramInfo);
3578
3579 return paramInfo;
3580 }
3581
3582 Vst::ProgramListInfo getProgramListInfo (int index) const
3583 {
3584 Vst::ProgramListInfo paramInfo{};
3585
3586 if (unitInfo != nullptr)
3587 unitInfo->getProgramListInfo (index, paramInfo);
3588
3589 return paramInfo;
3590 }
3591
3592 void syncProgramNames()
3593 {
3594 programNames.clear();
3595
3596 if (processor == nullptr || editController == nullptr)
3597 return;
3598
3599 Vst::UnitID programUnitID;
3600 Vst::ParameterInfo paramInfo{};
3601
3602 {
3603 int idx, num = editController->getParameterCount();
3604
3605 for (idx = 0; idx < num; ++idx)
3606 if (editController->getParameterInfo (idx, paramInfo) == kResultOk
3607 && (paramInfo.flags & Steinberg::Vst::ParameterInfo::kIsProgramChange) != 0)
3608 break;
3609
3610 if (idx >= num)
3611 return;
3612
3613 programParameterID = paramInfo.id;
3614 programUnitID = paramInfo.unitId;
3615 }
3616
3617 if (unitInfo != nullptr)
3618 {
3619 Vst::UnitInfo uInfo{};
3620 const int unitCount = unitInfo->getUnitCount();
3621
3622 for (int idx = 0; idx < unitCount; ++idx)
3623 {
3624 if (unitInfo->getUnitInfo (idx, uInfo) == kResultOk
3625 && uInfo.id == programUnitID)
3626 {
3627 const int programListCount = unitInfo->getProgramListCount();
3628
3629 for (int j = 0; j < programListCount; ++j)
3630 {
3631 Vst::ProgramListInfo programListInfo{};
3632
3633 if (unitInfo->getProgramListInfo (j, programListInfo) == kResultOk
3634 && programListInfo.id == uInfo.programListId)
3635 {
3636 Vst::String128 name;
3637
3638 for (int k = 0; k < programListInfo.programCount; ++k)
3639 if (unitInfo->getProgramName (programListInfo.id, k, name) == kResultOk)
3640 programNames.add (toString (name));
3641
3642 return;
3643 }
3644 }
3645
3646 break;
3647 }
3648 }
3649 }
3650
3651 if (editController != nullptr && paramInfo.stepCount > 0)
3652 {
3653 auto numPrograms = paramInfo.stepCount + 1;
3654
3655 for (int i = 0; i < numPrograms; ++i)
3656 {
3657 auto valueNormalized = static_cast<Vst::ParamValue> (i) / static_cast<Vst::ParamValue> (paramInfo.stepCount);
3658
3659 Vst::String128 programName;
3660 if (editController->getParamStringByValue (paramInfo.id, valueNormalized, programName) == kResultOk)
3661 programNames.add (toString (programName));
3662 }
3663 }
3664 }
3665
3667};
3668
3669JUCE_END_IGNORE_WARNINGS_MSVC
3670
3671//==============================================================================
3672tresult VST3HostContext::beginEdit (Vst::ParamID paramID)
3673{
3674 if (plugin == nullptr)
3675 return kResultTrue;
3676
3677 if (auto* param = plugin->getParameterForID (paramID))
3678 {
3679 param->beginChangeGesture();
3680 return kResultTrue;
3681 }
3682
3683 return kResultFalse;
3684}
3685
3686tresult VST3HostContext::performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalised)
3687{
3688 if (plugin == nullptr)
3689 return kResultTrue;
3690
3691 if (auto* param = plugin->getParameterForID (paramID))
3692 {
3693 param->setValueNotifyingHost ((float) valueNormalised);
3694
3695 // did the plug-in already update the parameter internally
3696 if (! approximatelyEqual (plugin->editController->getParamNormalized (paramID), valueNormalised))
3697 return plugin->editController->setParamNormalized (paramID, valueNormalised);
3698
3699 return kResultTrue;
3700 }
3701
3702 return kResultFalse;
3703}
3704
3705tresult VST3HostContext::endEdit (Vst::ParamID paramID)
3706{
3707 if (plugin == nullptr)
3708 return kResultTrue;
3709
3710 if (auto* param = plugin->getParameterForID (paramID))
3711 {
3712 param->endChangeGesture();
3713 return kResultTrue;
3714 }
3715
3716 return kResultFalse;
3717}
3718
3719tresult VST3HostContext::restartComponent (Steinberg::int32 flags)
3720{
3721 // If you hit this, the plugin has requested a restart from a thread other than
3722 // the UI thread. JUCE should be able to cope, but you should consider filing a bug
3723 // report against the plugin.
3725
3726 componentRestarter.restart (flags);
3727 return kResultTrue;
3728}
3729
3730tresult PLUGIN_API VST3HostContext::setDirty (TBool needsSave)
3731{
3732 if (needsSave)
3733 plugin->updateHostDisplay (AudioPluginInstance::ChangeDetails{}.withNonParameterStateChanged (true));
3734
3735 return kResultOk;
3736}
3737
3738void VST3HostContext::restartComponentOnMessageThread (int32 flags)
3739{
3740 if (plugin == nullptr)
3741 {
3743 return;
3744 }
3745
3746 if (hasFlag (flags, Vst::kReloadComponent))
3747 plugin->reset();
3748
3749 if (hasFlag (flags, Vst::kIoChanged))
3750 {
3751 auto sampleRate = plugin->getSampleRate();
3752 auto blockSize = plugin->getBlockSize();
3753
3754 // Have to deactivate here, otherwise prepareToPlay might not pick up the new bus layouts
3755 plugin->releaseResources();
3756 plugin->prepareToPlay (sampleRate >= 8000 ? sampleRate : 44100.0,
3757 blockSize > 0 ? blockSize : 1024);
3758 }
3759
3760 if (hasFlag (flags, Vst::kLatencyChanged))
3761 if (plugin->processor != nullptr)
3762 plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples()));
3763
3764 if (hasFlag (flags, Vst::kMidiCCAssignmentChanged))
3765 plugin->updateMidiMappings();
3766
3767 if (hasFlag (flags, Vst::kParamValuesChanged))
3768 plugin->resetParameters();
3769
3770 plugin->updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)
3771 .withParameterInfoChanged (true));
3772}
3773
3774//==============================================================================
3775tresult VST3HostContext::ContextMenu::popup (Steinberg::UCoord x, Steinberg::UCoord y)
3776{
3777 Array<const Item*> subItemStack;
3778 OwnedArray<PopupMenu> menuStack;
3779 PopupMenu* topLevelMenu = menuStack.add (new PopupMenu());
3780
3781 for (int i = 0; i < items.size(); ++i)
3782 {
3783 auto& item = items.getReference (i).item;
3784 auto* menuToUse = menuStack.getLast();
3785
3786 if (hasFlag (item.flags, Item::kIsGroupStart & ~Item::kIsDisabled))
3787 {
3788 subItemStack.add (&item);
3789 menuStack.add (new PopupMenu());
3790 }
3791 else if (hasFlag (item.flags, Item::kIsGroupEnd))
3792 {
3793 if (auto* subItem = subItemStack.getLast())
3794 {
3795 if (auto* m = menuStack [menuStack.size() - 2])
3796 m->addSubMenu (toString (subItem->name), *menuToUse,
3797 ! hasFlag (subItem->flags, Item::kIsDisabled),
3798 nullptr,
3799 hasFlag (subItem->flags, Item::kIsChecked));
3800
3801 menuStack.removeLast (1);
3802 subItemStack.removeLast (1);
3803 }
3804 }
3805 else if (hasFlag (item.flags, Item::kIsSeparator))
3806 {
3807 menuToUse->addSeparator();
3808 }
3809 else
3810 {
3811 menuToUse->addItem (item.tag != 0 ? (int) item.tag : (int) zeroTagReplacement,
3812 toString (item.name),
3813 ! hasFlag (item.flags, Item::kIsDisabled),
3814 hasFlag (item.flags, Item::kIsChecked));
3815 }
3816 }
3817
3818 PopupMenu::Options options;
3819
3820 if (auto* ed = owner.getActiveEditor())
3821 {
3822 #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE
3823 if (auto* peer = ed->getPeer())
3824 {
3825 auto scale = peer->getPlatformScaleFactor();
3826
3827 x = roundToInt (x / scale);
3828 y = roundToInt (y / scale);
3829 }
3830 #endif
3831
3832 options = options.withTargetScreenArea (ed->getScreenBounds().translated ((int) x, (int) y).withSize (1, 1));
3833 }
3834
3835 #if JUCE_MODAL_LOOPS_PERMITTED
3836 // Unfortunately, Steinberg's docs explicitly say this should be modal..
3837 handleResult (topLevelMenu->showMenu (options));
3838 #else
3839 topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, addVSTComSmartPtrOwner (this)));
3840 #endif
3841
3842 return kResultOk;
3843}
3844
3845//==============================================================================
3846tresult VST3HostContext::notifyProgramListChange (Vst::ProgramListID, Steinberg::int32)
3847{
3848 if (plugin != nullptr)
3849 plugin->syncProgramNames();
3850
3851 return kResultTrue;
3852}
3853
3854//==============================================================================
3855//==============================================================================
3856VST3PluginFormat::VST3PluginFormat() = default;
3857VST3PluginFormat::~VST3PluginFormat() = default;
3858
3859bool VST3PluginFormat::setStateFromVSTPresetFile (AudioPluginInstance* api, const MemoryBlock& rawData)
3860{
3861 if (auto vst3 = dynamic_cast<VST3PluginInstance*> (api))
3862 return vst3->setStateFromPresetFile (rawData);
3863
3864 return false;
3865}
3866
3867void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier)
3868{
3869 if (! fileMightContainThisPluginType (fileOrIdentifier))
3870 return;
3871
3872 if (const auto fast = DescriptionLister::findDescriptionsFast (File (fileOrIdentifier)); ! fast.empty())
3873 {
3874 for (const auto& d : fast)
3875 results.add (new PluginDescription (d));
3876
3877 return;
3878 }
3879
3880 for (const auto& file : getLibraryPaths (fileOrIdentifier))
3881 {
3888 auto pluginFactory = addVSTComSmartPtrOwner (DLLHandleCache::getInstance()->findOrCreateHandle (file).getPluginFactory());
3889
3890 if (pluginFactory == nullptr)
3891 continue;
3892
3893 auto host = addVSTComSmartPtrOwner (new VST3HostContext());
3894
3895 for (const auto& d : DescriptionLister::findDescriptionsSlow (*host, *pluginFactory, File (file)))
3896 results.add (new PluginDescription (d));
3897 }
3898}
3899
3900void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& description, ARAFactoryCreationCallback callback)
3901{
3902 if (! description.hasARAExtension)
3903 {
3905 callback ({ {}, "The provided plugin does not support ARA features" });
3906 }
3907
3908 File file (description.fileOrIdentifier);
3909 auto pluginFactory = addVSTComSmartPtrOwner (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory());
3910 const auto* pluginName = description.name.toRawUTF8();
3911
3912 callback ({ ARAFactoryWrapper { ::juce::getARAFactory (pluginFactory.get(), pluginName) }, {} });
3913}
3914
3915static std::unique_ptr<AudioPluginInstance> createVST3Instance (VST3PluginFormat& format,
3916 const PluginDescription& description,
3917 const File& file)
3918{
3919 if (! format.fileMightContainThisPluginType (description.fileOrIdentifier))
3920 return nullptr;
3921
3922 struct ScopedWorkingDirectory
3923 {
3924 ~ScopedWorkingDirectory() { previousWorkingDirectory.setAsCurrentWorkingDirectory(); }
3925 File previousWorkingDirectory = File::getCurrentWorkingDirectory();
3926 };
3927
3928 const ScopedWorkingDirectory scope;
3929 file.getParentDirectory().setAsCurrentWorkingDirectory();
3930
3931 const VST3ModuleHandle::Ptr module { VST3ModuleHandle::findOrCreateModule (file, description) };
3932
3933 if (module == nullptr)
3934 return nullptr;
3935
3936 auto holder = std::make_unique<VST3ComponentHolder> (module);
3937
3938 if (! holder->initialise())
3939 return nullptr;
3940
3941 auto instance = std::make_unique<VST3PluginInstance> (std::move (holder));
3942
3943 if (! instance->initialise())
3944 return nullptr;
3945
3946 return instance;
3947}
3948
3949StringArray VST3PluginFormat::getLibraryPaths (const String& fileOrIdentifier)
3950{
3951 #if JUCE_WINDOWS
3952 if (! File (fileOrIdentifier).existsAsFile())
3953 {
3954 StringArray files;
3955 recursiveFileSearch (files, fileOrIdentifier, true);
3956 return files;
3957 }
3958 #endif
3959
3960 return { fileOrIdentifier };
3961}
3962
3963void VST3PluginFormat::createPluginInstance (const PluginDescription& description,
3964 double, int, PluginCreationCallback callback)
3965{
3966 for (const auto& file : getLibraryPaths (description.fileOrIdentifier))
3967 {
3968 if (auto result = createVST3Instance (*this, description, file))
3969 {
3970 callback (std::move (result), {});
3971 return;
3972 }
3973 }
3974
3975 callback (nullptr, TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3"));
3976}
3977
3978bool VST3PluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const
3979{
3980 return false;
3981}
3982
3983bool VST3PluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier)
3984{
3985 auto f = File::createFileWithoutCheckingPath (fileOrIdentifier);
3986
3987 return f.hasFileExtension (".vst3") && f.exists();
3988}
3989
3990String VST3PluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier)
3991{
3992 return fileOrIdentifier; //Impossible to tell because every VST3 is a type of shell...
3993}
3994
3995bool VST3PluginFormat::pluginNeedsRescanning (const PluginDescription& description)
3996{
3997 return File (description.fileOrIdentifier).getLastModificationTime() != description.lastFileModTime;
3998}
3999
4000bool VST3PluginFormat::doesPluginStillExist (const PluginDescription& description)
4001{
4002 return File (description.fileOrIdentifier).exists();
4003}
4004
4005StringArray VST3PluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool)
4006{
4007 StringArray results;
4008
4009 for (int i = 0; i < directoriesToSearch.getNumPaths(); ++i)
4010 recursiveFileSearch (results, directoriesToSearch[i], recursive);
4011
4012 return results;
4013}
4014
4015void VST3PluginFormat::recursiveFileSearch (StringArray& results, const File& directory, const bool recursive)
4016{
4017 for (const auto& iter : RangedDirectoryIterator (directory, false, "*", File::findFilesAndDirectories))
4018 {
4019 auto f = iter.getFile();
4020 bool isPlugin = false;
4021
4022 if (fileMightContainThisPluginType (f.getFullPathName()))
4023 {
4024 isPlugin = true;
4025 results.add (f.getFullPathName());
4026 }
4027
4028 if (recursive && (! isPlugin) && f.isDirectory())
4029 recursiveFileSearch (results, f, true);
4030 }
4031}
4032
4033FileSearchPath VST3PluginFormat::getDefaultLocationsToSearch()
4034{
4035 #if JUCE_WINDOWS
4036 const auto localAppData = File::getSpecialLocation (File::windowsLocalAppData) .getFullPathName();
4037 const auto programFiles = File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName();
4038 return FileSearchPath (localAppData + "\\Programs\\Common\\VST3;" + programFiles + "\\Common Files\\VST3");
4039 #elif JUCE_MAC
4040 return FileSearchPath ("~/Library/Audio/Plug-Ins/VST3;/Library/Audio/Plug-Ins/VST3");
4041 #else
4042 return FileSearchPath ("~/.vst3/;/usr/lib/vst3/;/usr/local/lib/vst3/");
4043 #endif
4044}
4045
4046JUCE_END_NO_SANITIZE
4047
4048} // namespace juce
4049
4050#endif // JUCE_PLUGINHOST_VST3
T any_of(T... args)
T begin(T... args)
T c_str(T... args)
@ kIBSeekSet
set absolute seek position
Definition ibstream.h:34
Class factory that any plug-in defines for creating class instances: IPluginFactory.
Memory based Stream for IBStream implementation (using malloc).
char * getData() const
returns the memory pointer
tresult PLUGIN_API seek(int64 pos, int32 mode, int64 *result) SMTG_OVERRIDE
Sets stream read-write position.
void setSize(TSize size)
set the memory size, a realloc will occur if memory already used
TSize getSize() const
returns the current memory size
Component base interface: Vst::IComponent.
static bool loadPreset(IBStream *stream, const FUID &classID, IComponent *component, IEditController *editController=nullptr, std::vector< FUID > *otherClassIDArray=nullptr)
Shortcut helper to load preset with component/controller state.
static bool savePreset(IBStream *stream, const FUID &classID, IComponent *component, IEditController *editController=nullptr, const char *xmlBuffer=nullptr, int32 xmlSize=-1)
Shortcut helper to create preset from component/controller state.
String getFileNameWithoutExtension() const
Returns the last part of the filename, without its file extension.
@ currentApplicationFile
Returns this application's location.
Definition juce_File.h:942
static File JUCE_CALLTYPE getSpecialLocation(const SpecialLocationType type)
Finds the location of a special type of file or directory, such as a home folder or documents folder.
static MessageManager * getInstance()
Returns the global instance of the MessageManager.
static String fromCFString(CFStringRef cfString)
OSX ONLY - Creates a String from an OSX CFString.
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Returns a Time object that is set to the current system time.
T count(T... args)
T data(T... args)
T emplace_back(T... args)
T emplace(T... args)
T end(T... args)
T find_if(T... args)
T flush(T... args)
T format(T... args)
int32 UCoord
Coordinates.
Definition ftypes.h:117
char TUID[16]
plain UID type
Definition funknown.h:209
T get(T... args)
T infinity(T... args)
T insert(T... args)
#define kVstAudioEffectClass
Class Category Name for Audio Processor Component.
#define kVstComponentControllerClass
Class Category Name for Controller Component.
#define JUCE_BEGIN_NO_SANITIZE(warnings)
Disable sanitizers for a range of functions.
void unregisterFdCallback(int fd)
Unregisters a previously registered file descriptor.
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
#define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
This macro is used to catch unsafe use of functions which expect to only be called on the message thr...
#define JUCE_ASSERT_MESSAGE_THREAD
This macro is used to catch unsafe use of functions which expect to only be called on the message thr...
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_MOVEABLE(className)
This is a shorthand macro for deleting a class's move constructor and move assignment operator.
#define DBG(textToWrite)
Writes a string to the standard error stream.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
#define JUCE_CALLTYPE
This macro defines the C calling convention used as the standard for JUCE calls.
auto & get(ProcessorChain< Processors... > &chain) noexcept
Non-member equivalent of ProcessorChain::get which avoids awkward member template syntax.
#define JUCE_IMPLEMENT_SINGLETON(Classname)
This is a counterpart to the JUCE_DECLARE_SINGLETON macros.
#define JUCE_DECLARE_SINGLETON(Classname, doNotRecreateAfterDeletion)
Macro to generate the appropriate methods and boilerplate for a singleton class.
typedef int
T lock(T... args)
T make_unique(T... args)
typedef double
T memcpy(T... args)
T min(T... args)
std::optional< ModuleInfo > parseJson(std::string_view jsonData, std::ostream *optErrorOutput)
parse a json formatted string to a ModuleInfo struct
T move(T... args)
uint32 ParamID
parameter identifier
Definition vsttypes.h:81
@ kRealtime
realtime processing
@ kOffline
offline processing
uint64 SpeakerArrangement
Bitset of speakers.
Definition vsttypes.h:104
@ kSample32
32-bit precision
@ kSample64
64-bit precision
JUCE Namespace.
void zerostruct(Type &structure) noexcept
Overwrites a structure or object with zeros.
Definition juce_Memory.h:32
constexpr auto enumerate(Range &&range, Index startingValue={})
Given a range and an optional starting offset, returns an IteratorPair that holds EnumerateIterators ...
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
signed int int32
A platform-independent 32-bit signed integer type.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
void createARAFactoryAsync(AudioPluginInstance &instance, std::function< void(ARAFactoryWrapper)> cb)
Calls the provided callback with an ARAFactoryWrapper object obtained from the provided AudioPluginIn...
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
open
T push_back(T... args)
T ref(T... args)
T reserve(T... args)
T reset(T... args)
T size(T... args)
typedef int64_t
T strcmp(T... args)
std::u16string toString(NumberT value)
convert an number to an UTF-16 string
T strlen(T... args)
T strncmp(T... args)
@ kIsProgramChange
parameter is a program change (unitId gives info about associated unit
typedef size_t
T terminate(T... args)
uname