BloxwapSFX
API reference

Recipes

The data format every sound is built from, exported from @bloxwap/sfx/recipes.

Every sound is plain data. You can inspect it from the @bloxwap/sfx/recipes entry point, for example to draw a visualization or document your sound design:

import { recipes, sourceEnd, echoTail, type Recipe } from '@bloxwap/sfx/recipes';

recipes.payout.layers.length; // 16

Recipe

interface Recipe {
  level: number;                // output level for the summed layers
  layers: readonly Layer[];
  echo?: Echo;                  // optional darkening feedback delay
}

interface Echo {
  delay: number;    // seconds between repeats
  feedback: number; // 0–1, level of each repeat relative to the previous one
  wet: number;      // level of the echo relative to the dry sound
  lowpass: number;  // Hz, applied on every repeat
}

Layers

Each layer has an envelope that rises exponentially from silence to peak over attack, then falls back over decay. It starts at seconds after the trigger. pan places it in the stereo field; leave it out to keep the layer centered.

interface ToneLayer {
  wave: 'sine' | 'triangle' | 'sawtooth' | 'square';
  freq: number;     // Hz
  to?: number;      // Hz: glide exponentially to this pitch
  glide?: number;   // glide time in seconds; defaults to attack + decay
  detune?: number;  // cents
  at: number; attack: number; decay: number; peak: number; pan?: number;
}

interface NoiseLayer {
  noise: 'lowpass' | 'bandpass' | 'highpass'; // white noise through this filter
  freq: number;     // filter frequency, Hz
  q?: number;       // filter Q (default 1)
  at: number; attack: number; decay: number; peak: number; pan?: number;
}

Example: chime

{
  level: 1.75,
  echo: { delay: 0.12, feedback: 0.25, wet: 0.18, lowpass: 4000 },
  layers: [
    { wave: 'sine', freq: 1046.5, at: 0,    attack: 0.006, decay: 0.22, peak: 0.09 },
    { wave: 'sine', freq: 1568,   at: 0.09, attack: 0.006, decay: 0.26, peak: 0.08 },
  ],
}

Timing helpers

  • sourceEnd(recipe) returns the seconds until the last layer's envelope ends.
  • echoTail(recipe) returns the seconds of echo after that, until the repeats fall below -60 dB.
  • duration(name) from the main entry returns the sum of the two.

Define your own sounds

import { define, play, preload, type Recipe } from '@bloxwap/sfx';

const coin = {
  level: 0.6,
  layers: [{ wave: 'sine', freq: 880, at: 0, attack: 0.006, decay: 0.15, peak: 0.2 }],
} satisfies Recipe;

define('coin', coin);
await preload(['coin']);
play('coin');

Use data-sound-press="coin" after defining it. SoundName accepts custom strings; isSound() checks whether a name has actually been registered. sounds and recipes remain the built-in catalog; getRecipe(name) from the recipes entry resolves either kind. preload() without arguments warms the built-ins; pass custom names to warm your own.

define() copies and freezes the data. Invalid timing, frequencies, filters, echo parameters, reserved names and attempts to replace built-ins throw RangeError. Defining a custom name again replaces its recipe and clears its old render cache. Sounds already playing finish. Definitions survive dispose(); that function releases audio resources.

Build a sound

import { define, play } from '@bloxwap/sfx';

define('coin', {
  "level": 0.6,
  "layers": [
    {
      "wave": "sine",
      "freq": 440,
      "at": 0,
      "attack": 0.006,
      "decay": 0.15,
      "peak": 0.2
    }
  ]
});
play('coin');
Edit on GitHub

On this page