diff options
| author | pepper <peppersclothescult@gmail.com> | 2015-01-10 21:32:32 -0800 |
|---|---|---|
| committer | pepper <peppersclothescult@gmail.com> | 2015-01-10 21:32:32 -0800 |
| commit | d53fa8a169832563c62262078b8d2ffe5cab8473 (patch) | |
| tree | b911d06d357d009c976709780f10e92ce915228a /source/plugin | |
first
Diffstat (limited to 'source/plugin')
27 files changed, 4265 insertions, 0 deletions
diff --git a/source/plugin/Plugin.c b/source/plugin/Plugin.c new file mode 100644 index 0000000..a307e8e --- /dev/null +++ b/source/plugin/Plugin.c @@ -0,0 +1,205 @@ +// +// Plugin.c - 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. +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "audio/AudioSettings.h" +#include "logging/EventLogger.h" +#include "plugin/Plugin.h" +#include "plugin/PluginGain.h" +#include "plugin/PluginLimiter.h" +#include "plugin/PluginPassthru.h" +#include "plugin/PluginVst2x.h" +#include "plugin/PluginSilence.h" + +static PluginInterfaceType _guessPluginInterfaceType(const CharString pluginName, const CharString pluginSearchRoot) +{ + PluginInterfaceType pluginType = PLUGIN_TYPE_INVALID; + + if (pluginName == NULL || charStringIsEmpty(pluginName)) { + logError("Attempt to guess plugin with empty name"); + return pluginType; + } + + logDebug("Trying to find plugin '%s'", pluginName->data); + + if (pluginVst2xExists(pluginName, pluginSearchRoot)) { + logInfo("Plugin '%s' is of type VST2.x", pluginName->data); + pluginType = PLUGIN_TYPE_VST_2X; + } else if (!strncmp(INTERNAL_PLUGIN_PREFIX, pluginName->data, strlen(INTERNAL_PLUGIN_PREFIX))) { + logInfo("Plugin '%s' is an internal plugin", pluginName->data); + pluginType = PLUGIN_TYPE_INTERNAL; + } else { + logError("Plugin '%s' could not be found", pluginName->data); + } + + return pluginType; +} + +static void _logPluginLocation(const CharString location) +{ + logInfo("Location (internal):", location->data); +} + +static void _listAvailablePluginsInternal(void) +{ + CharString internalLocation = newCharStringWithCString("(Internal)"); + _logPluginLocation(internalLocation); + logInfo(" %s", kInternalPluginPassthruName); + logInfo(" %s", kInternalPluginSilenceName); + freeCharString(internalLocation); +} + +void listAvailablePlugins(const CharString pluginRoot) +{ + listAvailablePluginsVst2x(pluginRoot); + _listAvailablePluginsInternal(); +} + +/** + * Used to check if an internal plugin (ie, starting with "mrs_" matches an + * internal plugin name. This function only compares to the length of the + * internal name, so that extra parameters can be appended to the end of the + * plugin name argument. + * @param pluginName Plugin name to check + * @param internalName Internal name to compare against + * @return True if the plugin is a match + */ +static boolByte _internalPluginNameMatches(const CharString pluginName, const char *internalName) +{ + return (boolByte)(strncmp(pluginName->data, internalName, strlen(internalName)) == 0); +} + +// Plugin newPlugin(PluginInterfaceType interfaceType, const CharString pluginName, const CharString pluginLocation) { +Plugin pluginFactory(const CharString pluginName, const CharString pluginRoot) +{ + PluginInterfaceType interfaceType = _guessPluginInterfaceType(pluginName, pluginRoot); + + if (interfaceType == PLUGIN_TYPE_INVALID) { + return NULL; + } + + switch (interfaceType) { + case PLUGIN_TYPE_VST_2X: + return newPluginVst2x(pluginName, pluginRoot); + + case PLUGIN_TYPE_INTERNAL: + if (_internalPluginNameMatches(pluginName, kInternalPluginGainName)) { + return newPluginGain(pluginName); + } else if (_internalPluginNameMatches(pluginName, kInternalPluginLimiterName)) { + return newPluginLimiter(pluginName); + } else if (_internalPluginNameMatches(pluginName, kInternalPluginPassthruName)) { + return newPluginPassthru(pluginName); + } else if (_internalPluginNameMatches(pluginName, kInternalPluginSilenceName)) { + return newPluginSilence(pluginName); + } else { + logError("'%s' is not a recognized internal plugin", pluginName->data); + return NULL; + } + + default: + logError("Could not find plugin type for '%s'", pluginName->data); + return NULL; + } +} + +boolByte openPlugin(Plugin self) +{ + if (self == NULL) { + logError("There is no plugin to open"); + return false; + } else if (self->isOpen) { + return true; + } + + if (!self->openPlugin(self)) { + logError("Plugin '%s' could not be opened", self->pluginName->data); + return false; + } else { + self->inputBuffer = newSampleBuffer((ChannelCount)self->getSetting(self, PLUGIN_NUM_INPUTS), getBlocksize()); + self->outputBuffer = newSampleBuffer((ChannelCount)self->getSetting(self, PLUGIN_NUM_OUTPUTS), getBlocksize()); + self->isOpen = true; + } + + return true; +} + +boolByte closePlugin(Plugin self) +{ + if (self == NULL) { + logError("There is no plugin to open"); + return false; + } + + self->closePlugin(self); + freeSampleBuffer(self->inputBuffer); + self->inputBuffer = NULL; + freeSampleBuffer(self->outputBuffer); + self->outputBuffer = NULL; + self->isOpen = false; + return true; +} + +Plugin _newPlugin(PluginInterfaceType interfaceType, PluginType pluginType) +{ + Plugin plugin = (Plugin)malloc(sizeof(PluginMembers)); + + plugin->interfaceType = interfaceType; + plugin->pluginType = pluginType; + plugin->pluginName = newCharString(); + plugin->pluginLocation = newCharString(); + plugin->pluginAbsolutePath = newCharString(); + + plugin->inputBuffer = NULL; + plugin->outputBuffer = NULL; + plugin->isOpen = false; + + return plugin; +} + +void freePlugin(Plugin self) +{ + if (self != NULL) { + if (self->extraData != NULL) { + self->freePluginData(self->extraData); + free(self->extraData); + } + + if (self->inputBuffer != NULL) { + freeSampleBuffer(self->inputBuffer); + } + if (self->outputBuffer != NULL) { + freeSampleBuffer(self->outputBuffer); + } + freeCharString(self->pluginName); + freeCharString(self->pluginLocation); + freeCharString(self->pluginAbsolutePath); + free(self); + } +} diff --git a/source/plugin/Plugin.h b/source/plugin/Plugin.h new file mode 100644 index 0000000..1fb9519 --- /dev/null +++ b/source/plugin/Plugin.h @@ -0,0 +1,195 @@ +// +// Plugin.h - 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. +// + +#ifndef MrsWatson_Plugin_h +#define MrsWatson_Plugin_h + +#include "audio/SampleBuffer.h" +#include "base/CharString.h" +#include "base/LinkedList.h" + +// All internal plugins should start with this string +#define INTERNAL_PLUGIN_PREFIX "mrs_" + +typedef enum { + PLUGIN_TYPE_INVALID, + PLUGIN_TYPE_VST_2X, + PLUGIN_TYPE_INTERNAL, + NUM_PLUGIN_INTERFACE_TYPES +} PluginInterfaceType; + +typedef enum { + PLUGIN_TYPE_UNKNOWN, + PLUGIN_TYPE_UNSUPPORTED, + PLUGIN_TYPE_EFFECT, + PLUGIN_TYPE_INSTRUMENT, + NUM_PLUGIN_TYPES +} PluginType; + +typedef enum { + PLUGIN_SETTING_TAIL_TIME_IN_MS, + PLUGIN_NUM_INPUTS, + PLUGIN_NUM_OUTPUTS, + PLUGIN_INITIAL_DELAY, + NUM_PLUGIN_SETTINGS +} PluginSetting; + +/** + * Called when a plugin is to be opened. This includes loading any dynamic + * libraries into memory initializing the plugin. + * @param pluginPtr self + */ +typedef boolByte (*PluginOpenFunc)(void *pluginPtr); +/** + * Called when the plugin should display some generic info about itself. This + * may be a list of supported parameters or programs, or any other information + * relevant to the user. See the implementation in the PluginVST2x class for an + * example. + * @param pluginPtr self + */ +typedef void (*PluginDisplayInfoFunc)(void *pluginPtr); +/** + * Used to gather information about the plugin, such as the number of inputs and + * outputs. See the PluginSetting enum for examples of information which may be + * requested. + * @param pluginPtr self + * @param pluginSetting Setting to query + */ +typedef int (*PluginGetSettingFunc)(void *pluginPtr, PluginSetting pluginSetting); +/** + * Called with the host wants to process a block of audio samples. + * @param pluginPtr self + * @param inputs Block of input samples to process + * @param outputs Block where output samples shall be written + */ +typedef void (*PluginProcessAudioFunc)(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs); +/** + * Called the host wants to process MIDI events. This will be called directly + * before the call to process audio. + * @param pluginPtr self + * @param midiEvents List of events to process. This should be non-empty, as + * this function is not called when there are no events to process. + */ +typedef void (*PluginProcessMidiEventsFunc)(void *pluginPtr, LinkedList midiEvents); +/** + * Set a parameter within a plugin + * @param pluginPtr self + * @param index Parameter index + * @param value New value + */ +typedef boolByte (*PluginSetParameterFunc)(void *pluginPtr, unsigned int index, float value); +/** + * Called once before audio processing begins. Some interfaces provide hooks for + * a plugin to prepare itself before audio blocks are sent to it. + * @param pluginPtr self + */ +typedef void (*PluginPrepareForProcessingFunc)(void *pluginPtr); +/** + * Called when the plugin is to be uninitialized and closed. + * @param pluginPtr self + */ +typedef void (*PluginCloseFunc)(void *pluginPtr); +/** + * Pointer to the free routine for the plugin interface + * @param pluginPtr self + */ +typedef void (*FreePluginDataFunc)(void *pluginDataPtr); + +typedef struct { + PluginInterfaceType interfaceType; + PluginType pluginType; + CharString pluginName; + CharString pluginLocation; + CharString pluginAbsolutePath; + + PluginOpenFunc openPlugin; + PluginDisplayInfoFunc displayInfo; + PluginGetSettingFunc getSetting; + PluginProcessAudioFunc processAudio; + PluginProcessMidiEventsFunc processMidiEvents; + PluginSetParameterFunc setParameter; + PluginPrepareForProcessingFunc prepareForProcessing; + PluginCloseFunc closePlugin; + FreePluginDataFunc freePluginData; + SampleBuffer inputBuffer; + SampleBuffer outputBuffer; + boolByte isOpen; + + void *extraData; +} PluginMembers; + +/** + * One of the base classes of the API, this represents a plugin. Currently only + * instrument and effect plugins are supported. + */ +typedef PluginMembers *Plugin; + +/** + * Create a plugin instance. The plugin interface type will be automatically + * determined. + * @param pluginName Plugin name. For plugins which supports loading directly + * from the filesystem, this argument may also be an absolute path. + * @param pluginRoot User-provided search root path. May be NULL or empty. + * @return Initialized object, or NULL if no matching plugin was found + */ +Plugin pluginFactory(const CharString pluginName, const CharString pluginRoot); + +/** + * List all known plugins of all known types on the system. + * @param pluginRoot User-provided search root path + */ +void listAvailablePlugins(const CharString pluginRoot); + +/** + * Open a plugin. + * @param self + */ +boolByte openPlugin(Plugin self); + +/** + * Close a plugin. + * @param self + */ +boolByte closePlugin(Plugin self); + +/** +* Create a new plugin. Considered "protected", only subclasses of Plugin should +* directly call this. +* @param interfaceType Plugin interface type +* @param pluginType Plugin type +* @return Plugin initialized base Plugin struct +*/ +Plugin _newPlugin(PluginInterfaceType interfaceType, PluginType pluginType); + +/** + * Release a plugin and all of its associated resources. Note that the plugin + * must be closed before this is called, or else resources will be leaked. + * @param self + */ +void freePlugin(Plugin self); + +#endif diff --git a/source/plugin/PluginChain.c b/source/plugin/PluginChain.c new file mode 100644 index 0000000..bbf2f9e --- /dev/null +++ b/source/plugin/PluginChain.c @@ -0,0 +1,420 @@ +// +// PluginChain.c - 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. +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "logging/EventLogger.h" +#include "plugin/PluginChain.h" +#include "audio/AudioSettings.h" + +PluginChain pluginChainInstance = NULL; + +PluginChain getPluginChain(void) +{ + return pluginChainInstance; +} + +void initPluginChain(void) +{ + pluginChainInstance = (PluginChain)malloc(sizeof(PluginChainMembers)); + + pluginChainInstance->numPlugins = 0; + pluginChainInstance->plugins = (Plugin *)malloc(sizeof(Plugin) * MAX_PLUGINS); + pluginChainInstance->presets = (PluginPreset *)malloc(sizeof(PluginPreset) * MAX_PLUGINS); + pluginChainInstance->audioTimers = (TaskTimer *)malloc(sizeof(TaskTimer) * MAX_PLUGINS); + pluginChainInstance->midiTimers = (TaskTimer *)malloc(sizeof(TaskTimer) * MAX_PLUGINS); + + pluginChainInstance->_realtime = false; + pluginChainInstance->_realtimeTimer = NULL; +} + +boolByte pluginChainAppend(PluginChain self, Plugin plugin, PluginPreset preset) +{ + if (plugin == NULL) { + return false; + } else if (self->numPlugins + 1 >= MAX_PLUGINS) { + logError("Could not add plugin '%s', maximum number reached", plugin->pluginName->data); + return false; + } else if (!openPlugin(plugin)) { + return false; + } else { + self->plugins[self->numPlugins] = plugin; + self->presets[self->numPlugins] = preset; + self->audioTimers[self->numPlugins] = newTaskTimer(plugin->pluginName, "Audio Processing"); + self->midiTimers[self->numPlugins] = newTaskTimer(plugin->pluginName, "MIDI Processing"); + self->numPlugins++; + return true; + } +} + +boolByte pluginChainAddFromArgumentString(PluginChain pluginChain, const CharString argumentString, const CharString userSearchPath) +{ + // Expect a semicolon-separated string of plugins with comma separators for preset names + // Example: plugin1,preset1name;plugin2,preset2name + char *substringStart; + char *pluginSeparator; + char *endChar; + CharString pluginNameBuffer = NULL; + CharString presetNameBuffer = NULL; + char *presetSeparator; + PluginPreset preset; + Plugin plugin; + size_t substringLength; + + if (charStringIsEmpty(argumentString)) { + logWarn("Plugin chain string is empty"); + return false; + } + + substringStart = argumentString->data; + pluginSeparator = strchr(argumentString->data, CHAIN_STRING_PLUGIN_SEPARATOR); + endChar = argumentString->data + strlen(argumentString->data); + + do { + if (pluginSeparator == NULL) { + substringLength = strlen(argumentString->data); + } else { + substringLength = pluginSeparator - substringStart; + } + + pluginNameBuffer = newCharString(); + strncpy(pluginNameBuffer->data, substringStart, substringLength); + + // Look for the separator for presets to load into these plugins + presetNameBuffer = newCharString(); + presetSeparator = strchr(pluginNameBuffer->data, CHAIN_STRING_PROGRAM_SEPARATOR); + + if (presetSeparator != NULL) { + // Null-terminate this string to force it to end, then extract preset name from next char + *presetSeparator = '\0'; + strncpy(presetNameBuffer->data, presetSeparator + 1, strlen(presetSeparator + 1)); + } + + // Find preset for this plugin (if given) + preset = NULL; + + if (strlen(presetNameBuffer->data) > 0) { + logInfo("Opening preset '%s' for plugin", presetNameBuffer->data); + preset = pluginPresetFactory(presetNameBuffer); + } + + // Guess the plugin type from the file extension, search root, etc. + plugin = pluginFactory(pluginNameBuffer, userSearchPath); + + if (plugin != NULL) { + if (!pluginChainAppend(pluginChain, plugin, preset)) { + logError("Plugin '%s' could not be added to the chain", pluginNameBuffer->data); + free(pluginNameBuffer); + free(presetNameBuffer); + return false; + } + } + + if (pluginSeparator == NULL) { + break; + } else { + substringStart = pluginSeparator + 1; + pluginSeparator = strchr(pluginSeparator + 1, CHAIN_STRING_PLUGIN_SEPARATOR); + } + } while (substringStart < endChar); + + freeCharString(pluginNameBuffer); + freeCharString(presetNameBuffer); + return true; +} + +static boolByte _loadPresetForPlugin(Plugin plugin, PluginPreset preset) +{ + if (pluginPresetIsCompatibleWith(preset, plugin)) { + if (!preset->openPreset(preset)) { + logError("Could not open preset '%s'", preset->presetName->data); + return false; + } + + if (!preset->loadPreset(preset, plugin)) { + logError("Could not load preset '%s' in plugin '%s'", preset->presetName->data, plugin->pluginName->data); + return false; + } + + logInfo("Loaded preset '%s' in plugin '%s'", preset->presetName->data, plugin->pluginName->data); + return true; + } else { + logError("Preset '%s' is not a compatible format for plugin", preset->presetName->data); + return false; + } +} + +ReturnCodes pluginChainInitialize(PluginChain pluginChain) +{ + Plugin plugin; + PluginPreset preset; + unsigned int i; + + for (i = 0; i < pluginChain->numPlugins; i++) { + plugin = pluginChain->plugins[i]; + + if (!openPlugin(plugin)) { + return RETURN_CODE_PLUGIN_ERROR; + } else { + if (i > 0 && plugin->pluginType == PLUGIN_TYPE_INSTRUMENT) { + logError("Instrument plugin '%s' must be first in the chain", plugin->pluginName->data); + return RETURN_CODE_INVALID_PLUGIN_CHAIN; + } else if (plugin->pluginType == PLUGIN_TYPE_UNKNOWN) { + logError("Plugin '%s' has unknown type; It was probably not loaded correctly", plugin->pluginName->data); + return RETURN_CODE_PLUGIN_ERROR; + } else if (plugin->pluginType == PLUGIN_TYPE_UNSUPPORTED) { + logError("Plugin '%s' is of unsupported type", plugin->pluginName->data); + return RETURN_CODE_PLUGIN_ERROR; + } + + preset = pluginChain->presets[i]; + + if (preset != NULL) { + if (!_loadPresetForPlugin(plugin, preset)) { + return RETURN_CODE_INVALID_ARGUMENT; + } + } + } + } + + return RETURN_CODE_SUCCESS; +} + +void pluginChainInspect(PluginChain pluginChain) +{ + Plugin plugin; + unsigned int i; + + for (i = 0; i < pluginChain->numPlugins; i++) { + plugin = pluginChain->plugins[i]; + plugin->displayInfo(plugin); + } +} + +void pluginChainPrepareForProcessing(PluginChain self) +{ + Plugin plugin; + unsigned int i; + + for (i = 0; i < self->numPlugins; i++) { + plugin = self->plugins[i]; + plugin->prepareForProcessing(plugin); + } +} + +int pluginChainGetMaximumTailTimeInMs(PluginChain pluginChain) +{ + Plugin plugin; + int tailTime; + int maxTailTime = 0; + unsigned int i; + + for (i = 0; i < pluginChain->numPlugins; i++) { + plugin = pluginChain->plugins[i]; + tailTime = plugin->getSetting(plugin, PLUGIN_SETTING_TAIL_TIME_IN_MS); + + if (tailTime > maxTailTime) { + maxTailTime = tailTime; + } + } + + return maxTailTime; +} + +unsigned long pluginChainGetProcessingDelay(PluginChain self) +{ + unsigned long processingDelay = 0; + unsigned int i; + + for (i = 0; i < self->numPlugins; i++) { + Plugin plugin = self->plugins[i]; + processingDelay += plugin->getSetting(plugin, PLUGIN_INITIAL_DELAY); + } + + return processingDelay; +} + +typedef struct { + Plugin plugin; + boolByte success; +} _PluginChainSetParameterPassData; + +void _pluginChainSetParameter(void *item, void *userData) +{ + // Expect that the linked list contains CharStrings, single that is what is + // being given from the command line. + char *parameterValue = (char *)item; + _PluginChainSetParameterPassData *passData = (_PluginChainSetParameterPassData *)userData; + Plugin plugin = passData->plugin; + char *comma = NULL; + int index; + float value; + + // If a previous attempt to set a parameter failed, then return right away + // since this method will return false anyways. + if (!passData->success) { + return; + } + + // TODO: Need a "pair" type, this string parsing is done several times in the codebase + comma = strchr(parameterValue, ','); + + if (comma == NULL) { + logError("Malformed parameter string, see --help parameter for usage"); + return; + } + + *comma = '\0'; + index = (int)strtod(parameterValue, NULL); + value = (float)strtod(comma + 1, NULL); + logDebug("Set parameter %d to %f", index, value); + passData->success = plugin->setParameter(plugin, (unsigned int)index, value); +} + +boolByte pluginChainSetParameters(PluginChain self, const LinkedList parameters) +{ + _PluginChainSetParameterPassData passData; + passData.plugin = self->plugins[0]; + passData.success = true; + logDebug("Setting parameters on head plugin in chain"); + linkedListForeach(parameters, _pluginChainSetParameter, &passData); + return passData.success; +} + +void pluginChainSetRealtime(PluginChain self, boolByte realtime) +{ + self->_realtime = realtime; + + if (realtime) { + self->_realtimeTimer = newTaskTimerWithCString("PluginChain", "Realtime"); + } else if (self->_realtimeTimer) { + freeTaskTimer(self->_realtimeTimer); + } +} + +void pluginChainProcessAudio(PluginChain pluginChain, SampleBuffer inBuffer, SampleBuffer outBuffer) +{ + Plugin plugin; + unsigned int i; + double processingTimeInMs; + double totalProcessingTimeInMs; + const double maxProcessingTimeInMs = inBuffer->blocksize * 1000.0 / getSampleRate(); + + if (pluginChain->_realtime) { + taskTimerStart(pluginChain->_realtimeTimer); + } + + SampleBuffer formerOutputBuffer = inBuffer; + SampleBuffer nextInputBuffer = NULL; + + for (i = 0; i < pluginChain->numPlugins; i++) { + plugin = pluginChain->plugins[i]; + logDebug("Processing audio with plugin '%s'", plugin->pluginName->data); + nextInputBuffer = plugin->inputBuffer; + nextInputBuffer->blocksize = formerOutputBuffer->blocksize; + sampleBufferCopyAndMapChannels(nextInputBuffer, formerOutputBuffer); + plugin->outputBuffer->blocksize = plugin->inputBuffer->blocksize; + taskTimerStart(pluginChain->audioTimers[i]); + plugin->processAudio(plugin, plugin->inputBuffer, plugin->outputBuffer); + processingTimeInMs = taskTimerStop(pluginChain->audioTimers[i]); + + if (processingTimeInMs > maxProcessingTimeInMs && pluginChain->_realtime) { + logWarn("Possible dropout! Plugin '%s' spent %dms processing time (%dms max)", + plugin->pluginName->data, (int)processingTimeInMs, (int)maxProcessingTimeInMs); + } else { + logDebug("Plugin '%s' spent %dms processing (%d%% effective CPU usage)", + plugin->pluginName->data, (int)processingTimeInMs, + (int)(processingTimeInMs / maxProcessingTimeInMs)); + } + + formerOutputBuffer = plugin->outputBuffer; + } + + nextInputBuffer = outBuffer; + nextInputBuffer->blocksize = formerOutputBuffer->blocksize; + sampleBufferCopyAndMapChannels(nextInputBuffer, formerOutputBuffer); + + if (pluginChain->_realtime) { + totalProcessingTimeInMs = taskTimerStop(pluginChain->_realtimeTimer); + + if (totalProcessingTimeInMs < maxProcessingTimeInMs) { + taskTimerSleep(maxProcessingTimeInMs - totalProcessingTimeInMs); + } + } +} + +void pluginChainProcessMidi(PluginChain pluginChain, LinkedList midiEvents) +{ + Plugin plugin; + + if (midiEvents->item != NULL) { + logDebug("Processing plugin chain MIDI events"); + // Right now, we only process MIDI in the first plugin in the chain + // TODO: Is this really the correct behavior? How do other sequencers do it? + plugin = pluginChain->plugins[0]; + taskTimerStart(pluginChain->midiTimers[0]); + plugin->processMidiEvents(plugin, midiEvents); + taskTimerStop(pluginChain->midiTimers[0]); + } +} + +void pluginChainShutdown(PluginChain pluginChain) +{ + Plugin plugin; + unsigned int i; + + for (i = 0; i < pluginChain->numPlugins; i++) { + plugin = pluginChain->plugins[i]; + logInfo("Closing plugin '%s'", plugin->pluginName->data); + closePlugin(plugin); + } +} + +void freePluginChain(PluginChain pluginChain) +{ + unsigned int i; + + for (i = 0; i < pluginChain->numPlugins; i++) { + freePluginPreset(pluginChain->presets[i]); + freePlugin(pluginChain->plugins[i]); + freeTaskTimer(pluginChain->audioTimers[i]); + freeTaskTimer(pluginChain->midiTimers[i]); + } + + free(pluginChain->presets); + free(pluginChain->plugins); + free(pluginChain->audioTimers); + free(pluginChain->midiTimers); + + if (pluginChain->_realtime) { + freeTaskTimer(pluginChain->_realtimeTimer); + } + + free(pluginChain); +} diff --git a/source/plugin/PluginChain.h b/source/plugin/PluginChain.h new file mode 100644 index 0000000..62c58a1 --- /dev/null +++ b/source/plugin/PluginChain.h @@ -0,0 +1,165 @@ +// +// PluginChain.h - 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. +// + +#ifndef MrsWatson_PluginChain_h +#define MrsWatson_PluginChain_h + +#include "app/ReturnCodes.h" +#include "base/LinkedList.h" +#include "plugin/Plugin.h" +#include "plugin/PluginPreset.h" +#include "time/TaskTimer.h" + +#define MAX_PLUGINS 8 +#define CHAIN_STRING_PLUGIN_SEPARATOR ';' +#define CHAIN_STRING_PROGRAM_SEPARATOR ',' + +typedef struct { + unsigned int numPlugins; + Plugin *plugins; + PluginPreset *presets; + TaskTimer *audioTimers; + TaskTimer *midiTimers; + + // Private fields + boolByte _realtime; + TaskTimer _realtimeTimer; +} PluginChainMembers; + +/** + * Class which holds multiple plugins which process audio in serial. Only one + * instrument may be present in a plugin chain. + */ +typedef PluginChainMembers *PluginChain; + +/** + * Get a reference to the global plugin chain instance. + * @return Reference to global plugin chain, or NULL if the global instance has + * not yet been initialized. + */ +PluginChain getPluginChain(void); + +/** + * Initialize the global plugin chain instance. Should be called fairly + * early in the program initialization. + */ +void initPluginChain(void); + +/** + * Append a plugin to the end of the chain + * @param self + * @param plugin Plugin to add + * @param preset Preset to be loaded into the plugin. If no preset is desired, + * passed NULL here. + * @return True if the plugin could be added to the end of the chain + */ +boolByte pluginChainAppend(PluginChain self, Plugin plugin, PluginPreset preset); + +// TODO: Deprecate and remove this function +boolByte pluginChainAddFromArgumentString(PluginChain self, const CharString argumentString, const CharString userSearchPath); + +/** + * Open and initialize all plugins in the chain. + * @param self + * @return RETURN_CODE_SUCCESS on success, other code on failure + */ +ReturnCodes pluginChainInitialize(PluginChain self); + +/** + * Inspect each plugin in the chain + * @param self + */ +void pluginChainInspect(PluginChain self); + +/** + * Get the maximum amount of tail time (post*processing time with empty input) + * needed for the chain. This is essentially the largest tail time value for any + * plug-in in the chain. + * @param self + * @return Maximum tail time, in milliseconds + */ +int pluginChainGetMaximumTailTimeInMs(PluginChain self); + +/** + * Get the total processing delay in frames. + * @param self + * @return Total processing delay, in frames. + */ +unsigned long pluginChainGetProcessingDelay(PluginChain self); + +/** + * Set parameters on the first plugin in a chain. + * @param self + * @param parameters List of parameters to be applied + * @return True if all parameters were set, false otherwise + */ +boolByte pluginChainSetParameters(PluginChain self, const LinkedList parameters); + +/** + * Set realtime mode for the plugin chain. When set, calls to pluginChainProcessAudio() + * will sleep for the additional time required to process the block in realtime. + * @param realtime True to enable realtime mode, false to disable (default) + * @param self + */ +void pluginChainSetRealtime(PluginChain self, boolByte realtime); + +/** + * Prepare each plugin in the chain for processing. This should be called before + * the first block of audio is sent to the chain. + * @param self + */ +void pluginChainPrepareForProcessing(PluginChain self); + +/** + * Process a single block of samples through each plugin in the chain. + * @param self + * @param inBuffer Input sample block + * @param outBuffer Output sample block + */ +void pluginChainProcessAudio(PluginChain self, SampleBuffer inBuffer, SampleBuffer outBuffer); + +/** + * Send a list of MIDI events to be processed by the chain. Currently, only the + * first plugin in the chain will receive these events. + * @param self + * @param midiEvents List of events to process + */ +void pluginChainProcessMidi(PluginChain self, LinkedList midiEvents); + +/** + * Close all plugins in the chain + * @param self + */ +void pluginChainShutdown(PluginChain self); + +/** + * Free the plugin chain and all associated resources (including all plugins) + * @param self + */ +void freePluginChain(PluginChain self); + +#endif diff --git a/source/plugin/PluginGain.c b/source/plugin/PluginGain.c new file mode 100644 index 0000000..d2f21b9 --- /dev/null +++ b/source/plugin/PluginGain.c @@ -0,0 +1,133 @@ +// +// PluginGain.c - MrsWatson +// Created by Nik Reiman on 26 May 14. +// Copyright (c) 2014 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. +// + +#include "audio/SampleBuffer.h" +#include "logging/EventLogger.h" +#include "plugin/PluginGain.h" + +const char *kInternalPluginGainName = INTERNAL_PLUGIN_PREFIX "gain"; + +static void _pluginGainEmpty(void *pluginPtr) +{ + // Nothing to do here +} + +static boolByte _pluginGainOpen(void *pluginPtr) +{ + return true; +} + +static void _pluginGainGetAbsolutePath(void *pluginPtr, CharString outPath) +{ + // Internal plugins don't have a path, and thus can't be copied. So just copy + // an empty string here and let any callers needing the absolute path to check + // for this value before doing anything important. + charStringClear(outPath); +} + +static void _pluginGainDisplayInfo(void *pluginPtr) +{ + logInfo("Information for Internal plugin '%s'", kInternalPluginGainName); + logInfo("Type: effect, parameters: none"); + logInfo("Description: a basic gain effect"); +} + +static int _pluginGainGetSetting(void *pluginPtr, PluginSetting pluginSetting) +{ + switch (pluginSetting) { + case PLUGIN_SETTING_TAIL_TIME_IN_MS: + return 0; + + case PLUGIN_NUM_INPUTS: + return 2; + + case PLUGIN_NUM_OUTPUTS: + return 2; + + default: + return 0; + } +} + +static void _pluginGainProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs) +{ + Plugin plugin = (Plugin)pluginPtr; + PluginGainSettings settings = (PluginGainSettings)plugin->extraData; + unsigned long channel, sample; + + sampleBufferCopyAndMapChannels(outputs, inputs); + + for (channel = 0; channel < outputs->numChannels; ++channel) { + for (sample = 0; sample < outputs->blocksize; ++sample) { + outputs->samples[channel][sample] *= settings->gain; + } + } +} + +static void _pluginGainProcessMidiEvents(void *pluginPtr, LinkedList midiEvents) +{ + // Nothing to do here +} + +static boolByte _pluginGainSetParameter(void *pluginPtr, unsigned int i, float value) +{ + Plugin plugin = (Plugin)pluginPtr; + PluginGainSettings settings = (PluginGainSettings)plugin->extraData; + + switch (i) { + case PLUGIN_GAIN_SETTINGS_GAIN: + settings->gain = value; + return true; + + default: + logError("Attempt to set invalid parameter %d on internal gain plugin", i); + return false; + } +} + +Plugin newPluginGain(const CharString pluginName) +{ + Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_EFFECT); + PluginGainSettings settings = (PluginGainSettings)malloc(sizeof(PluginGainSettingsMembers)); + + charStringCopy(plugin->pluginName, pluginName); + charStringCopyCString(plugin->pluginLocation, "Internal"); + + plugin->openPlugin = _pluginGainOpen; + plugin->displayInfo = _pluginGainDisplayInfo; + plugin->getSetting = _pluginGainGetSetting; + plugin->prepareForProcessing = _pluginGainEmpty; + plugin->processAudio = _pluginGainProcessAudio; + plugin->processMidiEvents = _pluginGainProcessMidiEvents; + plugin->setParameter = _pluginGainSetParameter; + plugin->closePlugin = _pluginGainEmpty; + plugin->freePluginData = _pluginGainEmpty; + + settings->gain = 1.0f; + plugin->extraData = settings; + return plugin; +} diff --git a/source/plugin/PluginGain.h b/source/plugin/PluginGain.h new file mode 100644 index 0000000..b1d6b65 --- /dev/null +++ b/source/plugin/PluginGain.h @@ -0,0 +1,47 @@ +// +// PluginGain.h - MrsWatson +// Created by Nik Reiman on 26 May 14. +// Copyright (c) 2014 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. +// + +#ifndef MrsWatson_PluginGain_h +#define MrsWatson_PluginGain_h + +#include "plugin/Plugin.h" + +extern const char *kInternalPluginGainName; + +typedef enum { + PLUGIN_GAIN_SETTINGS_GAIN, + PLUGIN_GAIN_NUM_SETTINGS +} PluginGainSettingsIndex; + +typedef struct { + float gain; +} PluginGainSettingsMembers; +typedef PluginGainSettingsMembers *PluginGainSettings; + +Plugin newPluginGain(const CharString pluginName); + +#endif diff --git a/source/plugin/PluginLimiter.c b/source/plugin/PluginLimiter.c new file mode 100644 index 0000000..4ae20d2 --- /dev/null +++ b/source/plugin/PluginLimiter.c @@ -0,0 +1,121 @@ +// +// PluginLimiter.c - MrsWatson +// Created by Nik Reiman on 26 May 14. +// Copyright (c) 2014 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. +// + +#include "audio/SampleBuffer.h" +#include "logging/EventLogger.h" +#include "plugin/PluginLimiter.h" + +const char *kInternalPluginLimiterName = INTERNAL_PLUGIN_PREFIX "limiter"; + +static void _pluginLimiterEmpty(void *pluginPtr) +{ + // Nothing to do here +} + +static boolByte _pluginLimiterOpen(void *pluginPtr) +{ + return true; +} + +static void _pluginLimiterGetAbsolutePath(void *pluginPtr, CharString outPath) +{ + // Internal plugins don't have a path, and thus can't be copied. So just copy + // an empty string here and let any callers needing the absolute path to check + // for this value before doing anything important. + charStringClear(outPath); +} + +static void _pluginLimiterDisplayInfo(void *pluginPtr) +{ + logInfo("Information for Internal plugin '%s'", kInternalPluginLimiterName); + logInfo("Type: effect, parameters: none"); + logInfo("Description: a brickwall limiter effect"); +} + +static int _pluginLimiterGetSetting(void *pluginPtr, PluginSetting pluginSetting) +{ + switch (pluginSetting) { + case PLUGIN_SETTING_TAIL_TIME_IN_MS: + return 0; + + case PLUGIN_NUM_INPUTS: + return 2; + + case PLUGIN_NUM_OUTPUTS: + return 2; + + default: + return 0; + } +} + +static void _pluginLimiterProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs) +{ + unsigned long channel, sample; + + sampleBufferCopyAndMapChannels(outputs, inputs); + + for (channel = 0; channel < outputs->numChannels; ++channel) { + for (sample = 0; sample < outputs->blocksize; ++sample) { + if (outputs->samples[channel][sample] > 1.0f) { + outputs->samples[channel][sample] = 1.0f; + } else if (outputs->samples[channel][sample] < -1.0f) { + outputs->samples[channel][sample] = -1.0f; + } + } + } +} + +static void _pluginLimiterProcessMidiEvents(void *pluginPtr, LinkedList midiEvents) +{ + // Nothing to do here +} + +static boolByte _pluginLimiterSetParameter(void *pluginPtr, unsigned int i, float value) +{ + return false; +} + +Plugin newPluginLimiter(const CharString pluginName) +{ + Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_EFFECT); + charStringCopy(plugin->pluginName, pluginName); + charStringCopyCString(plugin->pluginLocation, "Internal"); + + plugin->openPlugin = _pluginLimiterOpen; + plugin->displayInfo = _pluginLimiterDisplayInfo; + plugin->getSetting = _pluginLimiterGetSetting; + plugin->prepareForProcessing = _pluginLimiterEmpty; + plugin->processAudio = _pluginLimiterProcessAudio; + plugin->processMidiEvents = _pluginLimiterProcessMidiEvents; + plugin->setParameter = _pluginLimiterSetParameter; + plugin->closePlugin = _pluginLimiterEmpty; + plugin->freePluginData = _pluginLimiterEmpty; + + plugin->extraData = NULL; + return plugin; +} diff --git a/source/plugin/PluginLimiter.h b/source/plugin/PluginLimiter.h new file mode 100644 index 0000000..af6ddd7 --- /dev/null +++ b/source/plugin/PluginLimiter.h @@ -0,0 +1,37 @@ +// +// PluginLimiter.h - MrsWatson +// Created by Nik Reiman on 26 May 14. +// Copyright (c) 2014 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. +// + +#ifndef MrsWatson_PluginLimiter_h +#define MrsWatson_PluginLimiter_h + +#include "plugin/Plugin.h" + +extern const char *kInternalPluginLimiterName; + +Plugin newPluginLimiter(const CharString pluginName); + +#endif diff --git a/source/plugin/PluginPassthru.c b/source/plugin/PluginPassthru.c new file mode 100644 index 0000000..353eeed --- /dev/null +++ b/source/plugin/PluginPassthru.c @@ -0,0 +1,105 @@ +// +// PluginPassthru.c - MrsWatson +// Created by Nik Reiman on 8/17/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. +// + +#include <stdlib.h> + +#include "logging/EventLogger.h" +#include "plugin/PluginPassthru.h" + +const char *kInternalPluginPassthruName = INTERNAL_PLUGIN_PREFIX "passthru"; + +static void _pluginPassthruEmpty(void *pluginPtr) +{ + // Nothing to do here +} + +static boolByte _pluginPassthruOpen(void *pluginPtr) +{ + return true; +} + +static void _pluginPassthruDisplayInfo(void *pluginPtr) +{ + logInfo("Information for Internal plugin '%s'", kInternalPluginPassthruName); + logInfo("Type: effect, parameters: none"); + logInfo("Description: a passthru effect which copies input data to the output"); +} + +static int _pluginPassthruGetSetting(void *pluginPtr, PluginSetting pluginSetting) +{ + switch (pluginSetting) { + case PLUGIN_SETTING_TAIL_TIME_IN_MS: + return 0; + + case PLUGIN_NUM_INPUTS: + return 2; + + case PLUGIN_NUM_OUTPUTS: + return 2; + + case PLUGIN_INITIAL_DELAY: + return 0; + + default: + return 0; + } +} + +static void _pluginPassthruProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs) +{ + sampleBufferCopyAndMapChannels(outputs, inputs); +} + +static void _pluginPassthruProcessMidiEvents(void *pluginPtr, LinkedList midiEvents) +{ + // Nothing to do here +} + +static boolByte _pluginPassthruSetParameter(void *pluginPtr, unsigned int i, float value) +{ + return false; +} + +Plugin newPluginPassthru(const CharString pluginName) +{ + Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_EFFECT); + charStringCopy(plugin->pluginName, pluginName); + charStringCopyCString(plugin->pluginLocation, "Internal"); + + plugin->openPlugin = _pluginPassthruOpen; + plugin->displayInfo = _pluginPassthruDisplayInfo; + plugin->getSetting = _pluginPassthruGetSetting; + plugin->prepareForProcessing = _pluginPassthruEmpty; + plugin->processAudio = _pluginPassthruProcessAudio; + plugin->processMidiEvents = _pluginPassthruProcessMidiEvents; + plugin->setParameter = _pluginPassthruSetParameter; + plugin->closePlugin = _pluginPassthruEmpty; + plugin->freePluginData = _pluginPassthruEmpty; + + plugin->extraData = NULL; + return plugin; +} diff --git a/source/plugin/PluginPassthru.h b/source/plugin/PluginPassthru.h new file mode 100644 index 0000000..61848ad --- /dev/null +++ b/source/plugin/PluginPassthru.h @@ -0,0 +1,37 @@ +// +// PluginPassthru.h - MrsWatson +// Created by Nik Reiman on 8/17/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. +// + +#ifndef MrsWatson_PluginPassthru_h +#define MrsWatson_PluginPassthru_h + +#include "plugin/Plugin.h" + +extern const char *kInternalPluginPassthruName; + +Plugin newPluginPassthru(const CharString pluginName); + +#endif diff --git a/source/plugin/PluginPreset.c b/source/plugin/PluginPreset.c new file mode 100644 index 0000000..4c22211 --- /dev/null +++ b/source/plugin/PluginPreset.c @@ -0,0 +1,104 @@ +// +// PluginPreset.c - MrsWatson +// Created by Nik Reiman on 1/13/12. +// Copyright (c) 2011 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. +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "base/File.h" +#include "logging/EventLogger.h" +#include "plugin/PluginPreset.h" +#include "plugin/PluginPresetFxp.h" +#include "plugin/PluginPresetInternalProgram.h" + +static PluginPresetType _pluginPresetGuessType(const CharString presetName) +{ + if (presetName == NULL || charStringIsEmpty(presetName)) { + return PRESET_TYPE_INVALID; + } + + File presetFile = newFileWithPath(presetName); + CharString fileExtension = fileGetExtension(presetFile); + freeFile(presetFile); + + if (fileExtension == NULL) { + for (size_t i = 0; i < strlen(presetName->data); i++) { + if (!charStringIsNumber(presetName, i)) { + return PRESET_TYPE_INVALID; + } + } + + // If the preset name is all numeric, then it's an internal program number + return PRESET_TYPE_INTERNAL_PROGRAM; + } else if (charStringIsEqualToCString(fileExtension, "fxp", true)) { + freeCharString(fileExtension); + return PRESET_TYPE_FXP; + } else { + logCritical("Preset '%s' does not match any supported type", presetName->data); + freeCharString(fileExtension); + return PRESET_TYPE_INVALID; + } +} + +PluginPreset pluginPresetFactory(const CharString presetName) +{ + PluginPresetType presetType = _pluginPresetGuessType(presetName); + + switch (presetType) { + case PRESET_TYPE_FXP: + return newPluginPresetFxp(presetName); + + case PRESET_TYPE_INTERNAL_PROGRAM: + return newPluginPresetInternalProgram(presetName); + + default: + return NULL; + } +} + +void pluginPresetSetCompatibleWith(PluginPreset pluginPreset, PluginInterfaceType interfaceType) +{ + pluginPreset->compatiblePluginTypes |= (1 << interfaceType); +} + +boolByte pluginPresetIsCompatibleWith(const PluginPreset pluginPreset, const Plugin plugin) +{ + return (pluginPreset->compatiblePluginTypes & (1 << plugin->interfaceType)); +} + +void freePluginPreset(PluginPreset pluginPreset) +{ + if (pluginPreset != NULL) { + if (pluginPreset->extraData != NULL) { + pluginPreset->freePresetData(pluginPreset->extraData); + free(pluginPreset->extraData); + } + + freeCharString(pluginPreset->presetName); + free(pluginPreset); + } +} diff --git a/source/plugin/PluginPreset.h b/source/plugin/PluginPreset.h new file mode 100644 index 0000000..76e2b79 --- /dev/null +++ b/source/plugin/PluginPreset.h @@ -0,0 +1,111 @@ +// +// PluginPreset.h - MrsWatson +// Created by Nik Reiman on 1/13/12. +// Copyright (c) 2011 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. +// + +#ifndef MrsWatson_PluginPreset_h +#define MrsWatson_PluginPreset_h + +#include "base/CharString.h" +#include "plugin/Plugin.h" + +typedef enum { + PRESET_TYPE_INVALID, + PRESET_TYPE_FXP, + PRESET_TYPE_INTERNAL_PROGRAM, + NUM_PRESET_TYPES +} PluginPresetType; + +/** + * Called when the preset is to be loaded from the filesystem + * @param pluginPresetPtr self + */ +typedef boolByte (*OpenPresetFunc)(void *pluginPresetPtr); +/** + * Called when the preset is to be loaded into a plugin + * @param plugin Plugin which will receive the preset. This should check that + * the preset is compatible with the given plugging before loading it. + * @param pluginPresetPtr self + * @return True on success, false on failure + */ +typedef boolByte (*LoadPresetFunc)(void *pluginPresetPtr, Plugin plugin); +/** + * Free a preset and it's related data + * @param pluginPresetPtr self + */ +typedef void (*FreePresetDataFunc)(void *pluginPresetPtr); + +typedef struct { + PluginPresetType presetType; + CharString presetName; + unsigned int compatiblePluginTypes; + + OpenPresetFunc openPreset; + LoadPresetFunc loadPreset; + FreePresetDataFunc freePresetData; + + void *extraData; +} PluginPresetMembers; + +/** + * Class which is used to hold preset data which will be loaded into a plugin + * before audio processing. + */ +typedef PluginPresetMembers *PluginPreset; + +/** + * Create a new plugin preset from a given name. Usually this function inspects + * the name and guesses an appropriate handler based on the file extension. + * @param presetName Preset name + * @return Initialized PluginPreset or NULL if no preset type found + */ +PluginPreset pluginPresetFactory(const CharString presetName); + +/** + * Check if a preset will be compatible with a plugin. Some plugin interface + * types have extra safety checks to make sure that a preset must match the + * plugin's ID, this call essentially wraps this functionality. + * @param self + * @param plugin Plugin to check against + * @return True if the preset can be loaded into the plugin + */ +boolByte pluginPresetIsCompatibleWith(const PluginPreset self, const Plugin plugin); + +/** + * Set interface compatibility for a preset type. This function should only be + * called from a PluginPreset subclass, you should not have to manually call + * this in normal host operations. + * @param self + * @param interfaceType Interface type to set + */ +void pluginPresetSetCompatibleWith(PluginPreset self, PluginInterfaceType interfaceType); + +/** + * Free a PluginPreset and all associated resources + * @param self + */ +void freePluginPreset(PluginPreset self); + +#endif diff --git a/source/plugin/PluginPresetFxp.c b/source/plugin/PluginPresetFxp.c new file mode 100644 index 0000000..200d310 --- /dev/null +++ b/source/plugin/PluginPresetFxp.c @@ -0,0 +1,280 @@ +// +// PluginPresetFxp.c - MrsWatson +// Created by Nik Reiman on 1/13/12. +// Copyright (c) 2011 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. +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "base/Endian.h" +#include "logging/EventLogger.h" +#include "plugin/PluginPresetFxp.h" +#include "plugin/PluginVst2x.h" + +static boolByte _openPluginPresetFxp(void *pluginPresetPtr) +{ + PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr; + PluginPresetFxpData extraData = (PluginPresetFxpData)(pluginPreset->extraData); + extraData->fileHandle = fopen(pluginPreset->presetName->data, "rb"); + + if (extraData->fileHandle == NULL) { + logError("Preset '%s' could not be opened for reading", pluginPreset->presetName->data); + return false; + } + + return true; +} + +static boolByte _loadPluginPresetFxp(void *pluginPresetPtr, Plugin plugin) +{ + PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr; + PluginPresetFxpData extraData = (PluginPresetFxpData)(pluginPreset->extraData); + FxpProgram inProgram = NULL; + PluginPresetFxpProgramType programType; + + char *chunk; + size_t chunkSize; + unsigned int valueBuffer; + size_t numObjectsRead; + float parameterValue; + unsigned int i; + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at chunkMagic"); + return false; + } + + inProgram = (FxpProgram)malloc(sizeof(FxpProgramMembers)); + inProgram->chunkMagic = convertBigEndianIntToPlatform(valueBuffer); + + if (inProgram->chunkMagic != 0x43636E4B) { // 'CcnK' + logError("FXP preset file has bad chunk magic"); + free(inProgram); + return false; + } + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at byteSize"); + free(inProgram); + return false; + } + + inProgram->byteSize = convertBigEndianIntToPlatform(valueBuffer); + logDebug("FXP program has %d bytes in main chunk", inProgram->byteSize); + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at fxMagic"); + free(inProgram); + return false; + } + + inProgram->fxMagic = convertBigEndianIntToPlatform(valueBuffer); + + if (inProgram->fxMagic == 0x4678436b) { // 'FxCk' + programType = FXP_TYPE_REGULAR; + } else if (inProgram->fxMagic == 0x46504368) { // 'FPCh' + programType = FXP_TYPE_OPAQUE_CHUNK; + } else { + logError("FXP preset has invalid fxMagic type"); + free(inProgram); + return false; + } + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at version"); + free(inProgram); + return false; + } + + inProgram->version = convertBigEndianIntToPlatform(valueBuffer); + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at fxID"); + free(inProgram); + return false; + } + + inProgram->fxID = convertBigEndianIntToPlatform(valueBuffer); + logDebug("Preset's fxID is %d", inProgram->fxID); + + if (inProgram->fxID != pluginVst2xGetUniqueId(plugin)) { + logError("Preset '%s' is not compatible with plugin '%s'", pluginPreset->presetName->data, plugin->pluginName->data); + free(inProgram); + return false; + } + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at fxVersion"); + free(inProgram); + return false; + } + + inProgram->fxVersion = convertBigEndianIntToPlatform(valueBuffer); + + if (inProgram->fxVersion != pluginVst2xGetVersion(plugin)) { + logWarn("Plugin has version %ld, but preset has version %d. Loading this preset may result in unexpected behavior!", + pluginVst2xGetVersion(plugin), inProgram->fxVersion); + } else { + logDebug("Preset's version is %d", inProgram->fxVersion); + } + + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at numParams"); + free(inProgram); + return false; + } + + inProgram->numParams = convertBigEndianIntToPlatform(valueBuffer); + logDebug("Preset has %d params", inProgram->numParams); + + memset(inProgram->prgName, 0, sizeof(char) * 28); + numObjectsRead = fread(inProgram->prgName, sizeof(char), 28, extraData->fileHandle); + + if (numObjectsRead != 28) { + logError("Short read of FXP preset file at prgName"); + free(inProgram); + return false; + } + + charStringCopyCString(pluginPreset->presetName, inProgram->prgName); + logDebug("Preset's name is %s", pluginPreset->presetName->data); + + if (programType == FXP_TYPE_REGULAR) { + for (i = 0; i < inProgram->numParams; i++) { + float parameterBuffer = 0.0f; + numObjectsRead = fread(¶meterBuffer, sizeof(float), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset at parameter data"); + free(inProgram); + return false; + } + + parameterValue = convertBigEndianFloatToPlatform(parameterBuffer); + plugin->setParameter(plugin, i, parameterValue); + } + } else if (programType == FXP_TYPE_OPAQUE_CHUNK) { + numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle); + + if (numObjectsRead != 1) { + logError("Short read of FXP preset file at chunk size"); + free(inProgram); + return false; + } + + inProgram->content.data.size = convertBigEndianIntToPlatform(valueBuffer); + chunkSize = inProgram->content.data.size; + + if (chunkSize == 0) { + logError("FXP preset has chunk of 0 bytes"); + free(inProgram); + return false; + } else { + logDebug("Plugin has chunk size of %d bytes", chunkSize); + } + + chunk = (char *)malloc(sizeof(char) * chunkSize); + memset(chunk, 0, sizeof(char) * chunkSize); + numObjectsRead = fread(chunk, sizeof(char), chunkSize, extraData->fileHandle); + + if (numObjectsRead != chunkSize) { + logError("Short read of FXP preset file at chunk"); + free(inProgram); + free(chunk); + return false; + } + + // The chunk has been read, set it to the actual plugin + if (plugin->interfaceType == PLUGIN_TYPE_VST_2X) { + pluginVst2xSetProgramChunk(plugin, chunk, chunkSize); + free(inProgram); + free(chunk); + return true; + } else { + logInternalError("Load FXP preset to wrong plugin type"); + free(inProgram); + free(chunk); + return false; + } + } else { + logInternalError("Invalid FXP program type"); + free(inProgram); + return false; + } + + free(inProgram); + return true; +} + +static void _freePluginPresetDataFxp(void *extraDataPtr) +{ + PluginPresetFxpData extraData = extraDataPtr; + + if (extraData->fileHandle != NULL) { + fclose(extraData->fileHandle); + } + + if (extraData->chunk != NULL) { + free(extraData->chunk); + } +} + +PluginPreset newPluginPresetFxp(const CharString presetName) +{ + PluginPreset pluginPreset = (PluginPreset)malloc(sizeof(PluginPresetMembers)); + PluginPresetFxpData extraData = (PluginPresetFxpData)malloc(sizeof(PluginPresetFxpDataMembers)); + + pluginPreset->presetType = PRESET_TYPE_FXP; + pluginPreset->presetName = newCharString(); + charStringCopy(pluginPreset->presetName, presetName); + pluginPreset->compatiblePluginTypes = 0; + pluginPresetSetCompatibleWith(pluginPreset, PLUGIN_TYPE_VST_2X); + + pluginPreset->openPreset = _openPluginPresetFxp; + pluginPreset->loadPreset = _loadPluginPresetFxp; + pluginPreset->freePresetData = _freePluginPresetDataFxp; + + extraData->fileHandle = NULL; + extraData->chunk = NULL; + pluginPreset->extraData = extraData; + + return pluginPreset; +} + diff --git a/source/plugin/PluginPresetFxp.h b/source/plugin/PluginPresetFxp.h new file mode 100644 index 0000000..c951dbb --- /dev/null +++ b/source/plugin/PluginPresetFxp.h @@ -0,0 +1,74 @@ +// +// PluginPresetFxp.h - MrsWatson +// Created by Nik Reiman on 1/13/12. +// Copyright (c) 2011 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. +// + +#ifndef MrsWatson_PluginPresetFxp_h +#define MrsWatson_PluginPresetFxp_h + +#include <stdio.h> + +#include "plugin/PluginPreset.h" + +typedef enum { + FXP_TYPE_INVALID, + FXP_TYPE_REGULAR, + FXP_TYPE_OPAQUE_CHUNK, +} PluginPresetFxpProgramType; + +// Copied from the VST SDK. Yes, this is a bit lame, but otherwise the C++ "virus" +// starts to leak into the code again, and as vstfxstore.h is pure C, I don't see +// any reason why all these files must be compiled as C++. +typedef struct { + unsigned int chunkMagic; ///< 'CcnK' + unsigned int byteSize; ///< size of this chunk, excl. magic + byteSize + + unsigned int fxMagic; ///< 'FxCk' (regular) or 'FPCh' (opaque chunk) + unsigned int version; ///< format version (currently 1) + unsigned int fxID; ///< fx unique ID + unsigned int fxVersion; ///< fx version + + unsigned int numParams; ///< number of parameters + char prgName[28]; ///< program name (null-terminated ASCII string) + + union { + float *params; ///< variable sized array with parameter values + struct { + unsigned int size; ///< size of program data + char *chunk; ///< variable sized array with opaque program data + } data; ///< program chunk data + } content; ///< program content depending on fxMagic +} FxpProgramMembers; +typedef FxpProgramMembers *FxpProgram; + +typedef struct { + FILE *fileHandle; + byte *chunk; +} PluginPresetFxpDataMembers; +typedef PluginPresetFxpDataMembers *PluginPresetFxpData; + +PluginPreset newPluginPresetFxp(const CharString presetName); + +#endif diff --git a/source/plugin/PluginPresetInternalProgram.c b/source/plugin/PluginPresetInternalProgram.c new file mode 100644 index 0000000..27e5a89 --- /dev/null +++ b/source/plugin/PluginPresetInternalProgram.c @@ -0,0 +1,74 @@ +// +// PluginPresetInternalProgram.c - MrsWatson +// Created by Nik Reiman on 19 May 13. +// Copyright (c) 2013 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. +// + +#include <stdlib.h> + +#include "base/CharString.h" +#include "plugin/PluginPresetInternalProgram.h" +#include "plugin/PluginVst2x.h" + +static boolByte _openPluginPresetInternalProgram(void *pluginPresetPtr) +{ + PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr; + PluginPresetInternalProgramData extraData = (PluginPresetInternalProgramData)pluginPreset->extraData; + extraData->programNumber = (unsigned int)strtoul(pluginPreset->presetName->data, NULL, 10); + return true; +} + +static boolByte _loadPluginPresetInternalProgram(void *pluginPresetPtr, Plugin plugin) +{ + PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr; + PluginPresetInternalProgramData extraData = (PluginPresetInternalProgramData)pluginPreset->extraData; + return pluginVst2xSetProgram(plugin, extraData->programNumber); +} + +static void _freePluginPresetInternalProgram(void *extraDataPtr) +{ + // Nothing needed here +} + +PluginPreset newPluginPresetInternalProgram(const CharString presetName) +{ + PluginPreset pluginPreset = (PluginPreset)malloc(sizeof(PluginPresetMembers)); + PluginPresetInternalProgramData extraData; // Yay for long type names! + extraData = (PluginPresetInternalProgramData)malloc(sizeof(PluginPresetInternalProgramDataMembers)); + + pluginPreset->presetType = PRESET_TYPE_INTERNAL_PROGRAM; + pluginPreset->presetName = newCharString(); + charStringCopy(pluginPreset->presetName, presetName); + pluginPreset->compatiblePluginTypes = 0; + pluginPresetSetCompatibleWith(pluginPreset, PLUGIN_TYPE_VST_2X); + + pluginPreset->openPreset = _openPluginPresetInternalProgram; + pluginPreset->loadPreset = _loadPluginPresetInternalProgram; + pluginPreset->freePresetData = _freePluginPresetInternalProgram; + + extraData->programNumber = 0; + pluginPreset->extraData = extraData; + + return pluginPreset; +} diff --git a/source/plugin/PluginPresetInternalProgram.h b/source/plugin/PluginPresetInternalProgram.h new file mode 100644 index 0000000..d93795e --- /dev/null +++ b/source/plugin/PluginPresetInternalProgram.h @@ -0,0 +1,40 @@ +// +// PluginPresetInternalProgram.h - MrsWatson +// Created by Nik Reiman on 19 May 13. +// Copyright (c) 2013 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. +// + +#ifndef MrsWatson_PluginPresetInternalProgram_h +#define MrsWatson_PluginPresetInternalProgram_h + +#include "plugin/PluginPreset.h" + +typedef struct { + unsigned int programNumber; +} PluginPresetInternalProgramDataMembers; +typedef PluginPresetInternalProgramDataMembers *PluginPresetInternalProgramData; + +PluginPreset newPluginPresetInternalProgram(const CharString presetName); + +#endif diff --git a/source/plugin/PluginSilence.c b/source/plugin/PluginSilence.c new file mode 100644 index 0000000..f3f79a3 --- /dev/null +++ b/source/plugin/PluginSilence.c @@ -0,0 +1,105 @@ +// +// PluginSilence.c - MrsWatson +// Created by Nik Reiman on 19 May 13. +// Copyright (c) 2013 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. +// + +#include <stdlib.h> + +#include "logging/EventLogger.h" +#include "plugin/PluginSilence.h" + +const char *kInternalPluginSilenceName = INTERNAL_PLUGIN_PREFIX "silence"; + +static void _pluginSilenceEmpty(void *pluginPtr) +{ + // Nothing to do here +} + +static boolByte _pluginSilenceOpen(void *pluginPtr) +{ + return true; +} + +static void _pluginSilenceDisplayInfo(void *pluginPtr) +{ + logInfo("Information for Internal plugin '%s'", kInternalPluginSilenceName); + logInfo("Type: instrument, parameters: none"); + logInfo("Description: an instrument which generates silence"); +} + +static int _pluginSilenceGetSetting(void *pluginPtr, PluginSetting pluginSetting) +{ + switch (pluginSetting) { + case PLUGIN_SETTING_TAIL_TIME_IN_MS: + return 0; + + case PLUGIN_NUM_INPUTS: + return 0; + + case PLUGIN_NUM_OUTPUTS: + return 2; + + case PLUGIN_INITIAL_DELAY: + return 0; + + default: + return 0; + } +} + +static void _pluginSilenceProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs) +{ + sampleBufferClear(outputs); +} + +static void _pluginSilenceProcessMidiEvents(void *pluginPtr, LinkedList midiEvents) +{ + // Nothing to do here +} + +static boolByte _pluginSilenceSetParameter(void *pluginPtr, unsigned int i, float value) +{ + return false; +} + +Plugin newPluginSilence(const CharString pluginName) +{ + Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_INSTRUMENT); + charStringCopy(plugin->pluginName, pluginName); + charStringCopyCString(plugin->pluginLocation, "Internal"); + + plugin->openPlugin = _pluginSilenceOpen; + plugin->displayInfo = _pluginSilenceDisplayInfo; + plugin->getSetting = _pluginSilenceGetSetting; + plugin->prepareForProcessing = _pluginSilenceEmpty; + plugin->processAudio = _pluginSilenceProcessAudio; + plugin->processMidiEvents = _pluginSilenceProcessMidiEvents; + plugin->setParameter = _pluginSilenceSetParameter; + plugin->closePlugin = _pluginSilenceEmpty; + plugin->freePluginData = _pluginSilenceEmpty; + + plugin->extraData = NULL; + return plugin; +} diff --git a/source/plugin/PluginSilence.h b/source/plugin/PluginSilence.h new file mode 100644 index 0000000..bb5891a --- /dev/null +++ b/source/plugin/PluginSilence.h @@ -0,0 +1,37 @@ +// +// PluginSilence.h - MrsWatson +// Created by Nik Reiman on 19 May 13. +// Copyright (c) 2013 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. +// + +#ifndef MrsWatson_PluginSilence_h +#define MrsWatson_PluginSilence_h + +#include "plugin/Plugin.h" + +extern const char *kInternalPluginSilenceName; + +Plugin newPluginSilence(const CharString pluginName); + +#endif 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; + } +} diff --git a/source/plugin/PluginVst2x.h b/source/plugin/PluginVst2x.h new file mode 100644 index 0000000..8c52d4e --- /dev/null +++ b/source/plugin/PluginVst2x.h @@ -0,0 +1,91 @@ +// +// PluginVst2x.h - 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. +// + +#ifndef MrsWatson_PluginVst2x_h +#define MrsWatson_PluginVst2x_h + +#include "base/CharString.h" +#include "plugin/Plugin.h" + +static const char kPluginVst2xSubpluginSeparator = ':'; + +/** + * List all available VST2.x plugins in common system locations. Note that this + * function does not do recursive searches (yet). + * @param pluginRoot User-provided plugin root path to search + */ +void listAvailablePluginsVst2x(const CharString pluginRoot); + +/** + * Create a new instance of a VST 2.x plugin + * @param pluginName Plugin name + * @param pluginRoot User-defined plugin root path + * @return Initialized Plugin object, or NULL if no such plugin was found + */ +Plugin newPluginVst2x(const CharString pluginName, const CharString pluginRoot); + +/** + * Get the VST2.x unique ID + * @param self + * @return Unique ID, or 0 if not known (yes, this can happen) + */ +unsigned long pluginVst2xGetUniqueId(const Plugin self); + +/** + * Get the plugin's version number. Used to determine compatible FXB/FXP patches. + * @param self + * @return Plugin version, or 0 if an error occurred. Note that some (buggy) plugins + * could potentially have a declared version of 0 as well. + */ +unsigned long pluginVst2xGetVersion(const Plugin self); + +/** + * See if a VST2.x plugin exists with the given name. Absolute paths will also + * be respected if passed. + * @param pluginName Plugin name (short name or absolute path) + * @param pluginRoot User-provided plugin root path + * @return True if such a plugin exists in any location, false otherwise + */ +boolByte pluginVst2xExists(const CharString pluginName, const CharString pluginRoot); + +/** + * Set an internal program number for a VST2.x plugin. + * @param self + * @param programNumber Program to set + * @return True if the program could be set and verified + */ +boolByte pluginVst2xSetProgram(Plugin self, const int programNumber); + +/** + * Set chuck preset data from an FXP preset to a VST2.x plugin. + * @param self + * @param chunk Chunk data to set + * @param chunkSize Chunk size + */ +void pluginVst2xSetProgramChunk(Plugin self, char *chunk, size_t chunkSize); + +#endif diff --git a/source/plugin/PluginVst2xHostCallback.cpp b/source/plugin/PluginVst2xHostCallback.cpp new file mode 100644 index 0000000..12cc409 --- /dev/null +++ b/source/plugin/PluginVst2xHostCallback.cpp @@ -0,0 +1,433 @@ +// +// Vst2xHostCallback.cpp - MrsWatson +// Created by Nik Reiman on 1/5/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 <stdio.h> +#include <string.h> +#include <math.h> + +#include "app/BuildInfo.h" +#include "audio/AudioSettings.h" +#include "base/CharString.h" +#include "logging/EventLogger.h" +#include "plugin/PluginChain.h" +#include "plugin/PluginVst2x.h" +#include "plugin/PluginVst2xId.h" +#include "time/AudioClock.h" + + void pluginVst2xAudioMasterIOChanged(const Plugin self, AEffect const *const newValues); +} + +// Global variables (sigh, unfortunately yes). When plugins ask for the time, they +// expect that the host will pass them a pointer to a VstTimeInfo struct, a pointer +// to which they are not the owner of and are not expected to free afterwards... +// which is actually the correct thing to do, given that if this were the case, a +// huge number of plugins would probably fail to do this and leak memory all over +// the place. +// Anyways, since we cannot scope this variable intelligently, we instead keep one +// instance of it as a static variable, so it is always avaible to plugins when they +// ask for the time. +static VstTimeInfo vstTimeInfo; + +extern "C" { +// Current plugin ID, which is mostly used by shell plugins during initialization. +// Instance declared in PluginVst2x.cpp, see explanation for the global-ness and +// need of this variable there. + extern VstInt32 currentPluginUniqueId; + + static int _canHostDo(const char *pluginName, const char *canDoString) + { + boolByte supported = false; + + logDebug("Plugin '%s' asked if we can do '%s'", pluginName, canDoString); + + if (!strcmp(canDoString, EMPTY_STRING)) { + logWarn("Plugin '%s' asked if we can do an empty string. This is probably a bug.", pluginName); + } else if (!strcmp(canDoString, "sendVstEvents")) { + supported = true; + } else if (!strcmp(canDoString, "sendVstMidiEvent")) { + supported = true; + } else if (!strcmp(canDoString, "sendVstTimeInfo")) { + supported = true; + } else if (!strcmp(canDoString, "receiveVstEvents")) { + supported = false; + } else if (!strcmp(canDoString, "receiveVstMidiEvent")) { + supported = false; + } else if (!strcmp(canDoString, "reportConnectionChanges")) { + supported = false; + } else if (!strcmp(canDoString, "acceptIOChanges")) { + supported = false; + } else if (!strcmp(canDoString, "sizeWindow")) { + supported = false; + } else if (!strcmp(canDoString, "offline")) { + supported = false; + } else if (!strcmp(canDoString, "openFileSelector")) { + supported = false; + } else if (!strcmp(canDoString, "closeFileSelector")) { + supported = false; + } else if (!strcmp(canDoString, "startStopProcess")) { + supported = true; + } else if (!strcmp(canDoString, "shellCategory")) { + supported = true; + } else if (!strcmp(canDoString, "sendVstMidiEventFlagIsRealtime")) { + supported = false; + } else { + logInfo("Plugin '%s' asked if host canDo '%s' (unimplemented)", pluginName, canDoString); + } + + return supported; + } + + VstIntPtr VSTCALLBACK pluginVst2xHostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *dataPtr, float opt) + { + // This string is used in a bunch of logging calls below + PluginVst2xId pluginId; + + if (effect != NULL) { + pluginId = newPluginVst2xIdWithId((unsigned long)effect->uniqueID); + } else { + // During plugin initialization, the dispatcher can be called without a + // valid plugin instance, as the AEffect* struct is still not fully constructed + // at that point. + pluginId = newPluginVst2xId(); + } + + const char *pluginIdString = pluginId->idString->data; + VstIntPtr result = 0; + + logDebug("Plugin '%s' called host dispatcher with %d, %d, %d", pluginIdString, opcode, index, value); + + switch (opcode) { + case audioMasterAutomate: + // The plugin will call this if a parameter has changed via MIDI or the GUI, so the host can update + // itself accordingly. We don't care about this (for the time being), and as we don't support either + // GUI's or live MIDI, this opcode can be ignored. + break; + + case audioMasterVersion: + // We are a VST 2.4 compatible host + result = 2400; + break; + + case audioMasterCurrentId: + // Use the current plugin ID, needed by VST shell plugins to determine which sub-plugin to load + result = currentPluginUniqueId; + break; + + case audioMasterIdle: + // Ignore + result = 1; + break; + + case audioMasterPinConnected: + logDeprecated("audioMasterPinConnected", pluginIdString); + break; + + case audioMasterWantMidi: + // This (deprecated) call is sometimes made by VST2.3 instruments to tell + // the host that it is an instrument. We can safely ignore it. + result = 1; + break; + + case audioMasterGetTime: { + AudioClock audioClock = getAudioClock(); + + // These values are always valid + vstTimeInfo.samplePos = audioClock->currentFrame; + vstTimeInfo.sampleRate = getSampleRate(); + + // Set flags for transport state + vstTimeInfo.flags = 0; + vstTimeInfo.flags |= audioClock->transportChanged ? kVstTransportChanged : 0; + vstTimeInfo.flags |= audioClock->isPlaying ? kVstTransportPlaying : 0; + + // Fill values based on other flags which may have been requested + if (value & kVstNanosValid) { + // It doesn't make sense to return this value, as the plugin may try to calculate + // something based on the current system time. As we are running offline, anything + // the plugin calculates here will probably be wrong given the way we are running. + // However, for realtime mode, this flag should be implemented in that case. + logWarn("Plugin '%s' asked for time in nanoseconds (unsupported)", pluginIdString); + } + + if (value & kVstPpqPosValid) { + // TODO: Move calculations to AudioClock + double samplesPerBeat = (60.0 / getTempo()) * getSampleRate(); + // Musical time starts with 1, not 0 + vstTimeInfo.ppqPos = (vstTimeInfo.samplePos / samplesPerBeat) + 1.0; + logDebug("Current PPQ position is %g", vstTimeInfo.ppqPos); + vstTimeInfo.flags |= kVstPpqPosValid; + } + + if (value & kVstTempoValid) { + vstTimeInfo.tempo = getTempo(); + vstTimeInfo.flags |= kVstTempoValid; + } + + if (value & kVstBarsValid) { + if (!(value & kVstPpqPosValid)) { + logError("Plugin requested position in bars, but not PPQ"); + } + + // TODO: Move calculations to AudioClock + double currentBarPos = floor(vstTimeInfo.ppqPos / (double)getTimeSignatureBeatsPerMeasure()); + vstTimeInfo.barStartPos = currentBarPos * (double)getTimeSignatureBeatsPerMeasure() + 1.0; + logDebug("Current bar is %g", vstTimeInfo.barStartPos); + vstTimeInfo.flags |= kVstBarsValid; + } + + if (value & kVstCyclePosValid) { + // We don't support cycling, so this is always 0 + } + + if (value & kVstTimeSigValid) { + vstTimeInfo.timeSigNumerator = getTimeSignatureBeatsPerMeasure(); + vstTimeInfo.timeSigDenominator = getTimeSignatureNoteValue(); + vstTimeInfo.flags |= kVstTimeSigValid; + } + + if (value & kVstSmpteValid) { + logUnsupportedFeature("Current time in SMPTE format"); + } + + if (value & kVstClockValid) { + logUnsupportedFeature("Sample frames until next clock"); + } + + result = (VstIntPtr)&vstTimeInfo; + break; + } + + case audioMasterProcessEvents: + logUnsupportedFeature("VST master opcode audioMasterProcessEvents"); + break; + + case audioMasterSetTime: + logDeprecated("audioMasterSetTime", pluginIdString); + break; + + case audioMasterTempoAt: + logDeprecated("audioMasterTempoAt", pluginIdString); + break; + + case audioMasterGetNumAutomatableParameters: + logDeprecated("audioMasterGetNumAutomatableParameters", pluginIdString); + break; + + case audioMasterGetParameterQuantization: + logDeprecated("audioMasterGetParameterQuantization", pluginIdString); + break; + + case audioMasterIOChanged: { + if (effect != NULL) { + PluginChain pluginChain = getPluginChain(); + logDebug("Number of inputs: %d", effect->numInputs); + logDebug("Number of outputs: %d", effect->numOutputs); + logDebug("Number of parameters: %d", effect->numParams); + logDebug("Initial Delay: %d", effect->initialDelay); + result = -1; + + for (unsigned int i = 0; i < pluginChain->numPlugins; ++i) { + if ((unsigned long)effect->uniqueID == pluginVst2xGetUniqueId(pluginChain->plugins[i])) { + logDebug("Updating plugin"); + pluginVst2xAudioMasterIOChanged(pluginChain->plugins[i], effect); + result = 0; + break;//Only one plugin will match anyway. + } + } + + break; + } + } + + case audioMasterNeedIdle: + logDeprecated("audioMasterNeedIdle", pluginIdString); + break; + + case audioMasterSizeWindow: + logWarn("Plugin '%s' asked us to resize window (unsupported)", pluginIdString); + break; + + case audioMasterGetSampleRate: + result = (int)getSampleRate(); + break; + + case audioMasterGetBlockSize: + result = (VstIntPtr)getBlocksize(); + break; + + case audioMasterGetInputLatency: + // Input latency is not used, and is always 0 + result = 0; + break; + + case audioMasterGetOutputLatency: + // Output latency is not used, and is always 0 + result = 0; + break; + + case audioMasterGetPreviousPlug: + logDeprecated("audioMasterGetPreviousPlug", pluginIdString); + break; + + case audioMasterGetNextPlug: + logDeprecated("audioMasterGetNextPlug", pluginIdString); + break; + + case audioMasterWillReplaceOrAccumulate: + logDeprecated("audioMasterWillReplaceOrAccumulate", pluginIdString); + break; + + case audioMasterGetCurrentProcessLevel: + // We are not a multithreaded app and have no GUI, so this is unsupported. + result = kVstProcessLevelUnknown; + break; + + case audioMasterGetAutomationState: + // Automation is also not supported (for now) + result = kVstAutomationUnsupported; + break; + + case audioMasterOfflineStart: + logWarn("Plugin '%s' asked us to start offline processing (unsupported)", pluginIdString); + break; + + case audioMasterOfflineRead: + logWarn("Plugin '%s' asked to read offline data (unsupported)", pluginIdString); + break; + + case audioMasterOfflineWrite: + logWarn("Plugin '%s' asked to write offline data (unsupported)", pluginIdString); + break; + + case audioMasterOfflineGetCurrentPass: + logWarn("Plugin '%s' asked for current offline pass (unsupported)", pluginIdString); + break; + + case audioMasterOfflineGetCurrentMetaPass: + logWarn("Plugin '%s' asked for current offline meta pass (unsupported)", pluginIdString); + break; + + case audioMasterSetOutputSampleRate: + logDeprecated("audioMasterSetOutputSampleRate", pluginIdString); + break; + + case audioMasterGetOutputSpeakerArrangement: + logDeprecated("audioMasterGetOutputSpeakerArrangement", pluginIdString); + break; + + case audioMasterGetVendorString: + strncpy((char *)dataPtr, VENDOR_NAME, kVstMaxVendorStrLen); + result = 1; + break; + + case audioMasterGetProductString: + strncpy((char *)dataPtr, PROGRAM_NAME, kVstMaxProductStrLen); + result = 1; + break; + + case audioMasterGetVendorVersion: + // Return our version as a single string, in the form ABCC, which corresponds to version A.B.C + // Often times the patch can reach double-digits, so it gets two decimal places. + result = VERSION_MAJOR * 1000 + VERSION_MINOR * 100 + VERSION_PATCH; + break; + + case audioMasterVendorSpecific: + logWarn("Plugin '%s' made a vendor specific call (unsupported). Arguments: %d, %d, %f", pluginIdString, index, value, opt); + break; + + case audioMasterCanDo: + result = _canHostDo(pluginIdString, (char *)dataPtr); + break; + + case audioMasterSetIcon: + logDeprecated("audioMasterSetIcon", pluginIdString); + break; + + case audioMasterGetLanguage: + result = kVstLangEnglish; + break; + + case audioMasterOpenWindow: + logDeprecated("audioMasterOpenWindow", pluginIdString); + break; + + case audioMasterCloseWindow: + logDeprecated("audioMasterCloseWindow", pluginIdString); + break; + + case audioMasterGetDirectory: + logWarn("Plugin '%s' asked for directory pointer (unsupported)", pluginIdString); + break; + + case audioMasterUpdateDisplay: + // Ignore + break; + + case audioMasterBeginEdit: + logWarn("Plugin '%s' asked to begin parameter automation (unsupported)", pluginIdString); + break; + + case audioMasterEndEdit: + logWarn("Plugin '%s' asked to end parameter automation (unsupported)", pluginIdString); + break; + + case audioMasterOpenFileSelector: + logWarn("Plugin '%s' asked us to open file selector (unsupported)", pluginIdString); + break; + + case audioMasterCloseFileSelector: + logWarn("Plugin '%s' asked us to close file selector (unsupported)", pluginIdString); + break; + + case audioMasterEditFile: + logDeprecated("audioMasterEditFile", pluginIdString); + break; + + case audioMasterGetChunkFile: + logDeprecated("audioMasterGetChunkFile", pluginIdString); + break; + + case audioMasterGetInputSpeakerArrangement: + logDeprecated("audioMasterGetInputSpeakerArrangement", pluginIdString); + break; + + default: + logWarn("Plugin '%s' asked if host can do unknown opcode %d", pluginIdString, opcode); + break; + } + + freePluginVst2xId(pluginId); + return result; + } +} // extern "C" diff --git a/source/plugin/PluginVst2xHostCallback.h b/source/plugin/PluginVst2xHostCallback.h new file mode 100644 index 0000000..4f4ff46 --- /dev/null +++ b/source/plugin/PluginVst2xHostCallback.h @@ -0,0 +1,45 @@ +// +// PluginVst2xCallbacks.h - MrsWatson +// Created by Nik Reiman on 13 May 13. +// Copyright (c) 2013 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. +// + +#ifndef MrsWatson_PluginVst2xCallbacks_h +#define MrsWatson_PluginVst2xCallbacks_h + +#define VST_FORCE_DEPRECATED 0 +#include "aeffectx.h" + +// Callbacks used by VST2.x plugins +typedef AEffect *(*Vst2xPluginEntryFunc)(audioMasterCallback host); +typedef VstIntPtr (*Vst2xPluginDispatcherFunc)(AEffect *effect, VstInt32 opCode, VstInt32 index, VstIntPtr value, void *ptr, float opt); +typedef float (*Vst2xPluginGetParameterFunc)(AEffect *effect, VstInt32 index); +typedef void (*Vst2xPluginSetParameterFunc)(AEffect *effect, VstInt32 index, float value); +typedef void (*Vst2xPluginProcessFunc)(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames); + +extern "C" { + VstIntPtr VSTCALLBACK pluginVst2xHostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *dataPtr, float opt); +} + +#endif diff --git a/source/plugin/PluginVst2xId.c b/source/plugin/PluginVst2xId.c new file mode 100644 index 0000000..3fd8038 --- /dev/null +++ b/source/plugin/PluginVst2xId.c @@ -0,0 +1,101 @@ +// +// PluginVst2xId.c - MrsWatson +// Created by Nik Reiman on 07 Jun 13. +// Copyright (c) 2013 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. +// + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "base/CharString.h" +#include "plugin/PluginVst2xId.h" + +static CharString _convertIntIdToString(const unsigned long id) +{ + CharString result = newCharStringWithCapacity(5); + int i; + + for (i = 0; i < 4; i++) { + result->data[i] = (char)(id >> ((3 - i) * 8) & 0xff); + } + + return result; +} + +static unsigned long _convertStringIdToInt(const CharString idString) +{ + unsigned long result = 0; + int i; + + if (idString != NULL && strlen(idString->data) == 4) { + for (i = 0; i < 4; i++) { + result |= (unsigned long)(idString->data[i]) << ((3 - i) * 8); + } + } + + return result; +} + +PluginVst2xId newPluginVst2xId(void) +{ + PluginVst2xId pluginVst2xId = (PluginVst2xId)malloc(sizeof(PluginVst2xIdMembers)); + + pluginVst2xId->id = 0; + pluginVst2xId->idString = newCharStringWithCString(PLUGIN_VST2X_ID_UNKNOWN); + + return pluginVst2xId; +} + +PluginVst2xId newPluginVst2xIdWithId(unsigned long id) +{ + PluginVst2xId pluginVst2xId = newPluginVst2xId(); + + pluginVst2xId->id = id; + freeCharString(pluginVst2xId->idString); + pluginVst2xId->idString = _convertIntIdToString(id); + + return pluginVst2xId; +} + +PluginVst2xId newPluginVst2xIdWithStringId(const CharString idString) +{ + PluginVst2xId pluginVst2xId = newPluginVst2xId(); + + pluginVst2xId->id = _convertStringIdToInt(idString); + + if (idString != NULL && pluginVst2xId->id > 0) { + charStringCopy(pluginVst2xId->idString, idString); + } + + return pluginVst2xId; +} + +void freePluginVst2xId(PluginVst2xId self) +{ + if (self) { + freeCharString(self->idString); + free(self); + } +} diff --git a/source/plugin/PluginVst2xId.h b/source/plugin/PluginVst2xId.h new file mode 100644 index 0000000..185a9c1 --- /dev/null +++ b/source/plugin/PluginVst2xId.h @@ -0,0 +1,45 @@ +// +// PluginVst2xId.h - MrsWatson +// Created by Nik Reiman on 07 Jun 13. +// Copyright (c) 2013 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. +// + +#ifndef MrsWatson_PluginVst2xId_h +#define MrsWatson_PluginVst2xId_h + +#define PLUGIN_VST2X_ID_UNKNOWN "????" + +typedef struct { + unsigned long id; + CharString idString; +} PluginVst2xIdMembers; +typedef PluginVst2xIdMembers *PluginVst2xId; + +PluginVst2xId newPluginVst2xId(void); +PluginVst2xId newPluginVst2xIdWithId(unsigned long id); +PluginVst2xId newPluginVst2xIdWithStringId(const CharString stringId); + +void freePluginVst2xId(PluginVst2xId self); + +#endif diff --git a/source/plugin/PluginVst2xLinux.cpp b/source/plugin/PluginVst2xLinux.cpp new file mode 100644 index 0000000..5543344 --- /dev/null +++ b/source/plugin/PluginVst2xLinux.cpp @@ -0,0 +1,113 @@ +// +// PluginVst2xLinux.c - MrsWatson +// Created by Nik Reiman on 13 May 13. +// Copyright (c) 2013 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. +// + +#if LINUX +#define VST_FORCE_DEPRECATED 0 +#include "aeffectx.h" + +extern "C" { +#include <dlfcn.h> +#include <stdlib.h> +#include "base/CharString.h" +#include "base/LinkedList.h" +#include "logging/EventLogger.h" +#include "plugin/PluginVst2xHostCallback.h" + + LinkedList getVst2xPluginLocations(CharString currentDirectory) + { + LinkedList locations = newLinkedList(); + CharString locationBuffer; + char *vstPathEnv; + + linkedListAppend(locations, currentDirectory); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s/.vst", getenv("HOME")); + linkedListAppend(locations, locationBuffer); + + locationBuffer = newCharString(); + vstPathEnv = getenv("VST_PATH"); + + if (vstPathEnv != NULL) { + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s", vstPathEnv); + linkedListAppend(locations, locationBuffer); + } else { + freeCharString(locationBuffer); + } + + return locations; + } + + LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath) + { + void *libraryHandle = dlopen(pluginAbsolutePath->data, RTLD_NOW | RTLD_LOCAL); + + if (libraryHandle == NULL) { + logError("Could not open library, %s", dlerror()); + return NULL; + } + + return libraryHandle; + } + + AEffect *loadVst2xPlugin(LibraryHandle libraryHandle) + { + // Somewhat cheap hack to avoid a tricky compiler warning. Casting from void* to a proper function + // pointer will cause GCC to warn that "ISO C++ forbids casting between pointer-to-function and + // pointer-to-object". Here, we represent both types in a union and use the correct one in the given + // context, thus avoiding the need to cast anything. + // See also: http://stackoverflow.com/a/2742234/14302 + union { + Vst2xPluginEntryFunc entryPointFuncPtr; + void *entryPointVoidPtr; + } entryPoint; + + entryPoint.entryPointVoidPtr = dlsym(libraryHandle, "VSTPluginMain"); + + if (entryPoint.entryPointVoidPtr == NULL) { + entryPoint.entryPointVoidPtr = dlsym(libraryHandle, "main"); + + if (entryPoint.entryPointVoidPtr == NULL) { + logError("Couldn't get a pointer to plugin's main()"); + return NULL; + } + } + + Vst2xPluginEntryFunc mainEntryPoint = entryPoint.entryPointFuncPtr; + AEffect *plugin = mainEntryPoint(pluginVst2xHostCallback); + return plugin; + } + + void closeLibraryHandle(LibraryHandle libraryHandle) + { + if (dlclose(libraryHandle) != 0) { + logWarn("Could not safely close plugin, possible resource leak"); + } + } + +} // extern "C" +#endif diff --git a/source/plugin/PluginVst2xMac.cpp b/source/plugin/PluginVst2xMac.cpp new file mode 100644 index 0000000..75369d4 --- /dev/null +++ b/source/plugin/PluginVst2xMac.cpp @@ -0,0 +1,136 @@ +// +// PluginVst2xMac.c - MrsWatson +// Created by Nik Reiman on 13 May 13. +// Copyright (c) 2013 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. +// + +#if MACOSX +#define VST_FORCE_DEPRECATED 0 +#include "aeffectx.h" + +extern "C" { +#include <stdlib.h> +#include <CoreFoundation/CFBundle.h> +#include "base/CharString.h" +#include "logging/EventLogger.h" +#include "plugin/PluginVst2xHostCallback.h" + + LinkedList getVst2xPluginLocations(CharString currentDirectory); + LinkedList getVst2xPluginLocations(CharString currentDirectory) + { + LinkedList locations = newLinkedList(); + CharString locationBuffer; + + linkedListAppend(locations, currentDirectory); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "/Library/Audio/Plug-Ins/VST"); + linkedListAppend(locations, locationBuffer); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s/Library/Audio/Plug-Ins/VST", getenv("HOME")); + linkedListAppend(locations, locationBuffer); + + return locations; + } + + LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath); + LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath) + { + // Create a path to the bundle + CFStringRef pluginPathStringRef = CFStringCreateWithCString(NULL, pluginAbsolutePath->data, kCFStringEncodingASCII); + CFURLRef bundleUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPathStringRef, kCFURLPOSIXPathStyle, true); + + if (bundleUrl == NULL) { + logError("Couldn't make URL reference for plugin"); + return NULL; + } + + // Open the bundle + CFBundleRef bundleRef = CFBundleCreate(kCFAllocatorDefault, bundleUrl); + + if (bundleRef == NULL) { + logError("Couldn't create bundle reference"); + CFRelease(pluginPathStringRef); + CFRelease(bundleUrl); + return NULL; + } + + // Clean up + CFRelease(pluginPathStringRef); + CFRelease(bundleUrl); + + return bundleRef; + } + + AEffect *loadVst2xPlugin(LibraryHandle libraryHandle); + AEffect *loadVst2xPlugin(LibraryHandle libraryHandle) + { + // Somewhat cheap hack to avoid a tricky compiler warning. Casting from void* to a proper function + // pointer will cause GCC to warn that "ISO C++ forbids casting between pointer-to-function and + // pointer-to-object". Here, we represent both types in a union and use the correct one in the given + // context, thus avoiding the need to cast anything. + // See also: http://stackoverflow.com/a/2742234/14302 + union { + Vst2xPluginEntryFunc entryPointFuncPtr; + void *entryPointVoidPtr; + } entryPoint; + + entryPoint.entryPointVoidPtr = CFBundleGetFunctionPointerForName(libraryHandle, CFSTR("VSTPluginMain")); + Vst2xPluginEntryFunc mainEntryPoint = entryPoint.entryPointFuncPtr; + + // VST plugins previous to the 2.4 SDK used main_macho for the entry point name + if (mainEntryPoint == NULL) { + entryPoint.entryPointVoidPtr = CFBundleGetFunctionPointerForName(libraryHandle, CFSTR("main_macho")); + mainEntryPoint = entryPoint.entryPointFuncPtr; + } + + if (mainEntryPoint == NULL) { + logError("Couldn't get a pointer to plugin's main()"); + CFBundleUnloadExecutable(libraryHandle); + CFRelease(libraryHandle); + return NULL; + } + + AEffect *plugin = mainEntryPoint(pluginVst2xHostCallback); + + if (plugin == NULL) { + logError("Plugin's main() returns null"); + CFBundleUnloadExecutable(libraryHandle); + CFRelease(libraryHandle); + return NULL; + } + + return plugin; + } + + void closeLibraryHandle(LibraryHandle libraryHandle); + void closeLibraryHandle(LibraryHandle libraryHandle) + { + CFBundleUnloadExecutable(libraryHandle); + CFRelease(libraryHandle); + } + +} // extern "C" +#endif diff --git a/source/plugin/PluginVst2xWindows.cpp b/source/plugin/PluginVst2xWindows.cpp new file mode 100644 index 0000000..bbc2c8c --- /dev/null +++ b/source/plugin/PluginVst2xWindows.cpp @@ -0,0 +1,114 @@ +// +// PluginVst2xWindows.c - MrsWatson +// Created by Nik Reiman on 13 May 13. +// Copyright (c) 2013 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. +// + +#if WINDOWS +#define VST_FORCE_DEPRECATED 0 +#include "aeffectx.h" +#include "plugin/PluginVst2xHostCallback.h" + +extern "C" { +#include <stdio.h> +#include "base/PlatformInfo.h" +#include "logging/EventLogger.h" + + static const char *kPlatformWindowsProgramFolder = "C:\\Program Files"; + static const char *kPlatformWindows32BitProgramFolder = "C:\\Program Files (x86)"; + + LinkedList getVst2xPluginLocations(CharString currentDirectory) + { + LinkedList locations = newLinkedList(); + CharString locationBuffer; + const char *programFiles = (!platformInfoIsRuntime64Bit() && platformInfoIsHost64Bit()) ? + kPlatformWindows32BitProgramFolder : kPlatformWindowsProgramFolder; + + linkedListAppend(locations, currentDirectory); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "C:\\VstPlugins"); + linkedListAppend(locations, locationBuffer); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s\\VstPlugIns", programFiles); + linkedListAppend(locations, locationBuffer); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s\\Common Files\\VstPlugIns", programFiles); + linkedListAppend(locations, locationBuffer); + + locationBuffer = newCharString(); + snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s\\Steinberg\\VstPlugIns", programFiles); + linkedListAppend(locations, locationBuffer); + + return locations; + } + + LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath) + { + HMODULE libraryHandle = LoadLibraryExA((LPCSTR)pluginAbsolutePath->data, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + DWORD errorCode = GetLastError(); + + if (libraryHandle == NULL) { + if (errorCode == ERROR_BAD_EXE_FORMAT) { + logError("Could not open library, wrong architecture"); + } else { + logError("Could not open library, error code '%s'", stringForLastError(errorCode)); + } + + return NULL; + } + + return libraryHandle; + } + + AEffect *loadVst2xPlugin(LibraryHandle libraryHandle) + { + Vst2xPluginEntryFunc entryPoint = (Vst2xPluginEntryFunc)GetProcAddress(libraryHandle, "VSTPluginMain"); + + if (entryPoint == NULL) { + entryPoint = (Vst2xPluginEntryFunc)GetProcAddress(libraryHandle, "VstPluginMain()"); + } + + if (entryPoint == NULL) { + entryPoint = (Vst2xPluginEntryFunc)GetProcAddress(libraryHandle, "main"); + } + + if (entryPoint == NULL) { + logError("Couldn't get a pointer to plugin's main()"); + return NULL; + } + + AEffect *plugin = entryPoint(pluginVst2xHostCallback); + return plugin; + } + + void closeLibraryHandle(LibraryHandle libraryHandle) + { + FreeLibrary(libraryHandle); + } + +} // extern "C" +#endif |
