The Problem

When dealing with large audio applications one must be careful on how to communicate information between the message thread (UI releated stuff) and the audio thread. Master4Streaming as in addition to that an additional thread which needs to get updated from UI - the offline processing thread.

Reactive

Our first approach was to use a pattern called reactive programming and dynamic dispatch. The UI can dispatch so called Actions, these contain information on what to do. The “backend” then processes the actions and provides the how to perform these actions. Master4Streaming’s backend consits of three large components:

  • ProcessorGraph
  • TransportSourceManager
  • MainComponent

all these components will receive all actions and can act on them accordingly. One problem we encountered with this approach was, that there was no good way to communicate to the frontend that an action has been completed and what the new state is. The UI had to know in advance what will be changed by an action, rending the decoupling of the what and how void. In addition to that performing certain actions asynchronously was really hard, since there was no way to notify the UI, that the action has been performed.

ValueTree 🌳

ValueTrees to then rescue! These are like XML on steroids. A XML like structure with the ability to listen for changes.

The new data model in Master4Streaming uses one central ValueTree which holds the entire application state. All components (including the UI) listen to changes and handle these accordingly. The UI will redraw, the ProcessorGraph will make sure that the processors are in the correct order, etc.

Thread Saftey

All ValueTree notifications happen on the message thread, that means performing expensive calculations should be avoided. At the same time we cannot mess with data from the audio thread as well. One solution for UI is to use an AsyncUpdater from JUCE to postpone these changes.

The audio releated stuff keeps an internal structure of all processors and audio sources and updates this internal structure on every update. We used a simple ReadWrite Lock to prevent race conditions. Usually one should not lock on the audio thread, but in this case we have no choice - we need to lock somehow and these locks haben quite infrequently (only on changes to the order of plugins or addition of such and loading of new audio tracks).