Documentation menu

API Reference

This page documents every public export of Pue JS. All signatures are written in TypeScript; the same API is available from plain JavaScript.

Core

createEmitter()

Creates an emitter bound to a scroll container.

TypeScript
function createEmitter(target: Window | Element, options?: EmitterOptions): Emitter;
ParameterTypeDescription
targetWindow | ElementThe scroll container to observe.
optionsEmitterOptionsOptional configuration. Merged over the selected preset.

Returns an Emitter in the idle state. Invalid options throw E_INVALID_OPTION. On the server, or without Web Audio, the returned emitter is inert: its methods resolve immediately and it never emits.

TypeScript
import { createEmitter } from "@puejs/core"; const emitter = createEmitter(document.querySelector("#feed")!, { preset: "crisp" });

EmitterOptions

OptionTypeDefaultDescription
presetPresetName | AcousticProfile"organic"Base acoustic profile. Other options are merged over it.
odorsOdor[]preset odorsSamples available to the emitter. See odors.
pitchnumber | PitchRangepresetPlayback rate. A range is sampled per emission.
resonancenumberpresetLow-frequency body, from 0 to 1.
durationnumberpresetMaximum emission length in milliseconds, ending with a fade.
volumenumberpresetMaster gain, from 0 to 1.
tonnageTonnageCurveName | TonnageCurve"logarithmic"Maps velocity to tonnage.
thresholdnumber80Minimum velocity, in px/s, required to emit.
throttle"velocity" | "distance" | number"velocity"Emission rate strategy, or a fixed interval in milliseconds.
respectReducedMotionbooleantrueStay silent when the user prefers reduced motion.
autoUnlockbooleantrueResume a suspended emitter on the next user gesture.
audioContextAudioContextprivate contextShare an existing context between emitters.

Emitter

TypeScript
interface Emitter {  readonly state: EmitterState;  readonly context: AudioContext | null;  readonly options: ResolvedOptions;   start(): Promise<void>;  stop(): void;  update(options: Partial<EmitterOptions>): void;  emit(input?: EmissionInput): void;  on<E extends keyof EmitterEvents>(event: E, handler: (payload: EmitterEvents[E]) => void): () => void;  off<E extends keyof EmitterEvents>(event: E, handler: (payload: EmitterEvents[E]) => void): void;  destroy(): void;}

start()

Creates the audio context if needed, decodes the odors, starts observing the scroll container and resumes audio. Resolves once the emitter is running, or suspended when no user gesture is available. Calling start() on a started emitter is a no-op.

stop()

Stops observing. In-flight emissions finish naturally. The emitter returns to idle and can be started again.

update()

Merges new options without interrupting observation. Invalid options throw E_INVALID_OPTION and leave the current options untouched. audioContext cannot change after start().

TypeScript
emitter.update({ preset: "deep", volume: 0.5 });

emit()

Triggers an emission manually, independently of scroll. Ignored unless the emitter is running.

FieldTypeDefaultDescription
tonnagenumber0.5Intensity of the emission, from 0 to 1.
odorOdorautoForce a specific odor instead of automatic selection.

on() and off()

Subscribes to and unsubscribes from emitter events. on() returns an unsubscribe function.

TypeScript
const unsubscribe = emitter.on("emit", ({ tonnage, odor }) => {  console.debug(`emission · ${odor} · tonnage=${tonnage.toFixed(2)}`);});

destroy()

Removes every listener, stops every voice and closes the private audio context, if any. Any later call throws E_DESTROYED, except destroy() and off(), which are safe to repeat.

Events

TypeScript
interface EmitterEvents {  emit: EmissionEvent;  statechange: { previous: EmitterState; current: EmitterState };  unlock: { context: AudioContext };  error: PueError;}

EmissionEvent

FieldTypeDescription
tonnagenumberComputed tonnage, from 0 to 1.
velocitynumberSmoothed scroll velocity at trigger time, in px/s.
direction1 | -11 when scrolling forward, -1 when scrolling backward.
pitchnumberEffective playback rate.
odorstringName of the odor that produced the emission.
latencynumberTime from motion detection to scheduling, in ms.
timestampnumberAudioContext.currentTime at which the emission starts.

Presets

