pluginbridge / Source /Plugin /PluginBridgeProcessor.cpp
RAM2118's picture
Upload Source/Plugin/PluginBridgeProcessor.cpp
06d413f verified
Raw History Blame
10.8 kB
#include "PluginBridgeProcessor.h"
#include "PluginBridgeEditor.h"
#include "McpServer.h"
#include "json.hpp"
using json = nlohmann::json;
PluginBridgeProcessor::PluginBridgeProcessor()
: AudioProcessor(BusesProperties()
.withInput("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
// Register plugin formats
#if JUCE_PLUGINHOST_VST3
formatManager.addFormat(new juce::VST3PluginFormat());
#endif
#if JUCE_PLUGINHOST_AU
formatManager.addFormat(new juce::AudioUnitPluginFormat());
#endif
// Start MCP server
mcpServer = std::make_unique<McpServer>(PluginBridgeConstants::kDefaultMcpPort, *this);
mcpServer->start();
}
PluginBridgeProcessor::~PluginBridgeProcessor()
{
mcpServer->stop();
unloadPlugin();
}
void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
currentSampleRate = sampleRate;
currentBlockSize = samplesPerBlock;
juce::ScopedLock sl(pluginLock);
if (hostedPlugin)
{
hostedPlugin->setRateAndBufferSizeDetails(sampleRate, samplesPerBlock);
hostedPlugin->prepareToPlay(sampleRate, samplesPerBlock);
}
}
void PluginBridgeProcessor::releaseResources()
{
juce::ScopedLock sl(pluginLock);
if (hostedPlugin)
hostedPlugin->releaseResources();
}
void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples());
juce::ScopedLock sl(pluginLock);
if (hostedPlugin)
hostedPlugin->processBlock(buffer, midi);
}
juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
{
return new PluginBridgeEditor(*this);
}
// --- Plugin Loading (IN-PROCESS) ---
void PluginBridgeProcessor::loadPlugin(const juce::String& path)
{
unloadPlugin();
// Check blocklist
if (blocklist.isBlocked(path))
{
if (onLoadFailed)
onLoadFailed("Plugin is blocklisted (crashed previously)");
return;
}
juce::File pluginFile(path);
if (!pluginFile.exists())
{
if (onLoadFailed)
onLoadFailed("Plugin file not found");
return;
}
// Find plugin description
juce::OwnedArray<juce::PluginDescription> typesFound;
for (int i = 0; i < formatManager.getNumFormats(); ++i)
{
auto* format = formatManager.getFormat(i);
bool isVST3 = pluginFile.getFileExtension() == ".vst3";
bool isAU = pluginFile.getFileExtension() == ".component";
if (isVST3 && format->getName() != "VST3") continue;
if (isAU && format->getName() != "AudioUnit") continue;
format->findAllTypesForFile(typesFound, pluginFile.getFullPathName());
if (typesFound.size() > 0)
break;
}
if (typesFound.isEmpty())
{
if (onLoadFailed)
onLoadFailed("Plugin format not recognized");
return;
}
// Create plugin instance
juce::String errorMessage;
auto instance = formatManager.createPluginInstance(
*typesFound[0], currentSampleRate, currentBlockSize, errorMessage);
if (instance == nullptr)
{
DBG("PluginBridgeProcessor: Failed to load: " + errorMessage);
// Add to blocklist if it fails
blocklist.addBlocked(path);
if (onLoadFailed)
onLoadFailed("Failed to load: " + errorMessage);
return;
}
// Configure the plugin
instance->enableAllBuses();
auto layout = juce::AudioProcessor::BusesLayout();
layout.inputBuses.add(juce::AudioChannelSet::stereo());
layout.outputBuses.add(juce::AudioChannelSet::stereo());
if (instance->checkBusesLayoutSupported(layout))
instance->setBusesLayout(layout);
instance->setRateAndBufferSizeDetails(currentSampleRate, currentBlockSize);
instance->prepareToPlay(currentSampleRate, currentBlockSize);
// Swap in
{
juce::ScopedLock sl(pluginLock);
hostedPlugin = std::move(instance);
}
pluginPath = path;
DBG("PluginBridgeProcessor: Loaded " + getLoadedPluginName() +
" (" + juce::String(getLoadedPluginParamCount()) + " params)");
if (onPluginLoaded)
onPluginLoaded();
}
void PluginBridgeProcessor::unloadPlugin()
{
{
juce::ScopedLock sl(pluginLock);
if (hostedPlugin)
{
hostedPlugin->releaseResources();
hostedPlugin.reset();
}
}
pluginPath.clear();
if (onPluginUnloaded)
onPluginUnloaded();
}
juce::String PluginBridgeProcessor::getLoadedPluginName() const
{
juce::ScopedLock sl(pluginLock);
return hostedPlugin ? hostedPlugin->getName() : juce::String();
}
int PluginBridgeProcessor::getLoadedPluginParamCount() const
{
juce::ScopedLock sl(pluginLock);
return hostedPlugin ? hostedPlugin->getParameters().size() : 0;
}
// --- Parameters ---
std::vector<PluginBridgeProcessor::ParamInfo> PluginBridgeProcessor::searchParams(const juce::String& keyword) const
{
std::vector<ParamInfo> results;
juce::ScopedLock sl(pluginLock);
if (!hostedPlugin) return results;
auto& params = hostedPlugin->getParameters();
auto keywordLower = keyword.toLowerCase();
for (int i = 0; i < params.size(); ++i)
{
auto* param = params[i];
auto name = param->getName(128);
if (keyword.isEmpty() || name.toLowerCase().contains(keywordLower))
{
ParamInfo info;
info.index = i;
info.name = name;
info.value = param->getValue();
info.displayText = param->getText(param->getValue(), 64);
if (auto* hosted = dynamic_cast<juce::AudioPluginInstance::HostedParameter*>(param))
info.id = hosted->getParameterID();
else
info.id = juce::String(i);
results.push_back(info);
if (results.size() >= PluginBridgeConstants::kMaxParamResults)
break;
}
}
return results;
}
std::vector<std::pair<int, float>> PluginBridgeProcessor::getParams(const std::vector<int>& ids) const
{
std::vector<std::pair<int, float>> results;
juce::ScopedLock sl(pluginLock);
if (!hostedPlugin) return results;
auto& params = hostedPlugin->getParameters();
for (int idx : ids)
{
if (idx >= 0 && idx < params.size())
results.push_back({idx, params[idx]->getValue()});
}
return results;
}
bool PluginBridgeProcessor::setParams(const std::map<int, float>& values)
{
juce::ScopedLock sl(pluginLock);
if (!hostedPlugin) return false;
auto& params = hostedPlugin->getParameters();
for (auto& [idx, val] : values)
{
if (idx >= 0 && idx < params.size())
{
auto* param = params[idx];
param->beginChangeGesture();
param->setValueNotifyingHost(juce::jlimit(0.0f, 1.0f, val));
param->endChangeGesture();
}
}
return true;
}
// --- Hosted Plugin Editor ---
juce::AudioProcessorEditor* PluginBridgeProcessor::createHostedPluginEditor()
{
juce::ScopedLock sl(pluginLock);
if (hostedPlugin && hostedPlugin->hasEditor())
return hostedPlugin->createEditor();
return nullptr;
}
// --- Plugin List ---
juce::Array<juce::File> PluginBridgeProcessor::getInstalledPluginFiles() const
{
juce::Array<juce::File> results;
juce::Array<juce::File> dirs = {
juce::File("/Library/Audio/Plug-Ins/VST3"),
juce::File::getSpecialLocation(juce::File::userHomeDirectory)
.getChildFile("Library/Audio/Plug-Ins/VST3"),
juce::File("/Library/Audio/Plug-Ins/Components"),
juce::File::getSpecialLocation(juce::File::userHomeDirectory)
.getChildFile("Library/Audio/Plug-Ins/Components")
};
for (auto& dir : dirs)
{
if (dir.isDirectory())
{
auto vst3s = dir.findChildFiles(juce::File::findDirectories, false, "*.vst3");
auto aus = dir.findChildFiles(juce::File::findDirectories, false, "*.component");
results.addArray(vst3s);
results.addArray(aus);
}
}
results.sort();
return results;
}
// --- Blocklist ---
bool PluginBridgeProcessor::isBlocked(const juce::String& path) const
{
return blocklist.isBlocked(path);
}
// --- State ---
void PluginBridgeProcessor::getStateInformation(juce::MemoryBlock& destData)
{
juce::ValueTree state("PluginBridgeState");
state.setProperty("pluginPath", pluginPath, nullptr);
juce::ScopedLock sl(pluginLock);
if (hostedPlugin)
{
juce::MemoryBlock pluginState;
hostedPlugin->getStateInformation(pluginState);
state.setProperty("pluginState", pluginState.toBase64Encoding(), nullptr);
}
juce::MemoryOutputStream stream(destData, false);
state.writeToStream(stream);
}
void PluginBridgeProcessor::setStateInformation(const void* data, int sizeInBytes)
{
auto state = juce::ValueTree::readFromData(data, (size_t)sizeInBytes);
if (state.isValid())
{
auto path = state.getProperty("pluginPath").toString();
if (path.isNotEmpty())
{
loadPlugin(path);
auto pluginStateB64 = state.getProperty("pluginState").toString();
if (pluginStateB64.isNotEmpty())
{
juce::MemoryBlock pluginState;
if (pluginState.fromBase64Encoding(pluginStateB64))
{
juce::ScopedLock sl(pluginLock);
if (hostedPlugin)
hostedPlugin->setStateInformation(pluginState.getData(), (int)pluginState.getSize());
}
}
}
}
}
// --- MCP ---
bool PluginBridgeProcessor::isMcpServerRunning() const
{
return mcpServer && mcpServer->isRunning();
}
int PluginBridgeProcessor::getMcpServerPort() const
{
return mcpServer ? mcpServer->getPort() : 0;
}
// --- Test Safety (Helper-based) ---
PluginBridgeProcessor::TestResult PluginBridgeProcessor::testPluginSafety(const juce::String& path)
{
// TODO: Spawn helper to test-load plugin
// For now, all plugins are treated as safe unless blocklisted
if (blocklist.isBlocked(path))
return TestResult::Unsafe;
return TestResult::Safe;
}
// --- Entry Point ---
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new PluginBridgeProcessor();
}