波形回放
波形数据的实时回放与设备命令映射
高频广播系统
BroadcastStrategyManager 是波形回放的核心组件,负责将波形数据点转换为蓝牙广播指令。它实现了频率控制、智能去重、数据压缩等优化策略,确保设备响应既及时又稳定。
核心工作流程
波形数据点 BroadcastStrategyManager 蓝牙设备
│ │ │
│ addDataPoint(point) │ │
│ ───────────────────────────▶ │ │
│ │ │
│ shouldBroadcast(point) │
│ ┌─────────┴─────────┐ │
│ │ 频率控制 │ │
│ │ 档位变化检测 │ │
│ │ 停止指令去重 │ │
│ └─────────┬─────────┘ │
│ │ │
│ executeBroadcastDirectly(point) │
│ │ │
│ generateBroadcastCommand(point) │
│ ┌─────────┴─────────┐ │
│ │ intensity → 档位 │ │
│ │ 查找长指令 │ │
│ └─────────┬─────────┘ │
│ │ │
│ bluetoothService.broadcastCommand() │
│ │ ─────────────────────────────▶ │
│ │ BLE Advertisement │模式预设与广播间隔
系统为不同的使用场景提供了预设的广播策略配置:
划屏模式 (swipe)
{
maxHistoryPoints: 300,
similarityThreshold: 0.95,
compressionRatio: 0.2,
broadcastInterval: 150, // 150ms 间隔,快速响应手势
enableSmoothing: true,
enableCompression: true,
useSimplifiedMode: true
}适用于用户手指滑动控制场景。高相似度阈值 (0.95) 避免微小抖动导致的重复广播,150ms 的广播间隔保证了手势的实时反馈。
音乐模式 (music)
{
maxHistoryPoints: 500,
similarityThreshold: 0.85,
compressionRatio: 0.4,
broadcastInterval: 200, // 200ms 间隔,跟随音频节拍
enableSmoothing: true,
enableCompression: true,
useSimplifiedMode: true
}适用于音频节奏同步场景。500 个历史数据点的缓存容量可以记录较长的音乐波形数据。相似度阈值较低 (0.85),允许更多的节拍变化通过。
视频模式 (video)
{
maxHistoryPoints: 400,
similarityThreshold: 0.90,
compressionRatio: 0.3,
broadcastInterval: 200, // 200ms 间隔,跟随视频内容
enableSmoothing: true,
enableCompression: true,
useSimplifiedMode: true
}适用于视频内容同步场景。视频模式有专用的数据入口 addDataVideoPoint() 和独立的去重逻辑 shouldBroadcastVideo(),针对视频伪随机算法产生的小幅抖动进行过滤。
声控模式 (voice)
{
maxHistoryPoints: 300,
similarityThreshold: 0.90,
compressionRatio: 0.3,
broadcastInterval: 150, // 150ms 间隔,快速响应声音变化
enableSmoothing: true,
enableCompression: true,
useSimplifiedMode: true
}适用于环境声音控制场景。高相似度阈值 (0.90) 过滤环境噪音引起的频繁变化,150ms 间隔确保声音变化的及时响应。
强度到指令的映射
广播设备的指令生成
generateBroadcastCommand() 方法将波形强度值转换为蓝牙广播指令:
// 1. 强度为 0:发送停止指令
if (dataPoint.intensity === 0) {
const stopCommands = getStopCommand(modelName)
for (let cmd of stopCommands) {
await bluetoothService.broadcastCommand(cmd)
}
return
}
// 2. 强度 > 0:映射到长指令档位
// intensity 值 (1-3) 映射为档位索引 (0-2)
const intensityIndex = Math.max(0, Math.min(2, Math.floor(intensity) - 1))
// 根据设备品牌获取对应的长指令集
const modes = getClassicModes(modelName)
const command = modes[intensityIndex].command
// 通过蓝牙服务发送广播
await bluetoothService.broadcastCommand(command)点对点设备的指令生成
对于点对点连接的设备,波形数据通过 function-command-generator 模块进行指令映射:
// 1. 根据选中的功能和强度,生成指令序号数组
const commandArray = generateFunctionSequenceArray(
selectedFunctionIntensities, // Map<功能键, 强度值>
deviceFunctions // 设备支持的功能列表
)
// 示例: [2, 0, 1] 表示功能1强度2,功能2关闭,功能3强度1
// 2. 将指令序号数组转换为蓝牙命令
const commands = convertArrayToBluetoothCommands(commandArray, deviceFunctions)
// 查找每个功能对应强度的实际 Hex 命令
// 3. 私有协议设备:使用 PrivateProtocol 编码
const uint8Array = PrivateProtocol.makeMotorControlFromArray(commandArray)
const hexCommand = PrivateProtocol.toHexString(uint8Array)去重与节流机制
通用模式的广播过滤 (shouldBroadcast)
通用模式(swipe、music、voice)使用以下过滤规则判断是否发送广播:
| 条件 | 行为 | 说明 |
|---|---|---|
intensity === 0 且上次也是 0,间隔 < 500ms | 跳过 | 停止指令去重,500ms 窗口内不重复发送 |
intensity === 0 且不满足上述条件 | 发送 | 停止指令优先通过 |
| 首次广播(无历史记录) | 发送 | 无条件通过 |
| 强度变化 >= 1.0 | 发送 | 档位发生变化,立即广播 |
| 强度变化 < 1.0 | 跳过 | 相同档位不重复发送(长指令会持续执行) |
关键设计:由于系统使用长指令,设备收到指令后会持续执行直到收到新指令或停止指令。因此,相同档位无需重复发送,只需在档位变化时发送新指令。
视频模式的广播过滤 (shouldBroadcastVideo)
视频模式有更精细的过滤逻辑,针对视频伪随机算法产生的噪音进行优化:
| 条件 | 行为 | 说明 |
|---|---|---|
intensity === 0 且上次也是 0,间隔 < 50ms | 跳过 | 更短的停止指令去重窗口 |
intensity === 0 且不满足上述条件 | 发送 | 停止指令优先通过 |
| 首次广播 | 发送 | 无条件通过 |
| 时间间隔 < 20ms | 跳过 | 最小广播间隔限制 |
| 强度差 < 0.5 且间隔 < 40ms | 跳过 | 微小变化过滤 |
| 强度差 < 1.0 且间隔 < 60ms | 跳过 | 伪随机噪音过滤 |
| 其他情况 | 发送 | 正常广播 |
视频模式的核心区别:
- 更短的停止指令去重窗口:50ms(通用模式为 500ms),因为视频可能快速切换开关状态
- 最小广播间隔:20ms,比通用模式更密集
- 渐进式噪音过滤:分两级过滤微小变化(< 0.5)和小幅抖动(< 1.0)
数据压缩
当 useSimplifiedMode 为 false 时,系统启用压缩缓冲模式。数据点进入压缩缓冲区,当缓冲区达到 5 个点时,使用加权平均算法压缩为 1 个点:
// 加权平均压缩:越新的数据点权重越高
points.forEach((point, index) => {
const weight = (index + 1) / points.length // 权重递增
totalWeight += weight
weightedIntensity += point.intensity * weight
})
const compressed = {
intensity: Math.round(weightedIntensity / totalWeight),
timestamp: latestTimestamp,
metadata: { compressed: true, originalCount: points.length }
}默认所有预设模式均启用简化模式(useSimplifiedMode: true),直接跳过压缩缓冲区进行广播,以获得最低延迟。
自动停止机制
广播执行后,系统会启动一个 2 秒的自动停止定时器:
广播指令发出
│
├─── 设备开始执行振动
│ isDeviceActive = true
│
│ ┌─── 如果 2 秒内收到新指令 ───┐
│ │ 清除旧定时器 │
│ │ 执行新指令 │
│ │ 重置 2 秒定时器 │
│ └─────────────────────────────┘
│
│ ┌─── 如果 2 秒内无新指令 ─────┐
│ │ isDeviceActive = false │
│ │ 记录虚拟停止数据点 │
│ │ 同步 UI 状态 │
│ └─────────────────────────────┘
▼停止数据点会自动记录到历史数据中,但不会触发新的广播:
const stopDataPoint: BroadcastDataPoint = {
intensity: 0,
timestamp: Date.now(),
metadata: {
autoStop: true,
reason: 'device_2s_timeout'
}
}历史数据与波形显示
BroadcastStrategyManager 维护了完整的历史数据,支持波形可视化显示:
| 方法 | 说明 |
|---|---|
getHistoryData() | 获取完整的历史数据数组 |
getCompressedHistoryData(targetPoints) | 按等距采样压缩历史数据,默认 200 个数据点 |
getSmoothHistoryData(windowSize) | 对历史数据应用滑动窗口平均平滑算法,默认窗口大小 5 |
历史数据上限由 maxHistoryPoints 配置控制,超出后自动丢弃最旧的数据。
完整使用示例
以下示例展示如何使用 BroadcastStrategyManager 实现音乐同步波形回放:
import {
BroadcastStrategyManager,
BroadcastPresets,
type BroadcastDataPoint
} from './broadcast/broadcast-strategy'
// 1. 创建策略管理器(使用音乐模式预设)
const manager = new BroadcastStrategyManager(BroadcastPresets.music)
// 2. 检查蓝牙是否就绪
if (!manager.isBluetoothReady()) {
const success = await manager.reinitializeBluetooth()
if (!success) {
console.error('蓝牙初始化失败')
return
}
}
// 3. 接收音频分析数据,添加波形数据点
function onAudioAnalysis(amplitude: number) {
// amplitude: 0-100 的音频振幅值
// 映射到设备强度: 0 = 停止, 1-3 = 设备档位
let intensity = 0
if (amplitude > 70) intensity = 3
else if (amplitude > 40) intensity = 2
else if (amplitude > 10) intensity = 1
const dataPoint: BroadcastDataPoint = {
intensity,
timestamp: Date.now(),
metadata: { source: 'music' }
}
// 策略管理器自动处理频率控制和指令生成
manager.addDataPoint(dataPoint)
}
// 4. 视频同步场景(使用专用方法)
function onVideoFrame(amplitude: number) {
const dataPoint: BroadcastDataPoint = {
intensity: Math.min(3, Math.ceil(amplitude / 33)),
timestamp: Date.now(),
metadata: {
source: 'video',
amplitude,
videoPosition: videoPlayer.currentTime
}
}
// 视频专用入口,使用独立的去重逻辑
manager.addDataVideoPoint(dataPoint)
}
// 5. 停止播放
function stopPlayback() {
manager.addDataPoint({
intensity: 0,
timestamp: Date.now(),
metadata: { source: 'music' }
})
}
// 6. 清理资源
function cleanup() {
manager.clear()
}
// 7. 获取性能统计
const stats = manager.getPerformanceStats()
console.log(`数据点: ${stats.totalPoints}, 队列: ${stats.queueLength}`)动态切换策略配置
在运行过程中,可以动态更新策略配置以适应不同场景:
// 从音乐模式切换到滑动模式
manager.updateConfig(BroadcastPresets.swipe)
// 或者微调单个参数
manager.updateConfig({
broadcastInterval: 120 // 进一步降低延迟
})
// 查看当前配置
const config = manager.getConfig()
console.log(`当前广播间隔: ${config.broadcastInterval}ms`)