/**
* Synth Toy Demo - procedural chip-tune sound effects with no audio files at all.
* @description Procedural chip-tune sound effects with no audio files: six presets plus a key that rolls a new one.
*
* Prerequisites:
* Keyboard Input https://demos.blit386.dev/keyboard-input
* Audio Basics https://demos.blit386.dev/audio-basics
*
* Live version: https://demos.blit386.dev/synth-toy
*
* Every sound on this page is built from scratch by the computer, the instant you press a
* key or tap a button - there are no sound files to download. AudioClip.synth() takes a
* small recipe called SynthParams (a waveform shape, a pitch, a length, and a few optional
* knobs like an attack/decay/sustain/release envelope, a pitch sweep, and a noise mix) and
* calculates every single sample of the resulting sound on the spot. BT.synthPreset bundles
* six of these recipes for common game sounds - jump, pickup, explosion, laser, hit, and a
* UI blip - tuned by hand so they sound right without you needing to pick every number
* yourself.
*
* The Randomize button goes the other way: instead of a hand-tuned recipe, it rolls a brand
* new SynthParams object with every field chosen at random, inside safe ranges, and shows
* you exactly what was rolled in the right-hand panel. Play it a few times and you will
* hear (and see) how much variety a handful of numbers can produce - from a clean sine
* "boop" to a noisy sawtooth growl. (The synth engine also supports a vibrato wobble on top
* of all this - this demo leaves it out to keep the panel small, but it is worth trying
* yourself.)
*
* The panels and buttons come from the shared UI kit in src/shared/ui.js, so every preset
* works three ways: click it, tap it on a phone, or press its keyboard shortcut.
*
* This page unlocks sound the same "click or press a key first" way audio-basics does -
* browsers refuse to make any sound until you interact with the page at least once.
*
* The engine's built-in overlay also shows live audio meters: little bars that move
* up and down with how loud each audio bus (main, music, sfx) is right now, plus a
* count of how many sounds are playing at once. This demo turns that feature on with
* `isOverlayAudioMetersEnabled: true` in configure() - watch the meters jump every time a
* preset or a randomized sound plays.
*
* Try this:
* - Click anywhere, or press any key, to unlock sound - watch the message at the top change
* once you do.
* - Tap the preset buttons (or press J, P, E, L, H, or B) to hear the jump, pickup,
* explosion, laser, hit, and blip presets.
* - Tap Randomize (or press R) to hear a randomized sound - watch the right-hand panel
* update with the waveform, frequency, duration, noise mix, and pitch sweep target that
* were rolled.
* - Randomize again and again - notice how differently the exact same few lines of "roll a
* random number" code can make the engine sound each time.
*/
import { class AudioClipDecoded audio asset with its winning source URL and buffer-derived metadata.
Construct instances with
{@link
AudioClip.load
}
,
{@link
AudioClip.loadAll
}
, or
{@link
AudioClip.synth
}
; there is no public constructor.AudioClip, function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT } from 'blit386';
import { import applyThemeapplyTheme, import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').Palette} Palette */
/** @typedef {import('blit386').SynthParams} SynthParams */
/** @typedef {import('blit386').HardwareSettings} HardwareSettings */
// The waveform shapes AudioClip.synth() accepts. The engine does not export this list as a
// runtime value (only as a TypeScript type), so we spell it out here ourselves - the
// randomizer picks one of these five names at random for every roll.
const const SYNTH_WAVEFORMS: {}SYNTH_WAVEFORMS = ['sine', 'square', 'triangle', 'sawtooth', 'noise'];
// One entry per preset. Each `factory` asks BT.synthPreset for a fresh, hand-tuned recipe
// (a SynthParams object) for that sound. `label` is the button text (which doubles as the
// keyboard hint), and `code` is the bound key.
const const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS = [
{ code: stringcode: 'KeyJ', label: stringlabel: 'J - Jump', name: stringname: 'Jump', factory: () => SynthParamsfactory: () => const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.synthPreset: {
jump: (seed?: number) => SynthParams;
pickup: (seed?: number) => SynthParams;
explosion: (seed?: number) => SynthParams;
laser: (seed?: number) => SynthParams;
hit: (seed?: number) => SynthParams;
blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup",
"explosion", "laser", "hit", "blip").
Each function returns a fresh
{@link
SynthParams
}
object; pass it to
{@link
AudioClip.synth
}
to render a clip, then play the result via
{@link
BT.soundPlay
}
.
An optional `seed` argument applies small, bounded, deterministic jitter to a few
hand-picked fields per preset, so repeated plays vary without losing reproducibility -
the same seed always renders the exact same variant.synthPreset.jump: (seed?: number) => SynthParamsPlatformer jump: a short square-wave tone that sweeps upward in pitch, like a classic
arcade jump cue.
`seed` jitters the base frequency (+/-8%) and duration (+/-12%), so jumping repeatedly
doesn't sound identical every time. Omit `seed` (or pass
{@link
DEFAULT_PRESET_SEED
}
) for a
fixed baseline variant.jump() },
{ code: stringcode: 'KeyP', label: stringlabel: 'P - Pickup', name: stringname: 'Pickup', factory: () => SynthParamsfactory: () => const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.synthPreset: {
jump: (seed?: number) => SynthParams;
pickup: (seed?: number) => SynthParams;
explosion: (seed?: number) => SynthParams;
laser: (seed?: number) => SynthParams;
hit: (seed?: number) => SynthParams;
blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup",
"explosion", "laser", "hit", "blip").
Each function returns a fresh
{@link
SynthParams
}
object; pass it to
{@link
AudioClip.synth
}
to render a clip, then play the result via
{@link
BT.soundPlay
}
.
An optional `seed` argument applies small, bounded, deterministic jitter to a few
hand-picked fields per preset, so repeated plays vary without losing reproducibility -
the same seed always renders the exact same variant.synthPreset.pickup: (seed?: number) => SynthParamsItem pickup / coin: a short, bright square-wave blip that sweeps upward an octave.
`seed` jitters the base frequency (+/-8%) and duration (+/-12%). Omit `seed` for a fixed
baseline variant.pickup() },
{ code: stringcode: 'KeyE', label: stringlabel: 'E - Explosion', name: stringname: 'Explosion', factory: () => SynthParamsfactory: () => const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.synthPreset: {
jump: (seed?: number) => SynthParams;
pickup: (seed?: number) => SynthParams;
explosion: (seed?: number) => SynthParams;
laser: (seed?: number) => SynthParams;
hit: (seed?: number) => SynthParams;
blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup",
"explosion", "laser", "hit", "blip").
Each function returns a fresh
{@link
SynthParams
}
object; pass it to
{@link
AudioClip.synth
}
to render a clip, then play the result via
{@link
BT.soundPlay
}
.
An optional `seed` argument applies small, bounded, deterministic jitter to a few
hand-picked fields per preset, so repeated plays vary without losing reproducibility -
the same seed always renders the exact same variant.synthPreset.explosion: (seed?: number) => SynthParamsExplosion: a low sawtooth rumble mixed heavily with noise, with a slow decay and release for
a boom that lingers.
`seed` jitters the base frequency (+/-8%), duration (+/-12%), and `noiseMix` (+/-10%,
clamped to `[0, 1]`) - the mix jitter alone gives every explosion a distinct noisy texture.
Omit `seed` for a fixed baseline variant.explosion() },
{ code: stringcode: 'KeyL', label: stringlabel: 'L - Laser', name: stringname: 'Laser', factory: () => SynthParamsfactory: () => const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.synthPreset: {
jump: (seed?: number) => SynthParams;
pickup: (seed?: number) => SynthParams;
explosion: (seed?: number) => SynthParams;
laser: (seed?: number) => SynthParams;
hit: (seed?: number) => SynthParams;
blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup",
"explosion", "laser", "hit", "blip").
Each function returns a fresh
{@link
SynthParams
}
object; pass it to
{@link
AudioClip.synth
}
to render a clip, then play the result via
{@link
BT.soundPlay
}
.
An optional `seed` argument applies small, bounded, deterministic jitter to a few
hand-picked fields per preset, so repeated plays vary without losing reproducibility -
the same seed always renders the exact same variant.synthPreset.laser: (seed?: number) => SynthParamsLaser / sci-fi zap: a bright sawtooth tone that sweeps rapidly downward in pitch.
`seed` jitters the base frequency (+/-8%) and duration (+/-12%). Omit `seed` for a fixed
baseline variant.laser() },
{ code: stringcode: 'KeyH', label: stringlabel: 'H - Hit', name: stringname: 'Hit', factory: () => SynthParamsfactory: () => const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.synthPreset: {
jump: (seed?: number) => SynthParams;
pickup: (seed?: number) => SynthParams;
explosion: (seed?: number) => SynthParams;
laser: (seed?: number) => SynthParams;
hit: (seed?: number) => SynthParams;
blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup",
"explosion", "laser", "hit", "blip").
Each function returns a fresh
{@link
SynthParams
}
object; pass it to
{@link
AudioClip.synth
}
to render a clip, then play the result via
{@link
BT.soundPlay
}
.
An optional `seed` argument applies small, bounded, deterministic jitter to a few
hand-picked fields per preset, so repeated plays vary without losing reproducibility -
the same seed always renders the exact same variant.synthPreset.hit: (seed?: number) => SynthParamsHit / damage taken: a short, low percussive tone mixed with noise for a punchy impact.
`seed` jitters the base frequency (+/-8%) and `noiseMix` (+/-10%, clamped to `[0, 1]`). Omit
`seed` for a fixed baseline variant.hit() },
{ code: stringcode: 'KeyB', label: stringlabel: 'B - Blip', name: stringname: 'Blip', factory: () => SynthParamsfactory: () => const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.synthPreset: {
jump: (seed?: number) => SynthParams;
pickup: (seed?: number) => SynthParams;
explosion: (seed?: number) => SynthParams;
laser: (seed?: number) => SynthParams;
hit: (seed?: number) => SynthParams;
blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup",
"explosion", "laser", "hit", "blip").
Each function returns a fresh
{@link
SynthParams
}
object; pass it to
{@link
AudioClip.synth
}
to render a clip, then play the result via
{@link
BT.soundPlay
}
.
An optional `seed` argument applies small, bounded, deterministic jitter to a few
hand-picked fields per preset, so repeated plays vary without losing reproducibility -
the same seed always renders the exact same variant.synthPreset.blip: (seed?: number) => SynthParamsUI blip / menu select: a very short, clean sine tone.
`seed` jitters only the base frequency, and only slightly (+/-3%) - UI feedback should stay
recognizably consistent rather than vary as much as a gameplay sound effect. Omit `seed` for
a fixed baseline variant.blip() },
];
// All preset buttons share one width so the left panel reads as a tidy keypad.
const const PRESET_BUTTON_W: 96PRESET_BUTTON_W = 96;
// Safe ranges for the randomizer. Every one of these stays inside the bounds
// AudioClip.synth() actually accepts (see blit386's synthValidation.ts): frequencies and
// durations above zero, everything described as a fraction kept between 0 and 1.
const const RANDOM_FREQUENCY_MIN_HZ: 80RANDOM_FREQUENCY_MIN_HZ = 80;
const const RANDOM_FREQUENCY_MAX_HZ: 1200RANDOM_FREQUENCY_MAX_HZ = 1200;
const const RANDOM_DURATION_MIN_S: 0.05RANDOM_DURATION_MIN_S = 0.05;
const const RANDOM_DURATION_MAX_S: 1RANDOM_DURATION_MAX_S = 1.0;
const const RANDOM_VOLUME_MIN: 0.8RANDOM_VOLUME_MIN = 0.8;
const const RANDOM_VOLUME_MAX: 1RANDOM_VOLUME_MAX = 1.0;
const const RANDOM_ATTACK_MAX_S: 0.1RANDOM_ATTACK_MAX_S = 0.1;
const const RANDOM_DECAY_MAX_S: 0.3RANDOM_DECAY_MAX_S = 0.3;
const const RANDOM_RELEASE_MIN_S: 0.02RANDOM_RELEASE_MIN_S = 0.02;
const const RANDOM_RELEASE_MAX_S: 0.4RANDOM_RELEASE_MAX_S = 0.4;
// A pitch sweep (the sound's pitch gliding from its starting frequency to a new one) is only
// added to the random recipe about half the time, so you get to compare "sweeps" against
// "flat pitch" sounds.
const const RANDOM_PITCH_SWEEP_CHANCE: 0.5RANDOM_PITCH_SWEEP_CHANCE = 0.5;
const const RANDOM_PITCH_SWEEP_MIN_MULTIPLIER: 0.3RANDOM_PITCH_SWEEP_MIN_MULTIPLIER = 0.3;
const const RANDOM_PITCH_SWEEP_MAX_MULTIPLIER: 3RANDOM_PITCH_SWEEP_MAX_MULTIPLIER = 3.0;
// Upper bound for the random seed handed to AudioClip.synth(). This seed only feeds the
// engine's internal noise generator - it is not something we need to remember or reproduce
// here, so any number in this range works.
const const RANDOM_SEED_MAX: 1000000RANDOM_SEED_MAX = 1_000_000;
/**
* Builds a brand new SynthParams recipe with every field chosen at random, inside the safe
* ranges above. Unlike the six named presets (which only nudge a couple of fields), this
* touches every knob AudioClip.synth() understands, so pressing Randomize is a tour of the
* whole parameter space rather than a small variation on one sound.
*
* @returns {SynthParams}
*/
function function buildRandomSynthParams(): SynthParamsBuilds a brand new SynthParams recipe with every field chosen at random, inside the safe
ranges above. Unlike the six named presets (which only nudge a couple of fields), this
touches every knob AudioClip.synth() understands, so pressing Randomize is a tour of the
whole parameter space rather than a small variation on one sound.buildRandomSynthParams() {
// BT.random is the engine's shared random number generator. pick() draws one item out of a list, float()
// returns a decimal between two values, bool() flips a weighted coin, and next() gives a plain decimal from
// 0 up to (but not including) 1 - which is exactly the range these normalized knobs want.
const const waveform: anywaveform = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.pick<any>(arr: readonly any[]): anyReturns one element chosen uniformly from a non-empty array.pick(const SYNTH_WAVEFORMS: {}SYNTH_WAVEFORMS);
const const frequency: numberfrequency = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const RANDOM_FREQUENCY_MIN_HZ: 80RANDOM_FREQUENCY_MIN_HZ, const RANDOM_FREQUENCY_MAX_HZ: 1200RANDOM_FREQUENCY_MAX_HZ);
const const duration: numberduration = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const RANDOM_DURATION_MIN_S: 0.05RANDOM_DURATION_MIN_S, const RANDOM_DURATION_MAX_S: 1RANDOM_DURATION_MAX_S);
const const hasPitchSweep: booleanhasPitchSweep = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.bool(probability?: number): booleanReturns true with the given probability.bool(const RANDOM_PITCH_SWEEP_CHANCE: 0.5RANDOM_PITCH_SWEEP_CHANCE);
return {
SynthParams.waveform: anyOscillator waveform shape.waveform,
SynthParams.frequency: numberBase carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).frequency,
SynthParams.duration: numberTotal clip duration in seconds. Must be greater than 0 and no more than
{@link
MAX_SYNTH_DURATION_SECONDS
}
.duration,
SynthParams.volume?: number | undefinedOverall output amplitude in [0, 1] (unclamped on the high end; final output is always
clamped to avoid clipping).
Defaults to
{@link
DEFAULT_VOLUME
}
.volume: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const RANDOM_VOLUME_MIN: 0.8RANDOM_VOLUME_MIN, const RANDOM_VOLUME_MAX: 1RANDOM_VOLUME_MAX),
SynthParams.envelope?: SynthEnvelope | undefinedOptional attack/decay/sustain/release envelope. Defaults to a full ADSR envelope; see
{@link
SynthEnvelope
}
.envelope: {
SynthEnvelope.attack?: number | undefinedTime in seconds to ramp from silence to full amplitude.
Defaults to
{@link
DEFAULT_ATTACK
}
.attack: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(0, const RANDOM_ATTACK_MAX_S: 0.1RANDOM_ATTACK_MAX_S),
SynthEnvelope.decay?: number | undefinedTime in seconds to fall from full amplitude to the `sustain` level.
Defaults to
{@link
DEFAULT_DECAY
}
.decay: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(0, const RANDOM_DECAY_MAX_S: 0.3RANDOM_DECAY_MAX_S),
SynthEnvelope.sustain?: number | undefinedGain level in [0, 1] held between the decay and release phases.
Defaults to
{@link
DEFAULT_SUSTAIN
}
.sustain: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.next(): numberReturns the next pseudo-random float in [0, 1).next(),
SynthEnvelope.release?: number | undefinedTime in seconds to fall from the sustain level to silence at the end of the clip.
Defaults to
{@link
DEFAULT_RELEASE
}
.release: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const RANDOM_RELEASE_MIN_S: 0.02RANDOM_RELEASE_MIN_S, const RANDOM_RELEASE_MAX_S: 0.4RANDOM_RELEASE_MAX_S),
},
SynthParams.noiseMix?: number | undefinedFraction of white noise mixed into the oscillator output, in [0, 1] (`0` is pure tone,
`1` is pure noise). Ignored when `waveform` is already `'noise'`.
Defaults to
{@link
DEFAULT_NOISE_MIX
}
.noiseMix: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.next(): numberReturns the next pseudo-random float in [0, 1).next(),
SynthParams.dutyCycle?: number | undefinedFraction of each cycle spent high, in [0, 1]. Only affects the `'square'` waveform.
Defaults to
{@link
DEFAULT_DUTY_CYCLE
}
.dutyCycle: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.next(): numberReturns the next pseudo-random float in [0, 1).next(),
SynthParams.seed: numberSeed for the deterministic PRNG driving noise generation - identical seeds render identical output.seed: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.int(minOrMaxExclusive: number, maxExclusive?: number): numberReturns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).int(const RANDOM_SEED_MAX: 1000000RANDOM_SEED_MAX),
// Only glide the pitch about half the time, and only when we do, add the field at
// all - AudioClip.synth() treats a missing pitchSweep as "stay at one pitch."
...(const hasPitchSweep: booleanhasPitchSweep
? {
SynthParams.pitchSweep?: SynthPitchSweep | undefinedOptional linear pitch sweep from `frequency` to a target frequency across the clip.pitchSweep: {
SynthPitchSweep.toFrequency: numberFrequency in Hz the carrier linearly reaches by the end of the clip.toFrequency:
const frequency: numberfrequency *
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const RANDOM_PITCH_SWEEP_MIN_MULTIPLIER: 0.3RANDOM_PITCH_SWEEP_MIN_MULTIPLIER, const RANDOM_PITCH_SWEEP_MAX_MULTIPLIER: 3RANDOM_PITCH_SWEEP_MAX_MULTIPLIER),
},
}
: {}),
};
}
/**
* Preset button playback plus a "roll the dice" randomizer, both built on AudioClip.synth().
*
* @implements {IBTDemo}
*/
class class DemoPreset button playback plus a "roll the dice" randomizer, both built on AudioClip.synth().Demo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
/** Palette slots of the shared UI theme colors, filled by applyTheme() in init(). */
Demo.theme: nullPalette slots of the shared UI theme colors, filled by applyTheme() in init().theme = null;
/** @type {AudioClip[]} Pre-rendered clips, one per PRESET_DEFINITIONS entry. */
Demo.presetClips: {}presetClips = [];
/** @type {number | null} Index into PRESET_DEFINITIONS of the last preset played. */
Demo.lastPresetIndex: number | nulllastPresetIndex = null;
/** @type {SynthParams | null} The most recently rolled random recipe, or null before the first roll. */
Demo.lastRandomParams: SynthParams | nulllastRandomParams = null;
/**
* Turns on the engine's built-in audio meters, shows the overlay from the very first
* frame, and styles the overlay so its gaps match the demo's background color.
*
* @returns {Partial<HardwareSettings>}
*/
Demo.configure(): Partial<HardwareSettings>Turns on the engine's built-in audio meters, shows the overlay from the very first
frame, and styles the overlay so its gaps match the demo's background color.configure() {
return {
// Live per-bus level meters and a voice-count readout in the overlay (explained in the header above).
isOverlayAudioMetersEnabled: booleanisOverlayAudioMetersEnabled: true,
// Shows the overlay body (title, FPS, backend, and the audio meters above) right
// from the first frame instead of waiting for a Backquote press or a click in the
// toggle-hint corner. This demo is all about sound, so the meters should be
// visible immediately.
isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true,
// gapPaletteIndex fills both the thin seams between overlay rows and the empty
// (unfilled) track behind each audio meter bar, so it must match the screen
// background or those seams would show up as a mismatched color. The shared UI
// theme puts its background color at THEME_DEFAULT_START_SLOT (applyTheme()'s
// default start slot - see init() below); configure() runs before init(), so we
// reference the constant directly instead of calling applyTheme() early.
overlayStyle: {
gapPaletteIndex: any;
}
overlayStyle: {
gapPaletteIndex: anygapPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT,
},
};
}
/**
* Renders every preset's SynthParams recipe into a ready-to-play clip, so pressing a
* preset later has zero delay, and installs the shared UI theme.
*
* @returns {Promise<boolean>}
*/
async Demo.init(): Promise<boolean>Renders every preset's SynthParams recipe into a ready-to-play clip, so pressing a
preset later has zero delay, and installs the shared UI theme.init() {
// AudioClip.synth() is async - it has to calculate every sample before it can hand
// back a clip. Promise.all() starts all six calculations at once and waits for them
// all to finish, the same way audio-basics preloads its sound files up front.
this.Demo.presetClips: {}presetClips = await Promise.all(const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS.map((def: anydef) => class AudioClipDecoded audio asset with its winning source URL and buffer-derived metadata.
Construct instances with
{@link
AudioClip.load
}
,
{@link
AudioClip.loadAll
}
, or
{@link
AudioClip.synth
}
; there is no public constructor.AudioClip.AudioClip.synth(params: SynthParams): Promise<AudioClip>Synthesizes a clip from deterministic procedural parameters - no source file, no
`OfflineAudioContext`, and no audio graph involved.
Rendering happens entirely on the CPU via the pure
{@link
renderSynthSamples
}
function
against an `AudioBuffer` allocated from the registered decode context. The returned clip
flows through the same
{@link
buffer
}
getter and playback path as a loaded clip, but uses
a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the
URL-keyed resolved cache and never deduplicated, so identical `params` still render a
fresh, independent `AudioBuffer` on every call. See
{@link
SynthParams
}
for the full
parameter set.synth(def: anydef.factory())));
this.Demo.palette: Palette | nullpalette = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// applyTheme() installs the twelve shared UI colors and reports their slots, so
// render() can clear the screen with the theme's background color.
this.Demo.theme: nullPalette slots of the shared UI theme colors, filled by applyTheme() in init().theme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
return true;
}
/**
* Runs the UI kit's once-per-tick housekeeping.
*
* The preset keys (J/P/E/L/H/B and R) are bound to the buttons declared in render()
* via their { key } option. ui.tick() is where the kit safely catches those presses -
* keyboard "was it just pressed?" flags can only be read reliably here in update(),
* never in render() (keyboard-input explains why in detail).
*/
Demo.update(): voidRuns the UI kit's once-per-tick housekeeping.
The preset keys (J/P/E/L/H/B and R) are bound to the buttons declared in render()
via their { key } option. ui.tick() is where the kit safely catches those presses -
keyboard "was it just pressed?" flags can only be read reliably here in update(),
never in render() (keyboard-input explains why in detail).update() {
import uiui.tick();
}
/**
* Clears the screen and declares the whole UI: title bar, unlock message, the preset
* keypad on the left, and the randomizer readout on the right.
*/
Demo.render(): voidClears the screen and declares the whole UI: title bar, unlock message, the preset
keypad on the left, and the randomizer readout on the right.render() {
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: nullPalette slots of the shared UI theme colors, filled by applyTheme() in init().theme.bg);
// The full-width title strip along the top edge.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR);
import uiui.panel('Synth Toy - six presets and a randomizer');
import uiui.end();
this.Demo.renderUnlockPrompt(): voidShows the shared unlock reminder until audio is unlocked, then switches to a plain
reminder of the controls. A borderless group - just one line of text pinned under
the title strip.renderUnlockPrompt();
this.Demo.renderPresetPanel(): voidLeft-hand panel: one button per preset, plus the last one played. Each button fires
on click, tap, or its keyboard shortcut - ui.button() treats all three the same.renderPresetPanel();
this.Demo.renderRandomPanel(): voidRight-hand panel: the Randomize button and the most recently rolled recipe, one
field per row.renderRandomPanel();
}
/**
* Shows the shared unlock reminder until audio is unlocked, then switches to a plain
* reminder of the controls. A borderless group - just one line of text pinned under
* the title strip.
*/
Demo.renderUnlockPrompt(): voidShows the shared unlock reminder until audio is unlocked, then switches to a plain
reminder of the controls. A borderless group - just one line of text pinned under
the title strip.renderUnlockPrompt() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { y: numbery: 28 });
// The shared "click to enable sound" row - it draws itself only while sound is
// still locked, and disappears on its own after the first click or key press.
import uiui.audioUnlockHint();
// Once sound works, swap in a short reminder of the controls instead.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAudioUnlocked: booleanWhether the audio context has been unlocked by a user gesture.
Browsers require a user gesture (pointer, key, or touch press) before
allowing audio playback. Starts `false`; flips to `true` for the rest of
the session after the first gesture successfully resumes the audio context.isAudioUnlocked) {
import uiui.label('Tap a button, or press its key', { color: stringcolor: 'dim' });
}
import uiui.end();
}
/**
* Left-hand panel: one button per preset, plus the last one played. Each button fires
* on click, tap, or its keyboard shortcut - ui.button() treats all three the same.
*/
Demo.renderPresetPanel(): voidLeft-hand panel: one button per preset, plus the last one played. Each button fires
on click, tap, or its keyboard shortcut - ui.button() treats all three the same.renderPresetPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_LEFT);
import uiui.panel('Presets');
for (let let i: numberi = 0; let i: numberi < const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS.length; let i: numberi++) {
const const def: anydef = const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS[let i: numberi];
if (import uiui.button(const def: anydef.label, { key: anykey: const def: anydef.code, width: numberwidth: const PRESET_BUTTON_W: 96PRESET_BUTTON_W })) {
// Play the pre-rendered clip and remember it for the "Last" row below.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRefPlays a loaded audio clip through the SFX voice pool.
Returns an inert
{@link
SoundRef
}
without allocating a voice when the clip hasn't finished
loading yet (or was already unloaded), when the pool has no free or stealable voice at this
priority, or before the engine has unlocked audio playback.soundPlay(this.Demo.presetClips: {}presetClips[let i: numberi]);
this.Demo.lastPresetIndex: number | nulllastPresetIndex = let i: numberi;
}
}
const const lastLabel: anylastLabel = this.Demo.lastPresetIndex: number | nulllastPresetIndex === null ? '-' : const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS[this.Demo.lastPresetIndex: numberlastPresetIndex].name;
import uiui.kv('Last', const lastLabel: anylastLabel);
import uiui.end();
}
/**
* Right-hand panel: the Randomize button and the most recently rolled recipe, one
* field per row.
*/
Demo.renderRandomPanel(): voidRight-hand panel: the Randomize button and the most recently rolled recipe, one
field per row.renderRandomPanel() {
const const params: SynthParams | nullparams = this.Demo.lastRandomParams: SynthParams | nulllastRandomParams;
import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_RIGHT);
import uiui.panel('Randomizer');
if (import uiui.button('R - Randomize', { key: stringkey: 'KeyR' })) {
this.Demo.triggerRandomize(): voidRolls a new random SynthParams recipe and starts rendering + playing it.
This method itself stays synchronous: it builds the recipe and updates what the panel
will show right away, so the numbers on screen always match the most recent press
even though the actual sound takes a moment longer to render (see playRandomParams()).triggerRandomize();
}
// Each row reports one part of the rolled recipe ('-' before the first roll).
import uiui.kv('Waveform', const params: SynthParams | nullparams === null ? '-' : const params: SynthParamsparams.SynthParams.waveform: anyOscillator waveform shape.waveform);
import uiui.kv('Frequency', const params: SynthParams | nullparams === null ? '-' : `${Math.round(const params: SynthParamsparams.SynthParams.frequency: numberBase carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).frequency)} Hz`);
import uiui.kv('Duration', const params: SynthParams | nullparams === null ? '-' : `${const params: SynthParamsparams.SynthParams.duration: numberTotal clip duration in seconds. Must be greater than 0 and no more than
{@link
MAX_SYNTH_DURATION_SECONDS
}
.duration.toFixed(2)} s`);
// Noise mix: how much white noise is mixed into the tone (0 is a pure, clean tone;
// 1 is pure hiss). The meter bar fills up left-to-right with the amount.
import uiui.kv('Noise mix', const params: SynthParams | nullparams === null ? '-' : const params: SynthParamsparams.SynthParams.noiseMix?: number | undefinedFraction of white noise mixed into the oscillator output, in [0, 1] (`0` is pure tone,
`1` is pure noise). Ignored when `waveform` is already `'noise'`.
Defaults to
{@link
DEFAULT_NOISE_MIX
}
.noiseMix.toFixed(2));
import uiui.meter(null, const params: SynthParams | nullparams === null ? 0 : const params: SynthParamsparams.SynthParams.noiseMix?: number | undefinedFraction of white noise mixed into the oscillator output, in [0, 1] (`0` is pure tone,
`1` is pure noise). Ignored when `waveform` is already `'noise'`.
Defaults to
{@link
DEFAULT_NOISE_MIX
}
.noiseMix);
const const sweepLabel: stringsweepLabel =
const params: SynthParams | nullparams === null ? '-' : const params: SynthParamsparams.SynthParams.pitchSweep?: SynthPitchSweep | undefinedOptional linear pitch sweep from `frequency` to a target frequency across the clip.pitchSweep ? `${Math.round(const params: SynthParamsparams.SynthParams.pitchSweep?: SynthPitchSweepOptional linear pitch sweep from `frequency` to a target frequency across the clip.pitchSweep.SynthPitchSweep.toFrequency: numberFrequency in Hz the carrier linearly reaches by the end of the clip.toFrequency)} Hz` : 'none';
import uiui.kv('Sweep', const sweepLabel: stringsweepLabel);
// The duty cycle only shapes square waves, so other waveforms show 'n/a'.
const const dutyLabel: anydutyLabel = const params: SynthParams | nullparams === null ? '-' : const params: SynthParamsparams.SynthParams.waveform: anyOscillator waveform shape.waveform === 'square' ? const params: SynthParamsparams.SynthParams.dutyCycle?: number | undefinedFraction of each cycle spent high, in [0, 1]. Only affects the `'square'` waveform.
Defaults to
{@link
DEFAULT_DUTY_CYCLE
}
.dutyCycle.toFixed(2) : 'n/a';
import uiui.kv('Duty', const dutyLabel: anydutyLabel);
import uiui.end();
}
/**
* Rolls a new random SynthParams recipe and starts rendering + playing it.
*
* This method itself stays synchronous: it builds the recipe and updates what the panel
* will show right away, so the numbers on screen always match the most recent press
* even though the actual sound takes a moment longer to render (see playRandomParams()).
*/
Demo.triggerRandomize(): voidRolls a new random SynthParams recipe and starts rendering + playing it.
This method itself stays synchronous: it builds the recipe and updates what the panel
will show right away, so the numbers on screen always match the most recent press
even though the actual sound takes a moment longer to render (see playRandomParams()).triggerRandomize() {
const const params: SynthParamsparams = function buildRandomSynthParams(): SynthParamsBuilds a brand new SynthParams recipe with every field chosen at random, inside the safe
ranges above. Unlike the six named presets (which only nudge a couple of fields), this
touches every knob AudioClip.synth() understands, so pressing Randomize is a tour of the
whole parameter space rather than a small variation on one sound.buildRandomSynthParams();
this.Demo.lastRandomParams: SynthParams | nulllastRandomParams = const params: SynthParamsparams;
// The kit calls render() synchronously, and this handler cannot be async, so we
// start the render-and-play and let it run in the background instead of waiting for
// it. The .catch() just logs a problem instead of crashing the page, as a safety
// net - it should never actually trigger with the ranges above.
this.Demo.playRandomParams(params: SynthParams): Promise<void>Renders a SynthParams recipe into a clip and plays it.playRandomParams(const params: SynthParamsparams).catch((error: anyerror) => console.error(error: anyerror));
}
/**
* Renders a SynthParams recipe into a clip and plays it.
*
* @param {SynthParams} params - Recipe to render and play.
* @returns {Promise<void>}
*/
async Demo.playRandomParams(params: SynthParams): Promise<void>Renders a SynthParams recipe into a clip and plays it.playRandomParams(params: SynthParams- Recipe to render and play.params) {
const const clip: Promise<AudioClip>clip = await class AudioClipDecoded audio asset with its winning source URL and buffer-derived metadata.
Construct instances with
{@link
AudioClip.load
}
,
{@link
AudioClip.loadAll
}
, or
{@link
AudioClip.synth
}
; there is no public constructor.AudioClip.AudioClip.synth(params: SynthParams): Promise<AudioClip>Synthesizes a clip from deterministic procedural parameters - no source file, no
`OfflineAudioContext`, and no audio graph involved.
Rendering happens entirely on the CPU via the pure
{@link
renderSynthSamples
}
function
against an `AudioBuffer` allocated from the registered decode context. The returned clip
flows through the same
{@link
buffer
}
getter and playback path as a loaded clip, but uses
a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the
URL-keyed resolved cache and never deduplicated, so identical `params` still render a
fresh, independent `AudioBuffer` on every call. See
{@link
SynthParams
}
for the full
parameter set.synth(params: SynthParams- Recipe to render and play.params);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRefPlays a loaded audio clip through the SFX voice pool.
Returns an inert
{@link
SoundRef
}
without allocating a voice when the clip hasn't finished
loading yet (or was already unloaded), when the pool has no free or stealable voice at this
priority, or before the engine has unlocked audio playback.soundPlay(const clip: Promise<AudioClip>clip);
}
}
function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap(class DemoPreset button playback plus a "roll the dice" randomizer, both built on AudioClip.synth().Demo);