//#region src/attributes.d.ts
/** Maps `data-sound-*` attributes to the sound engine via event delegation. */
/** Configuration options for DOM event delegation. */
interface ObserveOptions {
/** Element or Document to delegate listeners from. Defaults to `document`. */
root?: Document | Element;
/**
* Automatically calls `prime()` on the first click on a bound element. Hover and focus
* don't count: they are not user gestures, so a browser would not unlock audio for them.
* Defaults to `true`.
*/
primeOnFirstInteraction?: boolean;
}
/**
* Initializes global event delegation for `data-sound` attributes. Returns a cleanup function.
*
* Calls are reference-counted per root: observing the same root twice (the auto script plus
* a manual call, a remounted component) binds its listeners once, and they are removed only
* when every returned cleanup has run.
*
* @example
* ```html
*
* Next
* ```
*/
export declare function observe(options?: ObserveOptions): () => void;
//#endregion
//#region src/engine/theme-profiles.d.ts
/** Theme profiles for global sound reshaping. */
/** Configurable parameters that a theme can override. */
interface ThemeOverrides {
/** Overrides the oscillator waveform type. */
waveformOverride?: OscillatorType;
/** Multiplier for filter cutoff frequencies. */
cutoffMultiplier?: number;
/** Multiplier for reverb mix and impulse response decay length. */
reverbMultiplier?: number;
/** Global volume multiplier. */
volumeMultiplier?: number;
/** Multiplier for envelope attack durations. */
attackMultiplier?: number;
/** Multiplier for envelope decay and release durations. */
durationMultiplier?: number;
/** Multiplier for FM synthesis depth. */
fmMultiplier?: number;
/** Exponent scaling filter sweep depth (0 to 1). */
filterEnvDepth?: number;
/** Exponent scaling pitch sweep depth (0 to 1). */
pitchEnvMultiplier?: number;
/** Gain multiplier for body oscillator layers. */
oscillatorMultiplier?: number;
/** Gain multiplier for transient noise layers. */
noiseMultiplier?: number;
/** Gain multiplier for sub sine layers. */
subMultiplier?: number;
/** Master soft-saturation drive multiplier. */
saturationMultiplier?: number;
/** Humanization micro-variation amount (0 to disable). */
humanize?: number;
/**
* Multiplier for every frequency in a sound, 0.25 to 4: pitches, filter cutoffs and noise
* bands move together, so the whole sound changes size, not just its note. (`PlayOptions.pitch`
* moves oscillator pitches only.)
*/
frequencyMultiplier?: number;
/** Level of a short low thump added under each sound's first struck onset, 0 to 1 (0 to disable). */
bump?: number;
/** Reverb send added to every sound on top of its own `reverb.mix`, 0 to 0.5 (0 to disable). */
room?: number;
/** Shortest attack any layer gets, in seconds, 0 to 0.05: rounds hard onsets (0 to disable). */
attackFloor?: number;
/**
* Plays another theme's re-authored sounds (for example `"soft"`) wherever this theme
* registers none of its own. Without it, those sounds come from the default palette.
*/
soundsFrom?: ThemeName;
}
/** Fields that are not numbers, copied as given. */
type FlagKey = "waveformOverride" | "soundsFrom";
/** Complete theme profile with all required multipliers resolved. */
type ThemeProfile = Required> & Pick;
/** Names of the themes that ship with the library. */
type BuiltInThemeName = "default" | "soft" | "mechanical";
/**
* Creates a ThemeProfile by applying overrides on top of the neutral profile. A value that is
* omitted, `undefined` or not a finite number falls back to neutral, so a bad value can never
* reach an AudioParam, and a value outside its documented range is clamped to it.
*/
export declare function defineTheme(overrides?: ThemeOverrides): ThemeProfile;
/** Built-in theme profiles. Frozen: register a new theme instead of editing one. */
export declare const THEME_PROFILES: {
/** Neutral: the palette exactly as authored, with no master saturation. */
default: Readonly;
/**
* Felt mallets on wood. Custom sounds become pure sines, rounder, a little longer and
* roomier, with gentler clicks and pitch sweeps.
*/
soft: Readonly;
/**
* Switches, latches and ratchets. Custom sounds play through square waves with halved pitch
* sweeps, a little saturation and extra humanization.
*/
mechanical: Readonly;
};
/**
* A theme name: one of the built-ins, or any name passed to `engine.registerTheme`.
* The `string & {}` arm keeps editor autocomplete for the built-in names.
*/
type ThemeName = BuiltInThemeName | (string & {});
/** Default theme name. */
export declare const DEFAULT_THEME: BuiltInThemeName;
//#endregion
//#region src/engine/types.d.ts
/** Biquad filter configuration for a sound layer. */
interface FilterDef {
/** Web Audio filter type, e.g. `"lowpass"` or `"bandpass"`. */
type: BiquadFilterType;
/** Cutoff (or centre, for bandpass) in Hz. */
frequency: number;
/** Filter Q. Defaults to 1. For bandpass, higher is narrower: 8 and up rings with a pitch. */
resonance?: number;
/** Cutoff sweep: from `frequency` to `peak` Hz over `attack` seconds, back over `decay`. */
envelope?: {
attack?: number;
peak: number;
decay: number;
};
}
/** Frequency modulation configuration for a sound layer. */
interface FMDef {
/** Modulator frequency as a multiple of the carrier. */
ratio: number;
/** Modulation depth in Hz. */
depth: number;
}
/** Gain envelope of a struck sound. Times are in seconds. */
interface EnvelopeDef {
/** Rise to full gain. Defaults to 0 (the engine still ramps over 1 ms, so it never clicks). */
attack?: number;
/** Fall from full gain towards silence, as an RC curve: the gain is about -26 dB when it ends. */
decay: number;
/** Extra time the layer is held after the decay, so a pitch sweep lands later. Filter sweeps keep their own timing. */
release?: number;
}
/**
* Functional role of a sound layer, used for theme gain routing:
* - `transient`: contact click or noise burst.
* - `body`: pitched body/resonance.
* - `sub`: low-frequency weight.
* - `tone`: pitch-carrying core voice.
*/
type SoundLayerRole = "transient" | "body" | "sub" | "tone";
/** Configuration for a single sound synthesis layer. */
interface SoundLayer {
/** Oscillator waveform type. Defaults to "sine". */
type?: OscillatorType;
/** Overrides the inferred role used for theme gain routing. */
role?: SoundLayerRole;
/** Carrier frequency in Hz, or a start/end sweep range. */
frequency?: number | {
start: number;
end: number;
};
/** Noise source color instead of an oscillator. */
noise?: "white" | "pink";
/** Amplitude envelope. */
envelope?: EnvelopeDef;
/** Layer gain (0 to 1). */
gain?: number;
/** Delay in seconds relative to trigger time. */
delay?: number;
/** Stereo pan (-1 left to +1 right). */
pan?: number;
/** Filter configuration. */
filter?: FilterDef;
/** FM synthesis configuration. */
fm?: FMDef;
}
/** Reverb send settings for a sound. */
interface ReverbDef {
/** Reverb send level (0 to 1). */
mix: number;
}
/** Complete sound definition consisting of one or more synthesis layers. */
interface SoundDefinition {
/** Layers played together, each from its own `delay`. */
layers: SoundLayer[];
/** Optional reverb send configuration. */
reverb?: ReverbDef;
/** Minimum interval in milliseconds between triggers of this sound. Defaults to engine throttle. */
throttleMs?: number;
}
/** Runtime options for triggering a sound. */
interface PlayOptions {
/** Arbitrary argument passed to dynamic sound factories. */
arg?: string;
/** Pitch transposition multiplier (e.g. 1.25 for a major third up, 0.5 for an octave down). */
pitch?: number;
/** Force / velocity multiplier (0.01 to 2). Scales every layer's gain. */
force?: number;
/** Custom deduplication/gating bucket key. Disambiguates concurrent voices. */
key?: string;
}
/** Global engine configuration options. */
interface EngineConfig {
/** Master volume (0 to 1). */
volume?: number;
/** Global sound enabled state. */
enabled?: boolean;
/** Active theme name: a built-in or one added with `engine.registerTheme`. */
theme?: ThemeName;
/** Default throttle cooldown in milliseconds. */
defaultThrottleMs?: number;
/**
* Silences every sound while the user has `prefers-reduced-motion: reduce` set,
* tracking changes to the preference live. Defaults to `true`.
*/
respectReducedMotion?: boolean;
}
/** Names of all default palette sounds. */
type DefaultSoundName = "tap" | "click" | "key-press" | "hover" | "toggle-on" | "toggle-off" | "checkbox" | "select" | "deselect" | "tab-switch" | "expand" | "collapse" | "page-enter" | "page-exit" | "turn" | "success" | "error" | "warning" | "notification" | "info" | "deny" | "copy" | "send" | "delete" | "erase" | "undo" | "pop" | "swoosh" | "slide";
//#endregion
//#region src/engine/engine.d.ts
/** Default interval in milliseconds between plays of the same sound. */
export declare const DEFAULT_THROTTLE_MS = 45;
/** Factory function producing a sound definition based on a runtime argument. */
type SoundFactory = (arg?: string) => SoundDefinition;
/** Unified audio engine and sound dispatcher. */
export declare const engine: {
/** Returns the underlying AudioContext, initializing it if necessary. */
getContext(): AudioContext | null;
/** Returns the final output gain node before destination. */
getOutputGain(): GainNode | null;
/**
* Unlocks audio playback. Call it from a user-gesture handler: it resumes a suspended
* context (including one created earlier, outside a gesture) and plays a silent buffer,
* which is what iOS Safari needs to route sound.
*/
prime(): void;
/** Closes the AudioContext and resets internal state and caches. */
dispose(): void;
/** Sets the master volume level (0 to 1). Non-finite values are ignored. */
setVolume(volume: number): void;
/** Returns current user volume (0 to 1). */
getVolume(): number;
/** Enables or disables sound playback. */
setEnabled(value: boolean): void;
/** Returns whether sound playback is currently enabled. */
isEnabled(): boolean;
/** Sets the active theme by name: a built-in or one added with `registerTheme`. Unknown names are ignored. */
setTheme(theme: ThemeName): void;
/**
* Registers (or replaces) a named theme. Omitted multipliers fall back to neutral,
* as with `defineTheme`. Registering the active theme applies it immediately.
*/
registerTheme(name: string, profile: ThemeOverrides): void;
/** Returns the currently active theme name. */
getTheme(): ThemeName;
/** Returns the resolved theme profile for the active theme. */
getActiveProfile(): ThemeProfile;
/** Returns the names of every built-in and registered theme. */
getAvailableThemes(): ThemeName[];
/** Configures global engine settings. Does not rely on `this`, so it is safe to destructure. */
configure(this: void, config: EngineConfig): void;
/** Returns current global engine configuration. */
getConfig(): Required;
/** Registers a sound definition or factory function. */
registerSound(name: string, def: SoundDefinition | SoundFactory): void;
/**
* Registers a theme's own version of a sound. Themes whose `soundsFrom` names this theme
* play it too.
*/
registerThemeSound(theme: ThemeName, name: string, def: SoundDefinition | SoundFactory): void;
/**
* Resolves a sound definition by name, as `options.theme` (default: the active theme) would
* play it, with `options.arg` passed to a sound factory. Returns a copy: mutating it does not
* change the registered sound.
*/
getSound(name: DefaultSoundName | (string & {}), options?: {
theme?: ThemeName;
arg?: string;
}): SoundDefinition | undefined;
/** Plays a registered sound by name. Accepts an optional string argument or a PlayOptions object. */
play(name: DefaultSoundName | (string & {}), optionsOrArg?: PlayOptions | string): void;
};
//#endregion
//#region src/engine/feel.d.ts
/** A theme described by feel. Every control is optional; 0 (or leaving it out) keeps the base. */
interface ThemeFeel {
/** Built-in theme to start from: its sounds and its character. Defaults to `"default"`. */
base?: BuiltInThemeName;
/**
* -1 lighter and smaller to 1 deeper and bigger. Moves every pitch and band together, up to
* 4 semitones up or 5 down; deeper sounds also ring a little longer.
*/
depth?: number;
/** 0 to 1: a low thump under each sound's onset, for weight. */
bump?: number;
/** -1 darker to 1 brighter: filters close or open, clicks get quieter or louder. */
brightness?: number;
/** -1 shorter to 1 longer tails. */
length?: number;
/** 0 dry to 1 roomy: every sound gets some room, and the room gets bigger. */
space?: number;
/** -1 harder to 1 softer onsets: clicks get louder, or round off into felt. */
softness?: number;
/** 0 clean to 1 driven: saturation and denser overtones. */
grit?: number;
}
/** A feel control's range and what its ends sound like, for building UIs. */
interface FeelControl {
/** The `ThemeFeel` field it sets. */
key: Exclude;
/** Display name. */
label: string;
/** Lowest value; values below are clamped to it. */
min: number;
/** Highest value; values above are clamped to it. */
max: number;
/** What the low end sounds like. */
low: string;
/** What the high end sounds like. */
high: string;
}
/** Every feel control, in the order a UI should show them. */
export declare const FEEL_CONTROLS: readonly FeelControl[];
/**
* Builds theme overrides from a feel, on top of a built-in theme. Pass the result to
* `engine.registerTheme`:
*
* ```ts
* engine.registerTheme("deep", createTheme({ depth: 0.6, bump: 0.5, space: 0.3 }));
* ```
*/
export declare function createTheme(feel?: ThemeFeel): ThemeOverrides;
//#endregion
export type { BuiltInThemeName, DefaultSoundName, EngineConfig, EnvelopeDef, FMDef, FeelControl, FilterDef, ObserveOptions, PlayOptions, ReverbDef, SoundDefinition, SoundFactory, SoundLayer, SoundLayerRole, ThemeFeel, ThemeName, ThemeOverrides, ThemeProfile };