Sound design vocabulary
sound specs/sound/vocabulary.kmd
Canonical 8-cue vocabulary for UI audio in Koder products (focus / hover / press / success / error / notify / voice-wake / voice-end), with timbre family, token format, and mute contract. Pairs with voice/wake-word.kmd (handles voice-channel sound) and errors/user-facing-messages.kmd (handles error-channel pairing). Owner curates final timbre + .wav samples; Web Audio API synthesized cues ship as slice 1.
When this spec applies
All triggers
- Add audio feedback to any Koder UI surface
- Wire a new toast / dialog / form / voice interaction
- Audit a Koder product for missing or inconsistent sound cues
Specification body
Spec — Sound design vocabulary
Status: v0.1.0 Draft (2026-05-22). Owner sign-off required for timbre and final samples; Web Audio synth ships as slice 1.
R1 — The 8-cue catalog
The vocabulary is closed — only these 8 cues are canonical. A product needing a cue NOT in this list must amend the spec, not ship a one-off.
| Cue ID | Trigger | Default state | Duration target | Channel pairing |
|---|---|---|---|---|
focus | Focus enters an interactive element via keyboard | OFF | ≤ 80 ms | none required |
hover | Pointer enters a primary interactive element | OFF | ≤ 80 ms | none required |
press | Pointer / key activates a button or link | OFF | ≤ 100 ms | none required |
success | Operation completed successfully | ON | ≤ 200 ms | visual toast (errors/user-facing-messages.kmd) |
error | Operation failed | ON | ≤ 200 ms | visual toast (mandatory) |
notify | Background event surfaces to the user | OFF | ≤ 250 ms | visual badge/toast |
voice-wake | Wake-word detected, Voice mode entered | ON | ≤ 150 ms | visual mic indicator (voice/wake-word.kmd § Visual feedback) |
voice-end | Voice mode exited (timeout / dismissal) | ON | ≤ 150 ms | mic indicator hides |
Defaults: success, error, voice-wake, voice-end are ON
by default; focus, hover, press, notify are OFF. Per R4
the user can flip any of the 8 via Settings.
R2 — Timbre family
The Koder sound vocabulary is a single-octave synth palette — all cues share the same timbre, only the pitch / envelope changes. Owner curates the timbre; v0.1 advisory default is "soft sine with a short attack and ~50 ms tail." No samples ship until owner picks.
Slice 1 (this spec's first publication) ships Web Audio API
synthesized cues using deterministic parameters (see § R3 token
format). When owner ratifies the timbre, real .wav recordings
replace the synth path.
R3 — Token format
Each cue is addressed by a CSS-like custom property and a SDK key:
--kds-sound-<cue>: url(/sound/<cue>.wav)
--kds-sound-<cue>-duration: <ms>
--kds-sound-<cue>-gain: <0..1>
Slice 1 synth equivalents (Web Audio API):
const cues = {
focus: { freq: 660, dur: 60, type: 'sine' },
hover: { freq: 520, dur: 60, type: 'sine' },
press: { freq: 440, dur: 80, type: 'sine' },
success: { freq: 880, dur: 180, type: 'sine', glide: [880, 1320] },
error: { freq: 220, dur: 180, type: 'triangle', glide: [330, 220] },
notify: { freq: 660, dur: 220, type: 'sine', glide: [660, 880] },
'voice-wake': { freq: 880, dur: 140, type: 'sine', glide: [880, 1320] },
'voice-end': { freq: 880, dur: 140, type: 'sine', glide: [1320, 880] },
};
(Numbers above are the canonical synth slice 1 values. Real .wav ratification swaps the implementation but the public API stays.)
SDK-side:
// koder_kit
await KoderSound.play(KoderSoundCue.success);
// koder_web_kit
import { KoderSound } from '@koder/web-kit';
KoderSound.play('success');
R4 — Mute contract
The user controls sound playback via Settings → "Sounds":
| Setting | Default | Range |
|---|---|---|
| Master mute | OFF | ON / OFF (master overrides everything) |
| Per-cue override | (per R1 defaults) | ON / OFF per cue |
| Gain | 1.0 | 0.0 – 1.0 (global) |
Implementation rules:
- Master mute MUST be honored — even synthesized cues stop.
- Per-cue overrides MUST persist across sessions (i18n parity —
user picks once on any device, applies on every device per the
sync model in
koder-app/behaviors.kmd § Settings sync). - The OS-level mute (silent switch on iOS, "do not disturb" on Android) MUST take precedence over Koder's master mute (cannot override the OS).
- Voice cues (
voice-wake,voice-end) MUST also respect the voice/wake-word.kmdvoice.enabledtoggle — disabling voice silences these even when the per-cue setting is ON.
R5 — A11y contract
Sound is always a secondary channel. Every cue listed above
MUST have a visual twin (per the persona work in
accessibility/personas.kmd § P7 + P8):
| Cue | Visual twin (mandatory) |
|---|---|
focus | Visible focus ring (existing — policies/focus-management.kmd) |
hover | Hover state (existing) |
press | Press state (existing) |
success | Toast / banner — errors/user-facing-messages.kmd § success |
error | Toast / banner — errors/user-facing-messages.kmd § error |
notify | Badge / toast |
voice-wake | Mic indicator — voice/wake-word.kmd § Visual feedback |
voice-end | Mic indicator hides |
Audit fails if a cue plays without its visual twin.
R6 — Implementation surfaces
| Surface | Slice 1 (synth) | Slice 2 (samples) |
|---|---|---|
| Web (koder_web_kit) | Web Audio API per § R3 | <audio src="/sound/<cue>.wav"> via CSP media-src 'self' |
| Flutter (koder_kit) | audio_session + tone generator | assets/sound/<cue>.wav shipped in the SDK |
| Android native | SoundPool with synthesized PCM | res/raw/<cue>.wav |
| iOS native | AVAudioEngine tone | bundle <cue>.caf |
Cross-platform consumers SHOULD load the canonical Settings tile
(KodeVoiceSettingsTile extended with the Sound group) instead of
hand-rolling toggles.
R7 — Tests of the contract
| ID | Test |
|---|---|
| T1 | Calling KoderSound.play('success') with master-mute ON emits no audio. |
| T2 | Disabling voice setting silences voice-wake + voice-end even when their per-cue toggles are ON. |
| T3 | OS-level mute (silent switch / DND) suppresses every cue. |
| T4 | Every cue has its visual twin asserted (snapshot or DOM check). |
| T5 | Slice 1 synth respects the canonical frequencies / durations from § R3. |
| T6 | Settings persistence — mute survives app restart + sync across devices. |
| T7 | A consumer attempting to play a NON-canonical cue ID emits a dev-mode warning + audit failure. |
R8 — Cross-references
voice/wake-word.kmd— voice-channel sound handled by Talk Mode pipeline; the twovoice-*cues here are the boundary markers.errors/user-facing-messages.kmd—errorcue MUST pair with a toast whose copy follows that spec.accessibility/personas.kmd § P7 + P8— Deaf + situational hearing personas — visual twin is the load-bearing channel.koder-app/behaviors.kmd § Settings— Settings drawer "Sounds" group integration.policies/security.kmd § media-src— self-hosted-only audio files; no third-party CDN.services/ai/voice/— Voice consumer of thevoice-wake/voice-endcues.
R9 — Open questions
- Should
notifydefault ON when the OS notification permission was granted? (Microsoft Fluent default is ON for system-toast- accompanied sound; KDS default is OFF until user opts in.) - Should the SDK expose a
play(cue, { gain: 0.3 })runtime gain override, or is the user-level Settings gain the only control? - Wave 2 — does sound vocabulary extend to ambient music (login
screen background loop) or stay strictly transactional? Out of
scope here; tracked as potential
specs/sound/ambient.kmdfollow-up.
Sign-off
| Role | Owner |
|---|---|
| Author | @rpm (2026-05-22) — structural slice |
| Timbre direction | pending owner |
| .wav sample production | pending (slice 2) |
| SDK binding | pending koder_kit + koder_web_kit follow-up tickets |
| Ratification | pending |
References
tools/design-gen/backlog/done/056-sound-design-vocabulary.kmdmeta/docs/stack/specs/voice/wake-word.kmdmeta/docs/stack/specs/errors/user-facing-messages.kmdmeta/docs/stack/specs/accessibility/personas.kmdhttps://learn.microsoft.com/windows/apps/design/style/sound