PLUG'N SCRIPT
rapid plugin development
Tutorial
DSP
KUIML
How-to
Scripts
  • Organize your scripts
  • Make a gain plugin
  • Make a simple low-cut filter
  • Add params more conveniently
  • Share data between instances
How-toShare data between instances
January 23, 2023

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:

  1. GUI-side communication via built-in (and undocumented) features of Blue Cat Audio plugins;
  2. 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)

  1. // include required stuff
  2. #include <cmath>
  3. #include <string>
  4. #include <vector>
  5.  
  6. // include factory Blue Cat Audio libraries
  7. #include "../library/dspapi.h"
  8. #include "../library/cpphelpers.h"
  9.  
  10. // include LetiMix library to add params in a cleaner way
  11. // see https://pns.letimix.com/how-to/add-params-more-conveniently
  12. #include "../library/params.h"
  13.  
  14. // include shared memory functions (see below)
  15. #include "shared_memory.h"
  16.  
  17. // script name
  18. DSP_EXPORT string name="Shared memory";
  19.  
  20. // placeholders for input and output params indexes
  21. int GAIN, SHARED_GAIN;
  22.  
  23. // to prevent adding params twice
  24. bool params_were_initialized = false;
  25.  
  26. // initialize (usually done once on plugin loading)
  27. DSP_EXPORT bool initialize(){
  28. if (!params_were_initialized) {
  29. // add input params
  30. GAIN = ip("Local", "dB", -20, 20, 0, ".2");
  31.  
  32. // add output params
  33. SHARED_GAIN = op("Shared", "dB", -20, 20, 0, ".2");
  34. }
  35.  
  36. // try to initialize shared memory (see shared_memory.h)
  37. if (initSharedMemory() == false) {
  38. print("Shared memory unavailable!");
  39. return false;
  40. }
  41.  
  42. return true;
  43. }
  44.  
  45. ///////////////////////////////////////////////
  46. // now functions executed when the plugin is running
  47.  
  48. // read input param changes
  49. DSP_EXPORT void updateInputParametersForBlock(const TransportInfo* transportInfo) {
  50. double gain = IP[GAIN];
  51. if (sh != nullptr) {
  52. // set value in shared memory
  53. sh->gain = gain;
  54. // print("updated to " + std::to_string(sh->gain));
  55. }
  56. }
  57.  
  58. // output values
  59. DSP_EXPORT void computeOutputData() {
  60. if (sh != nullptr) {
  61. // output value from shared memory
  62. OP[SHARED_GAIN] = sh->gain;
  63. }
  64. }
  65.  
  66. ///////////////////////////////////////////////
  67. // when the plugin is unloading
  68.  
  69. // shutdown shared memory (see shared_memory.h)
  70. DSP_EXPORT void shutdown() {
  71. shutDownSharedMemory();
  72. }

