summaryrefslogtreecommitdiff
path: root/source/plugin/PluginVst2x.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/plugin/PluginVst2x.cpp')
-rw-r--r--source/plugin/PluginVst2x.cpp897
1 files changed, 897 insertions, 0 deletions
diff --git a/source/plugin/PluginVst2x.cpp b/source/plugin/PluginVst2x.cpp
new file mode 100644
index 0000000..e39f3ec
--- /dev/null
+++ b/source/plugin/PluginVst2x.cpp
@@ -0,0 +1,897 @@
+//
+// PluginVst2x.cpp - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+// C++ includes
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+#include "plugin/PluginVst2xHostCallback.h"
+
+// C includes
+extern "C" {
+#include <stdlib.h>
+
+#include "audio/AudioSettings.h"
+#include "base/File.h"
+#include "base/PlatformInfo.h"
+#include "base/Types.h"
+#include "logging/EventLogger.h"
+#include "midi/MidiEvent.h"
+#include "plugin/PluginVst2x.h"
+#include "plugin/PluginVst2xId.h"
+
+ extern LinkedList getVst2xPluginLocations(CharString currentDirectory);
+ extern LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath);
+ extern AEffect *loadVst2xPlugin(LibraryHandle libraryHandle);
+ extern void closeLibraryHandle(LibraryHandle libraryHandle);
+}
+
+// Opaque struct must be declared here rather than in the header, otherwise many
+// other files in this project must be compiled as C++ code. =/
+typedef struct {
+ AEffect *pluginHandle;
+ PluginVst2xId pluginId;
+ Vst2xPluginDispatcherFunc dispatcher;
+ LibraryHandle libraryHandle;
+ boolByte isPluginShell;
+ VstInt32 shellPluginId;
+ // Must be retained until processReplacing() is called, so best to keep a
+ // reference in the plugin's data storage.
+ struct VstEvents *vstEvents;
+} PluginVst2xDataMembers;
+typedef PluginVst2xDataMembers *PluginVst2xData;
+
+// Implementation body starts here
+extern "C" {
+// Current plugin ID, which is mostly used by shell plugins during initialization.
+// To support VST shell plugins, we must provide them with a unique ID of a sub-plugin
+// which they will ask for from the host (opcode audioMasterCurrentId). The problem
+// is that this host callback is made when the plugin's main() function is called for
+// the first time, in loadVst2xPlugin(). While the AEffect struct provides a void* user
+// member for storing a pointer to an arbitrary object which could be used to store this
+// value, that struct is not fully constructed when the callback is made to the host
+// (in fact, calling the plugin's main() *returns* the AEffect* which we save in our
+// extraData struct). Therefore it is not possible to have the plugin reach our host
+// callback with some custom data, and we must keep a global variable to the current
+// effect ID.
+// That said, this prevents initializing plugins in multiple threads in the future, as
+// as we must set this to the correct ID before calling the plugin's main() function
+// when setting up the effect chain.
+ VstInt32 currentPluginUniqueId;
+
+ static const char *_getVst2xPlatformExtension(void)
+ {
+ PlatformInfo platform = newPlatformInfo();
+ PlatformType platformType = platform->type;
+ freePlatformInfo(platform);
+
+ switch (platformType) {
+ case PLATFORM_MACOSX:
+ return ".vst";
+
+ case PLATFORM_WINDOWS:
+ return ".dll";
+
+ case PLATFORM_LINUX:
+ return ".so";
+
+ default:
+ return EMPTY_STRING;
+ }
+ }
+
+ static void _logPluginVst2xInLocation(void *item, void *userData)
+ {
+ File itemFile = (File)item;
+ CharString itemPath = newCharStringWithCString(itemFile->absolutePath->data);
+ boolByte *pluginsFound = (boolByte *)userData;
+ char *dot;
+
+ logDebug("Checking item '%s'", itemPath->data);
+ dot = strrchr(itemPath->data, '.');
+
+ if (dot != NULL) {
+ if (!strncmp(dot, _getVst2xPlatformExtension(), 3)) {
+ *dot = '\0';
+ logInfo(" %s", itemPath->data);
+ *pluginsFound = true;
+ }
+ }
+
+ freeCharString(itemPath);
+ }
+
+ static void _logPluginLocation(const CharString location)
+ {
+ logInfo("Location '%s', type VST 2.x:", location->data);
+ }
+
+ static void _listPluginsVst2xInLocation(void *item, void *userData)
+ {
+ CharString locationString;
+ File location = NULL;
+ LinkedList locationItems;
+ boolByte pluginsFound = false;
+
+ locationString = (CharString)item;
+ _logPluginLocation(locationString);
+ location = newFileWithPath(locationString);
+ locationItems = fileListDirectory(location);
+
+ if (linkedListLength(locationItems) == 0) {
+ // Empty or does not exist, return
+ logInfo(" (Empty or non-existent directory)");
+ freeLinkedList(locationItems);
+ freeFile(location);
+ return;
+ }
+
+ linkedListForeach(locationItems, _logPluginVst2xInLocation, &pluginsFound);
+
+ if (!pluginsFound) {
+ logInfo(" (No plugins found)");
+ }
+
+ freeFile(location);
+ freeLinkedListAndItems(locationItems, (LinkedListFreeItemFunc)freeFile);
+ }
+
+ void listAvailablePluginsVst2x(const CharString pluginRoot)
+ {
+ if (!charStringIsEmpty(pluginRoot)) {
+ _listPluginsVst2xInLocation(pluginRoot, NULL);
+ }
+
+ LinkedList pluginLocations = getVst2xPluginLocations(fileGetCurrentDirectory());
+ linkedListForeach(pluginLocations, _listPluginsVst2xInLocation, NULL);
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ }
+
+ static boolByte _doesVst2xPluginExistAtLocation(const CharString pluginName, const CharString locationName)
+ {
+ boolByte result = false;
+ const char *subpluginSeparator = NULL;
+ CharString pluginSearchName = NULL;
+ CharString pluginSearchExtension = NULL;
+ File location = NULL;
+ File pluginSearchPath = NULL;
+
+ subpluginSeparator = strrchr(pluginName->data, kPluginVst2xSubpluginSeparator);
+
+ if (subpluginSeparator != NULL) {
+ pluginSearchName = newCharString();
+ strncpy(pluginSearchName->data, pluginName->data, subpluginSeparator - pluginName->data);
+ result = _doesVst2xPluginExistAtLocation(pluginSearchName, locationName);
+ freeCharString(pluginSearchName);
+ return result;
+ }
+
+ logDebug("Looking for plugin '%s' in '%s'", pluginName->data, locationName->data);
+
+ location = newFileWithPath(locationName);
+
+ if (location == NULL || !fileExists(location) || location->fileType != kFileTypeDirectory) {
+ logWarn("Location '%s' is not a valid directory", locationName->data);
+ freeFile(location);
+ return result;
+ }
+
+ pluginSearchName = newCharStringWithCString(pluginName->data);
+ pluginSearchPath = newFileWithParent(location, pluginSearchName);
+ pluginSearchExtension = fileGetExtension(pluginSearchPath);
+
+ if (pluginSearchExtension == NULL) {
+ freeFile(pluginSearchPath);
+ charStringAppendCString(pluginSearchName, _getVst2xPlatformExtension());
+ pluginSearchPath = newFileWithParent(location, pluginSearchName);
+ }
+
+ if (fileExists(pluginSearchPath)) {
+ result = true;
+ }
+
+ freeCharString(pluginSearchExtension);
+ freeCharString(pluginSearchName);
+ freeFile(pluginSearchPath);
+ freeFile(location);
+ return result;
+ }
+
+ static CharString _getVst2xPluginLocation(const CharString pluginName, const CharString pluginRoot)
+ {
+ File pluginAbsolutePath = newFileWithPath(pluginName);
+
+ if (fileExists(pluginAbsolutePath)) {
+ File pluginParentDir = fileGetParent(pluginAbsolutePath);
+ CharString result = newCharStringWithCString(pluginParentDir->absolutePath->data);
+ freeFile(pluginParentDir);
+ freeFile(pluginAbsolutePath);
+ return result;
+ } else {
+ freeFile(pluginAbsolutePath);
+ }
+
+ // Then search the path given to --plugin-root, if given
+ if (!charStringIsEmpty(pluginRoot)) {
+ if (_doesVst2xPluginExistAtLocation(pluginName, pluginRoot)) {
+ return newCharStringWithCString(pluginRoot->data);
+ }
+ }
+
+ // If the plugin wasn't found in the user's plugin root, then try searching
+ // the default locations for the platform, starting with the current directory.
+ LinkedList pluginLocations = getVst2xPluginLocations(fileGetCurrentDirectory());
+
+ if (pluginLocations->item == NULL) {
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ return NULL;
+ }
+
+ LinkedListIterator iterator = pluginLocations;
+
+ while (iterator != NULL) {
+ CharString searchLocation = (CharString)(iterator->item);
+
+ if (_doesVst2xPluginExistAtLocation(pluginName, searchLocation)) {
+ CharString result = newCharStringWithCString(searchLocation->data);
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ return result;
+ }
+
+ iterator = (LinkedListIterator)iterator->nextItem;
+ }
+
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ return NULL;
+ }
+
+ boolByte pluginVst2xExists(const CharString pluginName, const CharString pluginRoot)
+ {
+ CharString pluginLocation = _getVst2xPluginLocation(pluginName, pluginRoot);
+ boolByte result = (boolByte)((pluginLocation != NULL) && !charStringIsEmpty(pluginLocation));
+ freeCharString(pluginLocation);
+ return result;
+ }
+
+ static short _canPluginDo(Plugin plugin, const char *canDoString)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ VstIntPtr result = data->dispatcher(data->pluginHandle, effCanDo, 0, 0, (void *)canDoString, 0.0f);
+ return (short)result;
+ }
+
+ static void _resumePlugin(Plugin plugin)
+ {
+ logDebug("Resuming plugin '%s'", plugin->pluginName->data);
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+
+ if (data->isPluginShell && data->shellPluginId == 0) {
+ logError("'%s' is a shell plugin, but no sub-plugin ID was given, run with --help plugin", plugin->pluginName->data);
+ }
+
+ data->dispatcher(data->pluginHandle, effMainsChanged, 0, 1, NULL, 0.0f);
+ data->dispatcher(data->pluginHandle, effStartProcess, 0, 0, NULL, 0.0f);
+ }
+
+ static void _suspendPlugin(Plugin plugin)
+ {
+ logDebug("Suspending plugin '%s'", plugin->pluginName->data);
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ data->dispatcher(data->pluginHandle, effMainsChanged, 0, 0, NULL, 0.0f);
+ data->dispatcher(data->pluginHandle, effStopProcess, 0, 0, NULL, 0.0f);
+ }
+
+ static void _setSpeakers(struct VstSpeakerArrangement *speakerArrangement, int channels)
+ {
+ memset(speakerArrangement, 0, sizeof(struct VstSpeakerArrangement));
+ speakerArrangement->numChannels = channels;
+
+ if (channels <= 8) {
+ speakerArrangement->numChannels = channels;
+ } else {
+ logInfo("Number of channels = %d. Will only arrange 8 speakers.", channels);
+ speakerArrangement->numChannels = 8;
+ }
+
+ switch (speakerArrangement->numChannels) {
+ case 0:
+ speakerArrangement->type = kSpeakerArrEmpty;
+ break;
+
+ case 1:
+ speakerArrangement->type = kSpeakerArrMono;
+ break;
+
+ case 2:
+ speakerArrangement->type = kSpeakerArrStereo;
+ break;
+
+ case 3:
+ speakerArrangement->type = kSpeakerArr30Music;
+ break;
+
+ case 4:
+ speakerArrangement->type = kSpeakerArr40Music;
+ break;
+
+ case 5:
+ speakerArrangement->type = kSpeakerArr50;
+ break;
+
+ case 6:
+ speakerArrangement->type = kSpeakerArr60Music;
+ break;
+
+ case 7:
+ speakerArrangement->type = kSpeakerArr70Music;
+ break;
+
+ case 8:
+ speakerArrangement->type = kSpeakerArr80Music;
+ break;
+
+ default:
+ logInternalError("Cannot arrange more than 8 speakers.");//The datastructure does not allow.
+ break;
+ }
+
+ for (int i = 0; i < speakerArrangement->numChannels; i++) {
+ speakerArrangement->speakers[i].azimuth = 0.0f;
+ speakerArrangement->speakers[i].elevation = 0.0f;
+ speakerArrangement->speakers[i].radius = 0.0f;
+ speakerArrangement->speakers[i].reserved = 0.0f;
+ speakerArrangement->speakers[i].name[0] = '\0';
+ speakerArrangement->speakers[i].type = kSpeakerUndefined;
+ }
+ }
+
+ static boolByte _initVst2xPlugin(Plugin plugin)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ PluginVst2xId subpluginId;
+
+ logDebug("Initializing VST2.x plugin '%s'", plugin->pluginName->data);
+
+ if (data->pluginHandle->flags & effFlagsIsSynth) {
+ plugin->pluginType = PLUGIN_TYPE_INSTRUMENT;
+ } else {
+ plugin->pluginType = PLUGIN_TYPE_EFFECT;
+ }
+
+ if (data->pluginHandle->dispatcher(data->pluginHandle, effGetPlugCategory, 0, 0, NULL, 0.0f) == kPlugCategShell) {
+ subpluginId = newPluginVst2xIdWithId((unsigned long)data->shellPluginId);
+ logDebug("VST is a shell plugin, sub-plugin ID '%s'", subpluginId->idString->data);
+ freePluginVst2xId(subpluginId);
+ data->isPluginShell = true;
+ }
+
+ data->dispatcher(data->pluginHandle, effOpen, 0, 0, NULL, 0.0f);
+ data->dispatcher(data->pluginHandle, effSetSampleRate, 0, 0, NULL, (float)getSampleRate());
+ data->dispatcher(data->pluginHandle, effSetBlockSize, 0, (VstIntPtr)getBlocksize(), NULL, 0.0f);
+ struct VstSpeakerArrangement inSpeakers;
+ _setSpeakers(&inSpeakers, data->pluginHandle->numInputs);
+ struct VstSpeakerArrangement outSpeakers;
+ _setSpeakers(&outSpeakers, data->pluginHandle->numOutputs);
+ data->dispatcher(data->pluginHandle, effSetSpeakerArrangement, 0, (VstIntPtr)&inSpeakers, &outSpeakers, 0.0f);
+
+ return true;
+ }
+
+ unsigned long pluginVst2xGetUniqueId(const Plugin self)
+ {
+ if (self->interfaceType == PLUGIN_TYPE_VST_2X) {
+ PluginVst2xData data = (PluginVst2xData)self->extraData;
+ return (unsigned long)data->pluginHandle->uniqueID;
+ }
+
+ return 0;
+ }
+
+ unsigned long pluginVst2xGetVersion(const Plugin self)
+ {
+ if (self->interfaceType == PLUGIN_TYPE_VST_2X) {
+ PluginVst2xData data = (PluginVst2xData)self->extraData;
+ return (unsigned long)data->pluginHandle->version;
+ }
+
+ return 0;
+ }
+
+ void pluginVst2xAudioMasterIOChanged(const Plugin self, AEffect const *const newValues)
+ {
+ PluginVst2xData data = (PluginVst2xData)(self->extraData);
+ data->pluginHandle->initialDelay = newValues->initialDelay;
+
+ if (newValues->numInputs != data->pluginHandle->numInputs || newValues->numOutputs != data->pluginHandle->numOutputs) {
+ data->pluginHandle->numInputs = newValues->numInputs;
+ struct VstSpeakerArrangement inSpeakers;
+ _setSpeakers(&inSpeakers, data->pluginHandle->numInputs);
+ data->pluginHandle->numOutputs = newValues->numOutputs;
+ struct VstSpeakerArrangement outSpeakers;
+ _setSpeakers(&outSpeakers, data->pluginHandle->numOutputs);
+ data->dispatcher(data->pluginHandle, effSetSpeakerArrangement, 0, (VstIntPtr)&inSpeakers, &outSpeakers, 0.0f);
+ }
+ }
+
+ static boolByte _openVst2xPlugin(void *pluginPtr)
+ {
+ boolByte result = false;
+ AEffect *pluginHandle;
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ File pluginPath = newFileWithPath(plugin->pluginName);
+ CharString pluginBasename = fileGetBasename(pluginPath);
+ char *subpluginSeparator = strrchr((char *)pluginBasename, kPluginVst2xSubpluginSeparator);
+ CharString subpluginIdString = NULL;
+
+ if (subpluginSeparator != NULL) {
+ *subpluginSeparator = '\0';
+ subpluginIdString = newCharStringWithCapacity(kCharStringLengthShort);
+ strncpy(subpluginIdString->data, subpluginSeparator + 1, 4);
+ PluginVst2xId subpluginId = newPluginVst2xIdWithStringId(subpluginIdString);
+ data->shellPluginId = (VstInt32)subpluginId->id;
+ currentPluginUniqueId = data->shellPluginId;
+ freePluginVst2xId(subpluginId);
+ }
+
+ logInfo("Opening VST2.x plugin '%s'", plugin->pluginName->data);
+
+ if (fileExists(pluginPath)) {
+ charStringCopy(plugin->pluginAbsolutePath, pluginPath->absolutePath);
+ } else {
+ File pluginLocationPath = newFileWithPath(plugin->pluginLocation);
+ CharString pluginNameWithExtension = newCharString();
+ charStringCopy(pluginNameWithExtension, plugin->pluginName);
+ charStringAppendCString(pluginNameWithExtension, _getVst2xPlatformExtension());
+ File pluginAbsolutePath = newFileWithParent(pluginLocationPath, pluginNameWithExtension);
+ charStringCopy(plugin->pluginAbsolutePath, pluginAbsolutePath->absolutePath);
+ freeFile(pluginAbsolutePath);
+ freeFile(pluginLocationPath);
+ freeCharString(pluginNameWithExtension);
+ }
+
+ freeFile(pluginPath);
+ logDebug("Plugin location is '%s'", plugin->pluginAbsolutePath->data);
+
+ data->libraryHandle = getLibraryHandleForPlugin(plugin->pluginAbsolutePath);
+
+ if (data->libraryHandle == NULL) {
+ return false;
+ }
+
+ pluginHandle = loadVst2xPlugin(data->libraryHandle);
+
+ if (pluginHandle == NULL) {
+ logError("Could not load VST2.x plugin '%s'", plugin->pluginAbsolutePath->data);
+ return false;
+ }
+
+ // The plugin name which is passed into this function is basically just used to find the
+ // actual location. Now that the plugin has been loaded, we can set a friendlier name.
+ CharString temp = plugin->pluginName;
+ plugin->pluginName = newCharStringWithCString(pluginBasename->data);
+ freeCharString(pluginBasename);
+ freeCharString(temp);
+
+ if (data->shellPluginId && subpluginIdString != NULL) {
+ charStringAppendCString(plugin->pluginName, " (");
+ charStringAppend(plugin->pluginName, subpluginIdString);
+ charStringAppendCString(plugin->pluginName, ")");
+ }
+
+ // Check plugin's magic number. If incorrect, then the file either was not loaded
+ // properly, is not a real VST plugin, or is otherwise corrupt.
+ if (pluginHandle->magic != kEffectMagic) {
+ logError("Plugin '%s' has bad magic number, possibly corrupt", plugin->pluginName->data);
+ } else {
+ data->dispatcher = (Vst2xPluginDispatcherFunc)(pluginHandle->dispatcher);
+ data->pluginHandle = pluginHandle;
+ result = _initVst2xPlugin(plugin);
+
+ if (result) {
+ data->pluginId = newPluginVst2xIdWithId((unsigned long)data->pluginHandle->uniqueID);
+ }
+ }
+
+ freeCharString(subpluginIdString);
+ return result;
+ }
+
+ static LinkedList _getCommonCanDos(void)
+ {
+ LinkedList result = newLinkedList();
+ linkedListAppend(result, (char *)"sendVstEvents");
+ linkedListAppend(result, (char *)"sendVstMidiEvent");
+ linkedListAppend(result, (char *)"receiveVstEvents");
+ linkedListAppend(result, (char *)"receiveVstMidiEvent");
+ linkedListAppend(result, (char *)"receiveVstTimeInfo");
+ linkedListAppend(result, (char *)"offline");
+ linkedListAppend(result, (char *)"midiProgramNames");
+ linkedListAppend(result, (char *)"bypass");
+ return result;
+ }
+
+ static const char *_prettyTextForCanDoResult(int result)
+ {
+ if (result == -1) {
+ return "No";
+ } else if (result == 0) {
+ return "Don't know";
+ } else if (result == 1) {
+ return "Yes";
+ } else {
+ return "Undefined response";
+ }
+ }
+
+ static void _displayVst2xPluginCanDo(void *item, void *userData)
+ {
+ char *canDoString = (char *)item;
+ short result = _canPluginDo((Plugin)userData, canDoString);
+ logInfo(" %s: %s", canDoString, _prettyTextForCanDoResult(result));
+ }
+
+ static void _displayVst2xPluginInfo(void *pluginPtr)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ CharString nameBuffer = newCharString();
+
+ logInfo("Information for VST2.x plugin '%s'", plugin->pluginName->data);
+ data->dispatcher(data->pluginHandle, effGetVendorString, 0, 0, nameBuffer->data, 0.0f);
+ logInfo("Vendor: %s", nameBuffer->data);
+ VstInt32 vendorVersion = (VstInt32)data->dispatcher(data->pluginHandle, effGetVendorVersion, 0, 0, NULL, 0.0f);
+ logInfo("Version: %d", vendorVersion);
+ charStringClear(nameBuffer);
+
+ logInfo("Unique ID: %s", data->pluginId->idString->data);
+ freeCharString(nameBuffer);
+
+ VstInt32 pluginCategory = (VstInt32)data->dispatcher(data->pluginHandle, effGetPlugCategory, 0, 0, NULL, 0.0f);
+
+ switch (plugin->pluginType) {
+ case PLUGIN_TYPE_EFFECT:
+ logInfo("Plugin type: effect, category %d", pluginCategory);
+ break;
+
+ case PLUGIN_TYPE_INSTRUMENT:
+ logInfo("Plugin type: instrument, category %d", pluginCategory);
+ break;
+
+ default:
+ logInfo("Plugin type: other, category %d", pluginCategory);
+ break;
+ }
+
+ logInfo("Version: %d", data->pluginHandle->version);
+ logInfo("I/O: %d/%d", data->pluginHandle->numInputs, data->pluginHandle->numOutputs);
+ logInfo("InitialDelay: %d frames", data->pluginHandle->initialDelay);
+
+ if (data->isPluginShell && data->shellPluginId == 0) {
+ logInfo("Sub-plugins:");
+ nameBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+
+ while (true) {
+ charStringClear(nameBuffer);
+ VstInt32 shellPluginId = (VstInt32)data->dispatcher(data->pluginHandle, effShellGetNextPlugin, 0, 0, nameBuffer->data, 0.0f);
+
+ if (shellPluginId == 0 || charStringIsEmpty(nameBuffer)) {
+ break;
+ } else {
+ PluginVst2xId subpluginId = newPluginVst2xIdWithId((unsigned long)shellPluginId);
+ logInfo(" '%s' (%s)", subpluginId->idString->data, nameBuffer->data);
+ freePluginVst2xId(subpluginId);
+ }
+ }
+
+ freeCharString(nameBuffer);
+ } else {
+ nameBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+ logInfo("Parameters (%d total):", data->pluginHandle->numParams);
+
+ for (VstInt32 i = 0; i < data->pluginHandle->numParams; i++) {
+ float value = data->pluginHandle->getParameter(data->pluginHandle, i);
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetParamName, i, 0, nameBuffer->data, 0.0f);
+ logInfo(" %d: '%s' (%f)", i, nameBuffer->data, value);
+
+ if (isLogLevelAtLeast(LOG_DEBUG)) {
+ logDebug(" Displaying common values for parameter:");
+
+ for (unsigned int j = 0; j < 128; j++) {
+ const float midiValue = (float)j / 127.0f;
+ // Don't use the other setParameter function, or else that will log like crazy to
+ // a different log level.
+ data->pluginHandle->setParameter(data->pluginHandle, i, midiValue);
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetParamDisplay, i, 0, nameBuffer->data, 0.0f);
+ logDebug(" %0.3f/MIDI value %d (0x%02x): %s", midiValue, j, j, nameBuffer->data);
+ }
+ }
+ }
+
+ logInfo("Programs (%d total):", data->pluginHandle->numPrograms);
+
+ for (int i = 0; i < data->pluginHandle->numPrograms; i++) {
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetProgramNameIndexed, i, 0, nameBuffer->data, 0.0f);
+ logInfo(" %d: '%s'", i, nameBuffer->data);
+ }
+
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetProgramName, 0, 0, nameBuffer->data, 0.0f);
+ logInfo("Current program: '%s'", nameBuffer->data);
+ freeCharString(nameBuffer);
+
+ logInfo("Common canDo's:");
+ LinkedList commonCanDos = _getCommonCanDos();
+ linkedListForeach(commonCanDos, _displayVst2xPluginCanDo, plugin);
+ freeLinkedList(commonCanDos);
+ }
+ }
+
+ static int _getVst2xPluginSetting(void *pluginPtr, PluginSetting pluginSetting)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS: {
+ VstInt32 tailSize = (VstInt32)data->dispatcher(data->pluginHandle, effGetTailSize, 0, 0, NULL, 0.0f);
+
+ // For some reason, the VST SDK says that plugins return a 1 here for no tail.
+ if (tailSize == 1 || tailSize == 0) {
+ return 0;
+ } else {
+ // If tailSize is not 0 or 1, then it is assumed to be in samples
+ return (int)((double)tailSize * getSampleRate() / 1000.0f);
+ }
+ }
+
+ case PLUGIN_NUM_INPUTS:
+ return data->pluginHandle->numInputs;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return data->pluginHandle->numOutputs;
+
+ case PLUGIN_INITIAL_DELAY:
+ return data->pluginHandle->initialDelay;
+
+ default:
+ logUnsupportedFeature("Plugin setting for VST2.x");
+ return 0;
+ }
+ }
+
+ void pluginVst2xSetProgramChunk(Plugin plugin, char *chunk, size_t chunkSize)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ data->dispatcher(data->pluginHandle, effSetChunk, 1, (VstIntPtr)chunkSize, chunk, 0.0f);
+ }
+
+ static void _processAudioVst2xPlugin(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ data->pluginHandle->processReplacing(data->pluginHandle, inputs->samples, outputs->samples, (VstInt32)outputs->blocksize);
+ }
+
+ static void _fillVstMidiEvent(const MidiEvent midiEvent, VstMidiEvent *vstMidiEvent)
+ {
+ switch (midiEvent->eventType) {
+ case MIDI_TYPE_REGULAR:
+ vstMidiEvent->type = kVstMidiType;
+ vstMidiEvent->byteSize = sizeof(VstMidiEvent);
+ vstMidiEvent->deltaFrames = (VstInt32)midiEvent->deltaFrames;
+ vstMidiEvent->midiData[0] = midiEvent->status;
+ vstMidiEvent->midiData[1] = midiEvent->data1;
+ vstMidiEvent->midiData[2] = midiEvent->data2;
+ vstMidiEvent->flags = 0;
+ vstMidiEvent->reserved1 = 0;
+ vstMidiEvent->reserved2 = 0;
+ break;
+
+ case MIDI_TYPE_SYSEX:
+ logUnsupportedFeature("VST2.x plugin sysex messages");
+ break;
+
+ case MIDI_TYPE_META:
+ // Ignore, don't care
+ break;
+
+ default:
+ logInternalError("Cannot convert MIDI event type '%d' to VstMidiEvent", midiEvent->eventType);
+ break;
+ }
+ }
+
+ static void _processMidiEventsVst2xPlugin(void *pluginPtr, LinkedList midiEvents)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)(plugin->extraData);
+ int numEvents = linkedListLength(midiEvents);
+
+ // Free events from the previous call
+ if (data->vstEvents != NULL) {
+ for (int i = 0; i < data->vstEvents->numEvents; i++) {
+ free(data->vstEvents->events[i]);
+ }
+
+ free(data->vstEvents);
+ }
+
+ data->vstEvents = (struct VstEvents *)malloc(sizeof(struct VstEvent) + (numEvents * sizeof(struct VstEvent *)));
+ data->vstEvents->numEvents = numEvents;
+
+ // Some monophonic instruments have problems dealing with the order of MIDI events,
+ // so send them all note off events *first* followed by any other event types.
+ LinkedListIterator iterator = midiEvents;
+ int outIndex = 0;
+
+ while (iterator != NULL && outIndex < numEvents) {
+ MidiEvent midiEvent = (MidiEvent)(iterator->item);
+
+ if (midiEvent != NULL && (midiEvent->status >> 4) == 0x08) {
+ VstMidiEvent *vstMidiEvent = (VstMidiEvent *)malloc(sizeof(VstMidiEvent));
+ _fillVstMidiEvent(midiEvent, vstMidiEvent);
+ data->vstEvents->events[outIndex] = (VstEvent *)vstMidiEvent;
+ outIndex++;
+ }
+
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ }
+
+ iterator = midiEvents;
+
+ while (iterator != NULL && outIndex < numEvents) {
+ MidiEvent midiEvent = (MidiEvent)(iterator->item);
+
+ if (midiEvent != NULL && (midiEvent->status >> 4) != 0x08) {
+ VstMidiEvent *vstMidiEvent = (VstMidiEvent *)malloc(sizeof(VstMidiEvent));
+ _fillVstMidiEvent(midiEvent, vstMidiEvent);
+ data->vstEvents->events[outIndex] = (VstEvent *)vstMidiEvent;
+ outIndex++;
+ }
+
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ }
+
+ data->dispatcher(data->pluginHandle, effProcessEvents, 0, 0, data->vstEvents, 0.0f);
+ }
+
+ boolByte pluginVst2xSetProgram(Plugin plugin, const int programNumber)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ CharString currentProgram;
+ VstInt32 result;
+
+ if (programNumber < data->pluginHandle->numPrograms) {
+ result = (VstInt32)data->pluginHandle->dispatcher(data->pluginHandle, effSetProgram, 0, programNumber, NULL, 0.0f);
+
+ if (result != 0) {
+ logError("Plugin '%s' failed to load program number %d", plugin->pluginName->data, programNumber);
+ return false;
+ } else {
+ result = (VstInt32)data->pluginHandle->dispatcher(data->pluginHandle, effGetProgram, 0, 0, NULL, 0.0f);
+
+ if (result != programNumber) {
+ logError("Plugin '%s' claimed to load program %d successfully, but current program is %d",
+ plugin->pluginName->data, programNumber, result);
+ return false;
+ } else {
+ currentProgram = newCharStringWithCapacity(kVstMaxProgNameLen + 1);
+ data->dispatcher(data->pluginHandle, effGetProgramName, 0, 0, currentProgram->data, 0.0f);
+ logDebug("Current program is now '%s'", currentProgram->data);
+ freeCharString(currentProgram);
+ return true;
+ }
+ }
+ } else {
+ logError("Cannot load program, plugin '%s' only has %d programs",
+ plugin->pluginName->data, data->pluginHandle->numPrograms - 1);
+ return false;
+ }
+ }
+
+ static boolByte _setParameterVst2xPlugin(void *pluginPtr, unsigned int index, float value)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)(plugin->extraData);
+
+ if (index < (unsigned int)data->pluginHandle->numParams) {
+ CharString valueBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+ data->pluginHandle->setParameter(data->pluginHandle, index, value);
+ data->dispatcher(data->pluginHandle, effGetParamDisplay, index, 0, valueBuffer->data, 0.0f);
+ logInfo("Set parameter %d on plugin '%s' to %f (%s)",
+ index, plugin->pluginName->data, value, valueBuffer->data);
+ freeCharString(valueBuffer);
+ return true;
+ } else {
+ logError("Cannot set parameter %d on plugin '%s', invalid index", index, plugin->pluginName->data);
+ return false;
+ }
+ }
+
+ static void _prepareForProcessingVst2xPlugin(void *pluginPtr)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ _resumePlugin(plugin);
+ }
+
+ static void _closeVst2xPlugin(void *pluginPtr)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ _suspendPlugin(plugin);
+ }
+
+ static void _freeVst2xPluginData(void *pluginDataPtr)
+ {
+ PluginVst2xData data = (PluginVst2xData)(pluginDataPtr);
+
+ data->dispatcher(data->pluginHandle, effClose, 0, 0, NULL, 0.0f);
+ data->dispatcher = NULL;
+ data->pluginHandle = NULL;
+ freePluginVst2xId(data->pluginId);
+ closeLibraryHandle(data->libraryHandle);
+
+ if (data->vstEvents != NULL) {
+ for (int i = 0; i < data->vstEvents->numEvents; i++) {
+ free(data->vstEvents->events[i]);
+ }
+
+ free(data->vstEvents);
+ }
+ }
+
+ Plugin newPluginVst2x(const CharString pluginName, const CharString pluginRoot)
+ {
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_VST_2X, PLUGIN_TYPE_UNKNOWN);
+ charStringCopy(plugin->pluginName, pluginName);
+ plugin->pluginLocation = _getVst2xPluginLocation(pluginName, pluginRoot);
+
+ plugin->openPlugin = _openVst2xPlugin;
+ plugin->displayInfo = _displayVst2xPluginInfo;
+ plugin->getSetting = _getVst2xPluginSetting;
+ plugin->processAudio = _processAudioVst2xPlugin;
+ plugin->processMidiEvents = _processMidiEventsVst2xPlugin;
+ plugin->setParameter = _setParameterVst2xPlugin;
+ plugin->prepareForProcessing = _prepareForProcessingVst2xPlugin;
+ plugin->closePlugin = _closeVst2xPlugin;
+ plugin->freePluginData = _freeVst2xPluginData;
+
+ PluginVst2xData extraData = (PluginVst2xData)malloc(sizeof(PluginVst2xDataMembers));
+ extraData->pluginHandle = NULL;
+ extraData->pluginId = NULL;
+ extraData->dispatcher = NULL;
+ extraData->libraryHandle = NULL;
+ extraData->isPluginShell = false;
+ extraData->shellPluginId = 0;
+ extraData->vstEvents = NULL;
+ plugin->extraData = extraData;
+
+ return plugin;
+ }
+}