Callbacks in TouchDesigner

Mastering Callbacks in TouchDesigner: A Deep Dive into Intermediate Techniques

Welcome to the first installment of our new series on intermediate to professional topics in TouchDesigner. Whether you’re actively working in a professional environment or looking to expand your knowledge, this series is designed to help you master the intricacies of TouchDesigner. Today, we’ll explore callbacks, a crucial tool for handling events and information within TouchDesigner.

Why Callbacks Matter in TouchDesigner

Callbacks are essential for managing events in TouchDesigner. They allow you to react to specific events and, in some cases, influence behavior or pass data to operators in ways that would otherwise be impossible. In essence, callbacks are DATs (Data Operators) treated as Python modules, containing a set of functions with predefined arguments.

Let’s dive into a practical example using the Timer CHOP. You have two main options for responding to events like the timer starting or stopping:

  • Using a Chop Execute DAT: This reacts to changes in channel values.
  • Utilizing the Timer CHOP’s Callbacks: This method is more concise and precise.

Consider the difference between these two approaches:

Callback Method:

def onStart(timerOp):
    pass

Chop Execute Method:

def onOffToOn(channel, sampleIndex, val, prev):

    if channel.name == "running": # Do something when running pass return

The callback method is not only more concise but also provides more information. For instance, with the onStart callback, you receive the specific timer object, allowing for more flexible and reusable code.

The Advantages of Callbacks

Callbacks offer several advantages over traditional methods like Chop Execute DATs:

  • Precision: Callbacks are 100% precise, even when multiple changes occur within a single frame.
  • Additional Data: Callbacks provide extra information, such as which segment of the timer you’ve entered.
  • Control: You can pass data back to the operator without altering parameters. For example, the onInitialize callback ensures specific criteria are met before the timer starts.

Here’s an example where we use the onInitialize callback to ensure a Movie File In TOP is fully loaded before starting the timer:

def onInitialize(timerOp, callCount):
    if op("moviefilein").isFullyPreRead and op("moviefilein").isOpen:
        return 0
    if callCount > 5: abortPlayback()
    return 2

Building Custom Components with Callbacks

You can create your own custom components using callbacks, reducing the need for boilerplate code. Here’s a simple example where we create a component that triggers a callback when the Shift+Z keys are pressed:

  • Create a Base COMP: Place the CallbackManager from your library and press the „Init“ button.
  • Set Up the Callback: Create a DAT called defaultCallbacks and add the following code:
def onShortcut():
    return
  • Edit the Keyboard In DAT:
def onKey(dat, key, character, alt, lAlt, rAlt, ctrl, lCtrl, rCtrl, shift, lShift, rShift, state, time, cmd, lCmd, rCmd):

    if state and key == "z" and shift: op("callbackManager").Do_Callback("onShortcut")

    return

This simple setup allows you to trigger the callback without worrying about the specifics of the Keyboard In DAT.

Advanced Usage: JSON Config COMP and Async Operations

Callbacks shine in more complex scenarios as well. For example, the JsonConfigCOMP allows you to pass pure Python data between the logic of a component and its external environment. This is especially useful when dealing with asynchronous operations, such as handling responses from a webClientDAT.

Here’s a practical example of using a callback for a WebSocket connection in a QR Code scanner app:

  • Create a Web Server DAT: Bind the Port and Active parameters to the parent component.
  • Set Up a FIFO DAT and CallbackManager.
  • Define the Callback:
def onWebSocketReceiveBinary(webServerDAT, client, data:bytes):
    receivedText = data.decode().strip()
    op("receivedData").appendRow(receivedText)
    op("callbackManager").Do_Callback("onScan", receivedText)
    return

This approach allows you to create a reusable component with a distinct callback, tailored to handle the specific needs of your project.

Conclusion

I hope this introduction to callbacks in TouchDesigner gives you the tools and inspiration to create more efficient and reusable components. As you continue to explore these techniques, remember that callbacks are a powerful way to manage events and pass data within your projects, offering both precision and flexibility.

If you found this guide helpful and want to support future content, consider becoming a Patreon supporter. Your contributions help me continue creating in-depth tutorials like this one. Happy designing!