---
title: "Sound 交互音效"
description: "默认关闭的合成 UI 反馈音"
category: Foundations
status: beta
since: 0.6.0
tags: [sound, audio, feedback, utils]
syncStatus: reviewed
verified: true
---

## API 参考

| 名称 | 说明 |
|---|---|
| `configureSound({ enabled, volume })` | 开关与主音量（0–1，越界夹取），返回生效后的配置 |
| `getSoundConfig()` | 当前配置的副本 |
| `playSound(type \| preset)` | 播放，返回是否真的播了 |
| `sound.click()` / `.key()` / `.toggle()` / `.success()` / `.error()` / `.open()` / `.close()` | 快捷方法 |
| `isSoundSupported()` | 浏览器是否支持 Web Audio |
| `disposeSound()` | 关闭音频图，下次播放时重建 |
| `SOUND_PRESETS` | 预设表，可读取参数或作为自定义起点 |

## 默认关闭

```ts
import { configureSound, sound } from '@talex-touch/tuffex/utils'

configureSound({ enabled: true, volume: 0.6 })
sound.click()
```

- 未开启时不发声，也不创建 `AudioContext`。
- 音频图懒建：浏览器会挂起非用户手势中创建的 `AudioContext`，从不播放的页面不应持有一个。

## 为什么用合成

几个振荡器加一条包络：零打包体积，任意采样率下不糊，也不会 404；代价是音色不如采样丰富。

## 预设

七个，刻意不多。峰值与时长经 `OfflineAudioContext` 逐样本实测。

| 预设 | 用途 | 峰值 | 时长 |
|---|---|---|---|
| `click` | 按钮按下 | 0.103 | 40ms |
| `key` | 文本框逐字符 | **0.056** | 27ms |
| `toggle` | 开关 / 复选框打开 | 0.089 | 65ms |
| `success` | 动作完成（上行两音） | 0.090 | 188ms |
| `error` | 动作被拒（下行两音） | 0.100 | 216ms |
| `open` | 面板 / 对话框打开 | 0.078 | 91ms |
| `close` | 面板 / 对话框关闭 | 0.077 | 91ms |

- `key` 峰值最低：它按字符触发，用 `click` 的音量会变成打字机。
- `open` / `close` 的峰值与时长镜像，只有滑音方向相反：方向承载语义，音量不承载。
- 全部不削波，峰值都在 0.06–0.11。
- 时长预算由测试守住：即时反馈（`click` / `key` / `toggle`）≤ 80ms，状态提示（其余）≤ 250ms。

## 自定义

`playSound` 也接受现场构造的音型：

```ts
import { playSound } from '@talex-touch/tuffex/utils'

playSound({
  layers: [
    { wave: 'sine', freq: [400, 700], gain: 0.09, decay: 0.12 },
    { wave: 'sine', freq: 900, gain: 0.06, decay: 0.1, delay: 0.08 },
  ],
})
```

`freq` 给两个值即为滑音；`delay` 让第二个音错开，构成两音音型而非和弦。

## 契约

- 永不抛错：关闭、不支持或浏览器尚未见到用户手势时，`playSound` 静默返回 `false`。
- 被自动播放策略挂起的 context 在下次播放时 `resume()`，失败也不抛。
- 每个声音结束后断开自己的包络节点，连打不会在主增益上堆积死节点。
- 噪声缓冲复用，不按次生成。
- 主音量改动立即作用于现有音频图，无需重建。

## 和震动的关系

`useVibrate` 是同层的另一条反馈通道，形态一致（预设表 + 主函数 + 快捷对象）。同一动作不要同时触发音效和震动：双重反馈读起来像触发了两次。

<TuffDocSourceLink />
