Download Source/Helper/HelperPluginHost.cpp from RAM2118/pluginbridge: direct link, hf CLI and curl.
- Browser
- Download file 9.25 kB
-
https://huggingface.co/RAM2118/pluginbridge/resolve/1dd0405171bf6efd5a8dfd67bc5b5d3a24dc7aee/Source/Helper/HelperPluginHost.cpp
- Command line
-
hf download hf://RAM2118/pluginbridge@1dd0405171bf6efd5a8dfd67bc5b5d3a24dc7aee/Source/Helper/HelperPluginHost.cpp
-
curl -L -o HelperPluginHost.cpp https://huggingface.co/RAM2118/pluginbridge/resolve/1dd0405171bf6efd5a8dfd67bc5b5d3a24dc7aee/Source/Helper/HelperPluginHost.cpp
9.25 kB
| // Force helper process to become a proper GUI app on macOS | |
| static void makeGuiProcess() | |
| { | |
| // [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular] | |
| id nsApp = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSApplication"), | |
| sel_registerName("sharedApplication")); | |
| ((void(*)(id, SEL, long))objc_msgSend)(nsApp, sel_registerName("setActivationPolicy:"), 0); // 0 = Regular | |
| ((void(*)(id, SEL, int))objc_msgSend)(nsApp, sel_registerName("activateIgnoringOtherApps:"), 1); | |
| } | |
| // Custom window with working close button | |
| class PluginGuiWindow : public juce::DocumentWindow | |
| { | |
| public: | |
| PluginGuiWindow(const juce::String& name) | |
| : DocumentWindow(name, juce::Colour(0xff1a1a2e), DocumentWindow::allButtons, true) | |
| { | |
| // Use JUCE's own title bar — works regardless of macOS activation policy | |
| setUsingNativeTitleBar(false); | |
| setTitleBarHeight(28); | |
| } | |
| void closeButtonPressed() override | |
| { | |
| setVisible(false); | |
| } | |
| }; | |
| HelperPluginHost::HelperPluginHost() | |
| { | |
| formatManager.addFormat(new juce::VST3PluginFormat()); | |
| formatManager.addFormat(new juce::AudioUnitPluginFormat()); | |
| } | |
| HelperPluginHost::~HelperPluginHost() | |
| { | |
| stopProcessing(); | |
| hideGui(); | |
| unloadPlugin(); | |
| } | |
| bool HelperPluginHost::loadPlugin(const juce::String& path, double sampleRate, int blockSize) | |
| { | |
| unloadPlugin(); | |
| currentSampleRate = sampleRate; | |
| currentBlockSize = blockSize; | |
| juce::File pluginFile(path); | |
| if (!pluginFile.exists()) | |
| return false; | |
| 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()) | |
| return false; | |
| juce::String errorMessage; | |
| plugin = formatManager.createPluginInstance(*typesFound[0], sampleRate, blockSize, errorMessage); | |
| if (plugin == nullptr) | |
| { | |
| DBG("HelperPluginHost: Failed to create plugin: " + errorMessage); | |
| return false; | |
| } | |
| plugin->enableAllBuses(); | |
| auto layout = juce::AudioProcessor::BusesLayout(); | |
| layout.inputBuses.add(juce::AudioChannelSet::stereo()); | |
| layout.outputBuses.add(juce::AudioChannelSet::stereo()); | |
| if (plugin->checkBusesLayoutSupported(layout)) | |
| plugin->setBusesLayout(layout); | |
| plugin->prepareToPlay(sampleRate, blockSize); | |
| DBG("HelperPluginHost: Loaded " + getPluginName() + " (" + juce::String(getParameterCount()) + " params)"); | |
| return true; | |
| } | |
| void HelperPluginHost::unloadPlugin() | |
| { | |
| hideGui(); | |
| if (plugin) | |
| { | |
| plugin->releaseResources(); | |
| plugin.reset(); | |
| } | |
| } | |
| juce::String HelperPluginHost::getPluginName() const | |
| { | |
| return plugin ? plugin->getName() : juce::String(); | |
| } | |
| int HelperPluginHost::getParameterCount() const | |
| { | |
| return plugin ? plugin->getParameters().size() : 0; | |
| } | |
| void HelperPluginHost::setSharedAudio(SharedAudioBuffer* shm, SharedSemaphore* inSem, SharedSemaphore* outSem) | |
| { | |
| sharedAudio = shm; | |
| inputSem = inSem; | |
| outputSem = outSem; | |
| } | |
| void HelperPluginHost::startProcessing() | |
| { | |
| if (processing.load()) return; | |
| processing.store(true); | |
| audioThread = std::thread([this]() { audioProcessingLoop(); }); | |
| } | |
| void HelperPluginHost::stopProcessing() | |
| { | |
| processing.store(false); | |
| if (inputSem) inputSem->signal(); | |
| if (audioThread.joinable()) | |
| audioThread.join(); | |
| } | |
| void HelperPluginHost::audioProcessingLoop() | |
| { | |
| struct sched_param param; | |
| param.sched_priority = sched_get_priority_max(SCHED_FIFO); | |
| pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); | |
| while (processing.load()) | |
| { | |
| if (!inputSem->wait(100)) | |
| continue; | |
| if (!processing.load()) break; | |
| if (!plugin || !sharedAudio) continue; | |
| auto* header = sharedAudio->getHeader(); | |
| int numChannels = header->numChannels.load(); | |
| int blockSize = header->blockSize.load(); | |
| juce::AudioBuffer<float> buffer(numChannels, blockSize); | |
| float* channelPtrs[2] = { buffer.getWritePointer(0), | |
| numChannels > 1 ? buffer.getWritePointer(1) : nullptr }; | |
| sharedAudio->readInput(channelPtrs, numChannels, blockSize); | |
| juce::MidiBuffer midi; | |
| plugin->processBlock(buffer, midi); | |
| const float* constPtrs[2] = { buffer.getReadPointer(0), | |
| numChannels > 1 ? buffer.getReadPointer(1) : nullptr }; | |
| sharedAudio->writeOutput(constPtrs, numChannels, blockSize); | |
| header->state.store(kStateOutputReady); | |
| outputSem->signal(); | |
| } | |
| } | |
| // --- Parameters --- | |
| std::vector<HelperPluginHost::ParamInfo> HelperPluginHost::searchParams(const juce::String& keyword) const | |
| { | |
| std::vector<ParamInfo> results; | |
| if (!plugin) return results; | |
| auto& params = plugin->getParameters(); | |
| auto keywordLower = keyword.toLowerCase(); | |
| for (int i = 0; i < params.size(); ++i) | |
| { | |
| auto* param = params[i]; | |
| auto name = param->getName(128); | |
| if (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>> HelperPluginHost::getParams(const std::vector<int>& ids) const | |
| { | |
| std::vector<std::pair<int, float>> results; | |
| if (!plugin) return results; | |
| auto& params = plugin->getParameters(); | |
| for (int idx : ids) | |
| { | |
| if (idx >= 0 && idx < params.size()) | |
| results.push_back({idx, params[idx]->getValue()}); | |
| } | |
| return results; | |
| } | |
| bool HelperPluginHost::setParams(const std::map<int, float>& values) | |
| { | |
| if (!plugin) return false; | |
| auto& params = plugin->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; | |
| } | |
| // --- GUI --- | |
| void HelperPluginHost::showGui() | |
| { | |
| if (!plugin || !plugin->hasEditor()) return; | |
| juce::MessageManager::callAsync([this]() | |
| { | |
| if (!plugin) return; | |
| // Activate as GUI process (macOS requires this for window interaction) | |
| makeGuiProcess(); | |
| // If window exists but hidden, just show it | |
| if (guiWindow) | |
| { | |
| guiWindow->setVisible(true); | |
| guiWindow->toFront(true); | |
| return; | |
| } | |
| auto* editor = plugin->createEditor(); | |
| if (!editor) return; | |
| guiWindow = std::make_unique<PluginGuiWindow>(plugin->getName()); | |
| guiWindow->setContentOwned(editor, true); | |
| guiWindow->setResizable(true, true); | |
| guiWindow->centreWithSize(editor->getWidth(), editor->getHeight() + 28); // +28 for title bar | |
| guiWindow->setVisible(true); | |
| guiWindow->toFront(true); | |
| }); | |
| } | |
| void HelperPluginHost::hideGui() | |
| { | |
| if (guiWindow) | |
| { | |
| juce::MessageManager::callAsync([this]() | |
| { | |
| guiWindow.reset(); | |
| }); | |
| } | |
| } | |
| // --- State --- | |
| juce::String HelperPluginHost::getStateBase64() const | |
| { | |
| if (!plugin) return {}; | |
| juce::MemoryBlock stateData; | |
| plugin->getStateInformation(stateData); | |
| return stateData.toBase64Encoding(); | |
| } | |
| bool HelperPluginHost::setStateBase64(const juce::String& base64State) | |
| { | |
| if (!plugin) return false; | |
| juce::MemoryBlock stateData; | |
| if (!stateData.fromBase64Encoding(base64State)) | |
| return false; | |
| plugin->setStateInformation(stateData.getData(), (int)stateData.getSize()); | |
| return true; | |
| } | |
| void HelperPluginHost::configure(double sampleRate, int blockSize, int channels) | |
| { | |
| currentSampleRate = sampleRate; | |
| currentBlockSize = blockSize; | |
| currentChannels = channels; | |
| if (plugin) | |
| { | |
| plugin->releaseResources(); | |
| plugin->prepareToPlay(sampleRate, blockSize); | |
| } | |
| } | |