VOID Shared Memory — Low latency data exchange between Ableton Live / Max and TouchDesigner

Hi everyone and Happy New Year2026 !

I’d like to share a research-oriented project developed at Structure Void, focused on high-performance, real-time data exchange between Ableton Live / Max for Live and TouchDesigner, without relying on OSC, MIDI or any network-based protocol.

VOID Shared Memory uses local shared memory to transfer continuous parameters, envelopes, triggers and arbitrary data between audio and visual environments, with very low latency and deterministic behavior, suited for live performance, generative visuals, installations and experimental systems.

On the TouchDesigner side, the project includes custom CHOP operators written in C++, allowing direct read/write access to shared memory streams coming from Ableton Live / Max.
On the Ableton / Max side, a set of dedicated Max for Live devices and Max externals (C++) handle data generation, analysis, encoding and transmission.

:link: Project page:
https://structure-void.com/tools/void-shared-memory/

:movie_camera: Demo playlist (Live ↔ TouchDesigner workflows):

This is not a closed “turnkey” tool, but a technical foundation: a set of modular, low-level building blocks developed entirely in-house, designed to be readable, hackable and repurposed in advanced pipelines.

Typical use cases include:

  • Driving generative or time-based visuals from Live with sample-accurate timing

  • Audio analysis (envelopes, frequency bands, triggers) feeding TouchDesigner systems

  • Bidirectional control flows between TD and Live without network overhead

  • Stable alternatives to OSC for performance-critical setups

The project is distributed as part of an R&D initiative, including:

  • TouchDesigner custom CHOPs

  • Max externals (reader / writer)

  • Max for Live devices

  • Ableton Live demo sets

  • TouchDesigner demo networks

All components are provided together to make experimentation and integration straightforward.

I will likely continue to develop additional Max for Live devices around this system, and I’m also planning to release an unofficial Max package gathering the core externals and tools related to VOID Shared Memory, to make standalone Max usage and integration easier.

Compatibility note (brief): Max externals and TouchDesigner operators are built as universal binaries. Due to current host-level constraints, Max for Live devices are officially supported on Apple Silicon systems.

Feedback, questions and discussion from the TouchDesigner community are very welcome.

— Julien Bayle

3 Likes

Hello Julien,

It seems cool and I will try it.

I will teach in a shared workshop with Ableton Live (Guillaume Feyler) / Millumin (Renaud Rubiano) / TD (myself) and I will show the proposition.

question:

– I have Ableton Live 10 standard and a Max 8, You think it can works ?

– It works on windows 10 ?

PS news about our project ?

1 Like

Hello Jacques,

As mentioned, these are released as experimental only.

Thanks for the interest!

At the moment, the setup is quite specific:

  • Ableton Live 12 is required (the Max for Live devices rely on features and behaviors from recent Live versions).

  • The Max for Live devices are officially supported on macOS Apple Silicon, with Live running natively.

  • Max 8 standalone hasn’t been fully tested yet.

  • The Max externals themselves are built as universal binaries (Apple Silicon + Intel).

  • The TouchDesigner CHOPs are currently macOS Apple Silicon only.

  • Windows 10 workflows are not supported yet.

So for now, it’s really targeted at single-machine, macOS Apple Silicon setups.

That said, this project is part of Structure Void’s ongoing R&D work. The goal is to explore a solid shared-memory foundation first, then gradually improve compatibility, tooling and documentation over time.

So yes — it’s still early, experimental, and evolving, but we’re actively pushing it forward. Actually, I planned to push it to other territories for sure. But very busy at the moment. I already tested and developed VERY specific connectors for specific studio projects I have been commissioned to.

Thanks for sharing concepts in your workshop, and feel free to reach out if you want to discuss here about use cases or feedback.

Thank you Julien for the precisions

Actually, I already have Max externals (reader/writer & reader~/writer~) working for windows x64.
I will publish an (initially unofficial) Max Packages with docs etc.

I still have to work on TD CHOPs for windows.
It could happen soon, or not.
I’ll post updates here & there :slight_smile:

1 Like

I went further.

I’ll come back with a a full Max package (Mac silicon AND Mac Intel AND Win). It will include message layer object, signal thread objects, and jitter matrix objects too… maybe more on Max side.

Beside of it, I’ll port my Mac silicon Touchdesigner CHOPs to Mac Intel ones. And I hope to windows too, to close the loop.

After that… Who knows?!
Pure data? Other real-time environments? It will depend on my time…

2 Likes

Curious about the possibility of sharing jitter matrix objects. What form would these take in TD?

