Bundleware, Toolbar Script Reference

Last site update 30th October 2026

useSound

HookAPI 2

Returns a deck of the toolbar’s own music and re-renders the component as the deck plays, pauses and stops. The sound is made with Web Audio in the toolbar’s world, so it needs no permission and never leaves the page.

SignatureuseSound(options?: { onEnd?: () => void }): SoundState & Sound
Take it fromconst { useSound } = Bundleware;

Parameters

ParameterTypeDescription
options{ onEnd?: () => void }Optional.
options.onEnd() => voidOptional. Runs when a score finishes; ended is then true. Use it to play the next song.

Return Value

A deck. It stops when its mount ends; a toolbar’s decks share one AudioContext, closed with the last.

MemberTypeDescription
state'stopped' | 'playing' | 'paused'What the deck is doing.
endedbooleantrue when the deck stopped because the score finished.
play(score, options?)voidPlays score from the start, replacing what the deck was playing. Notes are scheduled 1.5 seconds ahead, so a busy page does not stall them.
pause(), resume()voidresume continues where pause left off.
stop()voidStops the deck.
set({ volume, room })voidChanges the mix while playing.

Play Options

OptionValuesDefault
speed0.25 to 41. A tempo multiplier; other values throw a RangeError.
volume0 to 10.8
room0 to 10.2, the small room echo.
looptrue or falsefalse
instrumentsax, trumpet, flute, piano, keys, vibes, bass, padFor notes without one: the track’s General MIDI family, or the piano. Sax, trumpet and flute play one note at a time.

Example

A toolbar that plays one tune and plays it again when it ends. The Readout draws seven levels as block characters, so the widget needs no CSS.

hold-music.toolbar.tsxTSX
// ==BundlewareToolbar==
// @id hold-music
// @name Hold Music
// @version 1.0.0
// @description Plays one public-domain tune, then plays it again.
// @api 2
// ==/BundlewareToolbar==

const { Toolbar, Readout, Icon, music, useSound, useLevels } = Bundleware;
const tune = music.readMidi(TUNE); // A MIDI file as Base64, kept in your script.
const status = { stopped: 'Not on hold', playing: 'Please continue to hold', paused: 'Hold paused' };

function HoldMusic() {
  const sound = useSound({ onEnd: () => sound.play(tune) }); // Play it again.
  const playing = sound.state === 'playing';
  return (
    <Toolbar icon={Icon.Sound} theme={{ brandColor: '#2f2470' }}>
      <Toolbar.Button
        title={playing ? 'Pause' : 'Play'}
        onAction={() => {
          if (playing) sound.pause();
          else if (sound.state === 'paused') sound.resume();
          else sound.play(tune, { instrument: 'vibes', room: 0.5 });
        }}
      />
      <Toolbar.Widget><Levels /></Toolbar.Widget>
      <Toolbar.Right>
        <Toolbar.Status dot={playing ? 'green' : 'yellow'} text={status[sound.state]} />
      </Toolbar.Right>
    </Toolbar>
  );
}

function Levels() {
  const levels = useLevels(7, { smoothing: 0.7 });
  const meter = levels.map(level => '▁▂▃▄▅▆▇█'[Math.round(level * 7)]).join('');
  return <Readout label="Level" value={meter} />;
}

Bundleware.defineToolbar(<HoldMusic />);

What it renders

Hold Music
Level▃▅▇█▆▄▂ Please continue to hold

Hold Music while it plays. The host draws the button as an index tab, the Readout as a label cell and a value cell, and the status at the right edge.

Availability

Applicable versionsAPI 2 and higher
Bundleware0.3.0, build 2026.10.30
PermissionNone. The sound is made with Web Audio in the toolbar’s world and never leaves the page.
StartsAfter a click. Browsers start sound only after a click, so call play from an action or a click handler.
EndsWith its mount: when the toolbar is hidden, the page reloads, or navigation leaves its site rules.