From 63cb062d35d1f6c4c587fc5d214a21ddeddfae66 Mon Sep 17 00:00:00 2001 From: jules Date: Thu, 21 Nov 2013 13:27:36 +0000 Subject: [PATCH] Initial commit of VST3 hosting. --- .../Source/Project/jucer_AudioPluginModule.h | 44 +- .../Source/Project/jucer_Module.cpp | 17 +- .../Introjucer/Source/Project/jucer_Module.h | 1 + .../Source/Utility/jucer_PresetIDs.h | 1 + .../JuceDemoPlugin.xcodeproj/project.pbxproj | 4 + .../VisualStudio2005/JuceDemoPlugin.vcproj | 11 + .../VisualStudio2008/JuceDemoPlugin.vcproj | 11 + .../JuceLibraryCode/AppConfig.h | 7 + .../Plugin Host.xcodeproj/project.pbxproj | 14 +- .../VisualStudio2010/Plugin Host.vcxproj | 8 +- .../Plugin Host.vcxproj.filters | 6 + .../JuceLibraryCode/AppConfig.h | 4 + extras/audio plugin host/Plugin Host.jucer | 8 +- .../Builds/VisualStudio2008/juce_dll.vcproj | 11 + .../windows dll/JuceLibraryCode/AppConfig.h | 4 + .../format/juce_AudioPluginFormatManager.cpp | 8 + .../format_types/juce_VST3PluginFormat.cpp | 2463 +++++++++++++++++ .../format_types/juce_VST3PluginFormat.h | 72 + .../juce_audio_processors.cpp | 5 + .../juce_audio_processors.h | 17 +- 20 files changed, 2680 insertions(+), 36 deletions(-) create mode 100644 modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp create mode 100644 modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h diff --git a/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h b/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h index e559cde799..49975e8fb9 100644 --- a/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h +++ b/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h @@ -30,6 +30,7 @@ namespace { Value shouldBuildVST (Project& project) { return project.getProjectValue ("buildVST"); } + Value shouldBuildVST3 (Project& project) { return project.getProjectValue ("buildVST3"); } Value shouldBuildAU (Project& project) { return project.getProjectValue ("buildAU"); } Value shouldBuildRTAS (Project& project) { return project.getProjectValue ("buildRTAS"); } Value shouldBuildAAX (Project& project) { return project.getProjectValue ("buildAAX"); } @@ -120,6 +121,7 @@ namespace StringPairArray flags; //flags.set ("JUCE_MODAL_LOOPS_PERMITTED", "0"); flags.set ("JucePlugin_Build_VST", valueToBool (shouldBuildVST (project))); + flags.set ("JucePlugin_Build_VST3", valueToBool (shouldBuildVST3 (project))); flags.set ("JucePlugin_Build_AU", valueToBool (shouldBuildAU (project))); flags.set ("JucePlugin_Build_RTAS", valueToBool (shouldBuildRTAS (project))); flags.set ("JucePlugin_Build_AAX", valueToBool (shouldBuildAAX (project))); @@ -201,41 +203,47 @@ namespace //============================================================================== namespace VSTHelpers { - static Value getVSTFolder (ProjectExporter& exporter) { return exporter.getSetting (Ids::vstFolder); } + static Value getVSTFolder (ProjectExporter& exporter, bool isVST3) + { + return exporter.getSetting (isVST3 ? Ids::vst3Folder + : Ids::vstFolder); + } - static void addVSTFolderToPath (ProjectExporter& exporter, StringArray& searchPaths) + static void addVSTFolderToPath (ProjectExporter& exporter, bool isVST3) { - const String vstFolder (getVSTFolder (exporter).toString()); + const String vstFolder (getVSTFolder (exporter, isVST3).toString()); if (vstFolder.isNotEmpty()) { RelativePath path (exporter.rebaseFromProjectFolderToBuildTarget (RelativePath (vstFolder, RelativePath::projectFolder))); if (exporter.isVisualStudio()) - searchPaths.add (path.toWindowsStyle()); + exporter.extraSearchPaths.add (path.toWindowsStyle()); else if (exporter.isLinux() || exporter.isXcode()) - searchPaths.insert (0, path.toUnixStyle()); + exporter.extraSearchPaths.insert (0, path.toUnixStyle()); } } - static void createVSTPathEditor (ProjectExporter& exporter, PropertyListBuilder& props) + static void createVSTPathEditor (ProjectExporter& exporter, PropertyListBuilder& props, bool isVST3) { - props.add (new TextPropertyComponent (getVSTFolder (exporter), "VST Folder", 1024, false), - "If you're building a VST, this must be the folder containing the VST SDK. This should be an absolute path."); + const String vstFormat (isVST3 ? "VST3" : "VST"); + + props.add (new TextPropertyComponent (getVSTFolder (exporter, isVST3), vstFormat + " Folder", 1024, false), + "If you're building a " + vstFormat + ", this must be the folder containing the " + vstFormat + " SDK. This should be an absolute path."); } - static void fixMissingVSTValues (ProjectExporter& exporter) + static void fixMissingVSTValues (ProjectExporter& exporter, bool isVST3) { - if (getVSTFolder(exporter).toString().isEmpty()) - getVSTFolder(exporter) = (exporter.isWindows() ? "c:\\SDKs\\vstsdk2.4" - : "~/SDKs/vstsdk2.4"); + if (getVSTFolder (exporter, isVST3).toString().isEmpty()) + getVSTFolder (exporter, isVST3) = exporter.isWindows() ? (isVST3 ? "c:\\SDKs\\VST3 SDK" : "c:\\SDKs\\vstsdk2.4") + : (isVST3 ? "~/SDKs/VST3 SDK" : "~/SDKs/vstsdk2.4"); fixMissingXcodePostBuildScript (exporter); } - static inline void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) + static inline void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver, bool isVST3) { - fixMissingVSTValues (exporter); + fixMissingVSTValues (exporter, isVST3); writePluginCharacteristicsFile (projectSaver); exporter.makefileTargetSuffix = ".so"; @@ -246,7 +254,7 @@ namespace VSTHelpers RelativePath juceWrapperFolder (exporter.getProject().getGeneratedCodeFolder(), exporter.getTargetFolder(), RelativePath::buildTargetFolder); - addVSTFolderToPath (exporter, exporter.extraSearchPaths); + addVSTFolderToPath (exporter, isVST3); if (exporter.isWindows()) exporter.extraSearchPaths.add (juceWrapperFolder.toWindowsStyle()); @@ -254,10 +262,10 @@ namespace VSTHelpers exporter.extraSearchPaths.add (juceWrapperFolder.toUnixStyle()); } - static inline void createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props) + static inline void createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props, bool isVST3) { - fixMissingVSTValues (exporter); - createVSTPathEditor (exporter, props); + fixMissingVSTValues (exporter, isVST3); + createVSTPathEditor (exporter, props, isVST3); } } diff --git a/extras/Introjucer/Source/Project/jucer_Module.cpp b/extras/Introjucer/Source/Project/jucer_Module.cpp index 00e31e494f..6c89dd75ec 100644 --- a/extras/Introjucer/Source/Project/jucer_Module.cpp +++ b/extras/Introjucer/Source/Project/jucer_Module.cpp @@ -230,6 +230,7 @@ LibraryModule::LibraryModule (const ModuleDescription& d) bool LibraryModule::isAUPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_AU"); } bool LibraryModule::isVSTPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST"); } +bool LibraryModule::isVST3PluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3"); } File LibraryModule::getModuleHeaderFile (const File& folder) const { @@ -348,8 +349,8 @@ void LibraryModule::prepareExporter (ProjectExporter& exporter, ProjectSaver& pr addBrowsableCode (exporter, projectSaver, compiled, moduleInfo.getFolder()); } - if (isVSTPluginHost (project)) - VSTHelpers::addVSTFolderToPath (exporter, exporter.extraSearchPaths); + if (isVSTPluginHost (project)) VSTHelpers::addVSTFolderToPath (exporter, false); + if (isVST3PluginHost (project)) VSTHelpers::addVSTFolderToPath (exporter, true); if (exporter.isXcode()) { @@ -378,7 +379,8 @@ void LibraryModule::prepareExporter (ProjectExporter& exporter, ProjectSaver& pr if (moduleInfo.isPluginClient()) { - if (shouldBuildVST (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver); + if (shouldBuildVST (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver, false); + if (shouldBuildVST3 (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver, true); if (shouldBuildAU (project).getValue()) AUHelpers::prepareExporter (exporter, projectSaver); if (shouldBuildAAX (project).getValue()) AAXHelpers::prepareExporter (exporter, projectSaver); if (shouldBuildRTAS (project).getValue()) RTASHelpers::prepareExporter (exporter, projectSaver); @@ -389,11 +391,16 @@ void LibraryModule::createPropertyEditors (ProjectExporter& exporter, PropertyLi { if (isVSTPluginHost (exporter.getProject()) && ! (moduleInfo.isPluginClient() && shouldBuildVST (exporter.getProject()).getValue())) - VSTHelpers::createVSTPathEditor (exporter, props); + VSTHelpers::createVSTPathEditor (exporter, props, false); + + if (isVST3PluginHost (exporter.getProject()) + && ! (moduleInfo.isPluginClient() && shouldBuildVST3 (exporter.getProject()).getValue())) + VSTHelpers::createVSTPathEditor (exporter, props, true); if (moduleInfo.isPluginClient()) { - if (shouldBuildVST (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props); + if (shouldBuildVST (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props, false); + if (shouldBuildVST3 (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props, true); if (shouldBuildRTAS (exporter.getProject()).getValue()) RTASHelpers::createPropertyEditors (exporter, props); if (shouldBuildAAX (exporter.getProject()).getValue()) AAXHelpers::createPropertyEditors (exporter, props); } diff --git a/extras/Introjucer/Source/Project/jucer_Module.h b/extras/Introjucer/Source/Project/jucer_Module.h index 175585b53c..ba70147e93 100644 --- a/extras/Introjucer/Source/Project/jucer_Module.h +++ b/extras/Introjucer/Source/Project/jucer_Module.h @@ -112,6 +112,7 @@ private: bool isAUPluginHost (const Project&) const; bool isVSTPluginHost (const Project&) const; + bool isVST3PluginHost (const Project&) const; }; //============================================================================== diff --git a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h index 96517d43b6..a82137d5d8 100644 --- a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h +++ b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h @@ -50,6 +50,7 @@ namespace Ids DECLARE_ID (targetFolder); DECLARE_ID (intermediatesPath); DECLARE_ID (vstFolder); + DECLARE_ID (vst3Folder); DECLARE_ID (rtasFolder); DECLARE_ID (auFolder); DECLARE_ID (flags); diff --git a/extras/audio plugin demo/Builds/MacOSX/JuceDemoPlugin.xcodeproj/project.pbxproj b/extras/audio plugin demo/Builds/MacOSX/JuceDemoPlugin.xcodeproj/project.pbxproj index 6880676208..394e70176d 100644 --- a/extras/audio plugin demo/Builds/MacOSX/JuceDemoPlugin.xcodeproj/project.pbxproj +++ b/extras/audio plugin demo/Builds/MacOSX/JuceDemoPlugin.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 06F651E38334660DCFCD6D2D = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_InterprocessConnectionServer.cpp"; path = "../../../../modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp"; sourceTree = "SOURCE_ROOT"; }; 0703B6BD13EE29BD0422263D = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MemoryBlock.h"; path = "../../../../modules/juce_core/memory/juce_MemoryBlock.h"; sourceTree = "SOURCE_ROOT"; }; 070440AAAFFBE88D39492FC4 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Timer.cpp"; path = "../../../../modules/juce_events/timers/juce_Timer.cpp"; sourceTree = "SOURCE_ROOT"; }; + 070E3EFE91BE8407EE1EBD8C = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_VST3PluginFormat.h"; path = "../../../../modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h"; sourceTree = "SOURCE_ROOT"; }; 070F39D84506BCDF7C5CBA26 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_MultiTimer.cpp"; path = "../../../../modules/juce_events/timers/juce_MultiTimer.cpp"; sourceTree = "SOURCE_ROOT"; }; 071FC447F1DEE261E6D442B2 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MouseInputSource.h"; path = "../../../../modules/juce_gui_basics/mouse/juce_MouseInputSource.h"; sourceTree = "SOURCE_ROOT"; }; 073544D5D22C9975CC308E48 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_android_Files.cpp"; path = "../../../../modules/juce_core/native/juce_android_Files.cpp"; sourceTree = "SOURCE_ROOT"; }; @@ -913,6 +914,7 @@ F5A1D2AFCFA4E5563C3D494D = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ReverbAudioSource.cpp"; path = "../../../../modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp"; sourceTree = "SOURCE_ROOT"; }; F5A261506BD95F58790AD021 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_BubbleComponent.h"; path = "../../../../modules/juce_gui_basics/misc/juce_BubbleComponent.h"; sourceTree = "SOURCE_ROOT"; }; F6546500AA3A49A3BB76F825 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ImageComponent.cpp"; path = "../../../../modules/juce_gui_basics/widgets/juce_ImageComponent.cpp"; sourceTree = "SOURCE_ROOT"; }; + F6AE333028FC864D4653A7B5 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_VST3PluginFormat.cpp"; path = "../../../../modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp"; sourceTree = "SOURCE_ROOT"; }; F6CA6BC81FCC918A9BA798CC = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_AudioFormatManager.cpp"; path = "../../../../modules/juce_audio_formats/format/juce_AudioFormatManager.cpp"; sourceTree = "SOURCE_ROOT"; }; F74005802C3B92DB086A069A = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_SparseSet.h"; path = "../../../../modules/juce_core/containers/juce_SparseSet.h"; sourceTree = "SOURCE_ROOT"; }; F7454AD16EE05969CCF5FD7C = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RTAS_DigiCode2.cpp"; path = "../../../../modules/juce_audio_plugin_client/RTAS/juce_RTAS_DigiCode2.cpp"; sourceTree = "SOURCE_ROOT"; }; @@ -1159,6 +1161,8 @@ A9B46A5FF98D7B9DF8598C12, C19323831CE86566D60C725E, 9A6686BC6FC38F6D1917D7C7, + F6AE333028FC864D4653A7B5, + 070E3EFE91BE8407EE1EBD8C, A9C466FBA4FCF6484BCF86A2, 6501BB1AAFD5B3DC4A783F85, CC04A3CE3003C0A0AB35A7AF ); name = "format_types"; sourceTree = ""; }; diff --git a/extras/audio plugin demo/Builds/VisualStudio2005/JuceDemoPlugin.vcproj b/extras/audio plugin demo/Builds/VisualStudio2005/JuceDemoPlugin.vcproj index d4ceeae5a6..031e1d20b5 100644 --- a/extras/audio plugin demo/Builds/VisualStudio2005/JuceDemoPlugin.vcproj +++ b/extras/audio plugin demo/Builds/VisualStudio2005/JuceDemoPlugin.vcproj @@ -975,6 +975,17 @@ + + + + + + + + + + + + + + + + + + Disabled EditAndContinue - ..\..\JuceLibraryCode;..\..\..\..\modules;c:\SDKs\vstsdk2.4;%(AdditionalIncludeDirectories) + ..\..\JuceLibraryCode;..\..\..\..\modules;c:\SDKs\vstsdk2.4;c:\SDKs\VST3 SDK;%(AdditionalIncludeDirectories) WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2010_78A501D=1;%(PreprocessorDefinitions) MultiThreadedDebug true @@ -94,7 +94,7 @@ MinSpace - ..\..\JuceLibraryCode;..\..\..\..\modules;c:\SDKs\vstsdk2.4;%(AdditionalIncludeDirectories) + ..\..\JuceLibraryCode;..\..\..\..\modules;c:\SDKs\vstsdk2.4;c:\SDKs\VST3 SDK;%(AdditionalIncludeDirectories) WIN32;_WINDOWS;NDEBUG;JUCER_VS2010_78A501D=1;%(PreprocessorDefinitions) MultiThreaded true @@ -333,6 +333,9 @@ true + + true + true @@ -1296,6 +1299,7 @@ + diff --git a/extras/audio plugin host/Builds/VisualStudio2010/Plugin Host.vcxproj.filters b/extras/audio plugin host/Builds/VisualStudio2010/Plugin Host.vcxproj.filters index ef5fab1c3d..6ed715cf7a 100644 --- a/extras/audio plugin host/Builds/VisualStudio2010/Plugin Host.vcxproj.filters +++ b/extras/audio plugin host/Builds/VisualStudio2010/Plugin Host.vcxproj.filters @@ -526,6 +526,9 @@ Juce Modules\juce_audio_processors\format_types + + Juce Modules\juce_audio_processors\format_types + Juce Modules\juce_audio_processors\format_types @@ -1725,6 +1728,9 @@ Juce Modules\juce_audio_processors\format_types + + Juce Modules\juce_audio_processors\format_types + Juce Modules\juce_audio_processors\format_types diff --git a/extras/audio plugin host/JuceLibraryCode/AppConfig.h b/extras/audio plugin host/JuceLibraryCode/AppConfig.h index 73ca353d87..5c4ee1af89 100644 --- a/extras/audio plugin host/JuceLibraryCode/AppConfig.h +++ b/extras/audio plugin host/JuceLibraryCode/AppConfig.h @@ -102,6 +102,10 @@ #define JUCE_PLUGINHOST_VST 1 #endif +#ifndef JUCE_PLUGINHOST_VST3 + #define JUCE_PLUGINHOST_VST3 1 +#endif + #ifndef JUCE_PLUGINHOST_AU #define JUCE_PLUGINHOST_AU 1 #endif diff --git a/extras/audio plugin host/Plugin Host.jucer b/extras/audio plugin host/Plugin Host.jucer index ebb4841b05..874b0a5f1e 100644 --- a/extras/audio plugin host/Plugin Host.jucer +++ b/extras/audio plugin host/Plugin Host.jucer @@ -14,7 +14,7 @@ includeBinaryInAppConfig="1"> + objCExtraSuffix="M73TRi" vst3Folder="~/SDKs/VST3 SDK"> @@ -38,7 +38,8 @@ - + @@ -110,7 +111,8 @@ + JUCE_PLUGINHOST_VST="enabled" JUCE_PLUGINHOST_AU="enabled" JUCE_WEB_BROWSER="disabled" + JUCE_PLUGINHOST_VST3="enabled"/> diff --git a/extras/windows dll/Builds/VisualStudio2008/juce_dll.vcproj b/extras/windows dll/Builds/VisualStudio2008/juce_dll.vcproj index b26c329720..7738e5203a 100644 --- a/extras/windows dll/Builds/VisualStudio2008/juce_dll.vcproj +++ b/extras/windows dll/Builds/VisualStudio2008/juce_dll.vcproj @@ -908,6 +908,17 @@ + + + + + + + + + (formats[i]) == nullptr); #endif + #if JUCE_PLUGINHOST_VST3 + jassert (dynamic_cast (formats[i]) == nullptr); + #endif + #if JUCE_PLUGINHOST_AU && JUCE_MAC jassert (dynamic_cast (formats[i]) == nullptr); #endif @@ -54,6 +58,10 @@ void AudioPluginFormatManager::addDefaultFormats() formats.add (new VSTPluginFormat()); #endif + #if JUCE_PLUGINHOST_VST3 + formats.add (new VST3PluginFormat()); + #endif + #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX formats.add (new LADSPAPluginFormat()); #endif diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp new file mode 100644 index 0000000000..b694885e6a --- /dev/null +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -0,0 +1,2463 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_PLUGINHOST_VST3 + +} // namespace juce + +#if JUCE_MSVC // Wow, those VST guys really don't worry too much about compiler warnings... + #pragma warning (disable: 4505) + #pragma warning (push, 0) + #pragma warning (disable: 4702) +#elif JUCE_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wnon-virtual-dtor" + #pragma clang diagnostic ignored "-Wreorder" + #pragma clang diagnostic ignored "-Wunsequenced" + #pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#endif + +// Got an include error here? If so, you'll need to install the VST3 SDK somewhere, +// and use the introjucer or your IDE to add it to your include path. The introjucer +// has a special box for specifying this path for each export target. +#include "base/source/baseiids.cpp" +#include "base/source/fatomic.cpp" +#include "base/source/fbuffer.cpp" +#include "base/source/fdebug.cpp" +#include "base/source/fobject.cpp" +#include "base/source/frect.cpp" +#include "base/source/fstreamer.cpp" +#include "base/source/fstring.cpp" +#include "base/source/fthread.cpp" +#include "base/source/updatehandler.cpp" +#include "pluginterfaces/base/conststringtable.cpp" +#include "pluginterfaces/base/funknown.cpp" +#include "pluginterfaces/base/ustring.cpp" +#include "public.sdk/source/main/pluginfactoryvst3.cpp" +#include "public.sdk/source/common/memorystream.cpp" +#include "public.sdk/source/common/pluginview.cpp" +#include "public.sdk/source/vst/vstbus.cpp" +#include "public.sdk/source/vst/vstinitiids.cpp" +#include "public.sdk/source/vst/vstcomponent.cpp" +#include "public.sdk/source/vst/vstcomponentbase.cpp" +#include "public.sdk/source/vst/vstparameters.cpp" +#include "public.sdk/source/vst/vstsinglecomponenteffect.cpp" +#include "public.sdk/source/vst/hosting/hostclasses.cpp" + +#if JUCE_MSVC + #pragma warning (pop) +#elif JUCE_CLANG + #pragma clang diagnostic pop +#endif + +#undef ASSERT +#undef WARNING +#undef PRINTSYSERROR +#undef DEBUGSTR +#undef DBPRT0 +#undef DBPRT1 +#undef DBPRT2 +#undef DBPRT3 +#undef DBPRT4 +#undef DBPRT5 +#undef free +#undef malloc +#undef NEW +#undef NEWVEC +#undef realloc +#undef VERIFY +#undef VERIFY_IS +#undef VERIFY_NOT +#undef META_CREATE_FUNC +#undef CLASS_CREATE_FUNC +#undef SINGLE_CREATE_FUNC +#undef _META_CLASS +#undef _META_CLASS_IFACE +#undef META_CLASS +#undef META_CLASS_IFACE +#undef META_CLASS_SINGLE +#undef OBJ_METHODS +#undef SINGLETON +#undef QUERY_INTERFACE + +namespace juce +{ + +using namespace Steinberg; + +//============================================================================== +struct VST3Classes +{ + +#ifndef JUCE_VST3_DEBUGGING + #define JUCE_VST3_DEBUGGING 0 +#endif + +#if JUCE_VST3_DEBUGGING + #define VST3_DBG(a) Logger::writeToLog (a); +#else + #define VST3_DBG(a) +#endif + +#if JUCE_DEBUG +static void warnOnFailure (int result) +{ + const char* message = "Unknown result!"; + + switch (result) + { + case kResultOk: return; + case kNotImplemented: message = "kNotImplemented"; break; + case kNoInterface: message = "kNoInterface"; break; + case kResultFalse: message = "kResultFalse"; break; + case kInvalidArgument: message = "kInvalidArgument"; break; + case kInternalError: message = "kInternalError"; break; + case kNotInitialized: message = "kNotInitialized"; break; + case kOutOfMemory: message = "kOutOfMemory"; break; + default: break; + } + + DBG (message); +} +#else + #define warnOnFailure(x) x +#endif + +//============================================================================== +template +class ComSmartPtr +{ +public: + ComSmartPtr() noexcept : source (nullptr) {} + ComSmartPtr (ObjectType* object) noexcept : source (object) { if (source != nullptr) source->addRef(); } + ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } + ~ComSmartPtr() { if (source != nullptr) source->release(); } + + operator ObjectType*() const noexcept { return source; } + ObjectType* get() const noexcept { return source; } + ObjectType& operator*() const noexcept { return *source; } + ObjectType* operator->() const noexcept { return source; } + + ComSmartPtr& operator= (const ComSmartPtr& other) { return operator= (other.source); } + + ComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf) + { + ComSmartPtr p (newObjectToTakePossessionOf); + std::swap (p.source, source); + return *this; + } + + bool operator== (ObjectType* const other) noexcept { return source == other; } + bool operator!= (ObjectType* const other) noexcept { return source != other; } + + bool loadFrom (FUnknown* o) + { + *this = nullptr; + return o != nullptr && o->queryInterface (ObjectType::iid, (void**) &source) == kResultOk; + } + + bool loadFrom (IPluginFactory* factory, const TUID& uuid) + { + jassert (factory != nullptr); + *this = nullptr; + return factory->createInstance (uuid, ObjectType::iid, (void**) &source) == kResultOk; + } + + bool loadFrom (IPluginFactory* factory, const PClassInfo& info) + { + return loadFrom (factory, info.cid); + } + +private: + ObjectType* source; +}; + +//============================================================================== +#define JUCE_DECLARE_VST3_COM_REF_METHODS \ + Steinberg::uint32 JUCE_CALLTYPE addRef() { return (Steinberg::uint32) ++refCount; } \ + Steinberg::uint32 JUCE_CALLTYPE release() { const int r = --refCount; if (r == 0) delete this; return (Steinberg::uint32) r; } + +#define JUCE_DECLARE_VST3_COM_QUERY_METHODS \ + tresult PLUGIN_API JUCE_CALLTYPE queryInterface (const TUID, void** obj) \ + {\ + jassertfalse; \ + *obj = nullptr; \ + return kNotImplemented; \ + } + +//============================================================================== +static String toString (const char8* string) noexcept { return String (string); } +static String toString (const char16* string) noexcept { return String (CharPointer_UTF16 ((CharPointer_UTF16::CharType*) string)); } + +// NB: The casts are handled by a Steinberg::UString operator +static String toString (const UString128& string) noexcept { return toString (static_cast (string)); } +static String toString (const UString256& string) noexcept { return toString (static_cast (string)); } + +//============================================================================== +static int getHashForTUID (const TUID& tuid) noexcept +{ + int value = 0; + + for (int i = 0; i < numElementsInArray (tuid); ++i) + value = (value * 31) + tuid[i]; + + return value; +} + +template +static void fillDescriptionWith (PluginDescription& description, ObjectType& object) +{ + description.version = toString (object.version).trim(); + description.category = toString (object.subCategories).trim(); + + if (description.manufacturerName.isEmpty()) + description.manufacturerName = toString (object.vendor).trim(); +} + +static void createPluginDescription (PluginDescription& description, + const File& pluginFile, const String& company, const String& name, + const PClassInfo& info, PClassInfo2* info2, PClassInfoW* infoW, + int numInputs, int numOutputs) +{ + description.fileOrIdentifier = pluginFile.getFullPathName(); + description.lastFileModTime = pluginFile.getLastModificationTime(); + description.manufacturerName = company; + description.name = name; + description.pluginFormatName = "VST3"; + description.numInputChannels = numInputs; + description.numOutputChannels = numOutputs; + description.uid = getHashForTUID (info.cid); + + if (infoW != nullptr) fillDescriptionWith (description, *infoW); + else if (info2 != nullptr) fillDescriptionWith (description, *info2); + + if (description.category.isEmpty()) + description.category = toString (info.category).trim(); + + description.isInstrument = description.category.containsIgnoreCase ("Instrument"); // This seems to be the only way to find that out! ARGH! +} + +static int getNumSingleDirectionBussesFor (Vst::IComponent* component, + bool checkInputs, + bool checkAudioChannels) +{ + jassert (component != nullptr); + + return (int) component->getBusCount (checkAudioChannels ? Vst::kAudio : Vst::kEvent, + checkInputs ? Vst::kInput : Vst::kOutput); +} + +static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, + bool checkInputs, + bool checkAudioChannels) +{ + jassert (component != nullptr); + + const Vst::BusDirections direction = checkInputs ? Vst::kInput : Vst::kOutput; + const Vst::MediaTypes mediaType = checkAudioChannels ? Vst::kAudio : Vst::kEvent; + const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); + + int numChannels = 0; + + for (Steinberg::int32 i = numBuses; --i >= 0;) + { + Vst::BusInfo busInfo; + warnOnFailure (component->getBusInfo (mediaType, direction, i, busInfo)); + numChannels += busInfo.channelCount; + } + + return numChannels; +} + +static void activateAllBussesOfType (Vst::IComponent* component, + bool activateInputs, + bool activateAudioChannels) +{ + jassert (component != nullptr); + + const Vst::BusDirections direction = activateInputs ? Vst::kInput : Vst::kOutput; + const Vst::MediaTypes mediaType = activateAudioChannels ? Vst::kAudio : Vst::kEvent; + const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); + + for (Steinberg::int32 i = numBuses; --i >= 0;) + warnOnFailure (component->activateBus (mediaType, direction, i, true)); +} + +//============================================================================== +/** Assigns an AudioSampleBuffer's channels to an AudioBusBuffers' */ +static void associateBufferTo (Vst::AudioBusBuffers& vstBuffers, const AudioSampleBuffer& buffer) noexcept +{ + vstBuffers.channelBuffers32 = buffer.getArrayOfChannels(); + vstBuffers.numChannels = buffer.getNumChannels(); + vstBuffers.silenceFlags = 0; +} + +//============================================================================== +static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, double sampleRate) +{ + jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK + + using namespace Vst; + + zerostruct (context); + context.sampleRate = sampleRate; + + if (playHead != nullptr) + { + AudioPlayHead::CurrentPositionInfo position; + playHead->getCurrentPosition (position); + + context.projectTimeSamples = position.timeInSamples; //Must always be valid, as stated by the VST3 SDK + context.projectTimeMusic = position.timeInSeconds; //Does not always need to be valid... + context.tempo = position.bpm; + context.timeSigNumerator = position.timeSigNumerator; + context.timeSigDenominator = position.timeSigDenominator; + context.barPositionMusic = position.ppqPositionOfLastBarStart; + context.cycleStartMusic = position.ppqLoopStart; + context.cycleEndMusic = position.ppqLoopEnd; + + switch (position.frameRate) + { + case AudioPlayHead::fps24: context.frameRate.framesPerSecond = 24; break; + case AudioPlayHead::fps25: context.frameRate.framesPerSecond = 25; break; + case AudioPlayHead::fps30: context.frameRate.framesPerSecond = 30; break; + + case AudioPlayHead::fps2997: + case AudioPlayHead::fps2997drop: + case AudioPlayHead::fps30drop: + { + context.frameRate.framesPerSecond = 30; + context.frameRate.flags = FrameRate::kDropRate; + + if (position.frameRate == AudioPlayHead::fps2997drop) + context.frameRate.flags |= FrameRate::kPullDownRate; + } + break; + + default: jassertfalse; break; // New frame rate? + } + + if (position.isPlaying) context.state |= ProcessContext::kPlaying; + if (position.isRecording) context.state |= ProcessContext::kRecording; + if (position.isLooping) context.state |= ProcessContext::kCycleActive; + } + else + { + context.tempo = 120.0; + context.frameRate.framesPerSecond = 30; + context.timeSigNumerator = 4; + context.timeSigDenominator = 4; + } + + if (context.projectTimeMusic >= 0.0) context.state |= ProcessContext::kProjectTimeMusicValid; + if (context.barPositionMusic >= 0.0) context.state |= ProcessContext::kBarPositionValid; + if (context.tempo > 0.0) context.state |= ProcessContext::kTempoValid; + if (context.frameRate.framesPerSecond > 0) context.state |= ProcessContext::kSmpteValid; + + if (context.cycleStartMusic >= 0.0 + && context.cycleEndMusic > 0.0 + && context.cycleEndMusic > context.cycleStartMusic) + { + context.state |= ProcessContext::kCycleValid; + } + + if (context.timeSigNumerator > 0 && context.timeSigDenominator > 0) + context.state |= ProcessContext::kTimeSigValid; +} + +//============================================================================== +/** Get a list of speaker arrangements as per their speaker names + + (e.g.: 2 regular channels, aliased as 'kStringStereoS', is "L R") +*/ +static StringArray getSpeakerArrangements() +{ + using namespace Vst::SpeakerArr; + + const Vst::CString arrangements[] = + { + kStringMonoS, kStringStereoS, kStringStereoRS, kStringStereoCS, + kStringStereoSS, kStringStereoCLfeS, kString30CineS, kString30MusicS, + kString31CineS, kString31MusicS, kString40CineS, kString40MusicS, + kString41CineS, kString41MusicS, kString50S, kString51S, + kString60CineS, kString60MusicS, kString61CineS, kString61MusicS, + kString70CineS, kString70MusicS, kString71CineS, kString71MusicS, + kString80CineS, kString80MusicS, kString81CineS, kString81MusicS, + kString80CubeS, kStringBFormat1stOrderS, kString71CineTopCenterS, + kString71CineCenterHighS, kString71CineFrontHighS, kString71CineSideHighS, + kString71CineFullRearS, kString90S, kString91S, + kString100S, kString101S, kString110S, kString111S, + kString130S, kString131S, kString102S, kString122S, + nullptr + }; + + return StringArray (arrangements); +} + +/** Get a list of speaker arrangements as per their named configurations + + (e.g.: 2 regular channels, aliased as 'kStringStereoS', is "L R") +*/ +static StringArray getNamedSpeakerArrangements() +{ + using namespace Vst::SpeakerArr; + + const Vst::CString arrangements[] = + { + kStringEmpty, kStringMono, kStringStereo, kStringStereoR, + kStringStereoC, kStringStereoSide, kStringStereoCLfe, kString30Cine, + kString30Music, kString31Cine, kString31Music, kString40Cine, + kString40Music, kString41Cine, kString41Music, kString50, + kString51, kString60Cine, kString60Music, kString61Cine, + kString61Music, kString70Cine, kString70Music, kString71Cine, + kString71Music, kString71CineTopCenter, kString71CineCenterHigh, + kString71CineFrontHigh, kString71CineSideHigh, kString71CineFullRear, + kString80Cine, kString80Music, kString80Cube, kString81Cine, + kString81Music, kString102, kString122, kString90, + kString91, kString100, kString101, kString110, + kString111, kString130, kString131, + nullptr + }; + + return StringArray (arrangements); +} + +static Vst::SpeakerArrangement getSpeakerArrangementFrom (const String& string) +{ + return Vst::SpeakerArr::getSpeakerArrangementFromString (string.toUTF8()); +} + +/** + @note There can only be 1 arrangement per channel count. (i.e.: 4 channels == k31Cine OR k40Cine) +*/ +static void fillWithCorrespondingSpeakerArrangements (Array& destination, + int numChannels) +{ + using namespace Vst::SpeakerArr; + + destination.clearQuick(); + + if (numChannels <= 0) + { + destination.add (kEmpty); + return; + } + + /* + The order of the arrangement checks must be descending, since most plugins test for + the first arrangement to match their number of specified channels. + */ + if (numChannels >= 14) destination.add (k131); + if (numChannels >= 13) destination.add (k130); + if (numChannels >= 12) destination.add (k111); + if (numChannels >= 11) destination.add (k101); + if (numChannels >= 10) destination.add (k91); + if (numChannels >= 9) destination.add (k90); + if (numChannels >= 8) destination.add (k71CineFullFront); + if (numChannels >= 7) destination.add (k61Cine); + if (numChannels >= 6) destination.add (k51); + if (numChannels >= 5) destination.add (k50); + if (numChannels >= 4) destination.add (k31Cine); + if (numChannels >= 3) destination.add (k30Cine); + if (numChannels >= 2) destination.add (kStereo); + if (numChannels >= 1) destination.add (kMono); +} + +//============================================================================== +static StringArray getPluginEffectCategories() +{ + using namespace Vst::PlugType; + + const Vst::CString categories[] = + { + kFxAnalyzer, kFxDelay, kFxDistortion, kFxDynamics, + kFxEQ, kFxFilter, kFx, kFxInstrument, + kFxInstrumentExternal, kFxSpatial, kFxGenerator, kFxMastering, + kFxModulation, kFxPitchShift, kFxRestoration, kFxReverb, + kFxSurround, kFxTools, kSpatial, kSpatialFx, + nullptr + }; + + return StringArray (categories); +} + +static StringArray getPluginInstrumentCategories() +{ + using namespace Vst::PlugType; + + const Vst::CString categories[] = + { + kInstrumentSynthSampler, kInstrumentDrum, + kInstrumentSampler, kInstrumentSynth, + kInstrumentExternal, kFxInstrument, + kFxInstrumentExternal, kFxSpatial, + kFxGenerator, + nullptr + }; + + return StringArray (categories); +} + +//============================================================================== +class MidiEventList : public Vst::IEventList +{ +public: + MidiEventList() {} + virtual ~MidiEventList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + //============================================================================== + void clear() + { + events.clearQuick(); + } + + Steinberg::int32 PLUGIN_API getEventCount() override + { + return (Steinberg::int32) events.size(); + } + + // NB: This has to cope with out-of-range indexes from some plugins. + tresult PLUGIN_API getEvent (Steinberg::int32 index, Vst::Event& e) override + { + if (isPositiveAndBelow ((int) index, events.size())) + { + e = events.getReference ((int) index); + return kResultTrue; + } + + return kResultFalse; + } + + tresult PLUGIN_API addEvent (Vst::Event& e) override + { + events.add (e); + return kResultTrue; + } + + //============================================================================== + static void toMidiBuffer (MidiBuffer& result, Vst::IEventList& eventList) + { + using namespace Vst; + + for (Steinberg::int32 i = 0; i < eventList.getEventCount(); ++i) + { + Event event; + + if (eventList.getEvent (i, event) == kResultOk) + { + switch (event.type) + { + case Event::kNoteOnEvent: + result.addEvent (MidiMessage::noteOn (event.noteOn.channel + 1, event.noteOn.pitch, (uint8) (event.noteOn.velocity * 127.0f)), + event.sampleOffset); + break; + + case Event::kNoteOffEvent: + result.addEvent (MidiMessage::noteOff (event.noteOff.channel + 1, event.noteOff.pitch, (uint8) (event.noteOff.velocity * 127.0f)), + event.sampleOffset); + break; + + case Event::kPolyPressureEvent: + result.addEvent (MidiMessage::aftertouchChange (event.polyPressure.channel + 1, event.polyPressure.pitch, (int) (event.polyPressure.pressure * 127.0f)), + event.sampleOffset); + break; + + case Event::kDataEvent: + result.addEvent (MidiMessage::createSysExMessage (event.data.bytes, event.data.size), + event.sampleOffset); + break; + + default: + break; + } + } + } + } + + static void toEventList (Vst::IEventList& result, MidiBuffer& midiBuffer) + { + using namespace Vst; + + MidiBuffer::Iterator iterator (midiBuffer); + MidiMessage msg; + int midiEventPosition = 0; + + while (iterator.getNextEvent (msg, midiEventPosition)) + { + Event event = { 0 }; + + if (msg.isNoteOn()) + { + event.type = Event::kNoteOnEvent; + event.noteOn.channel = (Steinberg::int16) msg.getChannel() - 1; + event.noteOn.pitch = (Steinberg::int16) msg.getNoteNumber(); + event.noteOn.velocity = msg.getVelocity() / 127.0f; + } + else if (msg.isNoteOff()) + { + event.type = Event::kNoteOffEvent; + event.noteOff.channel = (Steinberg::int16) msg.getChannel() - 1; + event.noteOff.pitch = (Steinberg::int16) msg.getNoteNumber(); + event.noteOff.velocity = msg.getVelocity() / 127.0f; + } + else if (msg.isSysEx()) + { + event.type = Event::kDataEvent; + event.data.bytes = msg.getSysExData(); + event.data.size = msg.getSysExDataSize(); + } + else if (msg.isAftertouch()) + { + event.type = Event::kPolyPressureEvent; + event.polyPressure.channel = (Steinberg::int16) msg.getChannel() - 1; + event.polyPressure.pitch = (Steinberg::int16) msg.getNoteNumber(); + event.polyPressure.pressure = msg.getAfterTouchValue() / 127.0f; + } + + event.busIndex = 0; + event.sampleOffset = midiEventPosition; + + result.addEvent (event); + } + } + +private: + Array events; + Atomic refCount; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) +}; + +//============================================================================== +class VST3PluginInstance; + +class VST3HostContext : public Vst::IComponentHandler, //From VST V3.0.0 + public Vst::IComponentHandler2, //From VST V3.1.0 (a very well named class, of course!) + public Vst::IComponentHandler3, //From VST V3.5.0 (also very well named!) + public Vst::IContextMenuTarget, + public Vst::IHostApplication, + public Vst::IParamValueQueue, + public Vst::IUnitHandler +{ +public: + VST3HostContext (VST3PluginInstance* pluginInstance) : owner (pluginInstance) + { + appName = File::getSpecialLocation (File::currentApplicationFile).getFileNameWithoutExtension(); + attributeList = new AttributeList (this); + } + + virtual ~VST3HostContext() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + + //============================================================================== + tresult PLUGIN_API beginEdit (Vst::ParamID) override + { + // XXX todo.. + return kResultFalse; + } + + tresult PLUGIN_API performEdit (Vst::ParamID id, Vst::ParamValue valueNormalized) override + { + if (owner != nullptr) + return owner->editController->setParamNormalized (id, valueNormalized); + + return kResultFalse; + } + + tresult PLUGIN_API endEdit (Vst::ParamID) override + { + // XXX todo.. + return kResultFalse; + } + + tresult PLUGIN_API restartComponent (Steinberg::int32) override + { + if (owner != nullptr) + { + owner->reset(); + owner->updateHostDisplay(); + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API setDirty (TBool) override + { + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API requestOpenEditor (FIDString name) override + { + (void) name; + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API startGroupEdit() override + { + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API finishGroupEdit() override + { + jassertfalse; + return kResultFalse; + } + + Vst::IContextMenu* PLUGIN_API createContextMenu (IPlugView*, const Vst::ParamID*) override + { + jassertfalse; + return nullptr; + } + + tresult PLUGIN_API executeMenuItem (Steinberg::int32) override + { + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API getName (Vst::String128 name) override + { + Steinberg::String str (appName.toUTF8()); + str.copyTo (name, 0, 127); + return kResultOk; + } + + tresult PLUGIN_API createInstance (TUID cid, TUID iid, void** obj) override + { + *obj = nullptr; + + if (! doIdsMatch (cid, iid)) + { + jassertfalse; + return kInvalidArgument; + } + + if (doIdsMatch (cid, Vst::IMessage::iid) && doIdsMatch (iid, Vst::IMessage::iid)) + { + ComSmartPtr m (new Message (*this, attributeList)); + messageQueue.add (m); + m->addRef(); + *obj = m; + return kResultOk; + } + else if (doIdsMatch (cid, Vst::IAttributeList::iid) && doIdsMatch (iid, Vst::IAttributeList::iid)) + { + ComSmartPtr l (new AttributeList (this)); + l->addRef(); + *obj = l; + return kResultOk; + } + + jassertfalse; + return kNotImplemented; + } + + //============================================================================== + Vst::ParamID PLUGIN_API getParameterId() override + { + jassertfalse; + return 0; + } + + Steinberg::int32 PLUGIN_API getPointCount() override + { + jassertfalse; + return 0; + } + + tresult PLUGIN_API getPoint (Steinberg::int32, Steinberg::int32&, Vst::ParamValue&) override + { + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API addPoint (Steinberg::int32, Vst::ParamValue, Steinberg::int32&) override + { + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API notifyUnitSelection (Vst::UnitID) override + { + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) override + { + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override + { + if (doIdsMatch (iid, Vst::IAttributeList::iid)) + { + *obj = dynamic_cast (attributeList.get()); + return kResultOk; + } + + #define TEST_FOR_AND_RETURN_IF_VALID(ClassType) \ + if (doIdsMatch (iid, ClassType::iid)) \ + { \ + addRef(); \ + *obj = dynamic_cast (this); \ + return kResultOk; \ + } + + TEST_FOR_AND_RETURN_IF_VALID (Vst::IComponentHandler) + TEST_FOR_AND_RETURN_IF_VALID (Vst::IComponentHandler2) + TEST_FOR_AND_RETURN_IF_VALID (Vst::IComponentHandler3) + TEST_FOR_AND_RETURN_IF_VALID (Vst::IContextMenuTarget) + TEST_FOR_AND_RETURN_IF_VALID (Vst::IHostApplication) + TEST_FOR_AND_RETURN_IF_VALID (Vst::IParamValueQueue) + TEST_FOR_AND_RETURN_IF_VALID (Vst::IUnitHandler) + #undef TEST_FOR_AND_RETURN_IF_VALID + + *obj = nullptr; + return kNotImplemented; + } + +private: + //============================================================================== + Atomic refCount; + String appName; + VST3PluginInstance* owner; + + //============================================================================== + static bool doIdsMatch (const TUID a, const TUID b) noexcept + { + return std::memcmp (a, b, sizeof (TUID)) == 0; + } + + //============================================================================== + class Message : public Vst::IMessage + { + public: + Message (VST3HostContext& o, Vst::IAttributeList* list) + : owner (o), attributeList (list) + { + } + + Message (VST3HostContext& o, Vst::IAttributeList* list, FIDString id) + : owner (o), attributeList (list), messageId (toString (id)) + { + } + + Message (VST3HostContext& o, Vst::IAttributeList* list, FIDString id, const var& v) + : value (v), owner (o), attributeList (list), messageId (toString (id)) + { + } + + virtual ~Message() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + FIDString PLUGIN_API getMessageID() { return messageId.toRawUTF8(); } + void PLUGIN_API setMessageID (FIDString id) { messageId = toString (id); } + Vst::IAttributeList* PLUGIN_API getAttributes() { return attributeList; } + + var value; + + private: + VST3HostContext& owner; + ComSmartPtr attributeList; + String messageId; + Atomic refCount; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) + }; + + Array, CriticalSection> messageQueue; + + //============================================================================== + class AttributeList : public Vst::IAttributeList + { + public: + AttributeList (VST3HostContext* o) : owner (o) {} + virtual ~AttributeList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + //============================================================================== + tresult PLUGIN_API setInt (AttrID id, Steinberg::int64 value) override + { + jassert (id != nullptr); + + if (! setValueForId (id, value)) + owner->messageQueue.add (ComSmartPtr (new Message (*owner, this, id, value))); + + return kResultTrue; + } + + tresult PLUGIN_API setFloat (AttrID id, double value) override + { + jassert (id != nullptr); + + if (! setValueForId (id, value)) + owner->messageQueue.add (ComSmartPtr (new Message (*owner, this, id, value))); + + return kResultTrue; + } + + tresult PLUGIN_API setString (AttrID id, const Vst::TChar* string) override + { + jassert (id != nullptr); + jassert (string != nullptr); + + const String text (toString (string)); + + if (! setValueForId (id, text)) + owner->messageQueue.add (ComSmartPtr (new Message (*owner, this, id, text))); + + return kResultTrue; + } + + tresult PLUGIN_API setBinary (AttrID id, const void* data, Steinberg::uint32 size) override + { + jassert (id != nullptr); + jassert (data != nullptr && size > 0); + + MemoryBlock block (data, (size_t) size); + + if (! setValueForId (id, block)) + owner->messageQueue.add (ComSmartPtr (new Message (*owner, this, id, block))); + + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API getInt (AttrID id, Steinberg::int64& result) override + { + jassert (id != nullptr); + + if (fetchValueForId (id, result)) + return kResultTrue; + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getFloat (AttrID id, double& result) override + { + jassert (id != nullptr); + + if (fetchValueForId (id, result)) + return kResultTrue; + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getString (AttrID id, Vst::TChar* result, Steinberg::uint32 length) override + { + jassert (id != nullptr); + + String stringToFetch; + if (fetchValueForId (id, stringToFetch)) + { + Steinberg::String str (stringToFetch.toRawUTF8()); + str.copyTo (result, 0, (Steinberg::int32) jmin (length, (Steinberg::uint32) std::numeric_limits::max())); + + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getBinary (AttrID id, const void*& data, Steinberg::uint32& size) override + { + jassert (id != nullptr); + + for (int i = owner->messageQueue.size(); --i >= 0;) + { + Message* const message = owner->messageQueue.getReference (i); + + if (std::strcmp (message->getMessageID(), id) == 0) + { + if (MemoryBlock* binaryData = message->value.getBinaryData()) + { + data = binaryData->getData(); + size = (Steinberg::uint32) binaryData->getSize(); + return kResultTrue; + } + } + } + + return kResultFalse; + } + + private: + VST3HostContext* owner; + Atomic refCount; + + //============================================================================== + template + bool setValueForId (AttrID id, const Type& value) + { + jassert (id != nullptr); + + for (int i = owner->messageQueue.size(); --i >= 0;) + { + VST3HostContext::Message* const message = owner->messageQueue.getReference (i); + + if (std::strcmp (message->getMessageID(), id) == 0) + { + message->value = value; + return true; + } + } + + return false; // No message found with that Id + } + + template + bool fetchValueForId (AttrID id, Type& value) + { + jassert (id != nullptr); + + for (int i = owner->messageQueue.size(); --i >= 0;) + { + VST3HostContext::Message* const message = owner->messageQueue.getReference (i); + + if (std::strcmp (message->getMessageID(), id) == 0) + { + value = message->value; + return true; + } + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) + }; + + ComSmartPtr attributeList; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext) +}; + +//============================================================================== +class DescriptionFactory +{ +public: + DescriptionFactory (VST3HostContext* host, IPluginFactory* pluginFactory) + : vst3HostContext (host), factory (pluginFactory) + { + jassert (pluginFactory != nullptr); + } + + virtual ~DescriptionFactory() {} + + Result findDescriptionsAndPerform (const File& file) + { + StringArray foundNames; + PFactoryInfo factoryInfo; + factory->getFactoryInfo (&factoryInfo); + const String companyName (toString (factoryInfo.vendor).trim()); + + Result result (Result::ok()); + + const Steinberg::int32 numClasses = factory->countClasses(); + for (Steinberg::int32 i = 0; i < numClasses; ++i) + { + PClassInfo info; + factory->getClassInfo (i, &info); + + if (std::strcmp (info.category, kVstAudioEffectClass) != 0) + continue; + + const String name (toString (info.name).trim()); + + if (foundNames.contains (name, true)) + continue; + + ScopedPointer info2; + ScopedPointer infoW; + + { + ComSmartPtr pf2; + ComSmartPtr pf3; + + if (pf2.loadFrom (factory)) + { + info2 = new PClassInfo2(); + pf2->getClassInfo2 (i, info2); + } + + if (pf3.loadFrom (factory)) + { + infoW = new PClassInfoW(); + pf3->getClassInfoUnicode (i, infoW); + } + } + + foundNames.add (name); + + int numInputs = 0, numOutputs = 0; + + { + ComSmartPtr component; + + if (component.loadFrom (factory, info)) + { + if (component->initialize (dynamic_cast (vst3HostContext.get())) == kResultOk) + { + numInputs = getNumSingleDirectionChannelsFor (component, true, true); + numOutputs = getNumSingleDirectionChannelsFor (component, false, true); + + component->terminate(); + } + else + { + jassertfalse; + } + } + } + + PluginDescription desc; + + createPluginDescription (desc, file, companyName, name, + info, info2, infoW, numInputs, numOutputs); + + result = performOnDescription (desc); + + if (result.failed()) + break; + } + + return result; + } + +protected: + virtual Result performOnDescription (PluginDescription& description) = 0; + +private: + ComSmartPtr vst3HostContext; + ComSmartPtr factory; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionFactory) +}; + +struct MatchingDescriptionFinder : public DescriptionFactory +{ + MatchingDescriptionFinder (VST3HostContext* host, IPluginFactory* pluginFactory, const PluginDescription& desc) + : DescriptionFactory (host, pluginFactory), description (desc) + { + } + + static const char* getSuccessString() noexcept { return "Found Description"; } + + Result performOnDescription (PluginDescription& desc) + { + if (description.isDuplicateOf (desc)) + return Result::fail (getSuccessString()); + + return Result::ok(); + } + +private: + const PluginDescription& description; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MatchingDescriptionFinder) +}; + +struct DescriptionLister : public DescriptionFactory +{ + DescriptionLister (VST3HostContext* host, IPluginFactory* pluginFactory) + : DescriptionFactory (host, pluginFactory) + { + } + + Result performOnDescription (PluginDescription& desc) + { + list.add (new PluginDescription (desc)); + return Result::ok(); + } + + OwnedArray list; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionLister) +}; + +//============================================================================== +struct DLLHandle +{ + DLLHandle (const String& modulePath) + { + if (modulePath.trim().isNotEmpty()) + open (modulePath); + } + + ~DLLHandle() + { + typedef bool (PLUGIN_API *ExitModuleFn) (); + + #if JUCE_WINDOWS + if (ExitModuleFn exitFn = (ExitModuleFn) getFunction ("ExitDll")) + exitFn(); + + library.close(); + + #else + if (bundleRef != nullptr) + { + if (ExitModuleFn exitFn = (ExitModuleFn) getFunction ("bundleExit")) + exitFn(); + + CFRelease (bundleRef); + bundleRef = nullptr; + } + #endif + } + + void open (const PluginDescription& description) + { + #if JUCE_WINDOWS + jassert (description.fileOrIdentifier.isNotEmpty()); + jassert (File (description.fileOrIdentifier).existsAsFile()); + library.open (description.fileOrIdentifier); + #else + open (description.fileOrIdentifier); + #endif + } + + IPluginFactory* JUCE_CALLTYPE getPluginFactory() + { + if (GetFactoryProc proc = (GetFactoryProc) getFunction ("GetPluginFactory")) + return proc(); + + return nullptr; + } + + void* getFunction (const char* functionName) + { + #if JUCE_WINDOWS + return library.getFunction (functionName); + #else + if (bundleRef == nullptr) + return nullptr; + + CFStringRef name = String (functionName).toCFString(); + void* fn = CFBundleGetFunctionPointerForName (bundleRef, name); + CFRelease (name); + return fn; + #endif + } + +private: + #if JUCE_WINDOWS + DynamicLibrary library; + + bool open (const String& filePath) + { + if (library.open (filePath)) + { + typedef bool (PLUGIN_API *InitModuleProc) (); + if (InitModuleProc proc = (InitModuleProc) getFunction ("InitDll")) + { + if (proc()) + return true; + } + else + { + return true; + } + + library.close(); + } + + return false; + } + + #else + CFBundleRef bundleRef; + + bool open (const String& filePath) + { + const File file (filePath); + const char* const utf8 = file.getFullPathName().toRawUTF8(); + + if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, (CFIndex) std::strlen (utf8), file.isDirectory())) + { + bundleRef = CFBundleCreate (kCFAllocatorDefault, url); + CFRelease (url); + + if (bundleRef != 0) + { + CFErrorRef error = 0; + + if (CFBundleLoadExecutableAndReturnError (bundleRef, &error)) + { + typedef bool (*BundleEntryProc)(CFBundleRef); + + if (BundleEntryProc proc = (BundleEntryProc) getFunction ("bundleEntry")) + { + if (proc (bundleRef)) + return true; + } + else + { + return true; + } + } + + if (error != 0) + { + if (CFStringRef failureMessage = CFErrorCopyFailureReason (error)) + { + DBG (String::fromCFString (failureMessage)); + CFRelease (failureMessage); + } + + CFRelease (error); + } + + CFRelease (bundleRef); + bundleRef = 0; + } + } + + return false; + } + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DLLHandle) +}; + +//============================================================================== +class VST3ModuleHandle : public ReferenceCountedObject +{ +public: + explicit VST3ModuleHandle (const File& pluginFile) : file (pluginFile) + { + getActiveModules().add (this); + } + + ~VST3ModuleHandle() + { + getActiveModules().removeFirstMatchingValue (this); + } + + /** + Since there is no apparent indication if a VST3 plugin is a shell or not, + we're stuck iterating through a VST3's factory, creating a description + for every housed plugin. + */ + static bool getAllDescriptionsForFile (OwnedArray& results, + const String& fileOrIdentifier) + { + DLLHandle tempModule (fileOrIdentifier); + + ComSmartPtr pluginFactory (tempModule.getPluginFactory()); + + if (pluginFactory != nullptr) + { + ComSmartPtr host (new VST3HostContext (nullptr)); + DescriptionLister lister (host, pluginFactory); + const Result result (lister.findDescriptionsAndPerform (File (fileOrIdentifier))); + + results.addCopiesOf (lister.list); + + return result.wasOk(); + } + + return false; + } + + //============================================================================== + typedef ReferenceCountedObjectPtr Ptr; + + static VST3ModuleHandle::Ptr findOrCreateModule (const File& file, const PluginDescription& description) + { + Array& activeModules = getActiveModules(); + + for (int i = activeModules.size(); --i >= 0;) + { + VST3ModuleHandle* const module = activeModules.getUnchecked (i); + + // VST3s are basically shells, you must therefore check their name along with their file: + if (module->file == file && module->name == description.name) + return module; + } + + VST3ModuleHandle::Ptr m (new VST3ModuleHandle (file)); + + if (! m->open (file, description)) + m = nullptr; + + return m; + } + + //============================================================================== + IPluginFactory* getPluginFactory() { return dllHandle->getPluginFactory(); } + + File file; + String name; + +private: + ScopedPointer dllHandle; + + //============================================================================== + static Array& getActiveModules() + { + static Array activeModules; + return activeModules; + } + + //============================================================================== + bool open (const File& file, const PluginDescription& description) + { + dllHandle = new DLLHandle (file.getFullPathName()); + + ComSmartPtr pluginFactory (dllHandle->getPluginFactory()); + + ComSmartPtr host (new VST3HostContext (nullptr)); + MatchingDescriptionFinder finder (host, pluginFactory, description); + + const Result result (finder.findDescriptionsAndPerform (file)); + + if (result.getErrorMessage() == MatchingDescriptionFinder::getSuccessString()) + { + name = description.name; + return true; + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) +}; + +//============================================================================== +class VST3PluginWindow : public AudioProcessorEditor, + public ComponentMovementWatcher, + public IPlugFrame +{ +public: + VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) + : AudioProcessorEditor (owner), + ComponentMovementWatcher (this), + view (pluginView), + pluginHandle (nullptr), + recursiveResize (false) + { + setSize (10, 10); + setOpaque (true); + setVisible (true); + + ViewRect rect; + warnOnFailure (view->getSize (&rect)); + resizeWithRect (*this, rect); + + view->setFrame (this); // Done after to avoid recursive calls from plugins... + } + + ~VST3PluginWindow() + { + view->removed(); + getAudioProcessor()->editorBeingDeleted (this); + + #if JUCE_MAC + dummyComponent.setView (nullptr); + [pluginHandle release]; + #endif + } + + Steinberg::uint32 PLUGIN_API addRef() override { return 1; } + Steinberg::uint32 PLUGIN_API release() override { return 1; } + + tresult PLUGIN_API queryInterface (const TUID, void** obj) override + { + *obj = nullptr; + return kNotImplemented; + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override + { + view->onWheel (wheel.deltaY); + } + + void focusGained (FocusChangeType) override { view->onFocus (true); } + void focusLost (FocusChangeType) override { view->onFocus (false); } + + /** It seems that most, if not all, plugins do their own keyboard hooks, + but IPlugView does have a set of keyboard related methods... + */ + bool keyStateChanged (bool /*isKeyDown*/) override { return true; } + bool keyPressed (const KeyPress& /*key*/) override { return true; } + + //============================================================================== + void componentMovedOrResized (bool, bool wasResized) override + { + if (recursiveResize) + return; + + Component* const topComp = getTopLevelComponent(); + + if (topComp->getPeer() != nullptr) + { + #if JUCE_WINDOWS + const Point pos (topComp->getLocalPoint (this, Point())); + #endif + + recursiveResize = true; + + ViewRect rect; + + if (wasResized && view->canResize() == kResultTrue) + { + rect.right = (Steinberg::int32) getWidth(); + rect.bottom = (Steinberg::int32) getHeight(); + view->onSize (&rect); + } + else + { + view->getSize (&rect); + } + + #if JUCE_WINDOWS + SetWindowPos (pluginHandle, 0, + pos.x, pos.y, rect.getWidth(), rect.getHeight(), + isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); + #elif JUCE_MAC + dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); + #endif + + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); //Some plugins don't update their cursor correctly when mousing out the window + + recursiveResize = false; + } + } + + void componentPeerChanged() override { } + + void componentVisibilityChanged() override + { + attachPluginWindow(); + componentMovedOrResized (true, true); + } + + tresult PLUGIN_API resizeView (IPlugView* incomingView, ViewRect* newSize) override + { + if (incomingView != nullptr + && newSize != nullptr + && incomingView == view) + { + resizeWithRect (dummyComponent, *newSize); + setSize (dummyComponent.getWidth(), dummyComponent.getHeight()); + return kResultTrue; + } + + jassertfalse; + return kInvalidArgument; + } + +private: + //============================================================================== + ComSmartPtr view; + + #if JUCE_WINDOWS + struct ChildComponent : public Component + { + ChildComponent() {} + void paint (Graphics& g) { g.fillAll (Colours::cornflowerblue); } + + using Component::createNewPeer; + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent) + }; + + ChildComponent dummyComponent; + ScopedPointer peer; + typedef HWND HandleFormat; + #elif JUCE_MAC + NSViewComponent dummyComponent; + typedef NSView* HandleFormat; + #else + Component dummyComponent; + typedef void* HandleFormat; + #endif + + HandleFormat pluginHandle; //< Don't delete this + bool recursiveResize; + + //============================================================================== + static void resizeWithRect (Component& comp, const ViewRect& rect) + { + comp.setBounds (rect.left, rect.top, + jmax (10, std::abs ((int) rect.getWidth())), + jmax (10, std::abs ((int) rect.getHeight()))); + } + + void attachPluginWindow() + { + if (pluginHandle == nullptr) + { + #if JUCE_WINDOWS + if (Component* topComp = getTopLevelComponent()) + peer = dummyComponent.createNewPeer (0, topComp->getWindowHandle()); + else + peer = nullptr; + + if (peer != nullptr) + pluginHandle = (HandleFormat) peer->getNativeHandle(); + #elif JUCE_MAC + dummyComponent.setBounds (getBounds().withZeroOrigin()); + addAndMakeVisible (&dummyComponent); + pluginHandle = [[NSView alloc] init]; + dummyComponent.setView (pluginHandle); + #endif + + if (pluginHandle != nullptr) + view->attached (pluginHandle, + #if JUCE_WINDOWS + kPlatformTypeHWND); + #else + kPlatformTypeNSView); + #endif + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) +}; + +//============================================================================== +class VST3PluginInstance : public AudioPluginInstance +{ +public: + VST3PluginInstance (const VST3ModuleHandle::Ptr& handle) + : module (handle), + result (1, 1), + isComponentInitialised (false), + isControllerInitialised (false) + { + midiInputs = new MidiEventList(); + midiOutputs = new MidiEventList(); + inputParameterChanges = new ParameterChangeList(); + outputParameterChanges = new ParameterChangeList(); + + host = new VST3HostContext (this); + initialise(); + } + + ~VST3PluginInstance() + { + jassert (getActiveEditor() == nullptr); // You must delete any editors before deleting the plugin instance! + + releaseResources(); + + if (editControllerConnection != nullptr && componentConnection != nullptr) + { + editControllerConnection->disconnect (componentConnection); + componentConnection->disconnect (editControllerConnection); + } + + editController->setComponentHandler (nullptr); + + if (isControllerInitialised) editController->terminate(); + if (isComponentInitialised) component->terminate(); + + editController = nullptr; + component = nullptr; + } + + //============================================================================== + void fillInPluginDescription (PluginDescription& description) const override + { + jassert (module != nullptr); + + createPluginDescription (description, module->file, + company, module->name, + *info, info2, infoW, + getNumInputChannels(), + getNumOutputChannels()); + } + + void* getPlatformSpecificData() override { return component; } + void refreshParameterList() override {} + + //============================================================================== + const String getName() const override + { + return module != nullptr ? module->name : String::empty; + } + + void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) override + { + using namespace Vst; + + ProcessSetup setup; + setup.symbolicSampleSize = kSample32; + setup.maxSamplesPerBlock = estimatedSamplesPerBlock; + setup.sampleRate = sampleRate; + setup.processMode = isNonRealtime() ? kOffline : kRealtime; + + warnOnFailure (processor->setupProcessing (setup)); + + if (! isControllerInitialised) + isControllerInitialised = editController->initialize (dynamic_cast (host.get())) == kResultTrue; + + if (! isComponentInitialised) + isComponentInitialised = component->initialize (dynamic_cast (host.get())) == kResultTrue; + + editController->setComponentHandler (host); + + warnOnFailure (component->setActive (true)); + warnOnFailure (processor->setProcessing (true)); + + Array inArrangements, outArrangements; + + fillWithCorrespondingSpeakerArrangements (inArrangements, getNumInputChannels()); + fillWithCorrespondingSpeakerArrangements (outArrangements, getNumOutputChannels()); + + processor->setBusArrangements (inArrangements.getRawDataPointer(), + getNumSingleDirectionBussesFor (component, true, true), + outArrangements.getRawDataPointer(), + getNumSingleDirectionBussesFor (component, false, true)); + } + + void releaseResources() override + { + processor->setProcessing (false); + component->setActive (false); + } + + void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override + { + using namespace Vst; + + if (processor != nullptr + && processor->canProcessSampleSize (kSample32) == kResultTrue) + { + ProcessData data; + data.processMode = kRealtime; + data.symbolicSampleSize = kSample32; + data.numInputs = getNumInputChannels(); + data.numOutputs = getNumOutputChannels(); + data.inputParameterChanges = inputParameterChanges; + data.outputParameterChanges = outputParameterChanges; + + updateTimingInformation (data, getSampleRate()); + + for (int i = data.numInputs; i < buffer.getNumChannels(); ++i) + buffer.clear (i, 0, buffer.getNumSamples()); + + associateTo (data, buffer); + associateTo (data, midiMessages); + + processor->process (data); + + MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); + } + } + + //============================================================================== + String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const + { + const int numBusses = getNumSingleDirectionBussesFor (component, forInput, forAudioChannel); + int numCountedChannels = 0; + + for (int i = 0; i < numBusses; ++i) + { + Vst::BusInfo busInfo (getBusInfo (forInput, forAudioChannel, i)); + + numCountedChannels += busInfo.channelCount; + + if (channelIndex < numCountedChannels) + return toString (busInfo.name); + } + + return String::empty; + } + + const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, true, true); } + const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, false, true); } + + bool isInputChannelStereoPair (int channelIndex) const override + { + if (channelIndex < 0 || channelIndex >= getNumInputChannels()) + return false; + + return getBusInfo (true, true).channelCount == 2; + } + + bool isOutputChannelStereoPair (int channelIndex) const override + { + if (channelIndex < 0 || channelIndex >= getNumOutputChannels()) + return false; + + return getBusInfo (false, true).channelCount == 2; + } + + bool acceptsMidi() const override { return getBusInfo (true, false).channelCount > 0; } + bool producesMidi() const override { return getBusInfo (false, false).channelCount > 0; } + + //============================================================================== + bool silenceInProducesSilenceOut() const override + { + return processor == nullptr; + } + + /** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */ + double getTailLengthSeconds() const override + { + if (processor != nullptr) + return (double) jmin ((int) jmax ((Steinberg::uint32) 0, processor->getTailSamples()), 0x7fffffff) + * getSampleRate(); + + return 0.0; + } + + //============================================================================== + AudioProcessorEditor* createEditor() override + { + if (view == nullptr) + view = tryCreatingView(); + + if (view != nullptr) + return new VST3PluginWindow (this, view); + + return nullptr; + } + + bool hasEditor() const override + { + if (view == nullptr) + view = tryCreatingView(); + + return view != nullptr; + } + + //============================================================================== + int getNumParameters() override + { + if (editController != nullptr) + return (int) editController->getParameterCount(); + + return 0; + } + + const String getParameterName (int parameterIndex) override + { + return toString (getParameterInfoForIndex (parameterIndex).title); + } + + float getParameter (int parameterIndex) override + { + if (editController != nullptr) + { + const uint32 id = getParameterInfoForIndex (parameterIndex).id; + return (float) editController->getParamNormalized (id); + } + + return 0.0f; + } + + const String getParameterText (int parameterIndex) override + { + if (editController != nullptr) + { + const uint32 id = getParameterInfoForIndex (parameterIndex).id; + + Vst::String128 result; + warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result)); + + return toString (result); + } + + return String::empty; + } + + void setParameter (int parameterIndex, float newValue) override + { + if (editController != nullptr) + { + const uint32 id = getParameterInfoForIndex (parameterIndex).id; + editController->setParamNormalized (id, (double) newValue); + } + } + + //============================================================================== + int getNumPrograms() override { return getProgramListInfo (0).programCount; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + void changeProgramName (int, const String&) override {} + + const String getProgramName (int index) override + { + Vst::String128 result; + unitInfo->getProgramName (getProgramListInfo (0).id, index, result); + return toString (result); + } + + //============================================================================== + void getStateInformation (MemoryBlock& destData) override + { + XmlElement state ("VST3PluginState"); + + appendStateFrom (state, component, "IComponent"); + appendStateFrom (state, editController, "IEditController"); + + AudioProcessor::copyXmlToBinary (state, destData); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + ScopedPointer head (AudioProcessor::getXmlFromBinary (data, sizeInBytes)); + + if (head != nullptr) + { + ScopedPointer s (createMemoryStreamForState (*head, "IComponent")); + + if (s != nullptr && component != nullptr) + component->setState (s); + + if (editController != nullptr) + { + if (s != nullptr) + editController->setComponentState (s); + + s = createMemoryStreamForState (*head, "IEditController"); + + if (s != nullptr) + editController->setState (s); + } + } + } + + /** @note Not applicable to VST3 */ + void getCurrentProgramStateInformation (MemoryBlock& destData) override + { + destData.setSize (0, true); + } + + /** @note Not applicable to VST3 */ + void setCurrentProgramStateInformation (const void* data, int sizeInBytes) override + { + (void) data; + (void) sizeInBytes; + } + + ComSmartPtr editController; + +private: + //============================================================================== + VST3ModuleHandle::Ptr module; + ComSmartPtr host; + + // Information objects: + String company; + ScopedPointer info; + ScopedPointer info2; + ScopedPointer infoW; + + // Rudimentary interfaces: + ComSmartPtr processor; + ComSmartPtr component; + ComSmartPtr componentHandler; + ComSmartPtr componentHandler2; + ComSmartPtr editController2; + ComSmartPtr unitInfo; + ComSmartPtr programListData; + ComSmartPtr unitData; + ComSmartPtr componentConnection; + ComSmartPtr editControllerConnection; + + mutable ComSmartPtr view; + + AudioSampleBuffer result; + Vst::AudioBusBuffers inputs, outputs; + + //============================================================================== + template + static void appendStateFrom (XmlElement& head, ComSmartPtr& object, const String& identifier) + { + if (object != nullptr) + { + Steinberg::MemoryStream stream; + + if (object->getState (&stream) == kResultTrue) + { + MemoryBlock info (stream.getData(), (std::size_t) stream.getSize()); + head.createNewChildElement (identifier)->addTextElement (info.toBase64Encoding()); + } + } + } + + static Steinberg::MemoryStream* createMemoryStreamForState (XmlElement& head, StringRef identifier) + { + Steinberg::MemoryStream* stream = nullptr; + + if (XmlElement* const state = head.getChildByName (identifier)) + { + MemoryBlock mem; + + if (mem.fromBase64Encoding (state->getAllSubText())) + stream = new Steinberg::MemoryStream (mem.getData(), (TSize) mem.getSize()); + } + + return stream; + } + + //============================================================================== + class ParameterChangeList : public Vst::IParameterChanges + { + public: + ParameterChangeList() {} + virtual ~ParameterChangeList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + Steinberg::int32 PLUGIN_API getParameterCount() { return 0; } + + Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32) + { + return nullptr; + } + + Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID&, Steinberg::int32& index) + { + index = 0; + return nullptr; + } + + private: + Atomic refCount; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterChangeList) + }; + + ComSmartPtr inputParameterChanges, outputParameterChanges; + ComSmartPtr midiInputs, midiOutputs; + Vst::ProcessContext timingInfo; //< Only use this in processBlock()! + + bool isComponentInitialised; + bool isControllerInitialised; + + //============================================================================== + void fetchComponentAndController (IPluginFactory* factory, const Steinberg::int32 numClasses) + { + jassert (numClasses >= 0); // The plugin must provide at least an IComponent and IEditController! + + for (Steinberg::int32 i = 0; i < numClasses; ++i) + { + info = new PClassInfo(); + factory->getClassInfo (i, info); + + if (std::strcmp (info->category, kVstAudioEffectClass) != 0) + continue; + + const String name (toString (info->name).trim()); + + if (module->name != name) + continue; + + { + ComSmartPtr pf2; + ComSmartPtr pf3; + + if (pf2.loadFrom (factory)) + { + info2 = new PClassInfo2(); + pf2->getClassInfo2 (i, info2); + } + else + { + info2 = nullptr; + } + + if (pf3.loadFrom (factory)) + { + pf3->setHostContext (dynamic_cast (host.get())); + infoW = new PClassInfoW(); + pf3->getClassInfoUnicode (i, infoW); + } + else + { + infoW = nullptr; + } + } + + bool failed = true; + + if (component.loadFrom (factory, *info) && component != nullptr) + { + warnOnFailure (component->setIoMode (isNonRealtime() ? Vst::kOffline : Vst::kRealtime)); + + if (component->initialize (dynamic_cast (host.get())) == kResultOk) + { + isComponentInitialised = true; + + // Get the IEditController: + TUID controllerCID = { 0 }; + + if (component->getControllerClassId (controllerCID) == kResultTrue && FUID (controllerCID).isValid()) + editController.loadFrom (factory, controllerCID); + + if (editController == nullptr) + editController.loadFrom (component); + + if (editController == nullptr) + { + // Try finding the IEditController the long way around: + for (Steinberg::int32 i = 0; i < numClasses; ++i) + { + PClassInfo classInfo; + factory->getClassInfo (i, &classInfo); + + if (std::strcmp (classInfo.category, kVstComponentControllerClass) == 0) + editController.loadFrom (factory, classInfo); + } + } + + failed = editController == nullptr; + } + else + { + jassertfalse; + } + } + + if (failed) + { + jassertfalse; // The plugin won't function without a valid IComponent and IEditController implementation! + + if (component != nullptr) + { + component->terminate(); + component = nullptr; + } + + if (editController != nullptr) + { + editController->terminate(); + editController = nullptr; + } + + return; + } + + break; + } + } + + /** Some plugins need to be "connected" to intercommunicate between their implemented classes */ + void interconnectComponentAndController() + { + componentConnection.loadFrom (component); + editControllerConnection.loadFrom (editController); + + if (componentConnection != nullptr && editControllerConnection != nullptr) + { + warnOnFailure (editControllerConnection->connect (componentConnection)); + warnOnFailure (componentConnection->connect (editControllerConnection)); + } + } + + void synchroniseStates() + { + Steinberg::MemoryStream stream; + + if (component->getState (&stream) == kResultTrue) + warnOnFailure (editController->setComponentState (&stream)); + } + + void grabInformationObjects() + { + processor.loadFrom (component); + unitInfo.loadFrom (component); + programListData.loadFrom (component); + unitData.loadFrom (component); + editController2.loadFrom (component); + componentHandler.loadFrom (component); + componentHandler2.loadFrom (component); + + if (processor == nullptr) processor.loadFrom (editController); + if (unitInfo == nullptr) unitInfo.loadFrom (editController); + if (programListData == nullptr) programListData.loadFrom (editController); + if (unitData == nullptr) unitData.loadFrom (editController); + if (editController2 == nullptr) editController2.loadFrom (editController); + if (componentHandler == nullptr) componentHandler.loadFrom (editController); + if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); + } + + void setupIO() + { + activateAllBussesOfType (component, true, true); + activateAllBussesOfType (component, false, true); + + Vst::ProcessSetup setup; + setup.symbolicSampleSize = Vst::kSample32; + setup.maxSamplesPerBlock = 1024; + setup.sampleRate = 44100.0; + setup.processMode = Vst::kRealtime; + + warnOnFailure (processor->setupProcessing (setup)); + + setPlayConfigDetails (getNumSingleDirectionChannelsFor (component, true, true), + getNumSingleDirectionChannelsFor (component, false, true), + setup.sampleRate, (int) setup.maxSamplesPerBlock); + } + + void initialise() + { + jassert (module != nullptr); + + #if JUCE_WINDOWS + // On Windows it's highly advisable to create your plugins using the message thread, + // because many plugins need a chance to create HWNDs that will get their messages + // delivered by the main message thread, and that's not possible from a background thread. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #endif + + ComSmartPtr factory (module->getPluginFactory()); + + PFactoryInfo factoryInfo; + factory->getFactoryInfo (&factoryInfo); + company = toString (factoryInfo.vendor).trim(); + + fetchComponentAndController (factory, factory->countClasses()); + + jassert (info != nullptr); + + isControllerInitialised = editController->initialize (dynamic_cast (host.get())) == kResultTrue; + jassert (isControllerInitialised); + + editController->setComponentHandler (host); + + grabInformationObjects(); + synchroniseStates(); + interconnectComponentAndController(); + setupIO(); + } + + //============================================================================== + Vst::BusInfo getBusInfo (bool forInput, bool forAudio, int index = 0) const + { + Vst::BusInfo busInfo; + + component->getBusInfo (forAudio ? Vst::kAudio : Vst::kEvent, + forInput ? Vst::kInput : Vst::kOutput, + (Steinberg::int32) index, busInfo); + return busInfo; + } + + //============================================================================== + ComSmartPtr tryCreatingView() const + { + ComSmartPtr v (editController->createView (Vst::ViewType::kEditor)); + + if (v == nullptr) + v = editController->createView (nullptr); + + if (v == nullptr) + v.loadFrom (editController); + + return v; + } + + //============================================================================== + void associateTo (Vst::ProcessData& destination, AudioSampleBuffer& buffer) + { + result = buffer; + result.clear(); + + associateBufferTo (inputs, buffer); + associateBufferTo (outputs, result); + + destination.inputs = &inputs; + destination.outputs = &outputs; + destination.numSamples = buffer.getNumSamples(); + } + + void associateTo (Vst::ProcessData& destination, MidiBuffer& midiBuffer) + { + midiInputs->clear(); + midiOutputs->clear(); + + MidiEventList::toEventList (*midiInputs, midiBuffer); + + destination.inputEvents = midiInputs; + destination.outputEvents = midiOutputs; + } + + void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate) + { + toProcessContext (timingInfo, getPlayHead(), processSampleRate); + destination.processContext = &timingInfo; + } + + Vst::ParameterInfo getParameterInfoForIndex (int index) const + { + Vst::ParameterInfo info = { 0 }; + + if (processor != nullptr) + editController->getParameterInfo (index, info); + + return info; + } + + Vst::ProgramListInfo getProgramListInfo (int index) const + { + Vst::ProgramListInfo info = { 0 }; + + if (unitInfo != nullptr) + unitInfo->getProgramListInfo (index, info); + + return info; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginInstance) +}; + +}; + +//============================================================================== +VST3PluginFormat::VST3PluginFormat() {} +VST3PluginFormat::~VST3PluginFormat() {} + +void VST3PluginFormat::findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) +{ + if (! fileMightContainThisPluginType (fileOrIdentifier)) + return; + + VST3Classes::VST3ModuleHandle::getAllDescriptionsForFile (results, fileOrIdentifier); +} + +AudioPluginInstance* VST3PluginFormat::createInstanceFromDescription (const PluginDescription& description, double, int) +{ + ScopedPointer result; + + if (fileMightContainThisPluginType (description.fileOrIdentifier)) + { + File file (description.fileOrIdentifier); + + const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); + file.getParentDirectory().setAsCurrentWorkingDirectory(); + + if (const VST3Classes::VST3ModuleHandle::Ptr module = VST3Classes::VST3ModuleHandle::findOrCreateModule (file, description)) + result = new VST3Classes::VST3PluginInstance (module); + + previousWorkingDirectory.setAsCurrentWorkingDirectory(); + } + + return result.release(); +} + +bool VST3PluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) +{ + const File f (fileOrIdentifier); + + return f.hasFileExtension (".vst3") + #if JUCE_MAC + && f.exists(); + #else + && f.existsAsFile(); + #endif +} + +String VST3PluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) +{ + return fileOrIdentifier; //Impossible to tell because every VST3 is a type of shell... +} + +bool VST3PluginFormat::pluginNeedsRescanning (const PluginDescription& description) +{ + return File (description.fileOrIdentifier).getLastModificationTime() != description.lastFileModTime; +} + +bool VST3PluginFormat::doesPluginStillExist (const PluginDescription& description) +{ + return File (description.fileOrIdentifier).exists(); +} + +StringArray VST3PluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) +{ + StringArray results; + + for (int i = 0; i < directoriesToSearch.getNumPaths(); ++i) + recursiveFileSearch (results, directoriesToSearch[i], recursive); + + return results; +} + +void VST3PluginFormat::recursiveFileSearch (StringArray& results, const File& directory, const bool recursive) +{ + DirectoryIterator iter (directory, false, "*", File::findFilesAndDirectories); + + while (iter.next()) + { + const File f (iter.getFile()); + bool isPlugin = false; + + if (fileMightContainThisPluginType (f.getFullPathName())) + { + isPlugin = true; + results.add (f.getFullPathName()); + } + + if (recursive && (! isPlugin) && f.isDirectory()) + recursiveFileSearch (results, f, true); + } +} + +FileSearchPath VST3PluginFormat::getDefaultLocationsToSearch() +{ + #if JUCE_WINDOWS + const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); + return FileSearchPath (programFiles + "\\Common Files\\VST3"); + #elif JUCE_MAC + return FileSearchPath ("~/Library/Audio/Plug-Ins/VST3;/Library/Audio/Plug-Ins/VST3"); + #else + return FileSearchPath(); + #endif +} + +#endif //JUCE_PLUGINHOST_VST3 diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h new file mode 100644 index 0000000000..c637eba048 --- /dev/null +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h @@ -0,0 +1,72 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED +#define JUCE_VST3PLUGINFORMAT_H_INCLUDED + +#if JUCE_PLUGINHOST_VST3 +/** + Implements a plugin format for VST3s. +*/ +class JUCE_API VST3PluginFormat : public AudioPluginFormat +{ +public: + /** Constructor */ + VST3PluginFormat(); + + /** Destructor */ + ~VST3PluginFormat(); + + //============================================================================== + /** @internal */ + String getName() const override { return "VST3"; } + /** @internal */ + void findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) override; + /** @internal */ + AudioPluginInstance* createInstanceFromDescription (const PluginDescription& description, double, int) override; + /** @internal */ + bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; + /** @internal */ + String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; + /** @internal */ + bool pluginNeedsRescanning (const PluginDescription& description) override; + /** @internal */ + StringArray searchPathsForPlugins (const FileSearchPath& searchPath, bool recursive) override; + /** @internal */ + bool doesPluginStillExist (const PluginDescription& description) override; + /** @internal */ + FileSearchPath getDefaultLocationsToSearch() override; + /** @internal */ + bool canScanForPlugins() const override { return true; } + +private: + //============================================================================== + void recursiveFileSearch (StringArray&, const File&, bool recursive); + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) +}; + +#endif //JUCE_PLUGINHOST_VST3 +#endif //JUCE_VST3PLUGINFORMAT_H_INCLUDED \ No newline at end of file diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp index 955d770c3e..f3fc57e827 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -71,6 +71,10 @@ static inline bool arrayContainsPlugin (const OwnedArray& lis return false; } +#if JUCE_CLANG + #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + #include "format/juce_AudioPluginFormat.cpp" #include "format/juce_AudioPluginFormatManager.cpp" #include "processors/juce_AudioProcessor.cpp" @@ -80,6 +84,7 @@ static inline bool arrayContainsPlugin (const OwnedArray& lis #include "processors/juce_PluginDescription.cpp" #include "format_types/juce_LADSPAPluginFormat.cpp" #include "format_types/juce_VSTPluginFormat.cpp" +#include "format_types/juce_VST3PluginFormat.cpp" #include "format_types/juce_AudioUnitPluginFormat.mm" #include "scanning/juce_KnownPluginList.cpp" #include "scanning/juce_PluginDirectoryScanner.cpp" diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index d91fed99ab..78dc79f0fe 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -34,22 +34,32 @@ Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be installed on your machine. - @see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU + @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 */ #ifndef JUCE_PLUGINHOST_VST #define JUCE_PLUGINHOST_VST 0 #endif +/** Config: JUCE_PLUGINHOST_VST3 + Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be + installed on your machine. + + @see VSTPluginFormat, VVST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU +*/ +#ifndef JUCE_PLUGINHOST_VST3 + #define JUCE_PLUGINHOST_VST3 0 +#endif + /** Config: JUCE_PLUGINHOST_AU Enables the AudioUnit plugin hosting classes. This is Mac-only, of course. - @see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST + @see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_VST3 */ #ifndef JUCE_PLUGINHOST_AU #define JUCE_PLUGINHOST_AU 0 #endif -#if ! (JUCE_PLUGINHOST_AU || JUCE_PLUGINHOST_VST) +#if ! (JUCE_PLUGINHOST_AU || JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) // #error "You need to set either the JUCE_PLUGINHOST_AU anr/or JUCE_PLUGINHOST_VST flags if you're using this module!" #endif @@ -78,6 +88,7 @@ class AudioProcessor; #include "format_types/juce_LADSPAPluginFormat.h" #include "format_types/juce_VSTMidiEventList.h" #include "format_types/juce_VSTPluginFormat.h" +#include "format_types/juce_VST3PluginFormat.h" #include "scanning/juce_PluginDirectoryScanner.h" #include "scanning/juce_PluginListComponent.h"