Skip to content

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

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 IDTriggerDefault stateDuration targetChannel pairing
focusFocus enters an interactive element via keyboardOFF≤ 80 msnone required
hoverPointer enters a primary interactive elementOFF≤ 80 msnone required
pressPointer / key activates a button or linkOFF≤ 100 msnone required
successOperation completed successfullyON≤ 200 msvisual toast (errors/user-facing-messages.kmd)
errorOperation failedON≤ 200 msvisual toast (mandatory)
notifyBackground event surfaces to the userOFF≤ 250 msvisual badge/toast
voice-wakeWake-word detected, Voice mode enteredON≤ 150 msvisual mic indicator (voice/wake-word.kmd § Visual feedback)
voice-endVoice mode exited (timeout / dismissal)ON≤ 150 msmic 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":

SettingDefaultRange
Master muteOFFON / OFF (master overrides everything)
Per-cue override(per R1 defaults)ON / OFF per cue
Gain1.00.0 – 1.0 (global)

Implementation rules:

  1. Master mute MUST be honored — even synthesized cues stop.
  2. 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).
  3. 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).
  4. Voice cues (voice-wake, voice-end) MUST also respect the voice/wake-word.kmd voice.enabled toggle — 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):

CueVisual twin (mandatory)
focusVisible focus ring (existing — policies/focus-management.kmd)
hoverHover state (existing)
pressPress state (existing)
successToast / banner — errors/user-facing-messages.kmd § success
errorToast / banner — errors/user-facing-messages.kmd § error
notifyBadge / toast
voice-wakeMic indicator — voice/wake-word.kmd § Visual feedback
voice-endMic indicator hides

Audit fails if a cue plays without its visual twin.

R6 — Implementation surfaces

SurfaceSlice 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 generatorassets/sound/<cue>.wav shipped in the SDK
Android nativeSoundPool with synthesized PCMres/raw/<cue>.wav
iOS nativeAVAudioEngine tonebundle <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

IDTest
T1Calling KoderSound.play('success') with master-mute ON emits no audio.
T2Disabling voice setting silences voice-wake + voice-end even when their per-cue toggles are ON.
T3OS-level mute (silent switch / DND) suppresses every cue.
T4Every cue has its visual twin asserted (snapshot or DOM check).
T5Slice 1 synth respects the canonical frequencies / durations from § R3.
T6Settings persistence — mute survives app restart + sync across devices.
T7A 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 two voice-* cues here are the boundary markers.
  • errors/user-facing-messages.kmderror cue 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 the voice-wake / voice-end cues.

R9 — Open questions

  1. Should notify default 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.)
  2. Should the SDK expose a play(cue, { gain: 0.3 }) runtime gain override, or is the user-level Settings gain the only control?
  3. 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.kmd follow-up.

Sign-off

RoleOwner
Author@rpm (2026-05-22) — structural slice
Timbre directionpending owner
.wav sample productionpending (slice 2)
SDK bindingpending koder_kit + koder_web_kit follow-up tickets
Ratificationpending

References