Close

Write a Shared Memory CHOP


This article refers to how to write external applications to interface with Shared Mem Out CHOP and Shared Mem In CHOP.

In the TouchDesigner installation directory, under /touch/SharedMem/CHOP there is the header defined in CHOP_SharedMemHeader.h that needs to be filled in at the start of the shared memory buffer. The memory buffer should have this header, followed by the channel data and optionally the channel data.

NOTE: The 'Shared Mem Type' parameter in the CHOP must be set to 'Local' for your app to be able interface with it

Creating a Shared Memory Bufferedit

Refer to the article Using Shared Memory in TouchDesigner to learn how to create a shared memory buffer. If you are the sender, you need to create a buffer who's size will accommodate both the header, the channel data and the channel names (if you are sending them). Get the size of the header simply with

 sizeof(CHOP_SharedMemHeader)


The size of the channel data is

 number of channels * channel length * 4

4 because that's the number of bytes in a float.

The size of the channel names is the length of each channel name + 1 extra byte per channel to accommodate the null character at the end of each channel name. For example if you have 2 channels named cha1 and cha2 the required size would be

 4 + 4 + 2 = 10
 where the terms are: (length of cha1) + (length of cha2) + (number of channels, for the null characters)

Following the same example, if the channel length was 5 samples long, then you'd need to allocate a memory buffer that has the size in bytes of at least:

 sizeof(CHOP_SharedMemHeader) + 10 + (2 * 5 * 4)

Once you have a pointer to the memory, you can do the next step.

Writing/Reading the Headeredit

You can get the header file used to describe the data that is sent through a Shared Mem CHOP in Touch/SharedMem/CHOP (Under the directory where Touch is installed). The file is called CHOP_SharedMemHeader.h.

The header is the first thing that appears in the shared memory. So you can cast the pointer you get from UT_SharedMem to CHOP_SharedMemHeader.

 CHOP_SharedMemHeader *header = (CHOP_SharedMemHeader*)shm->getMemory();

If you are the sender, fill in all of the fields in this header. Refer to the header for more details one each field you need to fill in.

If you are the sender, make sure you fill in all required fields in the header, otherwise the receiver won't know where the data is

Most of the members of the header are self-explanatory. Typical usage would be like this

 header->magicNumber = CHOP_SHM_MAGIC_NUMBER; // Must be set to this
 header->version = CHOP_SHM_VERSION; // Must be set to this
 header->size = sharedMemSize; // As calculated when you created the shared memory
 header->numChans = numOfChannelsToSend;
 header->length = lengthOfEachChannel; // Number of samples
 header->sampleRate = theSampleRate;
 header->namesSent = amISendingNames; // 1 if you are sending channel names, 0 otherwise
 header->channelDataOffset = sizeof(CHOP_SharedMemHeader);
 header->nameDataOffset = header->channelDataOffset + (header->numChans * header->length * 4);

If you are the receiver, use the the information from the header however you see fit.

Reading/Writing the channel dataedit

Get a pointer to where you should read/write the channel data by simply using the getChannelData() call in the header

 float *data = header->getChannelData(); 

The channel data is tightly packed, one channel at a time. So the first channel's samples appear first, follow immediately by the second channel's samples.

A typical loop to read/write the channel data would look like this

 float *data = header->getChannelData(); 
 for (int i = 0; i < header->numChans; i++)
 {
    // data points to the channel data of the i'th channel
    for (int j = 0; j < header->length; j++)
    {
        // data[j] is the j'th sample in the current channel
        float sample = data[j];
        // do something with the data
        // or if you are writing data to the shared mem, you'd assign something to data[j]
    }
    // Move the data pointer forward so that it now points to the data for
    // the next channel
    data += header->length;
 }

Reading/Writing the channel namesedit

If you are writing channel names, make sure you set the namesSent field of the header to 1, otherwise the names will be ignored. If you don't send names, then the channels will keep their previous names, or use incremental names starting at 'chan1' if they don't have a previous name.

If you are reading channel names, make sure the namesSent field of the header is 1, otherwise you will be reading garbabe data.

Get a pointer to where you should read/write the channel names by simply using the getNameData() call in the header

 char *names = header->getNameData(); 

The channel names are tightly packed with the data containing a channel name, followed by a null character, followed immediately by the next channel name.

A typical loop to read the channel names would be:

 char *names = header->getNameData(); 
 for (int i = 0; i < header->numChans; i++)
 {
    // names pointer to the name of the i'th channel, so you can do whatever you normally can with a string it
    // such as
    cout << names << endl;
    
    // Now move the pointer forward to the next channel name
    // add the number of character in the name, plus the null character to the pointer
    names += strlen(names) + 1;
 }

A typical loop to write the channel names would be:

 // This example assumes you have an array of char*s called myNames that contain the channel names.
 char *names = header->getNameData(); 
 for (int i = 0; i < header->numChans; i++)
 {
    // names pointer to the name of the i'th channel, so copy the channel name to it, make sure you include the
    // null character at the end of the string (the + 1 in the size of the memcpy)
    memcpy(names, myNames[i], strlen(myNames[i]) + 1);
    
    // Now move the pointer forward to the next channel name
    // add the number of character in the name, plus the null character to the pointer
    names += strlen(myNames[i]) + 1;
 }