Skip to content

Sound Browsers


Large instruments often ship with hundreds of presets or sound variations. Users need a way to browse, filter, and audition those sounds without leaving the instrument UI. IVLS provides Stl.Browsers.Template, a tag-driven browser component that handles filtering, scrolling, selection, and fallback state -- leaving you to define the visual rendering and what "loading a sound" means for your product.

The Template


Stl.Browsers.Template is a parameterized node template. You instantiate it with from, providing four template parameters:

node MyBrowser from Stl.Browsers.Template(my.browser, 256, 16, 12):
    { ... }
end node

The parameters are:

  • #name# -- a namespace prefix. All generated functions, state, and hooks live under this prefix (e.g., my.browser.focused_item, my.browser.hooks.load_item).
  • #max_items# -- the maximum number of items the browser can hold.
  • #num_tags# -- the number of tag categories for filtering.
  • #num_entries# -- the number of visible rows in the browser UI at once.

Required Hooks


The template defines a set of hook functions with empty default implementations. You override them to connect the browser to your product's sounds and UI:

hooks.get_tag_state(idx) / hooks.set_tag_state(idx, state) -- read and write the on/off state of each tag button. These connect the browser's filter logic to your actual UI controls:

function my.browser.hooks.get_tag_state(idx) -> state override
    state := my.tag_btn[idx] -> value
end function

hooks.load_item(selected_item) -- called when the user selects an item. This is where your product actually loads the sound:

function my.browser.hooks.load_item(selected_item) override
    my_product.sound_id := selected_item
end function

ui.hooks.render_entry(entry_idx, state, visible, item_id) -- called for each visible row when the entry list refreshes. You set the button text, highlight state, and visibility:

function my.browser.ui.hooks.render_entry(entry_idx, state, visible, item_id) override
    my.entry_btn[entry_idx] -> value := OFF

    if item_id > -1
        my.entry_btn[entry_idx] -> text  := my.sound_names[item_id]
        my.entry_btn[entry_idx] -> value := state
    end if

    my.entry_btn[entry_idx] -> hide := HIDE_WHOLE_CONTROL * (1 - visible)
end function

ui.hooks.render_tag(tag_idx, tag_available) -- called per tag when the filter changes. You update fonts or pictures to show whether a tag would produce results if selected.

Other hooks include hooks.play_preview(), hooks.stop_preview(), hooks.fallback_load(), and hooks.on_entry_dbl_click(). All have safe defaults; override only what your product needs.

Tag Filtering with Bitmasks


Tags are stored as bits in Mask objects -- the same bitmask system used elsewhere in IVLS. Each item has a mask (#name#.masks[item]) with one bit set per tag it belongs to. The browser maintains a current filter mask (#name#.currentFilter), and an item passes the filter if all filter bits match.

When a user clicks a tag button, the browser reads all tag button states, writes them into the filter mask, and calls process_filter(), which rebuilds the visible item list:

{ This happens internally when you call reingest_tags() }
call my.browser.ui.tag_btns_to_filter()
call my.browser.process_filter()

The filter also tracks available tags -- tags that would produce at least one result if added to the current filter. Your render_tag hook receives this as the tag_available argument, letting you grey out tags that would produce an empty list.

Mutex-style tag groups (where selecting one tag deselects others in the same group) are handled in the ui.hooks.on_tag_select() override. The Sylvan Phrases browser, for example, makes time signature, BPM, and vowel tags mutually exclusive within their respective groups.

The Scrollbar Companion


Every browser needs a companion Stl.Scrollbar.Template node wired to the same #name# namespace. The scrollbar handles page-up/down buttons, thumb dragging, and scroll position:

node MyBrowserScrollbar from Stl.Scrollbar.Template( ...
        my.browser.scrollbar, ...
        scroll.orient.VERTICAL, ...
        Main.Browser.ScrollBar, ...
        Main.Browser.ScrollThumb, ...
        NUM_VISIBLE_ENTRIES, ...
        my.browser.current_items.count):
    { ... }
end node

The scrollbar's response function is already overridden by the browser template to refresh the entry list when the scroll position changes. You just need to instantiate it with the correct UI widgets.

Configurable Options


The browser exposes several runtime options:

  • #name#.options.auto_preview -- if TRUE, single-clicking an entry triggers a preview
  • #name#.options.auto_load -- if TRUE, single-clicking loads the item (default)
  • #name#.options.defaultFilter -- the Mask used when the filter is reset
  • #name#.options.alwaysAvailableTags -- tags that always appear available regardless of the current filter

The Fallback System


The browser includes a fallback mechanism for cancel/undo. When the user double-clicks an entry (confirming a selection), the current filter and focused item are saved as the fallback state. If the user later clicks the abort button, the browser restores that saved state -- both the item and the filter position.

Products can customize this via the hooks.fallback_load() override, or disable it entirely by overriding fallback() with an empty function.

A Production Example


In Sylvan Phrases, the browser is instantiated with 22 tags and 18 visible entries. Tags are organized into mutex groups: time signatures (4/4, 3/4), BPMs (60, 80, 100), vowels (Ah, Oo, Oh, Ee, Eh), and articulation types. The load_item hook writes the selected sound ID and tag state to the current phrase key. Preview playback routes audio to bus 15 (a dedicated preview output) unless the user holds Alt.


The browser template handles the hard parts -- filtering, scrolling, fallback state, tag availability tracking. Your product provides the bridge between the browser's abstract item indices and your actual sounds, and the bridge between the browser's rendering hooks and your actual UI widgets.