Skip to content

How to Add a Sound Browser

Add a filterable, tag-driven sound browser so users can search, filter, and preview sounds from within your product's UI.

Prerequisites

  • A working IVLS product with sounds to browse
  • UI widgets created in your performance view: entry buttons, tag buttons, a scrollbar XY pad, and a scrollbar thumb label
  • Stl.Scrollbar included via Ivls.STL (part of the standard library bundle)
  • Familiarity with sound browser concepts

Steps

1. Instantiate the browser template

Create a node that inherits from Stl.Browsers.Template. The template takes four parameters:

Parameter Purpose
#name# Namespace prefix for all browser state and functions
#max_items# Maximum number of browsable items
#num_tags# Number of filter tags
#num_entries# Number of visible rows in the browser list
define NUM_SOUNDS  := 200
define NUM_TAGS    := 16
define NUM_ENTRIES := 12

node MyProduct.Browser from Stl.Browsers.Template(my.browser, NUM_SOUNDS, NUM_TAGS, NUM_ENTRIES):
    cb Init:
        { Declare any product-specific browser state here }
        family my
            family browser
                declare entry_btn[NUM_ENTRIES]
                declare tag_btn[NUM_TAGS]
            end family
        end family
end node

2. Implement required hooks

Override the hook functions inside your browser node's cb Functions to connect the browser to your product's data and UI.

Load item -- called when the user selects a sound:

function my.browser.hooks.load_item(selected_item) override
    { Load the sound at index selected_item }
    my.current_sound := my.sound_ids[selected_item]
    call uir.update()
end function

Tag state -- reads/writes the tag button UI state:

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

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

Render entry -- updates how each row looks in the browser list:

function my.browser.ui.hooks.render_entry(entry_idx, state, visible, item_id) override
    if item_id > -1
        my.browser.entry_btn[entry_idx] -> text  := my.sound_names[item_id]
        my.browser.entry_btn[entry_idx] -> value := state
    else
        my.browser.entry_btn[entry_idx] -> text  := ""
        my.browser.entry_btn[entry_idx] -> value := OFF
    end if

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

Render tag -- updates tag button appearance based on availability:

function my.browser.ui.hooks.render_tag(tag_idx, tag_available) override
    my.browser.tag_btn[tag_idx] -> text := my.tag_names[tag_idx]

    if _true(tag_available)
        my.browser.tag_btn[tag_idx] -> picture := "btn-tag"
    else
        my.browser.tag_btn[tag_idx] -> picture := "btn-tag-dim"
    end if
end function

Preview (optional) -- play/stop sound previews:

function my.browser.hooks.play_preview(selected_item, key_modifiers) override
    { Trigger a preview of the sound }
end function

function my.browser.hooks.stop_preview() override
    { Stop any active preview }
end function

3. Create the scrollbar node

Instantiate Stl.Scrollbar.Template with matching namespace. The scrollbar tracks the browser's filtered item count automatically.

node MyProduct.BrowserScrollbar from Stl.Scrollbar.Template( ...
        my.browser.scrollbar, ...
        scroll.orient.VERTICAL, ...
        my.ui.Browser.ScrollXY, ...
        my.ui.Browser.ScrollThumb, ...
        NUM_ENTRIES, ...
        my.browser.current_items.count):
    { No additional code needed }
end node

4. Register both nodes in IVLS_NODES

define IVLS_NODES := ..., MyProduct.Browser, MyProduct.BrowserScrollbar, ...

5. Bind UI widgets

In your browser node's cb Init or cb PostInit, bind the entry and tag buttons to the browser's built-in UI callbacks:

cb Init:
    declare entry
    for entry := 0 to NUM_ENTRIES - 1
        my.browser.entry_btn[entry] := get_ui_id(my.ui.Browser.Entry[entry])
        uicb.Bind(my.browser.entry_btn[entry], my.browser.cb.entry)
    end for

    declare tag
    for tag := 0 to NUM_TAGS - 1
        my.browser.tag_btn[tag] := get_ui_id(my.ui.Browser.Tag[tag])
        uicb.Bind(my.browser.tag_btn[tag], my.browser.cb.tag)
    end for

6. Initialize item data

In cb FirstLoad, populate the browser's sorted_items and masks arrays with your sound data:

cb FirstLoad:
    declare i
    for i := 0 to NUM_SOUNDS - 1
        my.browser.sorted_items[i] := i

        { Create a Mask for each item's tags }
        declare mask := Mask.new()
        Mask[mask].part[0] := my.sound_tag_bits[i]
        my.browser.masks[i] := mask
    end for

Hook summary

Hook Required Purpose
#name#.hooks.load_item Yes Load the selected sound
#name#.hooks.get_tag_state Yes Read tag button state
#name#.hooks.set_tag_state Yes Write tag button state
#name#.ui.hooks.render_entry Yes Draw each browser row
#name#.ui.hooks.render_tag Yes Draw each tag button
#name#.hooks.play_preview No Play a sound preview
#name#.hooks.stop_preview No Stop active preview
#name#.hooks.fallback_load No Restore cancelled selection
#name#.hooks.on_entry_dbl_click No Handle double-click on entry

Verify

  1. Open the browser UI -- entry rows should populate with sound names
  2. Click a tag button -- the list should filter to matching sounds
  3. Click an entry -- the sound should load (and preview, if implemented)
  4. Scroll -- the scrollbar thumb should track the list position
  5. Click multiple tags -- only items matching all active tags should appear

Further reading

  • Guide: Sound Browsers for the architecture and filter system
  • Guide: UI Callbacks for the uicb.Bind system