/*
Synth Toy: 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.
Part of the BLIT386 demo series.
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
Guides:
- Audio: https://blit386.dev/docs/guides/audio#design-a-sound
- Random: https://blit386.dev/docs/guides/random#deterministic-by-default
- Overlay: https://blit386.dev/docs/guides/overlay#colors-and-hud-palette-slots
Reference:
- Audio: https://blit386.dev/docs/api/audio#synth
- Random: https://blit386.dev/docs/api/random#generators
- Overlay: https://blit386.dev/docs/api/overlay#audio-meters-optional
---
WHAT YOU WILL SEE
-----------------
The engine overlay is open from the start: white bars along the top with the audio meter
band (three bus level bars - main, music, sfx - and a voice count), and a hint bar along
the bottom. Between them, one centered line of text reads "Click or press a key to enable
sound" in warm orange until sound is unlocked, then a dim "Tap a button, or press its
key."
Two panels of the same height sit side by side under that line. "Presets" on the left is
a keypad of six equal buttons in two columns - "J Jump", "P Pickup", "E Explosion",
"L Laser", "H Hit", and "B Blip" - with a "Last" row naming the preset played most
recently. "Randomizer" on the right has an "R - Randomize" button and a readout of the
last rolled recipe: Waveform, Frequency in Hz, Duration in seconds, Noise mix with a meter
bar, Sweep, and Duty.
WHAT YOU WILL LEARN
-------------------
- AudioClip.synth(params) calculates every sample of a sound from a SynthParams recipe:
a waveform, a frequency, a duration, and optional knobs (volume, an
attack/decay/sustain/release envelope, a pitchSweep, a noiseMix, a dutyCycle, a seed).
- BT.synthPreset.jump(), .pickup(), .explosion(), .laser(), .hit(), and .blip() return
hand-tuned recipes for common game sounds.
- Rendering the six presets once in init() with Promise.all(), so pressing a button later
plays instantly.
- BT.random.pick(), .float(), .bool(), .next(), and .int() build a brand new recipe with
every field chosen at random inside safe ranges.
- Kicking off an async render-and-play from a synchronous button handler.
- isOverlayVisibleAtStart: true and isOverlayAudioMetersEnabled: true show the overlay
and its audio meters from the first frame, and overlayStyle / overlayAudioMeterStyle
pick its colors from the palette.
- Laying buttons out in a grid: the UI kit stacks a group's rows, so each keypad button
gets its own tiny group pinned to an exact spot on top of the panel.
HOW A SYNTH RECIPE WORKS
------------------------
Every sound on this page is built from scratch by the computer the instant you ask for
it; there are no sound files to download. The recipe is a small plain object called
SynthParams. The waveform is the basic shape of the wave (sine is smooth and pure, square
is hollow, triangle sits in between, sawtooth is harsh, noise is hiss). The frequency is
the pitch in Hz and the duration the length in seconds. The envelope shapes the loudness
over time: attack is how fast it ramps up, decay how fast it falls to the sustain level,
and release how it fades out at the end. A pitchSweep glides the pitch toward another
frequency, noiseMix blends in white noise, and dutyCycle changes the character of a square
wave only.
The six presets are recipes tuned by hand so they sound right without you picking every
number yourself. The Randomize button goes the other way: it rolls a brand new recipe with
every field chosen at random and shows exactly what was rolled in the right-hand panel.
Play it a few times and you will hear how much variety a handful of numbers can produce -
from a clean sine "boop" to a noisy sawtooth growl. (The engine also supports a vibrato
wobble on top of all this; the demo leaves it out to keep the panel small.)
TRY THIS
--------
- Click anywhere, or press any key, to unlock sound - watch the line under the overlay
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. The "Last" row names the one you played.
- Tap Randomize (or press R) and watch the right-hand panel update with the waveform,
frequency, duration, noise mix, pitch sweep target, and duty cycle that were rolled. The
Duty row shows a number only for square waves and "n/a" otherwise; Sweep reads "none"
about half the time.
- Randomize again and again - notice how differently the same few lines of "roll a random
number" code can make the engine sound each time.
- Watch the overlay's audio meters (already open) jump every time a preset or a randomized
sound plays.
*/
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;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT } from 'blit386';
import { import applyThemeapplyTheme, import FONT_WFONT_W, import THEME_OVERLAY_AUDIO_METER_STYLETHEME_OVERLAY_AUDIO_METER_STYLE, import THEME_OVERLAY_STYLETHEME_OVERLAY_STYLE, import uiui } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
/** @typedef {import('blit386').SynthParams} SynthParams */
// The waveform shapes AudioClip.synth() accepts. The engine exports this list only as a
// TypeScript type, not as a runtime value, 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), `name` is what the "Last" row shows, 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;
... 124 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;
... 124 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;
... 124 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;
... 124 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;
... 124 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;
... 124 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() },
];
// Screen layout, in display pixels. The engine overlay is open from the start: its bars
// cover the top 56 rows and its hint bar the bottom 14, so everything sits in the band
// between them. The status line comes first, then two panels side by side, equally tall.
const const STATUS_Y: 64STATUS_Y = 64;
const const PANEL_Y: 76PANEL_Y = 76;
const const PRESET_PANEL_X: 4PRESET_PANEL_X = 4;
const const PRESET_PANEL_W: 174PRESET_PANEL_W = 174;
const const RANDOM_PANEL_X: 184RANDOM_PANEL_X = 184;
const const RANDOM_PANEL_W: 132RANDOM_PANEL_W = 132;
// Panel metrics from the UI kit: content starts 22 pixels below a panel's top edge
// (under its title) and 9 pixels in from its left edge, and is 20 pixels narrower than
// the panel.
const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22;
const const PANEL_CONTENT_X: 9PANEL_CONTENT_X = 9;
const const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK = 20;
// The presets form a keypad of two columns and three rows, all buttons the same width.
// The kit stacks a group's rows top to bottom, so the keypad reserves its space inside
// the panel with an empty row and then places each button with its own tiny group.
const const PRESET_COLUMNS: 2PRESET_COLUMNS = 2;
const const PRESET_GAP_X: 6PRESET_GAP_X = 6;
const const PRESET_BUTTON_W: numberPRESET_BUTTON_W = (const PRESET_PANEL_W: 174PRESET_PANEL_W - const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK - const PRESET_GAP_X: 6PRESET_GAP_X) / const PRESET_COLUMNS: 2PRESET_COLUMNS;
const const PRESET_ROW_H: 27PRESET_ROW_H = 27;
const const PRESET_GRID_X: numberPRESET_GRID_X = const PRESET_PANEL_X: 4PRESET_PANEL_X + const PANEL_CONTENT_X: 9PANEL_CONTENT_X;
// The keypad starts under the panel title and the one-line key hint.
const const PRESET_GRID_Y: numberPRESET_GRID_Y = const PANEL_Y: 76PANEL_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 14;
const const PRESET_GRID_H: numberPRESET_GRID_H = (const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS.length / const PRESET_COLUMNS: 2PRESET_COLUMNS) * const PRESET_ROW_H: 27PRESET_ROW_H;
// A group pinned with pad 0 starts its first row 5 pixels above the y it is given, so
// each button group is placed this much lower to land on its keypad row.
const const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT = 5;
// Breathing room: under the keypad before the "Last" row, under the Randomize button
// before the readout, and at the bottom of both panels. These make both panels end on
// the same line, with "Last" and "Duty" on the same row.
const const PRESET_PANEL_FILLER: 1PRESET_PANEL_FILLER = 1;
const const RANDOM_PANEL_FILLER: 6RANDOM_PANEL_FILLER = 6;
const const PANEL_TRAILING_SPACE: 5PANEL_TRAILING_SPACE = 5;
// One text row of the UI kit, and where the "Last" row sits: under the keypad.
const const ROW_H: 10ROW_H = 10;
const const LAST_ROW_Y: numberLAST_ROW_Y = const PRESET_GRID_Y: numberPRESET_GRID_Y + const PRESET_GRID_H: numberPRESET_GRID_H + const PRESET_PANEL_FILLER: 1PRESET_PANEL_FILLER;
// The value column of the randomizer readout starts after this many characters, so the
// longest key, "Frequency", keeps a space after it.
const const RANDOM_KV_COLS: 10RANDOM_KV_COLS = 10;
// Safe ranges for the randomizer. Every one of these stays inside the bounds
// AudioClip.synth() actually accepts (see the engine's synthValidation.ts): frequencies
// and durations above zero, envelope times at or above zero, and 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 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 "sweep" sounds
// against "flat pitch" sounds. The target is the start frequency times a number in this
// range.
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. The six presets are hand-tuned recipes where a seed only nudges a
// couple of fields; this touches every knob AudioClip.synth() understands except vibrato,
// so pressing Randomize is a tour of the whole parameter space rather than a small
// variation on one sound.
function function buildRandomSynthParams(): {
pitchSweep?: {
toFrequency: number;
} | undefined;
waveform: any;
frequency: number;
duration: number;
volume: number;
envelope: {
attack: number;
decay: number;
sustain: number;
release: number;
};
noiseMix: number;
dutyCycle: number;
seed: number;
}
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, next() gives a decimal from 0 up to (but not including) 1 - exactly the range
// the fraction knobs want - and int() gives a whole number from 0 up to a limit.
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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 {
waveform: anywaveform,
frequency: numberfrequency,
duration: numberduration,
volume: numbervolume: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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),
envelope: {
attack: number;
decay: number;
sustain: number;
release: number;
}
envelope: {
attack: numberattack: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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),
decay: numberdecay: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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),
sustain: numbersustain: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(),
release: numberrelease: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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),
},
noiseMix: numbernoiseMix: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(),
dutyCycle: numberdutyCycle: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(),
seed: numberseed: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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 then add the field at all -
// AudioClip.synth() treats a missing pitchSweep as "stay at one pitch". The `...`
// spreads either a one-field object or an empty one into the recipe.
...(const hasPitchSweep: booleanhasPitchSweep
? {
pitchSweep: {
toFrequency: number;
}
pitchSweep: {
toFrequency: numbertoFrequency:
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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().
class class DemoDemo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Palette slots of the shared UI theme colors, filled by applyTheme() in init().
Demo.theme: nulltheme = null;
// Pre-rendered clips, one per PRESET_DEFINITIONS entry, in the same order.
/** @type {AudioClip[]} */
Demo.presetClips: {}presetClips = [];
// Index into PRESET_DEFINITIONS of the last preset played, or null before the first.
/** @type {number | null} */
Demo.lastPresetIndex: number | nulllastPresetIndex = null;
// The most recently rolled random recipe, or null before the first roll.
/** @type {SynthParams | null} */
Demo.lastRandomParams: SynthParams | nulllastRandomParams = null;
// Turns on the engine's built-in audio meters, shows the overlay from the very first
// frame, and colors it from the shared UI theme.
Demo.configure(): {
isOverlayAudioMetersEnabled: boolean;
isOverlayVisibleAtStart: boolean;
overlayStyle: any;
overlayAudioMeterStyle: any;
}
configure() {
return {
// Live per-bus level bars and a voices/steal/drop readout in the overlay. Off
// by default.
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,
// The overlay's default colors live in palette slots 1 and 2, which this demo
// never fills - they would draw solid black. These two shared styles point
// the bars, text, and meters at the theme's colors instead, with the screen
// background in the gaps between bands and behind each meter bar.
overlayStyle: anyoverlayStyle: import THEME_OVERLAY_STYLETHEME_OVERLAY_STYLE,
overlayAudioMeterStyle: anyoverlayAudioMeterStyle: import THEME_OVERLAY_AUDIO_METER_STYLETHEME_OVERLAY_AUDIO_METER_STYLE,
};
}
// 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.
async Demo.init(): unknowninit() {
// 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 music preloads its tracks 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;
... 124 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: nulltheme = 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;
... 124 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(): voidupdate() {
import uiui.tick();
}
// Clears the screen and declares the whole UI: the status line, the preset keypad on
// the left, and the randomizer readout on the right.
Demo.render(): voidrender() {
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;
... 124 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: nulltheme.bg);
this.Demo.renderStatusLine(): voidrenderStatusLine();
this.Demo.renderPresetPanel(): voidrenderPresetPanel();
this.Demo.renderRandomPanel(): voidrenderRandomPanel();
}
// One centered line just under the overlay bars: the shared unlock reminder in warm
// orange until sound is unlocked, then a dim reminder of the controls.
Demo.renderStatusLine(): voidrenderStatusLine() {
const const isLocked: booleanisLocked = !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;
... 124 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;
const const text: "Click or press a key to enable sound" | "Tap a button, or press its key."text = const isLocked: booleanisLocked ? 'Click or press a key to enable sound' : 'Tap a button, or press its key.';
// FONT_W is the width of one character; the last one's empty spacing column is
// left out, so the visible letters are what gets centered.
const const x: numberx = Math.floor(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;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.displaySize: Vector2iActive logical render resolution in pixels.
This is the game/simulation coordinate space configured by the demo, not
the canvas element's CSS size. Each read returns a clone.displaySize.Vector2i.x: numberHorizontal component (defaults to 0).x / 2) - Math.floor((const text: "Click or press a key to enable sound" | "Tap a button, or press its key."text.length * import FONT_WFONT_W - 1) / 2);
import uiui.caption(const x: numberx, const STATUS_Y: 64STATUS_Y, const text: "Click or press a key to enable sound" | "Tap a button, or press its key."text, { color: stringcolor: const isLocked: booleanisLocked ? 'warm' : 'dim' });
}
// Left-hand panel: a keypad of six preset buttons, plus the last one played. Each
// button fires on click, tap, or its keyboard shortcut - ui.button() treats all three
// the same.
//
// A group is drawn the moment it ends, in call order, so the panel comes first with
// empty rows where the keypad and the "Last" row go, and those are placed on top of
// it afterwards. The "Last" row comes after the buttons, so a press shows up in it on
// the same frame.
Demo.renderPresetPanel(): voidrenderPresetPanel() {
import uiui.begin('topLeft', { x: numberx: const PRESET_PANEL_X: 4PRESET_PANEL_X, y: numbery: const PANEL_Y: 76PANEL_Y, width: numberwidth: const PRESET_PANEL_W: 174PRESET_PANEL_W });
import uiui.panel('Presets');
import uiui.label('Keys J P E L H B or tap', { color: stringcolor: 'dim' });
import uiui.spacer(4);
import uiui.spacer(const PRESET_GRID_H: numberPRESET_GRID_H + const PRESET_PANEL_FILLER: 1PRESET_PANEL_FILLER + const ROW_H: 10ROW_H + const PANEL_TRAILING_SPACE: 5PANEL_TRAILING_SPACE);
import uiui.end();
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];
const const x: numberx = const PRESET_GRID_X: numberPRESET_GRID_X + (let i: numberi % const PRESET_COLUMNS: 2PRESET_COLUMNS) * (const PRESET_BUTTON_W: numberPRESET_BUTTON_W + const PRESET_GAP_X: 6PRESET_GAP_X);
const const y: numbery = const PRESET_GRID_Y: numberPRESET_GRID_Y + Math.floor(let i: numberi / const PRESET_COLUMNS: 2PRESET_COLUMNS) * const PRESET_ROW_H: 27PRESET_ROW_H + const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT;
import uiui.begin('topLeft', { x: numberx, y: numbery, pad: numberpad: 0 });
if (import uiui.button(const def: anydef.label, { key: anykey: const def: anydef.code, width: numberwidth: const PRESET_BUTTON_W: numberPRESET_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;
... 124 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;
}
import uiui.end();
}
const const lastLabel: anylastLabel = this.Demo.lastPresetIndex: number | nulllastPresetIndex === null ? '-' : const PRESET_DEFINITIONS: {}PRESET_DEFINITIONS[this.Demo.lastPresetIndex: numberlastPresetIndex].name;
import uiui.begin('topLeft', { x: numberx: const PRESET_GRID_X: numberPRESET_GRID_X, y: numbery: const LAST_ROW_Y: numberLAST_ROW_Y + const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT, pad: numberpad: 0 });
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(): voidrenderRandomPanel() {
import uiui.begin('topLeft', { x: numberx: const RANDOM_PANEL_X: 184RANDOM_PANEL_X, y: numbery: const PANEL_Y: 76PANEL_Y, width: numberwidth: const RANDOM_PANEL_W: 132RANDOM_PANEL_W, kvCols: numberkvCols: const RANDOM_KV_COLS: 10RANDOM_KV_COLS });
import uiui.panel('Randomizer');
if (import uiui.button('R - Randomize', { key: stringkey: 'KeyR', width: numberwidth: const RANDOM_PANEL_W: 132RANDOM_PANEL_W - const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK })) {
this.Demo.triggerRandomize(): voidtriggerRandomize();
}
import uiui.spacer(const RANDOM_PANEL_FILLER: 6RANDOM_PANEL_FILLER);
// Read after the button, so a roll made this frame already shows below.
const const params: SynthParams | nullparams = this.Demo.lastRandomParams: SynthParams | nulllastRandomParams;
// 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, { width: numberwidth: const RANDOM_PANEL_W: 132RANDOM_PANEL_W - const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK });
// The sweep target in Hz, or 'none' when this roll kept a flat pitch.
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.spacer(const PANEL_TRAILING_SPACE: 5PANEL_TRAILING_SPACE);
import uiui.end();
}
// Rolls a new random SynthParams recipe and starts rendering and 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(): voidtriggerRandomize() {
const const params: {
pitchSweep?: {
toFrequency: number;
} | undefined;
waveform: any;
frequency: number;
duration: number;
volume: number;
envelope: {
attack: number;
decay: number;
sustain: number;
release: number;
};
noiseMix: number;
dutyCycle: number;
seed: number;
}
params = function buildRandomSynthParams(): {
pitchSweep?: {
toFrequency: number;
} | undefined;
waveform: any;
frequency: number;
duration: number;
volume: number;
envelope: {
attack: number;
decay: number;
sustain: number;
release: number;
};
noiseMix: number;
dutyCycle: number;
seed: number;
}
buildRandomSynthParams();
this.Demo.lastRandomParams: SynthParams | nulllastRandomParams = const params: {
pitchSweep?: {
toFrequency: number;
} | undefined;
waveform: any;
frequency: number;
duration: number;
volume: number;
envelope: {
attack: number;
decay: number;
sustain: number;
release: number;
};
noiseMix: number;
dutyCycle: number;
seed: number;
}
params;
// 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: any): anyplayRandomParams(const params: {
pitchSweep?: {
toFrequency: number;
} | undefined;
waveform: any;
frequency: number;
duration: number;
volume: number;
envelope: {
attack: number;
decay: number;
sustain: number;
release: number;
};
noiseMix: number;
dutyCycle: number;
seed: number;
}
params).catch((error: anyerror) => console.error(error: anyerror));
}
// Renders a SynthParams recipe into a clip and plays it.
async Demo.playRandomParams(params: any): anyplayRandomParams(params: anyparams) {
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: anyparams);
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;
... 124 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 DemoDemo);