Skip to content

UI Routines


When a preset loads, all of your engine box parameters are restored automatically by Data.PCCB(). But what about the rest of the UI? If your instrument has a page indicator, a layer selector, a waveform display, or any visual element that depends on the current state of the engine, that element needs to be refreshed too.

You could scatter update calls throughout your par_cb.response() functions, but that gets messy fast. If changing the mode also needs to update the page indicator, and changing the layer also needs to update the page indicator, you end up with duplicate logic in multiple places -- and you have to remember to add the call every time you add a new parameter that affects the display.

UI routines solve this by giving you a central registry of named update functions. Each routine is a small function that refreshes one aspect of the UI. When a preset loads, the framework runs all of them. When a parameter changes, you can trigger just the relevant ones.

Registering a UI Routine


UI routines are registered by adding names to the STL.UI_ROUTINES extension point:

node MyUI:
    cb Init:
        define STL.UI_ROUTINES += update_page_display

This registers a routine called update_page_display. The name becomes both an identifier constant (uir.id.update_page_display) and the expected function name (uir.update_page_display()).

You can register multiple routines at once:

define STL.UI_ROUTINES += update_page_display, ...
                          update_layer_display, ...
                          update_waveform

Implementing the Routine


Each registered routine needs a corresponding function in cb Functions:

    cb Functions:
        function uir.update_page_display()
            { Refresh the page indicator based on current state }
            Main.PageLabel -> text := page_names[current_page]
            Main.PageIcon -> picture_state := current_page
        end function

The function takes no arguments. It reads whatever state it needs from your engine's current values and updates the relevant UI elements.

Triggering All Routines


When you call uir.update(), the framework runs every registered routine exactly once:

call uir.update()

This is automatically called during cb PostReload -- after all cb Reload callbacks have finished and all engine parameters have been restored. So your UI routines will always run after a preset load without any extra work on your part.

You can also call uir.update() manually whenever you need a full UI refresh -- for example, after changing a mode that affects multiple display elements.

The Update Cache


UI routines use a deduplication cache. Each routine runs at most once per update cycle. If two different parameter changes both trigger the same routine, it only executes once. The cache is automatically cleared at the start of each uir.update() call.

This means you can freely call uir.update() from inside a par_cb.response() without worrying about redundant work. The routines that have already run during this cycle will be skipped.

Routine Groups


Sometimes you want to run a subset of routines rather than all of them. uir.RunGroup() lets you define named groups:

    cb Init:
        define UIR.LAYER_VISUALS := update_layer_display, ...
                                    update_waveform

Then trigger the group:

uir.RunGroup(LAYER_VISUALS)

This runs only uir.update_layer_display() and uir.update_waveform(), leaving other routines untouched. Groups are useful when a specific action (like switching layers) only affects a known subset of the UI.

Auto-Linkage with Engine Box Parameters


For the common case where a parameter change should always trigger a specific UI routine, you can link them directly using par.ui_routine:

    cb Init:
        my_engine.par.ui_routine[my_engine.par.MODE] := uir.id.update_page_display

When the MODE parameter is written (even via par.raw), the engine automatically runs the linked UI routine. You don't need to call it manually from par_cb.response().

This is cleaner than putting call uir.update_page_display() inside your par_cb, because the linkage is declared once and works regardless of how the parameter is changed -- by the user, by a preset load, or by your code.

When to Use a Routine vs. Updating in par_cb


A good rule of thumb:

  • If the update is directly caused by one parameter and nothing else triggers the same update, just do it in par_cb.response(). Setting a filter cutoff, applying a volume change -- these are direct consequences of the parameter.

  • If the update is shared across multiple parameters or needs to run on preset load, make it a UI routine. Page displays, layer indicators, anything that reflects the combined state of several parameters -- these belong in routines.

  • If you find yourself writing the same call uir.some_update() in multiple par_cb cases, that's a sign you should use par.ui_routine auto-linkage instead.


UI routines keep your display logic organized and deduplicated. Combined with engine boxes, they give you a clean separation: the engine owns the data and reacts to changes, the routines own the display and react to state.