summaryrefslogtreecommitdiff
path: root/source/plugin/PluginChain.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/plugin/PluginChain.c')
-rw-r--r--source/plugin/PluginChain.c420
1 files changed, 420 insertions, 0 deletions
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);
+}