How to share data between instances
If you want your Plug'n Script-made plugins to communicate you can achieve this in various ways. We'll show two possible solutions tested in our projects:
- GUI-side communication via built-in (and undocumented) features of Blue Cat Audio plugins;
- Using shared memory (DSP side, C/C++ only);
Using shared memory (DSP side)
If you've already switched to C/C++ (from AngelScript), you can try various ways to manipulate memory. Scripts written in AngelScript don't have a direct access to memory, so the only way to commucate with the "outer world" seems to be reading and writing to files, which is quite slow. We also did some experiments opening virtual COM-ports as files, but we wouldn't recommend it as a reliable option.
If you're in C++ you can think that making a variable "static" will make it shared between instances. But in fact each time loading your compiled code it's copied and opened as a new library, so the plugins don't see each other this way.
One of the options that Blue Cat Audio recommends is making another dynamic library that you can load, and use it as a place for sharing data. We tried that and it works, but there's probably a more convenient way - creating and opening virtual files in shared memory.
Opening a virtual file in shared memory
In this demo we've created a virtual file in shared memory (allocated some space), got an address of the first byte of that shared memory and used it to share a simple value. See more comments and explanations in the code.
Contents of the main dsp file (test_shared_mem.cpp)
- // include required stuff
- #include <cmath>
- #include <string>
- #include <vector>
- // include factory Blue Cat Audio libraries
- #include "../library/dspapi.h"
- #include "../library/cpphelpers.h"
- // include LetiMix library to add params in a cleaner way
- // see https://pns.letimix.com/how-to/add-params-more-conveniently
- #include "../library/params.h"
- // include shared memory functions (see below)
- #include "shared_memory.h"
- // script name
- DSP_EXPORT string name="Shared memory";
- // placeholders for input and output params indexes
- int GAIN, SHARED_GAIN;
- // to prevent adding params twice
- bool params_were_initialized = false;
- // initialize (usually done once on plugin loading)
- if (!params_were_initialized) {
- // add input params
- GAIN = ip("Local", "dB", -20, 20, 0, ".2");
- // add output params
- SHARED_GAIN = op("Shared", "dB", -20, 20, 0, ".2");
- }
- // try to initialize shared memory (see shared_memory.h)
- if (initSharedMemory() == false) {
- print("Shared memory unavailable!");
- return false;
- }
- return true;
- }
- ///////////////////////////////////////////////
- // now functions executed when the plugin is running
- // read input param changes
- double gain = IP[GAIN];
- if (sh != nullptr) {
- // set value in shared memory
- sh->gain = gain;
- // print("updated to " + std::to_string(sh->gain));
- }
- }
- // output values
- if (sh != nullptr) {
- // output value from shared memory
- OP[SHARED_GAIN] = sh->gain;
- }
- }
- ///////////////////////////////////////////////
- // when the plugin is unloading
- // shutdown shared memory (see shared_memory.h)
- shutDownSharedMemory();
- }
Contents of the shared_memory.h
- // THIS IS A DEMO LIBRARY FOR USING SHARED MEMORY
- // Jan 2023 // Ilya Orlov // support@letimix.com
- // Down below be add a function called 'sharedMem_getAddr'
- // implemented for both Mac and Windows machines.
- // This function gets a 'virtual file name' and
- // size in bytes (the amount of shared memory you need).
- // On success it returns a pointer to start of that shared memory block.
- // You can use this memory the way you want,
- // for example use that start address (memAddr) + offset to read or write
- // using something like memset or memmove
- // But it's convenient to make a "structure" and place it
- // in the beginning of that shared memory block
- // so you can later read and write easily like this
- // sh->gain = 0.5; or double freq = sh->freq;
- struct sharedParamsStruct{
- double gain = 0;
- double freq = 0;
- double some_data[512];
- };
- // this will be a pointer to that sharedParamsStruct in shared memory
- // so we could use it like sh->gain = 10;
- sharedParamsStruct * sh = nullptr;
- // variables for shared memory allocation
- int memSize = 0; // will get the value after allocation
- void* memAddr = nullptr; // will get the value after allocation
- ////////////////////////
- // SOME HELPERS WE USE
- // PnS print implementation with std::string
- void print(std::string s){
- if (s.length() > 0)
- print(s.c_str());
- }
- /////////////////////////////////////////////////////////////
- // NOW LET'S IMPLEMENT A FUNCTION TO GET THAT SHARED MEMORY
- /////////////////////////
- // The implementation is different on Windows and Mac, but the result
- // is the same - you get an address in shared memory where
- // you can read and write.
- // Notice that this address will be different for each plugin instance.
- // Because it's not the "real" address, it's translated by the OS.
- // But it works like you're accessing the same memory.
- ////////////////////////
- // for Mac
- #if defined(__clang__)
- // MAC
- #include <sys/mman.h>
- #include <sys/fcntl.h>
- #include <errno.h>
- #include <unistd.h>
- int fdMapFile = -1;
- void* pMemAddr = nullptr;
- size_t _memSize = 0;
- // returns pointer to shared memory addess
- void* sharedMem_getAddr(const char* virtualFileName, int & sizeToAllocate){
- // get page size (or granularity, which is bigger) on current system
- double pageSize = 0.0 + sysconf(_SC_PAGE_SIZE);
- // update sizeToAllocate
- sizeToAllocate = pageSize*ceil((0.0 + sizeToAllocate)/pageSize);
- // get shared memory file descriptor
- fdMapFile = shm_open(virtualFileName, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
- if (fdMapFile == -1) {
- print("shm_open error");
- pMemAddr = nullptr;
- return pMemAddr;
- }
- // extend shared memory object as by default it's initialized with size 0
- ftruncate(fdMapFile, sizeToAllocate);
- // map shared memory to process address space
- pMemAddr = mmap(NULL, sizeToAllocate, PROT_WRITE|PROT_READ, MAP_SHARED, fdMapFile, 0);
- if (pMemAddr == MAP_FAILED) {
- print("mmap error. sizeToAllocate: " + std::to_string(sizeToAllocate));
- pMemAddr = nullptr;
- return pMemAddr;
- } else {
- _memSize = sizeToAllocate;
- }
- return pMemAddr;
- }
- // on closing
- void sharedMem_onShutdown(){
- if (pMemAddr != nullptr) {
- munmap(pMemAddr, _memSize);
- }
- if (fdMapFile >-1) {
- close(fdMapFile);
- }
- // we could also delete the virtual file, but we don't have to
- // shm_unlink(virtualFileName);
- }
- #else
- // WINDOWS
- #define WIN32_LEAN_AND_MEAN 1
- #include <windows.h>
- HANDLE hMapFile = 0;
- void* pMemAddr = nullptr;
- // returns pointer to shared memory address
- void* sharedMem_getAddr(const char* baseFileName, int & sizeToAllocate){
- // define real size to allocate depending on page size
- // get page size (or granularity, which is bigger) on current system
- double pageSize = 0;
- SYSTEM_INFO system_info;
- GetSystemInfo(&system_info);
- pageSize = system_info.dwPageSize;
- if (system_info.dwAllocationGranularity > pageSize) {
- pageSize = system_info.dwAllocationGranularity;
- }
- // update sizeToAllocate
- sizeToAllocate = pageSize*ceil((0.0 + sizeToAllocate)/pageSize);
- std::string virtualFileName = std::string("Local\\") + baseFileName;
- // get handle to virtual file
- hMapFile = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, virtualFileName.c_str());
- if (hMapFile == nullptr) {
- hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeToAllocate, virtualFileName.c_str());
- }
- if (hMapFile == nullptr) {
- print("Could not create file mapping object. Error: "+std::to_string(GetLastError()));
- pMemAddr = nullptr;
- return pMemAddr;
- }
- // map view of virtual file in memory
- pMemAddr = (void*) MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeToAllocate);
- if (pMemAddr == nullptr) {
- print("Could not map view of file. Error: "+std::to_string(GetLastError()));
- CloseHandle(hMapFile);
- pMemAddr = nullptr;
- return pMemAddr;
- }
- return pMemAddr;
- }
- // on closing script
- void sharedMem_onShutdown(){
- UnmapViewOfFile(pMemAddr);
- CloseHandle(hMapFile);
- }
- #endif
- ////////////////////////
- // initialize shared memory
- bool initSharedMemory() {
- // prevent double initialization of shared memory
- if (sh == nullptr) {
- // for the 'virtual file name' you may also want to add
- // name of the current app (DAW), current userid and other stuff
- // this way you'll have different 'shared memories'
- // when using plugin in different DAWs on the same computer
- // or when using multiple user accounts on the same machine
- std::string virtualFileName = std::string("my_virtual_file");
- // how much memory we need
- int extra_memory = 16384; // in addition to sizeof sharedParamsStruct
- int memSizeToRequest = sizeof(sharedParamsStruct) + extra_memory;
- // ask for memory
- memSize = memSizeToRequest;
- memAddr = sharedMem_getAddr(virtualFileName.c_str(), memSize);
- // if we got a memory address, it's a success
- if (memAddr != nullptr) {
- // place the "sh" pointer at the start of that memory
- sh = (sharedParamsStruct *) memAddr;
- } else {
- sh = nullptr;
- }
- // output some debug info
- print("Virtual file name: '" + virtualFileName + "'; requested: " + std::to_string(memSizeToRequest) + "; allocated: "+ std::to_string(memSize) + " bytes");
- }
- if (sh == nullptr)
- return false;
- else
- return true;
- }
- // on unloading the plugin
- void shutDownSharedMemory(){
- // release what we're using
- sharedMem_onShutdown();
- }