Skip to content

Stl.Browsers.Template

Stl.Browsers.Template is a parameterized browser component for building tag-filtered, scrollable sound and preset selection UIs. Products instantiate it once per browser with a unique namespace, then override a small set of required hooks to wire in their data model and UI widgets.

Why This Exists

A sound browser combines tag filtering, scrollable lists, preview playback, cancel/restore state, keyboard navigation, and display rendering. These concerns are common to every instrument that has a browser, but the data each browser operates on is product-specific. The template pattern separates the mechanism (filtering, scrolling, selection) from the data (what to show, how to render it, how to load it).

Key Concepts

Template Declaration

node MyBrowser from Stl.Browsers.Template(#name#, #max_items#, #num_tags#, #num_entries#):
    // implement required hooks
end node
Parameter Purpose
#name# Namespace prefix for all generated identifiers
#max_items# Total item capacity
#num_tags# Number of tag filter buttons
#num_entries# Number of visible rows in the browser list

Multiple independent browsers coexist without collision because all generated identifiers are prefixed with #name#.

Required Hooks

Hook Purpose
hooks.get_tag_state(idx) -> state Return ON/OFF state of tag button
hooks.set_tag_state(idx, state) Write ON/OFF state back to tag button
hooks.load_item(selected_item) Load and activate the item
hooks.play_preview(selected_item, key_modifiers) Start audio preview
hooks.stop_preview() Stop any active preview
ui.hooks.render_entry(entry_idx, state, visible, item_id) Redraw visible entry row
ui.hooks.render_tag(tag_idx, tag_available) Redraw tag button

Tag Filtering

Tags are stored as bitmasks. Each item has a mask assigned to #name#.masks[item]. The current active filter is #name#.currentFilter. Items that match the filter are appended to #name#.current_items[].

#name#.availableTags tracks which tags have at least one matching item under the current filter — render_tag receives this as tag_available so products can visually disable tags that would produce no results.

Filter update flow:

User clicks tag button
  → tag state toggled
  → #name#.reingest_tags()
    → ui.tag_btns_to_filter()     // reads widget states into currentFilter
    → process_filter()            // rebuilds current_items[], updates availableTags
      → render_tag() for each tag
      → scrollbar.update()
      → ui.entry_list.refresh()
        → render_entry() for each visible row

Fallback (Cancel) State

The browser maintains a cancel-restore point:

#name#.set_fallback(generic_id, item, filter_mask)
#name#.current_to_fallback()    // saves current filter + focused_item
#name#.fallback()               // restores saved filter + item
#name#.fallback_sound_only()    // restores item only, not filter

fallback is called by the abort/cancel button callback. On double-click, current_to_fallback() saves the state as a new restore point.

Scrollbar Companion

Every browser requires a companion scrollbar node instantiated with the same namespace:

node MyBrowserScrollbar from Stl.Scrollbar.Template(
    #name#.scrollbar,
    scroll.orient.VERTICAL,
    MyBrowser.ScrollXY,
    MyBrowser.ScrollThumb,
    NUM_ENTRIES,
    #name#.current_items.count
):

The scrollbar automatically wires a response function that calls #name#.ui.entry_list.refresh() whenever its position changes.

taskfunc #name#.jump_focus_by(offset)   // -1 or +1, wired to prev/next buttons
function #name#.jump_to_focused_item(item)  // scroll to bring item into view

Configuration Options

#name#.options.defaultFilter         // Mask: tags active when no filter applied
#name#.options.alwaysAvailableTags   // Mask: tags always shown regardless of filter
#name#.options.auto_preview := TRUE  // single-click plays preview
#name#.options.auto_load    := TRUE  // single-click loads item

Sylvan Phrases Example

Sylvan's Phr.Browser uses 18 visible entries and 22 tags. In cb FirstLoad it initializes all sound masks (phr.browser.init_family_tags()). render_entry sets button text, BPM label, and font/color states. render_tag sets button image frames and enable/disable state.

hooks.load_item triggers sound assignment, refreshes the key mapping, calls purge.execute(), and redraws the main UI. hooks.play_preview fires a preview note event.

Connections to Other Parts of IVLS

The browser's hooks.load_item typically triggers purge (to load the newly assigned sound's groups) and ui-routines (to update the main UI state). stl-play-event is ultimately triggered when a browser item is previewed or played.

Patterns and Caveats

  • Initialize all masks (Mask objects for each item) in cb FirstLoad, not cb Init. At cb Init time the NKA data may not yet be loaded.
  • The browser uses uicb.Bind to wire entry and tag buttons. All binding must happen in cb Init after widget IDs are resolved.
  • auto_preview = TRUE and auto_load = TRUE can conflict in instruments where selecting a sound and loading it are separate actions. Set them based on whether single-click should have immediate effect.
  • The fallback state is reset to generic_id = -1 on browser open. Call set_fallback or current_to_fallback explicitly to establish a restore point before any user interaction.
  • stl-play-event — triggered when browser item is previewed or loaded
  • purge — load_item typically calls purge.execute() to make the new sound's groups resident