#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(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& 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 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::searchParams(const juce::String& keyword) const { std::vector 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(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> PluginBridgeProcessor::getParams(const std::vector& ids) const { std::vector> 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& 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 PluginBridgeProcessor::getInstalledPluginFiles() const { juce::Array results; juce::Array 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(); }