Sound design vocabulary

draft

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.

Spec — Sound design vocabulary

*tatus* v0.1.0 Draft (20260522). Owner sign-off required for timbre and final samples; Web Audio synth ships as slice 1.

R1 — The 8-cue catalog

The vocabulary is *losed*— 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/userfacingmessages.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

*efaults* 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 *ingle-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 *eb 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 *anonical 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:

  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 *lways*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 (koderwebkit) 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 NONcanonical cue ID emits a devmode 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 — selfhostedonly 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 systemtoast 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 *mbient music*(login

    screen background loop) or stay strictly transactional? Out of scope here; tracked as potential specs/sound/ambient.kmd follow-up.

Sign-off

Role Owner
Author @rpm (20260522) — structural slice
Timbre direction pending owner_
.wav sample production pending_ (slice 2)
SDK binding pending koderkit + koderwebkit follow-up tickets
Ratification pending_

Source: ../home/koder/dev/koder/meta/docs/stack/specs/sound/vocabulary.kmd