Contents of the shared_memory.h

  1. // THIS IS A DEMO LIBRARY FOR USING SHARED MEMORY
  2. // Jan 2023 // Ilya Orlov // support@letimix.com
  3.  
  4. // Down below be add a function called 'sharedMem_getAddr'
  5. // implemented for both Mac and Windows machines.
  6. // This function gets a 'virtual file name' and
  7. // size in bytes (the amount of shared memory you need).
  8. // On success it returns a pointer to start of that shared memory block.
  9.  
  10. // You can use this memory the way you want,
  11. // for example use that start address (memAddr) + offset to read or write
  12. // using something like memset or memmove
  13.  
  14. // But it's convenient to make a "structure" and place it
  15. // in the beginning of that shared memory block
  16. // so you can later read and write easily like this
  17. // sh->gain = 0.5; or double freq = sh->freq;
  18. struct sharedParamsStruct{
  19. double gain = 0;
  20. double freq = 0;
  21. double some_data[512];
  22. };
  23.  
  24. // this will be a pointer to that sharedParamsStruct in shared memory
  25. // so we could use it like sh->gain = 10;
  26. sharedParamsStruct * sh = nullptr;
  27.  
  28. // variables for shared memory allocation
  29. int memSize = 0; // will get the value after allocation
  30. void* memAddr = nullptr; // will get the value after allocation
  31.  
  32. ////////////////////////
  33. // SOME HELPERS WE USE
  34.  
  35. // PnS print implementation with std::string
  36. void print(std::string s){
  37. if (s.length() > 0)
  38. print(s.c_str());
  39. }
  40.  
  41. /////////////////////////////////////////////////////////////
  42. // NOW LET'S IMPLEMENT A FUNCTION TO GET THAT SHARED MEMORY
  43. /////////////////////////
  44. // The implementation is different on Windows and Mac, but the result
  45. // is the same - you get an address in shared memory where
  46. // you can read and write.
  47. // Notice that this address will be different for each plugin instance.
  48. // Because it's not the "real" address, it's translated by the OS.
  49. // But it works like you're accessing the same memory.
  50. ////////////////////////
  51.  
  52. // for Mac
  53. #if defined(__clang__)
  54.  
  55. // MAC
  56. #include <sys/mman.h>
  57. #include <sys/fcntl.h>
  58. #include <errno.h>
  59. #include <unistd.h>
  60.  
  61. int fdMapFile = -1;
  62. void* pMemAddr = nullptr;
  63. size_t _memSize = 0;
  64.  
  65. // returns pointer to shared memory addess
  66. void* sharedMem_getAddr(const char* virtualFileName, int & sizeToAllocate){
  67. // get page size (or granularity, which is bigger) on current system
  68. double pageSize = 0.0 + sysconf(_SC_PAGE_SIZE);
  69.  
  70. // update sizeToAllocate
  71. sizeToAllocate = pageSize*ceil((0.0 + sizeToAllocate)/pageSize);
  72.  
  73. // get shared memory file descriptor
  74. fdMapFile = shm_open(virtualFileName, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  75. if (fdMapFile == -1) {
  76. print("shm_open error");
  77. pMemAddr = nullptr;
  78. return pMemAddr;
  79. }
  80.  
  81. // extend shared memory object as by default it's initialized with size 0
  82. ftruncate(fdMapFile, sizeToAllocate);
  83.  
  84. // map shared memory to process address space
  85. pMemAddr = mmap(NULL, sizeToAllocate, PROT_WRITE|PROT_READ, MAP_SHARED, fdMapFile, 0);
  86. if (pMemAddr == MAP_FAILED) {
  87. print("mmap error. sizeToAllocate: " + std::to_string(sizeToAllocate));
  88. pMemAddr = nullptr;
  89. return pMemAddr;
  90. } else {
  91. _memSize = sizeToAllocate;
  92. }
  93.  
  94. return pMemAddr;
  95. }
  96.  
  97. // on closing
  98. void sharedMem_onShutdown(){
  99. if (pMemAddr != nullptr) {
  100. munmap(pMemAddr, _memSize);
  101. }
  102. if (fdMapFile >-1) {
  103. close(fdMapFile);
  104. }
  105.  
  106. // we could also delete the virtual file, but we don't have to
  107. // shm_unlink(virtualFileName);
  108. }
  109.  
  110. #else
  111. // WINDOWS
  112. #define WIN32_LEAN_AND_MEAN 1
  113. #include <windows.h>
  114.  
  115. HANDLE hMapFile = 0;
  116. void* pMemAddr = nullptr;
  117.  
  118. // returns pointer to shared memory address
  119. void* sharedMem_getAddr(const char* baseFileName, int & sizeToAllocate){
  120. // define real size to allocate depending on page size
  121.  
  122. // get page size (or granularity, which is bigger) on current system
  123. double pageSize = 0;
  124. SYSTEM_INFO system_info;
  125. GetSystemInfo(&system_info);
  126. pageSize = system_info.dwPageSize;
  127. if (system_info.dwAllocationGranularity > pageSize) {
  128. pageSize = system_info.dwAllocationGranularity;
  129. }
  130.  
  131. // update sizeToAllocate
  132. sizeToAllocate = pageSize*ceil((0.0 + sizeToAllocate)/pageSize);
  133.  
  134. std::string virtualFileName = std::string("Local\\") + baseFileName;
  135.  
  136. // get handle to virtual file
  137. hMapFile = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, virtualFileName.c_str());
  138. if (hMapFile == nullptr) {
  139. hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeToAllocate, virtualFileName.c_str());
  140. }
  141. if (hMapFile == nullptr) {
  142. print("Could not create file mapping object. Error: "+std::to_string(GetLastError()));
  143. pMemAddr = nullptr;
  144. return pMemAddr;
  145. }
  146. // map view of virtual file in memory
  147. pMemAddr = (void*) MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeToAllocate);
  148. if (pMemAddr == nullptr) {
  149. print("Could not map view of file. Error: "+std::to_string(GetLastError()));
  150. CloseHandle(hMapFile);
  151. pMemAddr = nullptr;
  152. return pMemAddr;
  153. }
  154.  
  155. return pMemAddr;
  156. }
  157.  
  158. // on closing script
  159. void sharedMem_onShutdown(){
  160. UnmapViewOfFile(pMemAddr);
  161. CloseHandle(hMapFile);
  162. }
  163.  
  164. #endif
  165.  
  166. ////////////////////////
  167.  
  168. // initialize shared memory
  169. bool initSharedMemory() {
  170.  
  171. // prevent double initialization of shared memory
  172. if (sh == nullptr) {
  173.  
  174. // for the 'virtual file name' you may also want to add
  175. // name of the current app (DAW), current userid and other stuff
  176. // this way you'll have different 'shared memories'
  177. // when using plugin in different DAWs on the same computer
  178. // or when using multiple user accounts on the same machine
  179. std::string virtualFileName = std::string("my_virtual_file");
  180.  
  181. // how much memory we need
  182. int extra_memory = 16384; // in addition to sizeof sharedParamsStruct
  183. int memSizeToRequest = sizeof(sharedParamsStruct) + extra_memory;
  184.  
  185. // ask for memory
  186. memSize = memSizeToRequest;
  187. memAddr = sharedMem_getAddr(virtualFileName.c_str(), memSize);
  188. // if we got a memory address, it's a success
  189. if (memAddr != nullptr) {
  190. // place the "sh" pointer at the start of that memory
  191. sh = (sharedParamsStruct *) memAddr;
  192. } else {
  193. sh = nullptr;
  194. }
  195.  
  196. // output some debug info
  197. print("Virtual file name: '" + virtualFileName + "'; requested: " + std::to_string(memSizeToRequest) + "; allocated: "+ std::to_string(memSize) + " bytes");
  198. }
  199.  
  200. if (sh == nullptr)
  201. return false;
  202. else
  203. return true;
  204.  
  205. }
  206.  
  207. // on unloading the plugin
  208. void shutDownSharedMemory(){
  209. // release what we're using
  210. sharedMem_onShutdown();
  211. }

Comments

2020 © Plug'n Script and KUIML by Blue Cat Audio  |  Site by LetiMix