Memory mapped files with proper synchronization is the best approach.
JUCE’ MemoryMappedFile and InterprocessLock are within the grasp of the average JUCE project.
In a more sophisticated setting, having your own ‘helper agent’ which accepts socket connections from your other plugins might be a fruitful approach as well, albeit with heavier load for the user.
[1] JUCE: MemoryMappedFile Class Reference
[2] JUCE: InterProcessLock Class Reference
#pragma once
#include <juce_core/juce_core.h>
// Manages shared memory for a single float variable
class SharedMemoryManager {
public:
SharedMemoryManager(const juce::String& memoryName, const juce::String& lockName)
: memoryName(memoryName), lockName(lockName) {
// Initialize shared memory (size for one float)
memoryFile = std::make_unique<juce::MemoryMappedFile>(
memoryName, sizeof(float), juce::MemoryMappedFile::readWrite);
// Initialize interprocess lock
lock = std::make_unique<juce::InterprocessLock>(lockName);
// Initialize the shared variable if it doesn't exist
if (memoryFile->getData()) {
juce::ScopedLock sl(*lock);
*static_cast<float*>(memoryFile->getData()) = 0.0f; // Default value
}
}
~SharedMemoryManager() = default;
bool write(float value) {
if (!memoryFile->getData())
return false;
juce::ScopedLock sl(*lock);
*static_cast<float*>(memoryFile->getData()) = value;
return true;
}
bool read(float& value) {
if (!memoryFile->getData())
return false;
juce::ScopedLock sl(*lock);
value = *static_cast<float*>(memoryFile->getData());
return true;
}
private:
juce::String memoryName;
juce::String lockName;
std::unique_ptr<juce::MemoryMappedFile> memoryFile;
std::unique_ptr<juce::InterprocessLock> lock;
};