TypeScript
const presets: Readonly<Record<PresetName, AcousticProfile>>;
NamePitchResonanceDurationVolumeOdors
organic0.90 – 1.100.40320 ms0.8staccato, sustained
crisp1.10 – 1.350.20180 ms0.8staccato
deep0.60 – 0.800.70620 ms0.8sustained
discreet0.95 – 1.050.25220 ms0.4staccato

Presets are frozen objects. Spread them to derive your own:

TypeScript
import { presets, type AcousticProfile } from "@puejs/core"; export const brand: AcousticProfile = { ...presets.organic, resonance: 0.55, duration: 360 };

Tonnage curves

defineTonnageCurve()

TypeScript
function defineTonnageCurve(curve: (velocity: number) => number): TonnageCurve;

The function receives the smoothed velocity in px/s and must return a value between 0 and 1; other values are clamped. It runs once per emitting frame: keep it pure and allocation-free.

Odors

TypeScript
import { cathedral, fortissimo, pesante, sforzando, soprano, staccato, sustained } from "@puejs/core/odors";
OdorTonnage rangeDurationIn presets
staccato0 – 0.60.31 sYes
sustained0.4 – 10.21 sYes
soprano0.3 – 0.80.37 sNo
pesante0.5 – 10.94 sNo
sforzando0.7 – 11.01 sNo
fortissimo0.85 – 12.52 sNo
cathedral0.9 – 12.15 sNo

Odors longer than the emitter's duration are faded out when they reach it.

defineOdor()

Declares an odor from your own sample.

TypeScript
function defineOdor(definition: { name: string; src: string; tonnage?: [number, number] }): Odor;
FieldTypeDescription
namestringIdentifier reported in EmissionEvent.odor.
srcstringURL or base64 data URI of an audio file the browser can decode.
tonnage[number, number]Tonnage range for which the odor is eligible. Default [0, 1].

URL odors are fetched and decoded on start().

Utilities

isSupported()

TypeScript
function isSupported(): boolean;

Returns true when the Web Audio API is available. Always false on the server.

unlock()

TypeScript
function unlock(context?: AudioContext): Promise<boolean>;

Resumes the given audio context — or every context created by Pue JS — from within a user gesture. Resolves with true if playback is now allowed.

Types

TypeScript
type PresetName = "organic" | "crisp" | "deep" | "discreet"; type EmitterState = "idle" | "suspended" | "running" | "destroyed"; type TonnageCurveName = "linear" | "logarithmic" | "exponential"; type ThrottleStrategy = "velocity" | "distance" | number; interface PitchRange {  min: number;  max: number;} interface AcousticProfile {  odors: readonly Odor[];  pitch: number | PitchRange;  resonance: number;  duration: number;  volume: number;  tonnage: TonnageCurveName | TonnageCurve;}

Errors

Every error thrown or dispatched by Pue JS is a PueError with a stable code.

TypeScript
class PueError extends Error {  readonly code: "E_DESTROYED" | "E_INVALID_OPTION" | "E_DECODE";}
CodeWhen
E_DESTROYEDA method is called on a destroyed emitter.
E_INVALID_OPTIONAn option is out of range, or immutable after start().
E_DECODEAn odor could not be fetched or decoded. Dispatched through the error event; the emitter keeps running with its other odors.

React adapter

Available from @puejs/react. See Framework Adapters for usage.

usePue()

TypeScript
function usePue<T extends Element = Element>(  options?: EmitterOptions | false,  config?: { autoStart?: boolean },): {  ref: (element: T | null) => void;  state: EmitterState;  start: () => Promise<void>;  stop: () => void;  emit: (input?: EmissionInput) => void;  emitter: Emitter | null;};

Options are compared by value: pitch ranges structurally, odor lists element by element, everything else by identity. Removing an option reverts it to its default. Inline functions, profiles and odor lists built during render are compared by identity and applied on every render: define them outside the component to avoid needless updates. Changing the shared audioContext recreates the emitter.

Without ref, the hook observes window. Once ref has been attached, removing the element disables the emitter until a new element is attached. On the server and while disabled, emitter is null, state is "idle", and start, stop and emit are safe no-ops with stable identities.

PueProvider

PropTypeDescription
defaultsEmitterOptionsMerged under the options of every usePue call in the subtree.
audioContextAudioContextShared by every emitter in the subtree. Never closed.