tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ExternalPlugin.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14static juce::String getDeprecatedPluginDescSuffix (const juce::PluginDescription& d)
15{
18}
19
20juce::String createIdentifierString (const juce::PluginDescription& d)
21{
22 return d.pluginFormatName + "-" + d.name + getDeprecatedPluginDescSuffix (d);
23}
24
26 private juce::AsyncUpdater
27{
29 {
30 if (auto pi = plugin.getAudioPluginInstance())
31 pi->addListener (this);
32 else
34 }
35
37 {
39
40 if (auto pi = plugin.getAudioPluginInstance())
41 pi->removeListener (this);
42 else
44 }
45
46 void audioProcessorParameterChanged (juce::AudioProcessor*, int, float) override
47 {
48 if (plugin.edit.isLoading())
49 return;
50
51 paramChanged = true;
53 }
54
55 void audioProcessorChanged (juce::AudioProcessor*, const ChangeDetails&) override
56 {
57 if (plugin.edit.isLoading())
58 return;
59
60 processorChanged = true;
62 }
63
64 ExternalPlugin& plugin;
65
66private:
68
69 static bool hasAnyModifiers (AutomatableEditItem& item)
70 {
71 for (auto param : item.getAutomatableParameters())
72 if (! getModifiersOfType<Modifier> (*param).isEmpty())
73 return true;
74
75 return false;
76 }
77
78 void updateFromPlugin()
79 {
80 TRACKTION_ASSERT_MESSAGE_THREAD
81 bool wasLatencyChange = false;
82
83 if (auto pi = plugin.getAudioPluginInstance())
84 {
85 if (plugin.latencySamples != pi->getLatencySamples())
86 {
87 wasLatencyChange = true;
88
89 if (plugin.isInstancePrepared)
90 {
91 plugin.latencySamples = pi->getLatencySamples();
92 plugin.latencySeconds = plugin.latencySamples / plugin.sampleRate;
93 }
94
95 plugin.edit.restartPlayback(); // Restart playback to rebuild audio graph for the new latency to take effect
96
97 plugin.edit.getTransport().triggerClearDevicesOnStop(); // This will fully re-initialise plugins
98 }
99
100 pi->refreshParameterList();
101
102 // refreshParameterList can delete the AudioProcessorParameter that our ExternalAutomatableParameters
103 // are listening too so re-attach any possibly deleted listeners here
104 for (auto p : plugin.autoParamForParamNumbers)
105 {
106 if (p != nullptr)
107 {
108 p->unregisterAsListener();
109 p->registerAsListener();
110 }
111 }
112 }
113 else
114 {
116 }
117
118 // Annoyingly, because we can't tell the cause of the plugin change, we'll have to simply not
119 // refresh parameter values if any modifiers have been assigned or they'll blow away the original
120 // modifier values
121 if (! wasLatencyChange && ! hasAnyModifiers (plugin))
122 plugin.refreshParameterValues();
123
124 plugin.changed();
125 plugin.edit.pluginChanged (plugin);
126 }
127
128 void handleAsyncUpdate() override
129 {
130 if (paramChanged)
131 {
132 paramChanged = false;
133 plugin.edit.pluginChanged (plugin);
134 }
135 if (processorChanged)
136 {
137 processorChanged = false;
138 updateFromPlugin();
139 }
140 }
141
142 bool paramChanged = false, processorChanged = false;
143};
144
145//==============================================================================
148{
149 AsyncPluginDeleter() = default;
150
151 ~AsyncPluginDeleter() override
152 {
153 stopTimer();
154
155 while (plugins.size() > 0)
156 timerCallback();
157
158 clearSingletonInstance();
159 }
160
162
163 void deletePlugin (juce::AudioPluginInstance* p)
164 {
165 if (p != nullptr)
166 {
167 plugins.add (p);
168 startTimer (100);
169 }
170 }
171
172 bool releaseNextDanglingPlugin()
173 {
174 if (plugins.size() > 0)
175 {
176 timerCallback();
177 return true;
178 }
179
180 return false;
181 }
182
183 void timerCallback() override
184 {
185 if (plugins.isEmpty())
186 {
187 stopTimer();
188 return;
189 }
190
191 if (recursive)
192 return;
193
194 CRASH_TRACER_PLUGIN (plugins.getLast()->getName().toUTF8());
195
196 const juce::ScopedValueSetter<bool> setter (recursive, true, false);
197
198 juce::Component modal;
199 modal.enterModalState (false);
200
201 plugins.removeLast();
202 }
203
204private:
206 bool recursive = false;
207
209};
210
212
213void cleanUpDanglingPlugins()
214{
215 #if JUCE_MODAL_LOOPS_PERMITTED
216 if (auto d = AsyncPluginDeleter::getInstanceWithoutCreating())
217 {
218 for (int count = 400; --count > 0 && d->releaseNextDanglingPlugin();)
219 {
220 juce::Component modal;
221 modal.enterModalState (false);
222
223 juce::MessageManager::getInstance()->runDispatchLoopUntil (10);
224 }
225 }
226 #endif
227
228 AsyncPluginDeleter::deleteInstance();
229}
230
231//==============================================================================
232#if JUCE_PLUGINHOST_VST
233struct ExtraVSTCallbacks : public juce::VSTPluginFormat::ExtraFunctions
234{
235 ExtraVSTCallbacks (Edit& ed) : edit (ed) {}
236
237 juce::int64 getTempoAt (juce::int64 samplePos) override
238 {
239 auto sampleRate = edit.engine.getDeviceManager().getSampleRate();
240 return (juce::int64) (10000.0 * edit.tempoSequence.getTempoAt (TimePosition::fromSamples (samplePos, sampleRate)).getBpm());
241 }
242
243 // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write
244 int getAutomationState() override
245 {
246 auto& am = edit.getAutomationRecordManager();
247
248 bool r = am.isReadingAutomation();
249 bool w = am.isWritingAutomation();
250
251 return r ? (w ? 4 : 2) : (w ? 3 : 1);
252 }
253
254 Edit& edit;
255
256 ExtraVSTCallbacks() = delete;
258};
259#endif // JUCE_PLUGINHOST_VST
260
261//==============================================================================
263{
264public:
266 : plugin (p)
267 {
268 }
269
275 {
276 if (rc != nullptr)
277 {
278 time = rc->editTime.getStart();
279 isPlaying = rc->isPlaying;
280
281 const auto loopTimeRange = plugin.edit.getTransport().getLoopRange();
282 loopStart.set (loopTimeRange.getStart());
283 loopEnd.set (loopTimeRange.getEnd());
284 currentPos.set (time);
285 }
286 else
287 {
288 time = TimePosition();
289 isPlaying = false;
290 }
291 }
292
293 juce::Optional<PositionInfo> getPosition() const override
294 {
295 PositionInfo result;
296
297 result.setFrameRate (getFrameRate());
298
299 auto& transport = plugin.edit.getTransport();
300 auto localTime = time.load();
301
302 result.setIsPlaying (isPlaying);
303 result.setIsRecording (transport.isRecording());
304 result.setEditOriginTime (transport.getTimeWhenStarted().inSeconds());
305 result.setIsLooping (transport.looping);
306
307 if (transport.looping)
308 result.setLoopPoints (juce::AudioPlayHead::LoopPoints ({ loopStart.getPPQTime(), loopEnd.getPPQTime() }));
309
310 result.setTimeInSeconds (localTime.inSeconds());
311 result.setTimeInSamples (toSamples (localTime, plugin.sampleRate));
312
313 const auto timeSig = currentPos.getTimeSignature();
314 result.setBpm (currentPos.getTempo());
315 result.setTimeSignature (juce::AudioPlayHead::TimeSignature ({ timeSig.numerator, timeSig.denominator }));
316
317 const auto ppqPositionOfLastBarStart = currentPos.getPPQTimeOfBarStart();
318 result.setPpqPositionOfLastBarStart (ppqPositionOfLastBarStart);
319 result.setPpqPosition (std::max (ppqPositionOfLastBarStart, currentPos.getPPQTime()));
320
321 return result;
322 }
323
324private:
325 ExternalPlugin& plugin;
326 tempo::Sequence::Position currentPos { createPosition (plugin.edit.tempoSequence) };
327 tempo::Sequence::Position loopStart { createPosition (plugin.edit.tempoSequence) };
328 tempo::Sequence::Position loopEnd { createPosition (plugin.edit.tempoSequence) };
329 std::atomic<TimePosition> time { TimePosition() };
330 std::atomic<bool> isPlaying { false };
331
332 AudioPlayHead::FrameRateType getFrameRate() const
333 {
334 switch (plugin.edit.getTimecodeFormat().getFPS())
335 {
336 case 24: return AudioPlayHead::fps24;
337 case 25: return AudioPlayHead::fps25;
338 case 29: return AudioPlayHead::fps30drop;
339 case 30: return AudioPlayHead::fps30;
340 default: break;
341 }
342
343 return AudioPlayHead::fps30; //Just to cope with it.
344 }
345
347};
348
349//==============================================================================
350namespace
351{
352 void readBusLayout (juce::AudioProcessor::BusesLayout& busesLayout,
353 const juce::ValueTree& state,
354 juce::AudioProcessor& plugin, bool isInput)
355 {
356 jassert (state.hasType (IDs::LAYOUT));
357 auto& targetBuses = (isInput ? busesLayout.inputBuses
358 : busesLayout.outputBuses);
359 int maxNumBuses = 0;
360
361 auto buses = state.getChildWithName (isInput ? IDs::INPUTS : IDs::OUTPUTS);
362
363 if (buses.isValid())
364 {
365 for (auto v : buses)
366 {
367 if (! v.hasType (IDs::BUS))
368 continue;
369
370 const int busIdx = v[IDs::index];
371 maxNumBuses = std::max (maxNumBuses, busIdx + 1);
372
373 // The number of buses on busesLayout may not be in sync with the plugin after adding buses
374 // because adding an input bus could also add an output bus
375 for (int actualIdx = plugin.getBusCount (isInput) - 1; actualIdx < busIdx; ++actualIdx)
376 if (! plugin.addBus (isInput))
377 break;
378
379 for (int actualIdx = targetBuses.size() - 1; actualIdx < busIdx; ++actualIdx)
380 targetBuses.add (plugin.getChannelLayoutOfBus (isInput, busIdx));
381
382 const auto layout = v[IDs::layout].toString();
383
384 if (layout.isNotEmpty())
385 targetBuses.getReference (busIdx) = juce::AudioChannelSet::fromAbbreviatedString (layout);
386 }
387 }
388
389 // If the plugin has more buses than specified in the xml, then try to remove them!
390 while (maxNumBuses < targetBuses.size())
391 {
392 if (! plugin.removeBus (isInput))
393 break;
394
395 targetBuses.removeLast();
396 }
397 }
398
399 juce::AudioProcessor::BusesLayout readBusesLayout (const juce::var& layout, juce::AudioProcessor& plugin)
400 {
402
403 if (auto* mb = layout.getBinaryData())
404 {
405 auto v = juce::ValueTree::readFromData (mb->getData(), mb->getSize());
406 readBusLayout (busesLayout, v, plugin, true);
407 readBusLayout (busesLayout, v, plugin, false);
408 }
409
410 return busesLayout;
411 }
412
413 juce::ValueTree createBusLayout (const juce::AudioProcessor::BusesLayout& layout, bool isInput)
414 {
415 auto& buses = (isInput ? layout.inputBuses : layout.outputBuses);
416
417 juce::ValueTree v (isInput ? IDs::INPUTS : IDs::OUTPUTS);
418
419 for (int busIdx = 0; busIdx < buses.size(); ++busIdx)
420 {
421 auto& set = buses.getReference (busIdx);
422 juce::String layoutName = set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString();
423
424 auto bus = createValueTree (IDs::BUS,
425 IDs::index, busIdx,
426 IDs::layout, layoutName);
427
428 v.addChild (bus, -1, nullptr);
429 }
430
431 return v.getNumChildren() > 0 ? v : juce::ValueTree();
432 }
433
434 juce::ValueTree createBusesLayout (const juce::AudioProcessor::BusesLayout& layout)
435 {
436 auto inputs = createBusLayout (layout, true);
437 auto outputs = createBusLayout (layout, false);
438
439 if (inputs.getNumChildren() == 0 && outputs.getNumChildren() == 0)
440 return {};
441
442 juce::ValueTree v (IDs::LAYOUT);
443 v.addChild (inputs, -1, nullptr);
444 v.addChild (outputs, -1, nullptr);
445
446 return v;
447 }
448
449 juce::MemoryBlock createBusesLayoutProperty (const juce::AudioProcessor::BusesLayout& layout)
450 {
452
453 auto v = createBusesLayout (layout);
454
455 if (v.isValid())
456 {
457 juce::MemoryOutputStream os (mb, false);
458 v.writeToStream (os);
459 }
460
461 return mb;
462 }
463
464 void restoreChannelLayout (ExternalPlugin& plugin)
465 {
466 auto setDefaultChannelConfig = [] (juce::AudioProcessor& ap, bool input)
467 {
468 // If the main bus is mono, set to stereo if supported
469 const int n = ap.getBusCount (input);
470
471 if (n >= 1)
472 {
473 auto bestSet = juce::AudioChannelSet::disabled();
474
475 if (auto bus = ap.getBus (input, 0))
476 {
477 if (bus->getCurrentLayout().size() < 2)
478 {
479 for (int i = 0; i < juce::AudioChannelSet::maxChannelsOfNamedLayout; i++)
480 {
481 auto set = bus->supportedLayoutWithChannels (i);
482
483 if (! set.isDisabled() && set.size() == 2)
484 {
485 bestSet = set;
486 break;
487 }
488 }
489
490 if (bestSet != juce::AudioChannelSet::disabled())
491 bus->setCurrentLayout (bestSet);
492 }
493 }
494 }
495 };
496
497 if (auto ap = plugin.getAudioPluginInstance())
498 {
499 if (plugin.state.hasProperty (IDs::layout))
500 {
501 plugin.setBusesLayout (readBusesLayout (plugin.state.getProperty (IDs::layout), *ap));
502 }
503 else
504 {
505 // It appears that most AUs don't like to have their channel layout changed so just leave the default here as was the old behaviour
506 if (plugin.isAU() || plugin.isVST3())
507 return;
508
509 setDefaultChannelConfig (*ap, true);
510 setDefaultChannelConfig (*ap, false);
511 }
512 }
513 }
514}
515
516//==============================================================================
517juce::ValueTree ExternalPlugin::create (Engine& e, const juce::PluginDescription& desc)
518{
519 auto v = createValueTree (IDs::PLUGIN,
520 IDs::type, xmlTypeName,
521 IDs::uniqueId, juce::String::toHexString (desc.uniqueId),
523 IDs::filename, desc.fileOrIdentifier,
524 IDs::name, desc.name,
525 IDs::manufacturer, desc.manufacturerName);
526
527 if (e.getPluginManager().areGUIsLockedByDefault())
528 v.setProperty (IDs::windowLocked, true, nullptr);
529
530 return v;
531}
532
533juce::String ExternalPlugin::getLoadError()
534{
535 if (pluginInstance != nullptr)
536 return {};
537
538 if (loadError.isEmpty())
539 return TRANS("ERROR! - This plugin couldn't be loaded!");
540
541 return loadError;
542}
543
544const char* ExternalPlugin::xmlTypeName = "vst";
545
547{
548 if (! fullyInitialised)
549 {
550 CRASH_TRACER_PLUGIN (getDebugName());
551 fullyInitialised = true;
552
553 doFullInitialisation();
554 restorePluginStateFromValueTree (state);
555 buildParameterList();
556 restoreChannelLayout (*this);
557 }
558}
559
560void ExternalPlugin::forceFullReinitialise()
561{
564 edit.getTransport().stop (false, true);
565 fullyInitialised = false;
567 changed();
568
569 if (isInstancePrepared && pluginInstance->getSampleRate() > 0 && pluginInstance->getBlockSize() > 0)
570 {
571 pluginInstance->prepareToPlay (pluginInstance->getSampleRate(), pluginInstance->getBlockSize());
572 }
573
574 edit.restartPlayback();
575 SelectionManager::refreshAllPropertyPanelsShowing (*this);
576
577 if (auto t = getOwnerTrack())
578 t->refreshCurrentAutoParam();
579}
580
581void ExternalPlugin::updateDebugName()
582{
583 debugName = desc.name + " (" + desc.pluginFormatName + ")";
584}
585
586void ExternalPlugin::buildParameterList()
587{
588 CRASH_TRACER_PLUGIN (getDebugName());
589 autoParamForParamNumbers.clear();
590 clearParameterList();
591 std::unordered_map<std::string, int> alreadyUsedParamNames;
592
593 addAutomatableParameter (dryGain);
594 addAutomatableParameter (wetGain);
595
596 if (pluginInstance != nullptr)
597 {
598 auto& parameters = pluginInstance->getParameters();
599 jassert (parameters.size() < 80000);
600 const auto maxAutoParams = std::min (80000, parameters.size());
601
602 for (int i = 0; i < maxAutoParams; ++i)
603 {
604 auto parameter = parameters.getUnchecked (i);
605
606 if (parameter->isAutomatable() && ! isParameterBlacklisted (*this, *pluginInstance, *parameter))
607 {
608 auto nm = parameter->getName (1024);
609
610 bool emptyName = nm.isEmpty();
611 if (emptyName)
612 nm = "Unnamed";
613
614 int count = 1;
615
616 if (alreadyUsedParamNames.find (nm.toStdString()) != alreadyUsedParamNames.end())
617 {
618 count = alreadyUsedParamNames[nm.toStdString()] + 1;
619 alreadyUsedParamNames[nm.toStdString()] = count;
620 nm << " (" << count << ")";
621 }
622 else
623 {
624 alreadyUsedParamNames[nm.toStdString()] = count;
625 }
626
627 // Just use the index for the ID for now until this has been added to JUCE
628 auto parameterID = juce::String (i);
629
630 if (auto paramWithID = dynamic_cast<juce::AudioProcessorParameterWithID*> (parameter))
631 parameterID = paramWithID->paramID;
632
633 auto p = new ExternalAutomatableParameter (parameterID, nm, *this, i, { 0.0f, 1.0f });
634 addAutomatableParameter (*p);
635 autoParamForParamNumbers.add (p);
636 p->valueChangedByPlugin();
637
638 if (count >= 2 && ! emptyName)
639 p->setDisplayName (nm);
640
641 }
642 else
643 {
644 autoParamForParamNumbers.add (nullptr);
645 }
646 }
647 }
648
650 buildParameterTree();
651}
652
653void ExternalPlugin::refreshParameterValues()
654{
655 for (auto p : autoParamForParamNumbers)
656 if (p != nullptr)
657 p->valueChangedByPlugin();
658}
659
660std::unique_ptr<juce::PluginDescription> ExternalPlugin::findDescForUID (int uid, int deprecatedUid) const
661{
662 if (uid != 0)
663 for (auto d : engine.getPluginManager().knownPluginList.getTypes())
664 if (d.uniqueId == uid)
665 return std::make_unique<juce::PluginDescription> (d);
666
667 if (deprecatedUid != 0)
668 for (auto d : engine.getPluginManager().knownPluginList.getTypes())
669 if (d.deprecatedUid == deprecatedUid)
670 return std::make_unique<juce::PluginDescription> (d);
671
672 return {};
673}
674
675std::unique_ptr<juce::PluginDescription> ExternalPlugin::findDescForFileOrID (const juce::String& fileOrID) const
676{
677 if (fileOrID.isNotEmpty())
678 {
679 auto& pm = engine.getPluginManager();
680
681 for (auto d : pm.knownPluginList.getTypes())
682 if (d.fileOrIdentifier == fileOrID)
683 return std::make_unique<juce::PluginDescription> (d);
684
685 return engine.getEngineBehaviour().findDescriptionForFileOrID (fileOrID);
686 }
687
688 return {};
689}
690
691static std::unique_ptr<juce::PluginDescription> findDescForName (Engine& engine, const juce::String& name,
692 const juce::String& format)
693{
694 if (name.isEmpty())
695 return {};
696
697 auto& pm = engine.getPluginManager();
698
699 auto findName = [&pm, format] (const juce::String& nameToFind) -> std::unique_ptr<juce::PluginDescription>
700 {
701 for (auto d : pm.knownPluginList.getTypes())
702 if (d.name == nameToFind && d.pluginFormatName == format)
703 return std::make_unique<juce::PluginDescription> (d);
704
705 return {};
706 };
707
708 if (auto p = findName (name))
709 return p;
710
711 #if JUCE_64BIT
712 if (auto p = findName (name + " (64 bit)"))
713 return p;
714
715 if (auto p = findName (name + " (64-bit)"))
716 return p;
717 #endif
718
719 return {};
720}
721
722std::unique_ptr<juce::PluginDescription> ExternalPlugin::findMatchingPlugin() const
723{
725 auto& pm = engine.getPluginManager();
726
727 if (auto p = pm.knownPluginList.getTypeForIdentifierString (createIdentifierString (desc)))
728 return p;
729
730 if (desc.pluginFormatName.isEmpty())
731 {
732 if (auto p = pm.knownPluginList.getTypeForIdentifierString ("VST" + createIdentifierString (desc)))
733 return p;
734
735 if (auto p = pm.knownPluginList.getTypeForIdentifierString ("AudioUnit" + createIdentifierString (desc)))
736 return p;
737 }
738
739 if (auto p = findDescForFileOrID (desc.fileOrIdentifier))
740 return p;
741
742 if (auto p = findDescForUID (desc.uniqueId, desc.deprecatedUid))
743 return p;
744
745 auto getPreferredFormat = [] (juce::PluginDescription d)
746 {
747 auto file = d.fileOrIdentifier.toLowerCase();
748 if (file.endsWith (".vst3")) return "VST3";
749 if (file.endsWith (".vst") || file.endsWith (".dll")) return "VST";
750 if (file.startsWith ("audiounit:")) return "AudioUnit";
751 return "";
752 };
753
754 if (auto p = findDescForName (engine, desc.name, getPreferredFormat (desc)))
755 return p;
756
757 for (auto d : pm.knownPluginList.getTypes())
758 if (d.name == desc.name)
759 return std::make_unique<juce::PluginDescription> (d);
760
761 for (auto d : pm.knownPluginList.getTypes())
762 if (juce::File::createFileWithoutCheckingPath (d.fileOrIdentifier).getFileNameWithoutExtension() == desc.name)
763 return std::make_unique<juce::PluginDescription> (d);
764
765 if (desc.uniqueId == 0x4d44416a || desc.deprecatedUid == 0x4d44416a) // old JX-10: hack to update to JX-16
766 if (auto p = findDescForUID (0x4D44414A, 0x4D44414A))
767 return p;
768
769 return {};
770}
771
772//==============================================================================
773void ExternalPlugin::processingChanged()
774{
775 Plugin::processingChanged();
776
777 if (processing)
778 {
779 if (pluginInstance == nullptr)
780 forceFullReinitialise();
781 }
782 else
783 {
784 clearParameterList();
785 autoParamForParamNumbers.clear();
786 getParameterTree().clear();
787
788 deletePluginInstance();
789 }
790}
791
792void ExternalPlugin::doFullInitialisation()
793{
794 if (auto foundDesc = findMatchingPlugin())
795 {
796 desc = *foundDesc;
797 identiferString = createIdentifierString (desc);
798 updateDebugName();
799
800 if (processing && pluginInstance == nullptr && engine.getEngineBehaviour().shouldLoadPlugin (*this))
801 {
802 if (isDisabled())
803 return;
804
805 CRASH_TRACER_PLUGIN (getDebugName());
806 loadError = {};
807
808 callBlocking ([this, &foundDesc]
809 {
810 CRASH_TRACER_PLUGIN (getDebugName());
811 loadError = createPluginInstance (*foundDesc);
812 });
813
814 if (pluginInstance != nullptr)
815 {
816 #if JUCE_PLUGINHOST_VST
817 if (auto xml = juce::VSTPluginFormat::getVSTXML (pluginInstance.get()))
818 vstXML.reset (VSTXML::createFor (*xml));
819
820 juce::VSTPluginFormat::setExtraFunctions (pluginInstance.get(), new ExtraVSTCallbacks (edit));
821 #endif
822
823 pluginInstance->setPlayHead (playhead.get());
824 supportsMPE = pluginInstance->supportsMPE();
825
826 engine.getEngineBehaviour().doAdditionalInitialisation (*this);
827 }
828 else
829 {
830 TRACKTION_LOG_ERROR (loadError);
831 }
832 }
833 }
834}
835
837{
838 juce::MessageManager::callAsync ([this, pluginRef = makeSafeRef (*this)]
839 {
840 if (pluginRef == nullptr)
841 return;
842
843 if (auto t = getOwnerTrack(); t != nullptr && pluginInstance != nullptr)
844 {
845 auto n = t->getName();
846 auto c = t->getColour();
847
848 pluginInstance->updateTrackProperties ({n, c});
849 }
850 });
851}
852
853//==============================================================================
855{
856 for (auto param : autoParamForParamNumbers)
857 if (param != nullptr)
858 param->unregisterAsListener();
859
861}
862
863//==============================================================================
864void ExternalPlugin::flushPluginStateToValueTree()
865{
866 Plugin::flushPluginStateToValueTree();
867
868 auto* um = getUndoManager();
869
870 if (desc.fileOrIdentifier.isNotEmpty())
871 {
872 state.setProperty ("manufacturer", desc.manufacturerName, um);
873 state.setProperty (IDs::name, desc.name, um);
874 itemID.writeID (state, um);
875 state.setProperty (IDs::uid, getPluginUID(), um);
876 state.setProperty (IDs::filename, desc.fileOrIdentifier, um);
877 }
878
879 if (pluginInstance != nullptr)
880 {
881 if (pluginInstance->getNumPrograms() > 0)
882 state.setProperty (IDs::programNum, pluginInstance->getCurrentProgram(), um);
883
884 TRACKTION_ASSERT_MESSAGE_THREAD
885 juce::MemoryBlock chunk;
886
887 pluginInstance->suspendProcessing (true);
888 pluginInstance->getStateInformation (chunk);
890 pluginInstance->suspendProcessing (false);
891
892 engine.getEngineBehaviour().saveCustomPluginProperties (state, *pluginInstance, um);
893
894 if (chunk.getSize() > 0)
895 state.setProperty (IDs::state, chunk.toBase64Encoding(), um);
896 else
897 state.removeProperty (IDs::state, um);
898
899 flushBusesLayoutToValueTree();
900 }
901}
902
903void ExternalPlugin::flushBusesLayoutToValueTree()
904{
905 const juce::ScopedValueSetter<bool> svs (isFlushingLayoutToState, true);
906
907 // Save buses layout
908 if (auto ap = getAudioPluginInstance())
909 {
910 auto* um = getUndoManager();
911
912 auto mb = createBusesLayoutProperty (ap->getBusesLayout());
913
914 if (mb.getSize() > 0)
915 state.setProperty (IDs::layout, mb, um);
916 else
917 state.removeProperty (IDs::layout, um);
918 }
919}
920
921void ExternalPlugin::restorePluginStateFromValueTree (const juce::ValueTree& v)
922{
923 juce::String s;
924
925 if (v.hasProperty (IDs::state))
926 {
927 s = v.getProperty (IDs::state).toString();
928 }
929 else
930 {
931 auto vstDataTree = v.getChildWithName (IDs::VSTDATA);
932
933 if (vstDataTree.isValid())
934 {
935 s = vstDataTree.getProperty (IDs::DATA).toString();
936
937 if (s.isEmpty())
938 s = vstDataTree.getProperty (IDs::__TEXT).toString();
939 }
940 }
941
942 if (pluginInstance != nullptr && s.isNotEmpty())
943 {
944 CRASH_TRACER_PLUGIN (getDebugName());
945
946 if (getNumPrograms() > 1)
947 setCurrentProgram (v.getProperty (IDs::programNum), false);
948
949 juce::MemoryBlock chunk;
950 chunk.fromBase64Encoding (s);
951
952 if (chunk.getSize() > 0)
953 callBlocking ([this, &chunk]() { pluginInstance->setStateInformation (chunk.getData(), (int) chunk.getSize()); });
954 }
955}
956
957void ExternalPlugin::getPluginStateFromTree (juce::MemoryBlock& mb)
958{
959 auto s = state.getProperty (IDs::state).toString();
960 mb.reset();
961
962 if (s.isNotEmpty())
963 mb.fromBase64Encoding (s);
964}
965
966void ExternalPlugin::updateFromMirroredPluginIfNeeded (Plugin& changedPlugin)
967{
968 TRACKTION_ASSERT_MESSAGE_THREAD
969
970 if (changedPlugin.itemID == masterPluginID)
971 {
972 if (auto other = dynamic_cast<ExternalPlugin*> (&changedPlugin))
973 {
974 if (other->pluginInstance != nullptr && other->desc.isDuplicateOf (desc))
975 {
976 juce::MemoryBlock chunk;
977 other->pluginInstance->getStateInformation (chunk);
978
979 if (chunk.getSize() > 0)
980 pluginInstance->setStateInformation (chunk.getData(), (int) chunk.getSize());
981 }
982 }
983 }
984}
985
986//==============================================================================
988{
989 static constexpr int lowChannel = 2, highChannel = 16;
990
991 void reset() noexcept
992 {
993 for (auto& s : sourceAndChannel)
994 s = MidiMessageArray::notMPE;
995 }
996
997 void clearChannel (int channel) noexcept
998 {
999 sourceAndChannel[channel] = MidiMessageArray::notMPE;
1000 }
1001
1002 void remapMidiChannelIfNeeded (MidiMessageArray::MidiMessageWithSource& m) noexcept
1003 {
1004 auto channel = m.getChannel();
1005
1006 if (channel < lowChannel || channel > highChannel)
1007 return;
1008
1009 auto sourceAndChannelID = (((uint32_t) m.mpeSourceID << 5) | (uint32_t) (channel - 1));
1010
1011 if ((*m.getRawData() & 0xf0) != 0xf0)
1012 {
1013 ++counter;
1014
1015 if (applyRemapIfExisting (channel, sourceAndChannelID, m))
1016 return;
1017
1018 for (int chan = lowChannel; chan <= highChannel; ++chan)
1019 if (applyRemapIfExisting (chan, sourceAndChannelID, m))
1020 return;
1021
1022 if (sourceAndChannel[channel] == MidiMessageArray::notMPE)
1023 {
1024 lastUsed[channel] = counter;
1025 sourceAndChannel[channel] = sourceAndChannelID;
1026 return;
1027 }
1028
1029 auto chan = getBestChanToReuse();
1030 sourceAndChannel[chan] = sourceAndChannelID;
1031 lastUsed[chan] = counter;
1032 m.setChannel (chan);
1033 }
1034 }
1035
1036 uint32_t sourceAndChannel[17] = {};
1037 uint32_t lastUsed[17] = {};
1038 uint32_t counter = 0;
1039
1040 int getBestChanToReuse() const noexcept
1041 {
1042 for (int chan = lowChannel; chan <= highChannel; ++chan)
1043 if (sourceAndChannel[chan] == MidiMessageArray::notMPE)
1044 return chan;
1045
1046 auto bestChan = lowChannel;
1047 auto bestLastUse = counter;
1048
1049 for (int chan = lowChannel; chan <= highChannel; ++chan)
1050 {
1051 if (lastUsed[chan] < bestLastUse)
1052 {
1053 bestLastUse = lastUsed[chan];
1054 bestChan = chan;
1055 }
1056 }
1057
1058 return bestChan;
1059 }
1060
1061 bool applyRemapIfExisting (int channel, uint32_t sourceAndChannelID, MidiMessageArray::MidiMessageWithSource& m) noexcept
1062 {
1063 if (sourceAndChannel[channel] == sourceAndChannelID)
1064 {
1065 if (m.isNoteOff() || m.isAllNotesOff() || m.isResetAllControllers())
1066 sourceAndChannel[channel] = MidiMessageArray::notMPE;
1067 else
1068 lastUsed[channel] = counter;
1069
1070 m.setChannel (channel);
1071 return true;
1072 }
1073
1074 return false;
1075 }
1076};
1077
1078//==============================================================================
1079ExternalPlugin::ExternalPlugin (PluginCreationInfo info) : Plugin (info)
1080{
1082
1083 auto um = getUndoManager();
1084
1085 dryGain = new PluginWetDryAutomatableParam ("dry level", TRANS("Dry Level"), *this);
1086 wetGain = new PluginWetDryAutomatableParam ("wet level", TRANS("Wet Level"), *this);
1087
1088 dryValue.referTo (state, IDs::dry, um);
1089 wetValue.referTo (state, IDs::wet, um, 1.0f);
1090
1091 dryGain->attachToCurrentValue (dryValue);
1092 wetGain->attachToCurrentValue (wetValue);
1093
1094 desc.uniqueId = (int) state[IDs::uniqueId].toString().getHexValue64();
1095 desc.deprecatedUid = (int) state[IDs::uid].toString().getHexValue64();
1096 desc.fileOrIdentifier = state[IDs::filename];
1097 setEnabled (state.getProperty (IDs::enabled, true));
1098 desc.name = state[IDs::name];
1099 desc.manufacturerName = state[IDs::manufacturer];
1100 identiferString = createIdentifierString (desc);
1101
1103}
1104
1105ExternalPlugin::~ExternalPlugin()
1106{
1107 TRACKTION_ASSERT_MESSAGE_THREAD
1108 CRASH_TRACER_PLUGIN (getDebugName());
1109 notifyListenersOfDeletion();
1110 windowState->hideWindowForShutdown();
1111 deinitialise();
1112
1113 dryGain->detachFromCurrentValue();
1114 wetGain->detachFromCurrentValue();
1115
1116 const juce::ScopedLock sl (lock);
1117 deletePluginInstance();
1118}
1119
1120//==============================================================================
1121void ExternalPlugin::initialise (const PluginInitialisationInfo& info)
1122{
1123 CRASH_TRACER_PLUGIN (getDebugName());
1124
1125 const juce::ScopedLock sl (lock);
1126
1127 if (mpeRemapper == nullptr)
1129
1130 mpeRemapper->reset();
1131
1132 if (pluginInstance != nullptr)
1133 {
1134 // This used to releaseResources() before calling prepareToPlay().
1135 // However, with VST3, releaseResources() shuts down the MIDI
1136 // input buses and then there is no way to get them back, which
1137 // breaks all synths.
1138 if (! isInstancePrepared)
1139 {
1140 pluginInstance->prepareToPlay (info.sampleRate, info.blockSizeSamples);
1141 isInstancePrepared = true;
1142 }
1143 else if (info.sampleRate != lastSampleRate || info.blockSizeSamples != lastBlockSizeSamples)
1144 {
1145 pluginInstance->prepareToPlay (info.sampleRate, info.blockSizeSamples);
1146 }
1147
1148 lastSampleRate = info.sampleRate;
1149 lastBlockSizeSamples = info.blockSizeSamples;
1150
1151 latencySamples = pluginInstance->getLatencySamples();
1152 latencySeconds = latencySamples / info.sampleRate;
1153
1154 if (! desc.hasSharedContainer)
1155 {
1156 desc = pluginInstance->getPluginDescription();
1157 updateDebugName();
1158 }
1159
1160 pluginInstance->setPlayHead (nullptr);
1161 playhead = std::make_unique<PluginPlayHead> (*this);
1162 pluginInstance->setPlayHead (playhead.get());
1163 }
1164 else
1165 {
1166 playhead.reset();
1167 latencySamples = 0;
1168 latencySeconds = 0.0;
1169 isInstancePrepared = false;
1170 }
1171}
1172
1174{
1175 if (pluginInstance != nullptr)
1176 {
1177 CRASH_TRACER_PLUGIN (getDebugName());
1178
1179 const juce::ScopedLock sl (lock);
1180 pluginInstance->setPlayHead (nullptr); // must be done first!
1181
1182 if (playhead != nullptr)
1183 playhead->setCurrentContext (nullptr);
1184 }
1185}
1186
1188{
1189 if (pluginInstance != nullptr)
1190 {
1191 CRASH_TRACER_PLUGIN (getDebugName());
1192 const juce::ScopedLock sl (lock);
1193 pluginInstance->reset();
1194 }
1195}
1196
1197void ExternalPlugin::setEnabled (bool shouldEnable)
1198{
1199 Plugin::setEnabled (shouldEnable);
1200
1201 if (shouldEnable != isEnabled())
1202 {
1203 if (pluginInstance != nullptr)
1204 pluginInstance->reset();
1205
1206 propertiesChanged();
1207 }
1208}
1209
1210//==============================================================================
1211void ExternalPlugin::prepareIncomingMidiMessages (MidiMessageArray& incoming, int numSamples, bool isPlaying)
1212{
1213 if (incoming.isAllNotesOff)
1214 {
1215 uint32_t eventsSentOnChannel = 0;
1216
1217 activeNotes.iterate ([&eventsSentOnChannel, this, isPlaying] (int chan, int noteNumber)
1218 {
1219 midiBuffer.addEvent (juce::MidiMessage::noteOff (chan, noteNumber), 0);
1220
1221 if ((eventsSentOnChannel & (1u << chan)) == 0)
1222 {
1223 eventsSentOnChannel |= (1u << chan);
1224
1225 if (! supportsMPE)
1226 {
1227 midiBuffer.addEvent (juce::MidiMessage::controllerEvent (chan, 66 /* sustain pedal off */, 0), 0);
1228 midiBuffer.addEvent (juce::MidiMessage::controllerEvent (chan, 64 /* hold pedal off */, 0), 0);
1229 }
1230 else
1231 {
1232 // MPE standard (and JUCE implementation) now requires this instead of allNotesOff.
1235 }
1236
1237 // NB: Some buggy plugins seem to fail to respond to note-ons if they are preceded
1238 // by an all-notes-off, so avoid this if just dragging the cursor around while playing.
1239 if (! isPlaying)
1240 midiBuffer.addEvent (juce::MidiMessage::allNotesOff (chan), 0);
1241 }
1242 });
1243
1244 activeNotes.reset();
1245 mpeRemapper->reset();
1246
1247 // Reset MPE zone to match MIDI generated by clip
1248 if (supportsMPE)
1249 {
1250 juce::MPEZoneLayout layout;
1251 layout.setLowerZone (15);
1252
1253 auto layoutBuffer = juce::MPEMessages::setZoneLayout (layout);
1254 for (auto itr : layoutBuffer)
1255 {
1256 auto result = itr.getMessage();
1257 int samplePosition = itr.samplePosition;
1258
1259 midiBuffer.addEvent (result, samplePosition);
1260 }
1261 }
1262 }
1263
1264 for (auto& m : incoming)
1265 {
1266 if (supportsMPE)
1267 mpeRemapper->remapMidiChannelIfNeeded (m);
1268
1269 if (m.isNoteOn())
1270 {
1271 if (activeNotes.isNoteActive (m.getChannel(), m.getNoteNumber()))
1272 continue;
1273
1274 activeNotes.startNote (m.getChannel(), m.getNoteNumber());
1275 }
1276 else if (m.isNoteOff())
1277 {
1278 activeNotes.clearNote (m.getChannel(), m.getNoteNumber());
1279 }
1280
1281 auto sample = juce::jlimit (0, numSamples - 1, juce::roundToInt (m.getTimeStamp() * sampleRate));
1282 midiBuffer.addEvent (m, sample);
1283 }
1284
1285 #if 0
1286 if (! incoming.isEmpty())
1287 {
1288 const uint8_t* midiData;
1289 int numBytes, midiEventPos;
1290
1291 DBG ("----------");
1292
1293 for (juce::MidiBuffer::Iterator iter (midiBuffer); iter.getNextEvent (midiData, numBytes, midiEventPos);)
1294 DBG (juce::String::toHexString (midiData, numBytes) << " " << midiEventPos);
1295 }
1296 #endif
1297
1298 incoming.clear();
1299}
1300
1302{
1303 const bool processedBypass = fc.allowBypassedProcessing && ! isEnabled();
1304
1305 if (pluginInstance != nullptr && (processedBypass || isEnabled()))
1306 {
1307 CRASH_TRACER_PLUGIN (getDebugName());
1308 const juce::ScopedLock sl (lock);
1309 jassert (isInstancePrepared);
1310
1311 if (playhead != nullptr)
1312 playhead->setCurrentContext (&fc);
1313
1314 midiBuffer.clear();
1315
1316 if (fc.bufferForMidiMessages != nullptr)
1317 prepareIncomingMidiMessages (*fc.bufferForMidiMessages, fc.bufferNumSamples, fc.isPlaying);
1318
1319 if (fc.destBuffer != nullptr)
1320 {
1321 auto destNumChans = fc.destBuffer->getNumChannels();
1322 jassert (destNumChans > 0);
1323
1324 auto numInputChannels = pluginInstance->getTotalNumInputChannels();
1325 auto numOutputChannels = pluginInstance->getTotalNumOutputChannels();
1326 auto numChansToProcess = std::max (std::max (1, numInputChannels), numOutputChannels);
1327
1328 if (destNumChans == numChansToProcess)
1329 {
1330 processPluginBlock (fc, processedBypass);
1331 }
1332 else
1333 {
1334 AudioScratchBuffer asb (numChansToProcess, fc.bufferNumSamples);
1335 auto& buffer = asb.buffer;
1336
1337 // Copy or existing channel or clear data
1338 for (int i = 0; i < numChansToProcess; ++i)
1339 {
1340 if (i < destNumChans)
1341 buffer.copyFrom (i, 0, *fc.destBuffer, i, fc.bufferStartSample, fc.bufferNumSamples);
1342 else
1343 buffer.clear (i, 0, fc.bufferNumSamples);
1344 }
1345
1346 if (destNumChans == 1 && numInputChannels == 2)
1347 {
1348 // If we're getting a mono in and need stereo, dupe the channel..
1349 buffer.copyFrom (1, 0, buffer, 0, 0, fc.bufferNumSamples);
1350 }
1351 else if (destNumChans == 2 && numInputChannels == 1)
1352 {
1353 // If we're getting a stereo in and need mono, average the input..
1354 buffer.addFrom (0, 0, *fc.destBuffer, 1, fc.bufferStartSample, fc.bufferNumSamples);
1355 buffer.applyGain (0, 0, fc.bufferNumSamples, 0.5f);
1356 }
1357
1358 PluginRenderContext fc2 (fc);
1359 fc2.destBuffer = &asb.buffer;
1360 fc2.bufferStartSample = 0;
1361
1362 processPluginBlock (fc2, processedBypass);
1363
1364 // Copy sample data back clearing unprocessed channels
1365 for (int i = 0; i < destNumChans; ++i)
1366 {
1367 if (i < numChansToProcess)
1368 fc.destBuffer->copyFrom (i, fc.bufferStartSample, buffer, i, 0, fc.bufferNumSamples);
1369 else if (i < 2 && numChansToProcess > 0) // convert mono output to stereo for next plugin
1370 fc.destBuffer->copyFrom (i, fc.bufferStartSample, buffer, 0, 0, fc.bufferNumSamples);
1371 else
1373 }
1374 }
1375 }
1376 else
1377 {
1378 AudioScratchBuffer asb (std::max (pluginInstance->getTotalNumInputChannels(),
1379 pluginInstance->getTotalNumOutputChannels()), fc.bufferNumSamples);
1380
1381 if (processedBypass)
1382 pluginInstance->processBlockBypassed (asb.buffer, midiBuffer);
1383 else
1384 pluginInstance->processBlock (asb.buffer, midiBuffer);
1385 }
1386
1387 if (fc.bufferForMidiMessages != nullptr)
1388 {
1389 fc.bufferForMidiMessages->clear();
1390
1391 if (! midiBuffer.isEmpty())
1392 {
1393 for (auto itr : midiBuffer)
1394 {
1395 const auto& msg = itr.getMessage();
1396 int midiEventPos = itr.samplePosition;
1397
1398 auto midiData = msg.getRawData();
1399 auto numBytes = msg.getRawDataSize();
1400
1401 fc.bufferForMidiMessages->addMidiMessage (juce::MidiMessage (midiData, numBytes, fc.midiBufferOffset + midiEventPos / sampleRate),
1402 midiSourceID);
1403 }
1404 }
1405 }
1406 }
1407}
1408
1409void ExternalPlugin::processPluginBlock (const PluginRenderContext& fc, bool processedBypass)
1410{
1413
1414 auto dry = dryGain->getCurrentValue();
1415 auto wet = wetGain->getCurrentValue();
1416
1417 if (dry <= 0.00004f)
1418 {
1419 if (processedBypass)
1420 pluginInstance->processBlockBypassed (asb, midiBuffer);
1421 else
1422 pluginInstance->processBlock (asb, midiBuffer);
1423
1424 zeroDenormalisedValuesIfNeeded (asb);
1425
1426 if (wet < 0.999f)
1427 asb.applyGain (0, fc.bufferNumSamples, wet);
1428 }
1429 else
1430 {
1431 auto numChans = asb.getNumChannels();
1432 AudioScratchBuffer dryAudio (numChans, fc.bufferNumSamples);
1433
1434 for (int i = 0; i < numChans; ++i)
1435 dryAudio.buffer.copyFrom (i, 0, asb, i, 0, fc.bufferNumSamples);
1436
1437 if (processedBypass)
1438 pluginInstance->processBlockBypassed (asb, midiBuffer);
1439 else
1440 pluginInstance->processBlock (asb, midiBuffer);
1441
1442 zeroDenormalisedValuesIfNeeded (asb);
1443
1444 if (wet < 0.999f)
1445 asb.applyGain (0, fc.bufferNumSamples, wet);
1446
1447 for (int i = 0; i < numChans; ++i)
1448 asb.addFrom (i, 0, dryAudio.buffer.getReadPointer (i), fc.bufferNumSamples, dry);
1449 }
1450}
1451
1452//==============================================================================
1454{
1455 return std::max (1, getNumOutputs());
1456}
1457
1458void ExternalPlugin::getChannelNames (juce::StringArray* ins,
1459 juce::StringArray* outs)
1460{
1461 if (pluginInstance != nullptr)
1462 {
1463 CRASH_TRACER_PLUGIN (getDebugName());
1464
1465 auto getChannelName = [](juce::AudioProcessor::Bus* bus, int index)
1466 {
1467 return bus != nullptr ? juce::AudioChannelSet::getChannelTypeName (bus->getCurrentLayout().getTypeOfChannel (index))
1468 : juce::String();
1469 };
1470
1471 if (ins != nullptr)
1472 {
1473 const int num = pluginInstance->getTotalNumInputChannels();
1474
1475 for (int i = 0; i < num; ++i)
1476 {
1477 auto name = getChannelName (pluginInstance->getBus (true, 0), i);
1478 ins->add (name.isNotEmpty() ? name : TRANS("Unnamed"));
1479 }
1480 }
1481
1482 if (outs != nullptr)
1483 {
1484 const int num = pluginInstance->getTotalNumOutputChannels();
1485
1486 for (int i = 0; i < num; ++i)
1487 {
1488 auto name = getChannelName (pluginInstance->getBus (false, 0), i);
1489 outs->add (name.isNotEmpty() ? name : TRANS("Unnamed"));
1490 }
1491 }
1492 }
1493}
1494
1495bool ExternalPlugin::noTail()
1496{
1497 CRASH_TRACER_PLUGIN (getDebugName());
1498 return pluginInstance == nullptr || pluginInstance->getTailLengthSeconds() <= 0.0;
1499}
1500
1501double ExternalPlugin::getTailLength() const
1502{
1503 CRASH_TRACER_PLUGIN (getDebugName());
1504 return pluginInstance ? pluginInstance->getTailLengthSeconds() : 0.0;
1505}
1506
1507//==============================================================================
1508juce::File ExternalPlugin::getFile() const
1509{
1511
1512 if (f.exists())
1513 return f;
1514
1515 return {};
1516}
1517
1519{
1520 if (desc.pluginFormatName.isNotEmpty())
1521 return getName() + " (" + desc.pluginFormatName + " " + TRANS("Plugin") + ")";
1522
1523 return getName();
1524}
1525
1526//==============================================================================
1527int ExternalPlugin::getNumPrograms() const
1528{
1529 return pluginInstance ? pluginInstance->getNumPrograms() : 0;
1530}
1531
1532int ExternalPlugin::getCurrentProgram() const
1533{
1534 return pluginInstance ? pluginInstance->getCurrentProgram() : 0;
1535}
1536
1537juce::String ExternalPlugin::getProgramName (int index)
1538{
1539 if (index == getCurrentProgram())
1540 return getCurrentProgramName();
1541
1542 if (pluginInstance != nullptr)
1543 return pluginInstance->getProgramName (index);
1544
1545 return {};
1546}
1547
1548bool ExternalPlugin::hasNameForMidiNoteNumber (int note, int midiChannel, juce::String& name)
1549{
1550 ignoreUnused (note, midiChannel, name);
1551 #if TRACKTION_JUCE
1552 if (takesMidiInput())
1553 if (pluginInstance != nullptr)
1554 return pluginInstance->hasNameForMidiNoteNumber (note, midiChannel, name);
1555 #endif
1556
1557 return false;
1558}
1559
1560bool ExternalPlugin::hasNameForMidiProgram (int programNum, int bank, juce::String& name)
1561{
1562 if (takesMidiInput() && isSynth() && getNumPrograms() > 0)
1563 {
1564 programNum += (bank * 128);
1565
1566 if (programNum >= 0 && programNum < getNumPrograms())
1567 name = getProgramName (programNum);
1568 else
1569 name = TRANS("Unnamed");
1570
1571 return true;
1572 }
1573
1574 return false;
1575}
1576
1577juce::String ExternalPlugin::getNumberedProgramName (int i)
1578{
1579 auto s = getProgramName(i);
1580
1581 if (s.isEmpty())
1582 s = "(" + TRANS("Unnamed") + ")";
1583
1584 return juce::String (i + 1) + " - " + s;
1585}
1586
1587juce::String ExternalPlugin::getCurrentProgramName()
1588{
1589 return pluginInstance ? pluginInstance->getProgramName (pluginInstance->getCurrentProgram())
1590 : juce::String();
1591}
1592
1593void ExternalPlugin::setCurrentProgramName (const juce::String& name)
1594{
1595 CRASH_TRACER_PLUGIN (getDebugName());
1596
1597 if (pluginInstance != nullptr)
1598 pluginInstance->changeProgramName (pluginInstance->getCurrentProgram(), name);
1599}
1600
1601void ExternalPlugin::setCurrentProgram (int index, bool sendChangeMessage)
1602{
1603 if (pluginInstance != nullptr && getNumPrograms() > 0)
1604 {
1605 CRASH_TRACER_PLUGIN (getDebugName());
1606
1607 index = juce::jlimit (0, getNumPrograms() - 1, index);
1608
1609 if (index != getCurrentProgram())
1610 {
1611 pluginInstance->setCurrentProgram (index);
1612 state.setProperty (IDs::programNum, index, nullptr);
1613
1614 if (sendChangeMessage)
1615 {
1616 changed();
1617 refreshParameterValues();
1618 }
1619 }
1620 }
1621}
1622
1623bool ExternalPlugin::takesMidiInput()
1624{
1625 return pluginInstance && pluginInstance->acceptsMidi();
1626}
1627
1629{
1630 return ! isDisabled() && pluginInstance == nullptr;
1631}
1632
1634{
1635 return engine.getEngineBehaviour().isPluginDisabled (identiferString);
1636}
1637
1638int ExternalPlugin::getNumInputs() const { return pluginInstance ? pluginInstance->getTotalNumInputChannels() : 0; }
1639int ExternalPlugin::getNumOutputs() const { return pluginInstance ? pluginInstance->getTotalNumOutputChannels() : 0; }
1640
1642{
1643 if (pluginInstance != nullptr)
1644 {
1646
1647 if (! baseClassNeedsInitialising())
1649
1650 jassert (baseClassNeedsInitialising());
1651
1652 // We need to release resources before changing the bus layout
1653 // prepareToPlay will be called when the above ScopedRenderStatus goes out of scope
1654 pluginInstance->releaseResources();
1655 isInstancePrepared = false;
1656
1657 if (pluginInstance->setBusesLayout (layout))
1658 {
1659 if (! edit.isLoading())
1660 {
1661 if (auto r = getOwnerRackType())
1662 r->checkConnections();
1663
1664 flushBusesLayoutToValueTree();
1665 }
1666
1667 return true;
1668 }
1669 }
1670
1671 return false;
1672}
1673
1674bool ExternalPlugin::setBusLayout (juce::AudioChannelSet set, bool isInput, int busIndex)
1675{
1676 if (pluginInstance != nullptr)
1677 {
1678 if (auto bus = pluginInstance->getBus (isInput, busIndex))
1679 {
1681
1682 if (! baseClassNeedsInitialising())
1684
1685 // We need to release resources before changing the bus layout
1686 // prepareToPlay will be called when the above ScopedRenderStatus goes out of scope
1687 pluginInstance->releaseResources();
1688 isInstancePrepared = false;
1689
1690 jassert (baseClassNeedsInitialising());
1691
1692 if (bus->setCurrentLayout (set))
1693 {
1694 if (! edit.isLoading())
1695 {
1696 if (auto r = getOwnerRackType())
1697 r->checkConnections();
1698
1699 flushBusesLayoutToValueTree();
1700 }
1701
1702 return true;
1703 }
1704 }
1705 }
1706
1707 return false;
1708}
1709
1710//==============================================================================
1711juce::String ExternalPlugin::createPluginInstance (const juce::PluginDescription& description)
1712{
1713 jassert (! pluginInstance); // This should have already been deleted!
1714
1715 auto& dm = engine.getDeviceManager();
1716
1717 juce::String error;
1718 pluginInstance = engine.getPluginManager().createPluginInstance (description, dm.getSampleRate(), dm.getBlockSize(), error);
1719
1720 if (pluginInstance != nullptr)
1721 {
1722 pluginInstance->enableAllBuses();
1723 processorChangedManager = std::make_unique<ProcessorChangedManager> (*this);
1724 }
1725
1726 return error;
1727}
1728
1729void ExternalPlugin::deletePluginInstance()
1730{
1731 processorChangedManager.reset();
1732 AsyncPluginDeleter::getInstance()->deletePlugin (pluginInstance.release());
1733}
1734
1735//==============================================================================
1736void ExternalPlugin::buildParameterTree() const
1737{
1738 auto& paramTree = getParameterTree();
1739
1740 if (paramTree.rootNode->subNodes.size() > 0)
1741 return;
1742
1743 CRASH_TRACER_PLUGIN (getDebugName());
1744 paramTree.rootNode->addSubNode (new AutomatableParameterTree::TreeNode (getAutomatableParameter (0)));
1745 paramTree.rootNode->addSubNode (new AutomatableParameterTree::TreeNode (getAutomatableParameter (1)));
1746
1747 juce::SortedSet<int> paramsInTree;
1748
1749 if (vstXML)
1750 {
1751 for (int i = 0; i < vstXML->paramTree.size(); ++i)
1752 {
1753 if (auto param = dynamic_cast<const VSTXML::Param*> (vstXML->paramTree[i]))
1754 {
1755 if (auto externalParameter = autoParamForParamNumbers[param->paramID])
1756 {
1757 paramTree.rootNode->addSubNode (new AutomatableParameterTree::TreeNode (externalParameter));
1758 paramsInTree.add (param->paramID);
1759 }
1760 }
1761
1762 if (auto group = dynamic_cast<const VSTXML::Group*> (vstXML->paramTree[i]))
1763 {
1764 auto treeNode = new AutomatableParameterTree::TreeNode (group->name);
1765 paramTree.rootNode->addSubNode (treeNode);
1766 buildParameterTree (group, treeNode, paramsInTree);
1767 }
1768 }
1769 }
1770
1771 for (int i = 0; i < getNumAutomatableParameters(); ++i)
1772 if (auto vstParam = dynamic_cast<ExternalAutomatableParameter*> (getAutomatableParameter (i).get()))
1773 if (! paramsInTree.contains (vstParam->getParameterIndex()))
1774 paramTree.rootNode->addSubNode (new AutomatableParameterTree::TreeNode (autoParamForParamNumbers [vstParam->getParameterIndex()]));
1775}
1776
1777void ExternalPlugin::buildParameterTree (const VSTXML::Group* group,
1778 AutomatableParameterTree::TreeNode* treeNode,
1779 juce::SortedSet<int>& paramsInTree) const
1780{
1781 for (int i = 0; i < group->paramTree.size(); ++i)
1782 {
1783 if (auto param = dynamic_cast<const VSTXML::Param*> (group->paramTree[i]))
1784 {
1785 if (auto externalParameter = autoParamForParamNumbers[param->paramID])
1786 {
1787 treeNode->addSubNode (new AutomatableParameterTree::TreeNode (externalParameter));
1788 paramsInTree.add (param->paramID);
1789 }
1790 }
1791
1792 if (auto subGroup = dynamic_cast<const VSTXML::Group*> (group->paramTree[i]))
1793 {
1794 auto subTreeNode = new AutomatableParameterTree::TreeNode (subGroup->name);
1795 treeNode->addSubNode (subTreeNode);
1796 buildParameterTree (subGroup, subTreeNode, paramsInTree);
1797 }
1798 }
1799}
1800
1806
1807juce::AudioPluginInstance* ExternalPlugin::getAudioPluginInstance() const
1808{
1809 return pluginInstance.get();
1810}
1811
1812void ExternalPlugin::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& id)
1813{
1814 if (v == state && id == IDs::layout)
1815 {
1816 if (isFlushingLayoutToState)
1817 return;
1818
1819 if (auto ap = getAudioPluginInstance())
1820 {
1821 auto stateLayout = readBusesLayout (v.getProperty(IDs::layout), *ap);
1822
1823 if (stateLayout != ap->getBusesLayout())
1824 setBusesLayout (stateLayout);
1825 }
1826 }
1827 else
1828 {
1829 Plugin::valueTreePropertyChanged (v, id);
1830 }
1831}
1832
1833juce::Array<Exportable::ReferencedItem> ExternalPlugin::getReferencedItems()
1834{
1835 return engine.getEngineBehaviour().getReferencedItems (*this);
1836}
1837
1838void ExternalPlugin::reassignReferencedItem (const ReferencedItem& itm, ProjectItemID newID, double newStartTime)
1839{
1840 engine.getEngineBehaviour().reassignReferencedItem (*this, itm, newID, newStartTime);
1841}
1842
1843
1844//==============================================================================
1845struct AudioProcessorEditorContentComp : public Plugin::EditorComponent
1846{
1847 AudioProcessorEditorContentComp (ExternalPlugin& plug) : plugin (plug)
1848 {
1850 {
1851 if (auto inst = plugin.getAudioPluginInstance())
1852 {
1853 editor.reset (inst->createEditorIfNeeded());
1854
1855 if (editor == nullptr)
1856 return;
1857
1858 addAndMakeVisible (*editor);
1859 }
1860 }
1861
1862 resizeToFitEditor (true);
1863 }
1864
1865 bool allowWindowResizing() override
1866 {
1867 if (isCmajorPatchPluginFormat (plugin.desc))
1868 return editor != nullptr && editor->isResizable();
1869
1870 // Allowing this for VSTs results in some hard-to-prevent size hysteresis..
1871 return plugin.isVST3()
1872 && plugin.getVendor().containsIgnoreCase ("Celemony");
1873 }
1874
1875 juce::ComponentBoundsConstrainer* getBoundsConstrainer() override
1876 {
1877 if (editor != nullptr)
1878 return editor->getConstrainer();
1879
1880 return {};
1881 }
1882
1883 void resized() override
1884 {
1885 if (editor != nullptr)
1886 editor->setBounds (getLocalBounds());
1887 }
1888
1889 void childBoundsChanged (juce::Component* c) override
1890 {
1891 if (c == editor.get())
1892 {
1893 plugin.edit.pluginChanged (plugin);
1894 resizeToFitEditor (false);
1895 }
1896 }
1897
1898 void resizeToFitEditor (bool force)
1899 {
1900 if (force || ! allowWindowResizing())
1901 {
1902 setSize (std::max (8, editor != nullptr ? editor->getWidth() : 0),
1903 std::max (8, editor != nullptr ? editor->getHeight() : 0));
1904 }
1905 }
1906
1907 void visibilityChanged() override
1908 {
1909 if (editor != nullptr && isShowing())
1910 {
1911 if (editor->isShowing())
1912 editor->grabKeyboardFocus();
1913
1914 editor->toFront (true);
1915 }
1916 }
1917
1918 ExternalPlugin& plugin;
1920
1923};
1924
1925std::unique_ptr<Plugin::EditorComponent> ExternalPlugin::createEditor()
1926{
1928
1929 if (ed->editor != nullptr)
1930 return ed;
1931
1932 return {};
1933}
1934
1935//==============================================================================
1936PluginWetDryAutomatableParam::PluginWetDryAutomatableParam (const juce::String& xmlTag, const juce::String& name, Plugin& owner)
1937 : AutomatableParameter (xmlTag, name, owner, { 0.0f, 1.0f })
1938{
1939}
1940
1941PluginWetDryAutomatableParam::~PluginWetDryAutomatableParam()
1942{
1943 notifyListenersOfDeletion();
1944}
1945
1946juce::String PluginWetDryAutomatableParam::valueToString (float value)
1947{
1949}
1950
1951float PluginWetDryAutomatableParam::stringToValue (const juce::String& s)
1952{
1953 return juce::Decibels::decibelsToGain (dbStringToDb (s));
1954}
1955
1956}} // namespace tracktion { inline namespace engine
void cancelPendingUpdate() noexcept
int getNumChannels() const noexcept
void clear() noexcept
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
Type *const * getArrayOfWritePointers() noexcept
static AudioChannelSet JUCE_CALLTYPE disabled()
static AudioChannelSet fromAbbreviatedString(const String &set)
static String JUCE_CALLTYPE getChannelTypeName(ChannelType)
Bus * getBus(bool isInput, int busIndex) noexcept
BusesLayout getBusesLayout() const
bool removeBus(bool isInput)
int getBusCount(bool isInput) const noexcept
bool addBus(bool isInput)
AudioChannelSet getChannelLayoutOfBus(bool isInput, int busIndex) const noexcept
void enterModalState(bool takeKeyboardFocus=true, ModalComponentManager::Callback *callback=nullptr, bool deleteWhenDismissed=false)
static Type decibelsToGain(Type decibels, Type minusInfinityDb=Type(defaultMinusInfinitydB))
static Type gainToDecibels(Type gain, Type minusInfinityDb=Type(defaultMinusInfinitydB))
static String toString(Type decibels, int decimalPlaces=2, Type minusInfinityDb=Type(defaultMinusInfinitydB), bool shouldIncludeSuffix=true, StringRef customMinusInfinityString={})
static File createFileWithoutCheckingPath(const String &absolutePath) noexcept
bool isValid() const noexcept
const String & toString() const noexcept
static MidiBuffer setZoneLayout(MPEZoneLayout layout)
bool fromBase64Encoding(StringRef encodedString)
void * getData() noexcept
size_t getSize() const noexcept
bool isEmpty() const noexcept
bool addEvent(const MidiMessage &midiMessage, int sampleNumber)
void clear() noexcept
static MidiMessage allNotesOff(int channel) noexcept
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
static MidiMessage allControllersOff(int channel) noexcept
int size() const noexcept
bool isEmpty() const noexcept
void removeLast(int howManyToRemove=1, bool deleteObjects=true)
ObjectClass * add(ObjectClass *newObject)
ObjectClass * getLast() const noexcept
bool add(const ElementType &newElement) noexcept
bool contains(const ElementType &elementToLookFor) const noexcept
void add(String stringToAdd)
bool isEmpty() const noexcept
static String toHexString(IntegerType number)
int hashCode() const noexcept
int64 getHexValue64() const noexcept
bool isNotEmpty() const noexcept
void stopTimer() noexcept
void startTimer(int intervalInMilliseconds) noexcept
static void setExtraFunctions(AudioPluginInstance *plugin, ExtraFunctions *functions)
static ValueTree readFromData(const void *data, size_t numBytes)
An audio scratch buffer that has pooled storage.
juce::AudioBuffer< float > & buffer
The buffer to use.
Base class for elements that have some kind of automatable parameters.
void restoreChangedParametersFromState()
Restores the value of any explicitly set parameters.
void saveChangedParametersToState()
Saves the explicit value of any parameters that have deviated to the state.
const EditItemID itemID
Every EditItem has an ID which is unique within the edit.
void restartPlayback()
Use this to tell the play engine to rebuild the audio graph if the toplogy has changed.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
void pluginChanged(Plugin &) noexcept
Plugins should call this when one of their parameters or state changes to mark the edit as unsaved.
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
virtual std::unique_ptr< juce::PluginDescription > findDescriptionForFileOrID(const juce::String &)
Gives an opportunity to load custom plugins for those that have been registered as custom formats but...
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
PluginManager & getPluginManager() const
Returns the PluginManager instance.
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
bool setBusLayout(juce::AudioChannelSet, bool isInput, int busIndex)
Attempts to change the layout of the plugin.
void deleteFromParent() override
Attempts to delete this plugin, whether it's a master plugin, track plugin, etc.
bool hasNameForMidiNoteNumber(int note, int midiChannel, juce::String &name) override
If it's a synth that names its notes, this can return the name it uses for this note 0-127.
bool setBusesLayout(juce::AudioProcessor::BusesLayout)
Attempts to change the layout of the plugin.
void reset() override
Should reset synth voices, tails, clear delay buffers, etc.
void setEnabled(bool enabled) override
Enable/disable the plugin.
void trackPropertiesChanged() override
Track name or colour has changed.
void selectableAboutToBeDeleted() override
Called just before the selectable is about to be deleted so any subclasses should still be valid at t...
bool isDisabled() override
Plugins can be disabled to avoid them crashing Edits.
void applyToBuffer(const PluginRenderContext &) override
Process the next block of data.
juce::String getName() const override
The name of the type, e.g.
void initialiseFully() override
Gives the plugin a chance to do extra initialisation when it's been added to an edit.
int getNumOutputChannelsGivenInputs(int numInputs) override
This must return the number of output channels that the plugin will produce, given a number of input ...
bool hasNameForMidiProgram(int programNum, int bank, juce::String &name) override
Returns the name for a midi program, if there is one.
void deinitialise() override
Called after play stops to release resources.
bool isMissing() override
for things like VSTs where the DLL is missing.
void changed() override
method from Selectable, that's been overridden here to also tell the edit that it's changed.
virtual void setEnabled(bool)
Enable/disable the plugin.
void selectableAboutToBeDeleted() override
Called just before the selectable is about to be deleted so any subclasses should still be valid at t...
Track * getOwnerTrack() const
Returns the track if it's a track or clip plugin.
virtual void deleteFromParent()
Attempts to delete this plugin, whether it's a master plugin, track plugin, etc.
void stop(bool discardRecordings, bool clearDevices, bool canSendMMCStop=true)
Stops recording, creating clips of newyly recorded files/MIDI data.
TimeRange getLoopRange() const noexcept
Returns the loop range.
void triggerClearDevicesOnStop()
Triggers a graph rebuild when playback stops.
virtual void recreatePluginWindowContentAsync(Plugin &)
Should trigger an asynchronous refresh of any editor components showing for this plugin.
T count(T... args)
T end(T... args)
T find(T... args)
T format(T... args)
T get(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define JUCE_AUTORELEASEPOOL
#define jassert(expression)
#define DBG(textToWrite)
#define JUCE_DECLARE_NON_COPYABLE(className)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
#define JUCE_IMPLEMENT_SINGLETON(Classname)
#define JUCE_DECLARE_SINGLETON(Classname, doNotRecreateAfterDeletion)
typedef int
T make_unique(T... args)
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
long long int64
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
SafeSelectable< SelectableType > makeSafeRef(SelectableType &selectable)
Creates a SafeSelectable for a given selectable object.
Passed into Plugins when they are being initialised, to give them useful contextual information that ...
T release(T... args)
T reset(T... args)
T sample(T... args)
typedef uint32_t
Array< AudioChannelSet > outputBuses
Array< AudioChannelSet > inputBuses
Represents a position in real-life time.
double getPPQTimeOfBarStart() const noexcept
Returns the PPQ start time of the bar the position is in.
double getPPQTime() const noexcept
Returns the position as a PPQ time.
TimeSignature getTimeSignature() const
Returns the current TimeSignature of the Position.
void set(TimePosition)
Sets the Position to a new time.
double getTempo() const
Returns the current tempo of the Position.
The context passed to plugin render methods to provide it with buffers to fill.
int bufferNumSamples
The number of samples to write into the audio buffer.
MidiMessageArray * bufferForMidiMessages
A buffer of MIDI events to process.
juce::AudioBuffer< float > * destBuffer
The target audio buffer which needs to be filled.
int bufferStartSample
The index of the start point in the audio buffer from which data must be written.
double midiBufferOffset
A time offset to add to the timestamp of any events in the MIDI buffer.
bool allowBypassedProcessing
If this is true and the plugin supports it, this will call the bypassed processing method of the plug...
bool isPlaying
True if the the playhead is currently playing.
TimeRange editTime
The edit time range this context represents.
specialised AutomatableParameter for wet/dry.
Is an Edit is playing back, this resumes playback when destroyed.
#define CRASH_TRACER_PLUGIN(p)
This macro adds the current location and the name of a plugin to a stack which gets logged if a crash...
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.