Download Source/Plugin/PluginBridgeProcessor.cpp from RAM2118/pluginbridge: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/RAM2118/pluginbridge/resolve/ddb115bf829f00a019effbe2c307028edd9a0d27/Source/Plugin/PluginBridgeProcessor.cpp
- Command line
-
hf download hf://RAM2118/pluginbridge@ddb115bf829f00a019effbe2c307028edd9a0d27/Source/Plugin/PluginBridgeProcessor.cpp
-
curl -L -o PluginBridgeProcessor.cpp https://huggingface.co/RAM2118/pluginbridge/resolve/ddb115bf829f00a019effbe2c307028edd9a0d27/Source/Plugin/PluginBridgeProcessor.cpp
10.8 kB
| using json = nlohmann::json; | |
| PluginBridgeProcessor::PluginBridgeProcessor() | |
| : AudioProcessor(BusesProperties() | |
| .withInput("Input", juce::AudioChannelSet::stereo(), true) | |
| .withOutput("Output", juce::AudioChannelSet::stereo(), true)) | |
| { | |
| // Register plugin formats | |
| formatManager.addFormat(new juce::VST3PluginFormat()); | |
| formatManager.addFormat(new juce::AudioUnitPluginFormat()); | |
| // 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(); | |
| } | |