公共警报声音识别 · 专家技能
适用对象与核心约定
本技能服务于仓库 sound-recognition(默认工作路径 D:\WorkBuddy\sound-recognition,pnpm + turbo monorepo,6 个 package)。动手前先记住四条不可违背的架构约定:
- 不内置音频文件:小程序体积受限,所有声音样板由
utils/audio-synth.ts实时合成 PCM(16kHz、单声道),试听与识别同源——改AudioSpec会同时改变听感和识别结果。 - 本地离线优先:识别全程在端上完成(
utils/recognizer.ts),云端只是增强;任何功能都必须有离线兜底,不得把结论完全交给网络。 - 安全语义优先:这是应急科普工具。识别不确定时必须明确"存疑"并提示以官方发布与现场指挥为准;行动指南区分「立即 / 后续 / 禁止」三类,禁止编造标准号与处置流程。
- 改数据即改行为:
data/sounds.ts既是展示数据,也是识别引擎的比对基准。新增一条声音等于往分类器里加一类,必须过识别测试。
项目地图(详见 references/project-map.md)
| 位置 | 职责 |
|---|---|
apps/miniprogram |
微信小程序(首页/声音库/详情/识别/学习/求助 6 页 + utils + data + tests) |
apps/web |
React + Vite 前端骨架(尚未完善) |
packages/shared-types |
全仓共享类型、常量、Zod schema |
packages/shared-algorithms |
共享 DSP / 特征提取 / 分类算法 |
packages/inference-engine |
推理引擎抽象层(TFJS / ONNX / TFLite / Cloud 适配器) |
services/cloudbase |
云函数:sounds recognize user_progress model_management ai_answer |
常用命令(仓库根目录,turbo 并行):
pnpm run typecheck # 全仓 tsc --noEmit
pnpm run lint # ESLint flat config
pnpm run test # 全仓 vitest run
pnpm run build # 全仓构建
只跑小程序测试:cd apps/miniprogram && npx vitest run tests/audio.test.ts(约 16 秒,含 60 种样板识别 + 鲁棒性)。
工作流 A:扩充或修改声音库
步骤
- 查清单:
node scripts/soundlib.mjs report --category <分类>查看现有参数分布,避免撞车。 - 选参数:按下面的"参数选型规则"挑一组(频率、波形、模式、鸣/停),优先避开已有(主频 ±13% 内、节奏 ±20% 内)的组合。
- 预检:
node scripts/soundlib.mjs check --spec '<json>'(或--candidates file.json)。脚本复刻识别引擎打分,输出候选与现有 60 条的双向相似度,并按"与自身得分的分差"判风险(分差 <0.12 危险 / <0.3 警戒 / 其余安全)。先预检再写数据,能省掉反复跑测试的十几秒。 - 写条目:从
assets/new-sound-entry.ts复制模板,填入PresetSound全部字段(字段规范见references/sound-library.md)。 - 体检:
node scripts/soundlib.mjs verify检查字段完整性、取值合法性与组合约束。 - 跑测试:
npx vitest run tests/audio.test.ts tests/sounds.test.ts,确认新声音 top-1 正确且confident === true(含噪声、音量衰减、中途起录三种扰动)。 - 回归:
pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build。
参数选型规则(经验值)
- 主频:
buzzer/bellharmonics 丰富,适合 1000Hz 以上;pure适合"叮咚"类双音;speech(语音广播)必须落在 500–950Hz——高于约 1000Hz 时主频检测会漂移到其他频段,导致该声音识别不到自己(历史事故:sv_heat_wave曾用 1400Hz,实测主频被检成 880Hz)。 - 节奏:
on/off是区分度最高的维度。新声音尽量让on与off与所有同类信号在 log2 尺度上拉开 2 倍以上;实在拉不开就换主频。 - 双音:
twoTone(持续交替,off: 0)靠freq + altFreq区分,注意altFreq与主频的比值不要与已有双音雷同;chime(叮咚)靠freq + altFreq + on/off,同节奏的 chime 极易混淆(历史事故:880Hz/0.5-0.5 与既有 900Hz/0.5-0.5 只差分差 0.076,达不到 0.12 阈值)。 - continuous(长鸣,
off: 0)与 pulse 类靠"有无停顿"区分,长鸣之间只能靠频率拉开。 - 语义不重复:新增前先检索名称与标签,避免与既有条目语义重复(历史事故:新增"铁路道口警报"与既有"铁道口警示"重复)。
已收录 60 条的完整参数
见 references/sound-library.md(含每条的主频、波形/模式、鸣停、紧急度)。
工作流 B:识别引擎调优与误判排查
引擎在 apps/miniprogram/utils/recognizer.ts,核心是"特征提取 + 五维加权几何平均打分":
| 维度 | 权重 | 要点 |
|---|---|---|
| 频率 freqScore | 0.30 | gauss(log2(观测/标准), σ=0.13);扫频按中心频率 σ=0.25 并比对跨度;双音需匹配第二音高 |
| 音色 toneScore | 0.10 | 规格没写双音却观测到第二音高 → 0.5,反之 0.45 |
| 鸣时长 onScore | 0.20 | gauss(log2(观测/标准), σ=0.6);只观测到截断段时按下界处理(超长即视为矛盾) |
| 间隔 gapScore | 0.25 | 同上;连续长鸣出现停顿会被重罚 |
| 模式 patternScore | 0.15 | continuous / pulse / twoTone / sweep / countdown / chime 各自的形态判据 |
判定门槛:ACCEPT_SCORE = 0.62、MARGIN = 0.12(与次优候选的分差)、MIN_TONALITY = 2.2(低于此值视为环境噪声,不给结论)、CONFIRM_RATIO = 0.6(录音短于周期 0.6 倍时提示延长监听)。
排查步骤:
- 复制
assets/diagnose.test.ts到apps/miniprogram/tests/,用 vitest 跑它,打印每个声音的 top-1、次优与分差,以及实际观测到的dominantFreq / secondFreq / onDurations / offDurations。 - 判读:
- top-1 不是自己 → 参数撞车,按工作流 A 改参数(优先改节奏,其次改频率)。
- top-1 正确但confident=false→ 分差不足或自身得分不足 0.62;拉大与次优的频率或节奏距离。
- 观测主频与spec.freq差很远 → 合成波形或频段选得不对(语音类最常见)。 - 改完删掉诊断文件,跑完整测试。
更多细节与调参陷阱见 references/recognition-engine.md。
工作流 C:AI 问答与 API Key
求助页问答按三级降级,任何一级失败都不许给用户空白答案:
- 自备 AI(
utils/ai.ts):用户在求助页「AI 设置」里填接口地址 / API Key / 模型名,密钥只存本机 storage(keysr_ai_settings),客户端直连 OpenAI 兼容的/chat/completions(30 秒超时,401/404/429 等有中文可读提示)。 - 云端:
utils/cloud.ts调云函数ai_answer的answeraction(需在 CloudBase 部署并配置大模型)。 - 本地:
utils/ask.ts的answerLocal知识库结论,永不失败。
扩展点:新增模型/厂商只需改 utils/ai.ts 的 DEFAULT_AI_SETTINGS 与 normalizeBaseUrl;改提示词改 AI_SYSTEM_PROMPT(务必保留"不确定要说明、以官方发布为准"的约束)。详见 references/ai-question-answering.md。
代码规范速查(最容易踩的三条)
- ESLint
curly: ["error", "all"]:单行if也必须写大括号;项目里普遍写成if (x) {return y;}的一行式。 @typescript-eslint/consistent-type-imports: error:类型必须用import type { X } from '...'。- Prettier:单引号、2 空格、行宽 100、尾分号、LF 换行;
no-console仅允许warn/error/info。
小程序端 tsconfig 是 CommonJS + noImplicitAny: false,与其他包(ESM、严格)不同,跨包复制代码时注意差异。测试里没有全局 setup,wx 由各测试文件自行 mock,模板见 assets/wx-mock.ts 与 references/workflows-and-testing.md。
资源索引
| 资源 | 用途 |
|---|---|
scripts/soundlib.mjs |
声音库清单 / 字段体检 / 新增参数冲突预检(纯 Node,无需构建) |
scripts/validate-plugin.mjs |
插件包自检:plugin.json、SKILL.md frontmatter、文档相对引用(上传前跑) |
references/project-map.md |
目录结构、包职责、命令、配置约束、云函数清单 |
references/sound-library.md |
60 条声音全表、PresetSound 与 AudioSpec 字段规范 |
references/recognition-engine.md |
特征提取、五维打分、阈值、调参陷阱、诊断流程 |
references/ai-question-answering.md |
三级降级、自备 API Key、云函数与测试 mock |
references/workflows-and-testing.md |
测试清单、wx mock 三档模板、发布检查表 |
assets/new-sound-entry.ts |
新声音条目模板(含全部字段与注释) |
assets/diagnose.test.ts |
识别诊断测试模板(打印 top-1 / 次优 / 分差 / 观测特征) |
assets/wx-mock.ts |
三档 wx 环境 mock 模板 |