应用概述
输入:用户手动录入房间尺寸 / 形状 / 装修材质、音箱与听音位位置,或用 AR 卷尺量房(无需 LiDAR)/ LiDAR(RoomPlan)扫描得到房间几何。
- 早期反射点定位
- RT60(实测 + 模拟)与 EDT / C50 / C80 / D50(模拟 RIR 派生)全套声学指标
- 房间模态频率列表
- 吸音板布置方案(位置、数量、产品 SKU)
- 3D 可视化 + 反射能量热力图
- 安装手册级 PDF 导出
- ESS 扫频 / 拍手脉冲实测(内置麦克风或 USB 测量麦)
- USB 测量麦校准与 SPL 标定、实时频谱 RTA
- 频响叠加与多位置平均、REW 互导、PEQ / 低音炮对齐、AI 声学助手
支持矩形、L 形、U 形与任意简单多边形房型,一阶反射与面积/包含判定按真实多边形计算;2/3 阶反射与房间模态按外接矩形近似(见 §9)。
声学模型
2.1 Sabine RT60
经典 Sabine 公式:
- 房间体积(m³),多边形房型走 Shoelace 面积 × 高度,扣除柱 / 梁等障碍物体积。
- 各面表面积乘吸声系数。系数采用 6 段倍频带(125 / 250 / 500 / 1k / 2k / 4k Hz)。
- = 24·ln(10)/c,其中 c = 343 m/s(20°C)。
应用层提供 4 个装修材质预设(硬质空房 / 普通硬装 / 软装家具 / 已有处理),每个预设给出按频段的吸声系数曲线;顶部单值 RT60 估算与板数预算用中高频平均吸声系数,6 段 Sabine 曲线用于分频段显示。目标 RT60 也按 6 段曲线给(不是单值),覆盖低频允许更长尾混响的 EBU Tech 3276 / THX 等行业标准。
2.2 图像源法(Image Source Method, ISM)
第一次反射点用镜像法:把声源关于墙面镜像,求“镜像源 → 听音位”的线段与墙面的交点即反射点。
- 矩形房间:直接对 6 面平面(4 墙 + 地 + 顶)做镜像,闭式解。
- 非矩形房间(
PolygonReflectionCalculator):把RoomPolygon.walls()拆出的每一段墙做镜像 + 段内参数 s ∈ [0, 1] 校验(地板/天花反射另要求反射点 XY 在多边形内);一阶路径还做 2D 遮挡检测——声源→反射点→听音位连线穿过其它墙段即丢弃。
多阶反射(1–3 阶)由 MultiOrderReflectionCalculator 按 pyroomacoustics 的递归思路实现:
- 枚举所有“墙面序列” [s₁, …, sₙ],相邻不同墙
- 镜像源迭代:S₀ = source,Sₖ = mirror(Sₖ₋₁, sₖ)
- 从 listener 反向 trace:从 Sₙ 拉线到 listener,与 sₙ 的交点是 Pₙ;再从 Sₙ₋₁ 拉到 Pₙ,与 sₙ₋₁ 的交点是 Pₙ₋₁;一路回推
- 每个交点必须在墙面有效范围内 + 段内参数 t ∈ (0, 1)
2.3 路径能量
宽带能量按反射次数 + 距离衰减:
- α 限制在 [0, 0.99],避免 α = 1 时出现 log(0) 或 pow(负, N) = NaN 污染整条路径
- 距离衰减按球面发散 1/r²
- 转 dB 用 10·log₁₀(能量域)
分频段能量按 6 段倍频带分别算(每段用 finishProfile.absorptionByBand 的频段系数)。
2.4 RT60 估算(实测)
ImpulseResponseEstimator:
- 匹配滤波:录到的扫频先做 ESS 反向滤波(Farina 法)压成单脉冲;处理增益 ≈ 10·log₁₀(T·B),以 3 s、180 Hz–4 kHz 手机粗测档计约 +41 dB 等效 SNR
- rmsEnvelope:滑窗 RMS 包络
- Schroeder 反向积分:E_back(t) = ∫ₜ^∞ s²(τ) dτ,做 10·log₁₀ 归一化得 EDC 曲线
- 斜率线性回归:按 SNR 优先取 T30 [−5, −35 dB],不够回退到 T20 [−5, −25] 或 T10 [−5, −15]
- RT₆₀ = −60 / slope
倍频带 RT60:在匹配滤波后的样本上,按实际扫频范围选取的 ISO 倍频带中心频率 + Q = √2 做带通滤波,对每段独立跑 Schroeder。
2.5 RIR 模拟
SimulatedRIRGenerator 把多阶反射路径转成离散脉冲事件,写到 8 kHz × 0.5 s PCM 缓冲:
for path in reflectionPaths:
t = path.totalDistance / speedOfSound
amp = 10^(path.relativeEnergyDB / 20)
impulse[round(t * sampleRate)] += amp
加上直达声脉冲,再做 Schroeder 反向积分提取 EDT / RT60 / C50 / C80 / D50 五个指标。
2.6 房间模态
矩形房间模态频率:
按 nₓ, n_y, n_z 中非零的个数分类:1 个 = 轴向(axial),2 个 = 切向(tangential),3 个 = 斜向(oblique)。
枚举范围:每轴 n ≤ 4 且频率 ≤ 300 Hz。非矩形房型用外接矩形近似估算(精确模态需 FEM 求解),在 UI 上明示。
几何系统
3.1 房型支持
- 矩形 — width × length × height
- L 形 — 在指定角去掉一个 nw × nl 凹槽
- U 形 — 在指定墙中央去掉一个 nw × nl 凹槽
- 自定义 — 任意简单多边形(≥ 3 顶点,≤ 24 顶点)
所有非矩形由 RoomPolygon.from(shape:, bboxWidth:, bboxLength:) 转成 CCW 顶点列表。一阶反射、面积/周长与包含判定直接用这套顶点;非法自定义多边形(顶点 < 3 / > 24、自相交、面积近零)会静默降级为外接矩形。
3.2 多边形几何工具
- 面积 — Shoelace 公式
- 周长 — 直接累加边长
- contains(x, y) — Ray-casting + 边界 epsilon 兜底(角点处避免误报)
- clamped(point) — 矩形房型走 [0, w] × [0, l] 快速路径;非矩形走
polygon.boundingBox的真实 [minₓ, maxₓ] × [min_y, max_y],然后 polygon.contains 测,不在多边形内时二分拉向 bbox 中心 - walls() — 把多边形拆成按 inward normal 分类的墙段(自动归到 leftWall / rightWall / frontWall / backWall),方便复用矩形版面板推荐器
- PolygonValidator.isSimple — 校验非自相交简单多边形
3.3 RoomPlan 扫描
通过 RoomCaptureSession(iOS 16+ API;App 内扫描功能整体门槛为 iOS 17)调起扫描,CapturedRoomMapper 把扫描结果映射为 ScannedRoom(包含 wallSegments + openings + furniture)。坐标系映射:
扫描结果按墙段数判定:恰好 4 段墙直接按矩形处理,其余走 RectangularRoomSimplifier.polygonShapeFromScan 把 wallSegments 链成 polygon 顶点。RoomPlan 端点典型误差 3–8 cm,12 段闭环可累积 5 cm,所以闭环 epsilon 用 12 cm。链不通则降级到外接矩形 + 提示用户重新扫描。
3.4 AR 卷尺量房
无 LiDAR 的机型走 ARTapeMeasureView(RealityKit ARView 世界跟踪):准星对准墙面,两次点击记录起点 / 终点,按 宽 → 长 → 高 分步量房,最后确认生成矩形房间;含相机权限引导与追踪失败提示。
3.5 自由顶点编辑器
FreeVertexEditorView 让用户在 SwiftUI Canvas 上拖动顶点:
- 长按删除(顶点数 > 3 时,删后仍 ≥ 3)
- 拖动逐帧做
isSimple校验,自相交的新位置不生效(顶点原地停住);空白处点按可在最近边插入顶点,另有逐顶点数值输入与「重置为外接矩形」 - 0.05 m 网格吸附
- bbox 外预留 max(0.5, bbox × 0.2) 米拖动余量,让用户能把顶点拖出当前 bbox(父视图自动撑大 room.width / length)
听音区与多 listener
AcousticConfiguration.listeners 支持 1..N 个听音位(0 听音位时自动合成默认主听位并告警;0 音箱时走 §5.2 的通用模板)。声学分析把每个 listener 都跑一次镜像法,反射点合并到一个数组(primary listener 的点排在前面)。面板推荐器按每面墙的反射点 count 加权(cap = max(6, speakerCount × listenerCount)),多座位共用一面墙自然把该墙推向预算上限。
- 3D 视图中 secondary listener 节点用 opacity 0.45 + scale 0.75
- 顶视图(RoomPlanView)中 primary 反射点全色 r=5,secondary 用 0.45 透明 r=3
- 反射点列表(RecommendationView)只列 primary 的,副听位用脚注 “+ N 处反射已计入推荐方案”
多阶反射 / RIR / 热力图仍按 primary listener 一次算(per-listener O(speakers × surfaces³) 计算量太重,且 RIR 是单点指标)。
推荐系统
PanelPlacementRecommender 输出 [PanelRecommendation],每条包含:位置(左墙 / 右墙 / 顶 / 前墙 / 后墙 / 角)、数量、优先级(必装 / 强推 / 推荐 / 可选)、effectivenessMultiplier、targetReflectionPointId。
5.1 比例分配(最大余数法)
老版本用贪心 add():左墙吃完 → 右墙 0;budget 紧时天花 / 前 / 后墙也会被饿死。新版用 largest-remainder method:
- 算每面 wall 的 raw desired(反射点数 + 房间深浅 + 用例)
- floor(raw × budget / total) 作为初始分配
- raw > 0 的墙保证至少 1 块;仅当预算小于有需求的墙面数时,按优先级从低到高归零
- 总和 > budget 时按 fractional remainder 从最小开始削
- 左右墙对称 pass:raw[0] == raw[1] 时强制 alloc[0] == alloc[1]
- 剩余 leftover 按 frac 最大重分
5.2 用例差异化模板
无音箱场景下走 use-case-specific 模板:
- 会议室:天花板 35% + 后墙 20% + 两侧墙各 12%
- 播客:麦克风正前 25% + 两侧各 18% + 顶 20%
- 录音棚:四面对称压死 + 预算内尽量凑满 4 个墙角低频陷阱(真正绕过预算的强制 4 角只在 §5.3 低频模态密集路径)
- 通用听音:对称基础铺底 + 提示加音箱
5.3 物理刚需绕过预算
墙角低频陷阱在 80–150 Hz 模态密集时强制 4 根,绕过 totalPanel 限制。理由:墙角声压最大,低频陷阱效率比墙面高 ~1.5×,是物理刚需而不是预算项。
5.4 听音位近端加权
后处理把听音位 1.5 m 半径内的墙面推荐标成 .nearListener,effectivenessMultiplier ×1.15,title / reason 提示用户这是“贴身吸声敏感区”。
测量
6.1 ESS 扫频
ESS 对数扫频(Farina 法),四档预设 + 自定义,扫频时长 3 / 6 / 12 秒可选:
- 手机粗测:内置输出 180 Hz–4 kHz;外置输出 75 Hz–4 kHz(唯一支持内置扬声器的档位)
- 低频 / 低音炮:15–300 Hz(需外置输出)
- 全频:20 Hz–20 kHz(需外置输出)
- 自定义:10 Hz–20 kHz 范围内任选起止频率,至少覆盖一个倍频程(需外置输出)
- 反向滤波(
makeInverseSweep)= 时间反转 + 幅度补偿(+6 dB/倍频程,随频率上升,抵消 ESS 的粉噪频谱) - 录音用 AVAudioRecorder 写无损 Linear PCM(16-bit 单声道 .caf,约 96 dB 动态范围,感知压缩不适合脉冲测量;采样率跟随音频会话——内置麦 44.1 kHz,USB 测量麦按其原生 48 kHz)
- 读回 → 匹配滤波 → analyzeImpulse
播放信号、逆扫频与分析共用同一配置,最高频率跟随实际采样率限制;低频 / 全频 / 自定义档在没有外置输出时阻止启动。RT60 倍频带按实际扫频范围选取;预设的频率范围是激励配置,实际可信范围仍受音响、麦克风与测量质量限制。
makeInverseSweep(sampleRate:) 已参数化)——匹配滤波在内置麦 44.1 kHz 与 USB 测量麦 48 kHz 下都始终生效。6.2 拍手脉冲
不放任何声音,用户在 1 秒窗口内大力拍一次手,共录 4.5 s(约 3.5 s 尾音)。直接走 analyzeImpulse(不需要匹配滤波,因为脉冲本身就是窄脉冲)。覆盖 125 Hz 倍频带(手机粗测档从 180 Hz 起,覆盖不到 125)。
6.3 实测 vs 模拟对比
MeasuredVsSimulatedCard 拉项目最近一次实测 RT60,跟当前几何模拟 RT60 并排显示偏差:
6.4 环境噪声 & 校准
AmbientNoiseEstimator 录 3 s 静默(掐头去尾各 100 ms)算房间背景噪声:宽带平均 / 峰值 dBFS + 5 档安静度评级(未标定时 dBFS 仅作相对比较;做过 SPL 标定则显示 dB SPL)。近场延迟校准复用同一 ESS 扫频:手机放在距音箱 30 cm 内播放,取录音起点到冲激峰的延迟(含刻意的 250 ms 预滚),建议测 2–3 次——最近 3 次读数散布 < 5 ms 判高置信、< 20 ms 判中置信。延迟值绑定当前「录音输入 + 播放路由」,任一端变化即自动作废;远场测量显示自动扣除后的传播延迟。
6.5 时间参考与测量质量
- 声学时间参考:扫频前置一个 chirp 作为时间锚点,每次测量重新检测;检出失败时回退并明确标注相位可信度,不能据此宣称跨测量的绝对对齐。
- 时钟漂移:估算播放与录音通路的时钟差,满足条件时校正。
- 电气回环参考:多输入 USB 接口可用第二通道做 loopback 参考;不满足条件时回退声学参考。Mac 播放助手路径不使用本机电气回环。
- 质量字段:每条记录保存可获得的 SNR、噪声底、剪波状态、IR 时间偏移、实际输入与采样率;无法确定的字段保留为空,不编造。
- 剪波处理:扫频检出剪波即作废并提示重测;拍手记录标记剪波并提示。
- 模拟与实测对照:把房间最近一次测量与几何模拟 RT60 并排比较,辅助评估模型吻合度(阈值见 6.3)。
6.6 USB 测量麦与输出路由
测量页的输入选择器列出内置麦与 class-compliant USB 输入(如 miniDSP UMIK-1 / UMIK-2);热插拔监听 routeChangeNotification,外置麦消失自动退回内置麦。录音与扫频采样率跟随会话实际值(UMIK-1 = 48 kHz)。支持导入 REW / UMIK 风格 .txt 频率校准文件(解析 Sens Factor 与序列号元数据,对数频率域插值;房间测量建议用 90° 文件),校准按输入端口 UID 绑定——切换到不匹配的输入时不套用原校准。
一次性 SPL 参考标定:把声级校准器(默认 94 dB @ 1 kHz)套在麦克风上,App 采 3 s 电平求 dBFS → SPL 偏移;只有输入匹配且存在 SPL 标定时才显示 dB SPL,未标定保持 dBFS。测量时校准快照:新扫频 / 拍手记录保存当时的校准曲线、名称、序列号、Sens Factor、SPL 偏移与输入 UID,此后替换、清空或重新绑定校准都不会改变这些记录的读数;测量详情、IR 窗后频响预览、频响文本导出与 PDF 使用同一保存快照。校准本身按输入端口 UID 持久化到 ~/Library/Application Support/mic_calibration.json。
播放侧走系统路由选择器(AVRoutePickerView 封装,放在扫频 / 近场校准卡上),session 放开 AirPlay / 蓝牙 A2DP / HFP,可经外置音响播放测试音;无外置输出时强制扬声器。录音输入总是显式钉住用户所选(USB 麦或内置麦),防止带麦蓝牙设备在播放时抢走输入、污染测量。Mac 播放助手:通过 Bonjour 发现局域网内的 AcouveroMacSpeaker,先把扫频 WAV 传到 Mac 再发送播放命令,iPhone 负责录音——适用于不能直接承接测量会话音频的 Mac 输出路径(该路径不使用本机电气回环)。
6.7 REW 与文件互操作
- 频响文本导入 / 导出:兼容常见分隔符、注释行、科学计数法与小数格式,导入时校验频率轴;文件没有相位列时不生成相位数据。
- 校准口径导出:存在有效 SPL 标定快照时导出 dB SPL,否则导出 dBFS,文件头如实标注单位。
- IR 音频导入:支持 WAV / AIFF 脉冲响应;多声道文件必须显式选择所用通道。
- 离线扫频分析:导出激励信号文件供外部播放,导入录音后须明确起止频率与时长再解卷积——不猜测扫频参数。
- 音频分享导出:可把可用的 IR 或原始录音以 WAV 分享出去。
- 导入结果可归档并进入适用的图表、叠加与分析流程;是否能参与测量组由数据类型与可信口径决定。目前不解析 REW
.mdat工程文件。
测量图表与分析
测量详情页按记录实际具备的数据展示图表,不补造缺失曲线;图表支持深浅色、动态字体与无障碍摘要。
- RT60:倍频段柱状图、目标线与状态色。
- Schroeder 衰减:EDC 曲线与 T20 拟合区间。
- 频率响应:对数频率轴、倍频程平滑、光标读数;校准快照有效时以 dB SPL 显示。
- 脉冲响应:波形、时间零点与峰值标记;幅度为相对峰值。
- 相位:wrapped / unwrapped 视图、极性反转、时间零点微调与参考可信度提示。
- 群延迟:有效频段群延迟,可选最小相位 / 过剩相位分析。
- IR 窗函数:peak 或 t=0 参考、左右窗宽,矩形 / Hann / Tukey / Blackman;原始与窗后频响并排预览,窗处理与相位微调只作用于预览,不改写原始测量。
- 频谱图:IR 时频衰减,时间范围与动态范围可调。
- 瀑布图与 RT60 Decay:低频衰减可视化,窗长 / 时间 / 动态范围可调;质量不足的拟合不显示。
- 实时频谱 RTA:1/1–1/24 倍频程显示,A / C / Z 计权,即时 / Fast / Slow 响应,峰值保持与清除,综合声级、峰值电平与主要频率。
- RTA 使用匹配输入的校准,未完成 SPL 标定时标为 dBFS;与正式测量互斥(进入时释放测量页电平监听,关闭后恢复),停止或切换输入后旧回调不覆盖新状态。
测量历史、多位置平均与叠加
- V2 归档:测量保存元数据、可用的原始录音 / IR sidecar 与分析数据;旧格式记录继续只读兼容,没有原始数据的旧记录不补造。
- 历史管理:按房间查看、删除测量,进入详情或选择记录做后续分析。
- before / after 对比:任选两条记录,对比整体与倍频段 RT60、频响叠加与有效频段差异。
- 2–6 条频响叠加:1/3–1/48 oct 平滑、逐条独立显隐、全频 / 低频等区间预设;电平可不对齐、中频归零或对齐首条,图例显示偏移;原始数据保持不变,不可叠加的记录给出原因。
- 测量组:记录位置标签、权重与 before / after 阶段;可从历史建组、加入组,或测量新位置后直接归组。
- 多位置平均:dB 算术平均、RMS 功率平均,以及具备可靠时间参考与复谱时的 vector 复数平均;显示平均线、成员曲线与逐频 min/max 空间范围。
- 组对比:对比两组的平均频响、空间范围与 RT60 均值,给出空间一致性结论。
PEQ 与低音炮对齐
9.1 PEQ 目标匹配
- 参数化目标曲线:目标电平、低频上扬、高频下倾与匹配频段。
- 滤波器约束:滤波器数量、最大提升 / 衰减与 Q 范围。
- 预测与理由:同图显示测量、目标、滤波器响应与预测结果曲线,给出改善指标与逐条滤波器推荐理由。
- 深陷波排除:声学相消造成的深陷波区域自动排除,避免用大幅提升去填。
- 导出:Generic 文本、Equalizer APO、miniDSP 系数与 CamillaDSP YAML——只提供计算与设置导出,不直接写入设备。
目前尚无手动滤波器编辑器、目标预设库或 EQ 结果归档。
9.2 两路与多低音炮对齐
- 两路对齐:选择参考音箱与可调低音炮两条测量,预测复数求和,计算延迟、增益与极性;显示对齐前后的求和曲线。
- 多炮多座位:1–4 个低音炮,按座位 × 声源选择独立测量;支持全座位目标、最差座位限制,也可显式仅优化主座位。
- 结果与分享:逐炮设置、逐座位改善、平均及最差指标,支持设置文本分享。
- 数据门控:缺少扫频配置、可靠时间参考或 IR 的记录会列出不可用原因,而不是静默排除。
多座位结果目前为数值表,尚无逐座位求和曲线叠加;对齐是预测,需实际应用设置后复测验证;跨采样率声源混用仍待验证。
AI 声学助手
测量历史工具栏提供聊天入口,使用用户自己的 Anthropic API Key(保存在本机 Keychain);助手把请求所需的对话与工具数据发送给 Anthropic,API 费用由用户自己的账户承担。
- 对话界面:流式回复、工具执行状态、停止与清空、用量 / 费用估算。
- 测量数据读取:列出记录、摘要与局部频响、测量对比。
- 计算工具:调用与手动界面完全相同的 PEQ 推荐与低音炮对齐算法。
- 房间与布局评估:列出房间、读取音箱 / 听音位与模态信息,在项目副本上预测移动既有设备或调整装修吸声概况后的变化——不改写已保存项目。
- 经确认发起测量:AI 提出用途与可选房间,用户确认后才启动扫频;拒绝或停止即取消请求。
- 共享测量设置:AI 与手动测量复用同一工作区,继承输入、Mac 播放助手、扫频范围 / 时长、延迟与回环设置;聊天页显示实时电平 / 进度,支持取消,结束后恢复原房间与测量组上下文。
- 会话恢复:保存并恢复一条当前会话,恢复时标记上次中断的工具执行;清空会话同时清除历史与用量。
可视化与输出
11.1 3D 视图
Room3DController 用 SceneKit 渲染:墙面 / 地面 / 顶面 / 音箱节点(薄板 cabinet + accent tick + channel pill)/ 听音位(FE glyph + drop stem)/ 障碍物(半透明 SCNBox)/ 推荐面板(按 wall 分布的吸音板 mockup)/ 多阶反射路径线段 / 反射能量热力图叠加。
11.2 反射能量热力图
ReflectionEnergyMapGenerator 把每条多阶反射路径的能量按经过的墙累加到该墙的 uCells × vCells 网格(默认 12×6)。每面墙一张能量图,3D 视图上叠加成能量色层(蓝 → 青 → 绿 → 黄 → 红)。
EnergyHeatmapRenderer 把网格转 UIImage:16 px/格 block 渲染(cell 间不插值,便于辨认网格边界;上墙平滑靠 SceneKit 纹理双线性过滤)+ 自绘 5 段色带(蓝→青→绿→黄→红)+ alpha 按能量在固定 −30…0 dB 标尺上的位置从 0.20 到 0.85 线性映射(−∞ 全透明)。
11.3 RIR 卷积预览
RIRPreviewPlayer 用 AVAudioEngine + AVAudioPlayerNode 把程序合成的干信号(拨弦 / 击掌 / 类元音语音,无内置采样)卷积模拟 RIR 后播放,让用户能听到“如果不装吸音板 / 装了之后”房间听感的差异。
时域卷积 RIRConvolver.convolve(dry:rir:) 直接做 O(N × M)(N = dry samples,M = RIR samples)。归一化按峰值 0.9 防止 clip。
11.4 PDF 安装手册
ReportExporter 用 UIGraphicsPDFRenderer 渲染 A4 PDF(595 × 842 pt):
- 房间概览 + 3D 快照
- 6 段 RT60 条形图:Sabine 估算 vs 目标,具备实测数据时叠加实测值
- 反射点表 + 模态表 + 风险摘要
- 推荐面板表(购物清单 BOM 在 App 内展示,不进 PDF)
- 具备测量数据时加入 RT60 / EDC / 频响 / 频谱 / 瀑布 / 相位图表与多位置平均摘要;频响单位使用测量时保存的校准快照(dB SPL 或 dBFS 如实标注)
- Geist 字体 + PingFangSC fallback(CoreText cascadeList,避免 CJK 渲染成 □□□)
11.5 项目持久化
ProjectStore(actor)写 ~/Library/Application Support/Acouvero/projects.json:[RoomProject],每条含完整 Room + speakers + listeners + recommendations + panelCountOverrides 等。首次启动自动从旧目录 FindEcho/ 迁移(FindEcho 是产品改名前的内部代号,代码符号仍沿用旧名)。
MeasurementStore(actor)写 ~/Library/Application Support/Acouvero/measurements.json:[MeasurementResult],按 roomId 关联到 project。
两者都有元素级 decode 恢复(Throwable<T> 包装)— 单条记录损坏不会让整个列表加载失败。
MicCalibrationStore 写 ~/Library/Application Support/mic_calibration.json(根目录,无子目录):REW 校准曲线 + SPL 偏移,按输入端口 UID 绑定,换输入需重新导入 / 标定。
工程实现要点
12.1 SwiftUI + @Observable
ViewModel(FindEchoViewModel)用 Swift 5.9 @Observable macro,状态字段直接观测,没有 Combine。AcousticAnalysis 计算结果按 AcousticConfiguration Equatable 比较做缓存:
var analysis: AcousticAnalysis {
let cfg = configuration
if let cached = cachedAnalysis, cachedConfiguration == cfg {
return cached
}
let result = analysisService.analyze(cfg)
cachedConfiguration = cfg
cachedAnalysis = result
return result
}
12.2 Actor 隔离
ProjectStore 与 MeasurementStore 是 actor(IO / 异步逻辑天然串行化),RoomPlanService 是 @MainActor 类。UI 调用方用 async / await 拿结果,主线程不会被磁盘 IO 阻塞。
12.3 AVAudio 生命周期
AudioSessionManager 集中管理 session 类别(.playback for RIR preview,.playAndRecord for 测量)。打断保护两条路径都有:测量侧 AudioMeasurementService 内置 InterruptionMonitor 监听 AVAudioSession.interruptionNotification,扫频 / 拍手 / 环境噪音任一路被电话 / Siri 打断即作废结果并报错;RIR 预览侧 RIRPreviewPlayer 同样监听打断,自动停 player node 并清 isPlaying。测量中途音频路由变化也会作废本次测量。
12.4 Task 取消
播放卷积是后台任务,用户连点多个 button 时 pendingPlayTask?.cancel() + Task.isCancelled 双重 check 防止乱序回 MainActor 写错 state。
12.5 测试覆盖
AllShapesAndUseCasesTests 用 Swift Testing 框架跑全场景矩阵:每种房型 × 每种用例(单听音位;音箱按用例取立体声对或单只居中),验证:
analysis.reflectionPaths.count >= 1analysis.warnings包含对非矩形房型的近似说明- 推荐板数总计非负(totalPanelCount ≥ 0;无预算相符断言)
- 自相交自定义多边形安全回退(不 crash,降级为矩形)
- 顶点 < 3 等边界情况的回退
12.6 平台与本地化
部署目标 iOS 17.0,iPhone + iPad;零第三方依赖(无 SPM / CocoaPods 包),只用系统框架(SwiftUI / SceneKit / ARKit / RoomPlan / AVFoundation / Accelerate)。本地化用 .xcstrings 字符串目录:源语言中文,另有 English / Français / Deutsch / Español / Português (Brasil) 共 6 种,App 内可跟随系统或手动切换。
已知限制
- 多阶反射在非矩形房间用外接矩形近似 — 凹角附近 2-3 阶 flutter / slapback 风险评估可能偏差。1 阶反射点本身按真实多边形几何。
- 房间模态在非矩形房间按外接矩形估算 — 精确模态需 FEM 求解。
- iPhone 内置麦克风测量 — 非校准麦克风,频率响应 / 灵敏度不平直;可信度始终标为
.low,作为与几何估算的交叉参考用。接 miniDSP UMIK-1 / UMIK-2 等 USB 测量麦并导入其 REW 校准文件时,分带电平按校准曲线修正;再用 94 dB 参考源做一次 SPL 标定后,环境噪声与测量电平可显示为绝对 dB SPL(未标定时只显示 dBFS)。 - 单声源 ISM 假设 — 没考虑音箱箱体衍射、号筒指向性等真实音箱特性。
- 不模拟低频陷阱 — 80 Hz 以下低频问题在 Sabine 模型外,需要专门的低频处理。
- AR 扫描精度 — RoomPlan 端点典型误差 3-8 cm,12 段闭环可累积 5+ cm。墙厚未建模。
- 分析口径边界 — 跨采样率的 vector 复数平均暂不支持;多座位对齐结果目前是数值表,尚无逐座位求和曲线叠加;频响叠加图尚未进入 PDF 报告。
- 尚未实现的专业工具 — 无 SPL Logger 长期记录、STI 语言清晰度、失真(THD)测量;阻抗 / Thiele-Small 与 REST API 也未实现。
- 待真机验收 — AI 声学助手的真实 API 链路(真实 Key 的流式对话 / 确认测量 / 录音归档)与 UMIK 测量麦真机全链路(含 UMIK-2 96 kHz)仍待验收;模拟器通过不代表真机音频闭环。
参考
- Sabine, W. C. (1922). Collected Papers on Acoustics. Harvard University Press.
- Allen, J. B., & Berkley, D. A. (1979). Image method for efficiently simulating small-room acoustics. JASA, 65(4), 943–950.
- Schroeder, M. R. (1965). New method of measuring reverberation time. JASA, 37(3), 409–412.
- Farina, A. (2000). Simultaneous measurement of impulse response and distortion with a swept-sine technique. Audio Engineering Society Convention 108.
- Scheibler, R., Bezzam, E., & Dokmanić, I. (2018). Pyroomacoustics: A Python package for audio room simulation and array processing algorithms. ICASSP 2018.
- EBU Tech 3276. Listening conditions for the assessment of sound programme material.
- ITU-R BS.1116. Methods for the subjective assessment of small impairments in audio systems.
- BB93 — Acoustic design of schools (UK).
- THX Pro Cinema Certification requirements.