Core

Sound Packs

9 sound packs with instrument-based architecture, effects system, and custom pack creation.

sensory-ui includes 9 distinct sound packs, each with a unique character. Swap between them by changing the theme value in sensory.config.js. All built-in packs generate audio programmatically via the Web Audio API. No audio files, no base64 blobs, no network requests.

Synthesis patterns adapted from Generating Sounds with AI. Effects system inspired by @web-kits/audio by Raphael Salaja.


Architecture: Tunes + Instruments + Effects

sensory-ui uses an instrument-based architecture that separates concerns:

Tunes (sounds/core/tunes.ts)

Define the musical content:

  • Frequencies, durations, pitch contours
  • Note sequences for arpeggios
  • Filter settings for noise-based sounds
  • Volume levels and decay times

Instruments (sounds/core/instruments.ts)

Define the synthesis technique:

  • Waveform type (sine, square, sawtooth, triangle)
  • Noise characteristics (white, pink, brown)
  • Filter settings and resonance
  • Envelope shapes (attack/decay curves)
  • Harmonic content and detuning
  • Effects chain (reverb, delay, chorus, distortion)

Effects (sounds/core/effects.ts)

Spatial/timbral processing:

  • Reverb via generated impulse responses (no external files)
  • Feedback delay with filter
  • Chorus via modulated delay
  • Waveshaper distortion
  • Chains of effects daisy-chained automatically

Factory (sounds/core/factory.ts)

Combines tunes with instruments:

  • Takes a tune definition and an instrument config
  • If the instrument has effects, routes output through the chain
  • Produces a SoundSynthesizer function

This separation allows the same tunes to be played by different instruments, creating distinct sound pack characters with minimal code duplication.


Effects System

Each InstrumentConfig can include an optional effects array. When present, the factory routes every sound through the effects chain before reaching ctx.destination.

interface InstrumentConfig {
  filterFreq: number;
  q: number;
  oscType: OscillatorWaveform;
  decayMult: number;
  gainMult: number;
  pitchMult: number;
  effects?: EffectDefinition[];  // optional effects chain
}

Available Effects

EffectOptionsDescription
reverb{ duration, decay, mix }Algorithmic reverb via noise-buffer impulse response
delay{ time, feedback, filterFreq, mix }Feedback delay with low-pass filter in loop
chorus{ rate, depth, detune, mix }Delay-based chorus with LFO modulation
distortion{ amount, filterFreq, mix }Waveshaper saturation with post-filter

All effects support a mix parameter (0–1) for wet/dry balance.

Pack Effects Mapping

PackEffectsCharacter Rationale
aeroreverb (long tail, 2s)Airy, ethereal: needs space
glassreverb (1.5s) + chorusCrystalline shimmer: chorus adds sparkle
retrochorus + delayAnalog warmth: chorus widens, delay echoes
industrialdistortionMetallic grit: distortion adds bite
organicreverb (short room, 0.4s)Natural warmth: subtle room ambience
softreverb (medium warm, 0.8s)Gentle presence: warm reverb body
arcade(dry)Chiptune: dry is authentic
minimal(dry)Pure: no processing
crisp(dry)Precise: dry keeps transients sharp

Built-in Sound Packs (9 total)

soft

Warm, rounded, gentle: felt mallets on soft pads.

aero

Airy, breathy, ethereal: wind through chimes. Default.

arcade

8-bit chiptune: square waves, punchy.

organic

Natural, warm, wooden: marimba, wood blocks.

glass

Crystalline, bright: struck glass or bells.

industrial

Metallic, harsh, mechanical: machines and metal.

minimal

Clean, sparse, understated: pure tones only.

retro

Analog synth: vintage synthesizers.

crisp

Sharp, defined, precise: high-quality headphones.


Tune Types

TypeDescriptionExample Roles
clickShort percussive transientinteraction.tap, interaction.subtle, interaction.confirm
popBrief tonal burst with attacknavigation.tab
toggleNoise click + tonal tailinteraction.toggle
sweepFrequency glide (up/down)navigation.forward, navigation.backward
chimeResonant tonal with decaynotification.info
arpeggioSequence of notesnotification.success, hero.complete, hero.milestone
risePitch ascendsoverlay.open, overlay.expand
dropPitch descendsoverlay.close, overlay.collapse

Instrument Properties

interface InstrumentConfig {
  filterFreq: number; // Filter cutoff frequency (1500–6000 Hz)
  q: number;          // Filter resonance (1–12)
  oscType: OscillatorType; // "sine" | "square" | "sawtooth" | "triangle"
  decayMult: number;  // Decay time multiplier (0.5–1.5)
  gainMult: number;   // Volume multiplier (0.4–1.0)
  pitchMult: number;  // Pitch multiplier (0.7–1.8)
  effects?: EffectDefinition[]; // Optional effects chain
}

Example Instrument Settings

PackfilterFreqqoscTypedecayMultgainMultpitchMultEffects
soft20001sine1.50.70.8reverb (warm)
aero35002sine1.00.91.0reverb (long)
arcade40008square0.51.01.5(none)
organic25003triangle1.30.850.9reverb (room)
glass600010sine1.20.751.8reverb + chorus
industrial300012sawtooth0.61.00.7distortion
minimal20001sine0.80.41.0(none)
retro15002square1.10.80.85chorus + delay
crisp55004triangle0.61.01.1(none)

Switching Packs

Via Config File

// sensory.config.js
module.exports = {
  theme: "organic", // soft, aero, arcade, organic, glass, industrial, minimal, retro, crisp
};

Via Provider Props

<SensoryUIProvider config={{ theme: "organic" }}>
  {children}
</SensoryUIProvider>

Creating a Custom Pack

Option 1: Define a New Instrument

// sounds/core/instruments.ts
const MY_CUSTOM_INSTRUMENT: InstrumentConfig = {
  filterFreq: 4000,
  q: 5,
  oscType: "triangle",
  decayMult: 1.0,
  gainMult: 0.85,
  pitchMult: 1.1,
  effects: [
    { type: "reverb", options: { duration: 1.0, decay: 2.0, mix: 0.2 } },
  ],
};

Then generate the pack:

import { generateSoundPack } from "./core/factory";
import { INSTRUMENTS } from "./core/instruments";

// Add to INSTRUMENTS map, then:
export const myPack = generateSoundPack(MY_CUSTOM_INSTRUMENT);
// myPack covers all 17 roles

Option 2: Modify Existing Pack

import { INSTRUMENTS } from "./core/instruments";
import { generateSoundPack } from "./core/factory";

const myInstrument = {
  ...INSTRUMENTS.glass,
  gainMult: 0.6,    // quieter
  pitchMult: 2.0,   // higher pitched
  effects: [
    { type: "reverb", options: { duration: 2.0, mix: 0.4 } },
  ],
};

export const myPack = generateSoundPack(myInstrument);