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.
sensory-ui uses an instrument-based architecture that separates concerns:
sounds/core/tunes.ts)Define the musical content:
sounds/core/instruments.ts)Define the synthesis technique:
sine, square, sawtooth, triangle)sounds/core/effects.ts)Spatial/timbral processing:
sounds/core/factory.ts)Combines tunes with instruments:
SoundSynthesizer functionThis separation allows the same tunes to be played by different instruments, creating distinct sound pack characters with minimal code duplication.
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
}| Effect | Options | Description |
|---|---|---|
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 | Character Rationale |
|---|---|---|
| aero | reverb (long tail, 2s) | Airy, ethereal: needs space |
| glass | reverb (1.5s) + chorus | Crystalline shimmer: chorus adds sparkle |
| retro | chorus + delay | Analog warmth: chorus widens, delay echoes |
| industrial | distortion | Metallic grit: distortion adds bite |
| organic | reverb (short room, 0.4s) | Natural warmth: subtle room ambience |
| soft | reverb (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 |
Warm, rounded, gentle: felt mallets on soft pads.
Airy, breathy, ethereal: wind through chimes. Default.
8-bit chiptune: square waves, punchy.
Natural, warm, wooden: marimba, wood blocks.
Crystalline, bright: struck glass or bells.
Metallic, harsh, mechanical: machines and metal.
Clean, sparse, understated: pure tones only.
Analog synth: vintage synthesizers.
Sharp, defined, precise: high-quality headphones.
| Type | Description | Example Roles |
|---|---|---|
| click | Short percussive transient | interaction.tap, interaction.subtle, interaction.confirm |
| pop | Brief tonal burst with attack | navigation.tab |
| toggle | Noise click + tonal tail | interaction.toggle |
| sweep | Frequency glide (up/down) | navigation.forward, navigation.backward |
| chime | Resonant tonal with decay | notification.info |
| arpeggio | Sequence of notes | notification.success, hero.complete, hero.milestone |
| rise | Pitch ascends | overlay.open, overlay.expand |
| drop | Pitch descends | overlay.close, overlay.collapse |
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
}| Pack | filterFreq | q | oscType | decayMult | gainMult | pitchMult | Effects |
|---|---|---|---|---|---|---|---|
| soft | 2000 | 1 | sine | 1.5 | 0.7 | 0.8 | reverb (warm) |
| aero | 3500 | 2 | sine | 1.0 | 0.9 | 1.0 | reverb (long) |
| arcade | 4000 | 8 | square | 0.5 | 1.0 | 1.5 | (none) |
| organic | 2500 | 3 | triangle | 1.3 | 0.85 | 0.9 | reverb (room) |
| glass | 6000 | 10 | sine | 1.2 | 0.75 | 1.8 | reverb + chorus |
| industrial | 3000 | 12 | sawtooth | 0.6 | 1.0 | 0.7 | distortion |
| minimal | 2000 | 1 | sine | 0.8 | 0.4 | 1.0 | (none) |
| retro | 1500 | 2 | square | 1.1 | 0.8 | 0.85 | chorus + delay |
| crisp | 5500 | 4 | triangle | 0.6 | 1.0 | 1.1 | (none) |
// sensory.config.js
module.exports = {
theme: "organic", // soft, aero, arcade, organic, glass, industrial, minimal, retro, crisp
};<SensoryUIProvider config={{ theme: "organic" }}>
{children}
</SensoryUIProvider>// 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 rolesimport { 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);