Also, have you considered extending the pattr system to a shared memory structure available in TD and visible in Max? This would be quite interesting as pattr fills a number of gaps that TD has regarding state management within the standard release.

Hi @jesgilbert,

Good questions !

1 / Jitter matrix → TouchDesigner

VOID Shared Memory uses a 1D memory contract by design, with a clear semantic mapping.

Rule:

  • Rows = channels

  • Columns = samples

So a Jitter matrix with:

  • 4 rows

  • 5 columns

will appear in TouchDesigner as:

  • 4 CHOP channels

  • 5 samples per channel

This matches the intuitive way matrices and tables are read and written: each row is a signal or parameter containing a sequence of values. Internally, data are stored linearly, written row by row, and reconstructed deterministically using the header (channels, length).

2 / Fully agnostic shared memory

The 1D nature is only about storage, not meaning.

Shared memory is completely agnostic to how data are produced or consumed:

  • you can write using a Jitter matrix and read the same data as lists in Max or CHOP channels in TD,

  • you can write CHOP channels in TD and reconstruct a Jitter matrix, tables, or lists on the Max side.

Shared memory:

  • does not store matrices, images, or objects,

  • only exposes a minimal, explicit data contract:
    (channels × samples × frame)

This keeps the system fast, deterministic, language-agnostic, and easy to re-implement in other hosts. All higher-level structures (matrices, grids, parameter banks, state vectors) are reconstructed above the shared memory layer.

3 / About pattr and shared memory

VOID Shared Memory is a transport and synchronization layer, not a state system.

pattr is deeply tied to Max’s object model (binding, hierarchy, notifications, UI coupling), so it is not mirrored into TouchDesigner.

The intended approach is:

  • pattr remains authoritative in Max,

  • selected state or parameter vectors are explicitly exported into shared memory,

  • TouchDesigner reads them as CHOPs for control, visualization, or rendering.

That said, any participant in the system (Max, TouchDesigner, or another host) can implement its own mechanism to save and recall shared memory states if needed. This lives above shared memory and is not its responsibility.

Such state-management tools can be designed on a per-host basis — and for Max, this is something that could reasonably be explored at the package level, without changing the shared memory contract.

In that sense, shared memory acts as a state bridge, not a pattr replacement.

Thanks for providing the detailed view. The matrix to CHOP channel abstraction sounds workable using single plane matrices (presumably the 1D constraints you mentioned), which is a pretty significant limitation. How are you planning to handle the various data types that a jit_matrix can describe?

Regarding state: although I certainly understand why you’d want to constrain the scope of what you’re supporting with VOID, it does feel like a missed opportunity given pattr’s considerable power and the lack of built-in state management in TD. Those of us who regularly do inter-process communication between Max and TD are already rolling our own state solutions, but it’s not all that straightforward given the data models inherent in these applications.

Totally agree — a lot of us who work regularly between Max and TouchDesigner, Openframeworks based stuff, juce stuff.. have been rolling our own IPC / state solutions for years. I’ve been doing the same for a long time too :slightly_smiling_face:

That said, this project isn’t about inventing yet another data pipe, nor about replacing existing TD tools like Shared Memory CHOPs or SOP/TOP workflows (which are great at what they do).

The idea is more about formalising a shared contract across environments (Max messages, Max audio-rate, Jitter, TD), with explicit lifecycle rules (connect, disconnect, invalidation, epoch changes, DSP state), so different objects and platforms behave predictably — without everyone re-implementing slightly different glue code each time.

Regarding matrices: in V2 the approach is deliberately restricted. We stick to single-plane, 2D matrices, where rows and columns map directly to channels and length. This keeps the mapping explicit, predictable, and fast, and avoids hidden conversions or per-type branching.

The matrix layer is really just an abstraction convenience for Max/Jitter, not a generic “transport everything a jit_matrix can describe” mechanism. The priority is deterministic layout, low latency, and identical behaviour across Max messages, audio-rate objects, Jitter, and TouchDesigner.

If the goal were to move richer or heterogeneous data types, higher-dimensional matrices, or full texture payloads, there are already excellent, purpose-built solutions (Syphon, Spout, NDI, GPU sharing, etc.), or entirely different architectural choices one could make. That problem space is well covered — and this isn’t trying to compete there.

So I see this as a small, well-defined building block that plays nicely with existing solutions, not a universal IPC hammer. If it saves a few people from rewriting the same boilerplate for the Nth time, that’s already a win :sign_of_the_horns:t2: