欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
演示效果

清醒状态:

一、 引言与神经电生理基础背景
脑电图(Electroencephalography, EEG)技术作为现代临床神经内科、睡眠医学以及脑机接口(Brain-Computer Interface, BCI)领域的绝对支柱,其本质是通过精密电极捕捉大脑皮层锥体细胞群在突触后电位突变时引发的微弱电位差。在宏观层面上,这种由数以亿计神经元同步放电形成的宏大生物电场变化,在时间轴上投射出呈现特定频率、振幅特征的交变电波。
相较于我们在前期工程中已实现的心电图(ECG)或者血氧饱和度(SpO2)等单信道、低频、高振幅的规律性体征信号,脑电波信号在计算机图形渲染与系统架构层面提出了几何级增长的挑战:
- 多频带与多通道并发 :完整的脑电分析极少依赖单路信号。在临床上,依据频率特性的差异,波形被严格解构为 α \alpha α (Alpha)、 β \beta β (Beta)、 θ \theta θ (Theta) 和 δ \delta δ (Delta) 等多个具有独立生理学意义的频带。它们处于同一时间维度下,需要前端界面保持绝对同步的多轨渲染。
- 极高采样率与计算密集型特征:为了不丢失高频成分(如 Gamma 波段),现代 EEG 设备的采样率通常起步于 250Hz,甚至高达 1000Hz。要在 UI 线程中处理傅里叶变换、带通滤波并无缝映射到屏幕的极坐标系上,单线程模型将不可避免地陷入阻塞与掉帧的灾难之中。
本篇博客旨在立足于 Flutter 跨端渲染层,通过底层的 Canvas 绘制引擎,结合 Dart 语言中独特的 Isolate 并发内存隔离模型,为您解剖如何构建一套具备医疗级刷新率(60FPS)、数据解耦且无视主线程负载的多通道脑电同步渲染终端。
二、 脑电波分频数学建构与离散映射
在进行工程架构之前,首先需要对绘制对象进行严谨的数学模型定义。脑电波虽然表面呈现随机的混沌状态,但其核心能量集中于几个固定的频段。
| 脑电波频带 | 频率范围 (Hz) | 典型振幅 ( μ V \mu V μV) | 神经精神状态物理意义 |
|---|---|---|---|
| Beta ( β \beta β) | 14 - 30 Hz | 5 - 20 | 处于高度清醒、逻辑运算、极度紧张或专注状态。 |
| Alpha ( α \alpha α) | 8 - 13 Hz | 20 - 100 | 处于闭目养神、安静放松、皮层活跃度下降的边缘。 |
| Theta ( θ \theta θ) | 4 - 7 Hz | 20 - 100 | 困倦状态、浅度睡眠阶段,或处于深度冥想中。 |
| Delta ( δ \delta δ) | 0.5 - 3 Hz | 20 - 200 | 深度睡眠(慢波睡眠),失去意识,抑或出现脑实质性损伤。 |
为了在离线环境中模拟真实的高频采样数据,我们在引擎层采用多阶正弦波叠加与伪随机白噪声融合的方式。依据傅里叶逆变换(IDFT)原理,复杂波形可以由多个基础正弦和谐波线性组合而成。针对某单一通道 C C C 的信号发生函数 S C ( t ) S_C(t) SC(t),其数学构造如下:
S C ( t ) = ∑ i = 1 k A i sin ( 2 π f i t + ϕ i ) + N ( 0 , σ 2 ) S_C(t) = \sum_{i=1}^{k} A_i \sin(2\pi f_i t + \phi_i) + \mathcal{N}(0, \sigma^2) SC(t)=i=1∑kAisin(2πfit+ϕi)+N(0,σ2)
其中, A i A_i Ai 表示特定频带的主导振幅系数, f i f_i fi 为基础频率,而 N ( 0 , σ 2 ) \mathcal{N}(0, \sigma^2) N(0,σ2) 表示均值为 0,方差为 σ 2 \sigma^2 σ2 的高斯白噪声(模拟电极底噪与肌电干扰)。
三、 架构设计:双引擎驱动与 Isolate 线程隔离模型
在单线程(Event Loop)的 Dart 虚拟机中,若将包含三角函数高频计算的大量数值操作强行混入主界面的渲染循环(Frame Pipeline)中,会导致垃圾回收(GC)过于频繁,甚至使得单帧渲染耗时突破 16.67ms(即 60Hz 刷新率的物理极限),进而引发肉眼可见的卡顿。
针对此痛点,必须启用 Dart 独有的基于内存隔离的并发模型------Isolate。与 Java 或 C++ 中共享堆内存的 Thread 模型不同,每一个 Isolate 拥有自己绝对私有的内存空间、执行堆栈与事件循环机制,它们之间只允许通过 SendPort 和 ReceivePort 以极其严格的消息序列化拷贝(Message Passing)方式进行通讯。
后台线程: 脑电DSP生成引擎
主线程: UI渲染与交互循环
跨线程指令调度
16ms 滴答
单向高速数据流传输
用户操作: 暂停/注入睡眠/重置
SendPort 发送控制指令
ReceivePort 监听
环形缓冲区 Queue 更新
ValueNotifier 触发微重绘
Canvas 双缓冲多轨渲染
指令解析与状态转移
高频定时器模拟 ADC 采样
数学合成: Beta/Alpha/Theta/Delta
SendPort 回传 EEGFrame 帧序列
该架构实现了数据发生层(后台)与视图表现层(前台)的彻底断层解耦。当主线程因为复杂的 UI 交互或者其他阻断性耗时操作产生抖动时,底层的 Isolate 引擎依旧在毫秒不差地执行着精准的时序数据生成与发送,绝不会产生"心跳漏拍"。
四、 核心代码拆解(一):Isolate 极速离线计算引擎
系统底层的计算引擎被剥离为一个顶级函数 _eegCalculationEngine。在被主线程通过 Isolate.spawn 唤起时,它即刻建立起自身的端口监听。
为了演示脑机接口级的数据动态响应特征,引擎内部维护了一个极为关键的状态枚举 _SimulationState。
dart
// 在 Isolate 内运行
Timer? dspTimer ??= Timer.periodic(const Duration(milliseconds: 16), (timer) {
t += 0.016; // 步进累计时间,精准对标约 62.5 Hz 采样率
// 依据当前系统处于的病理或生理状态,动态调配频段的振幅因子
double ampBeta = 1.0, ampAlpha = 1.0, ampTheta = 1.0, ampDelta = 1.0;
if (simState == _SimulationState.sleep) {
// 深度睡眠:抑制高频 Beta,大幅拉升低频 Delta 的峰值
ampBeta = 0.2; ampAlpha = 0.4; ampTheta = 1.2; ampDelta = 2.0;
} else if (simState == _SimulationState.seizure) {
// 癫痫或皮层大面积异常放电:全频段振幅暴涨
ampBeta = 4.0; ampAlpha = 3.0; ampTheta = 2.5; ampDelta = 2.0;
}
// 执行核心三角函数合成与噪声叠加 ...
// 打包并跨线程抛出帧结构
mainSendPort.send(EEGFrame(t, [betaV, alphaV, thetaV, deltaV]));
});
值得注意的是,此处通过调整 amp 系数包络线,可以完美地在 UI 端模拟出极其逼真的医疗临床表现。当进入深度睡眠(Sleep)时,极速波段变得平缓,深不可测的低频大波动充斥屏幕;一旦注入异常放电(Seizure),全频段信号会瞬间呈现暴躁的毛刺状撕裂。这种完全由数学模型主导的数据拟合,赋予了医疗软件工程无可匹敌的纯粹性。
五、 核心代码拆解(二):环形缓冲区(Circular Buffer)与视图隔断
当 16 ms 16\text{ms} 16ms 间隔的密集数据雨点般地砸向主线程的 ReceivePort 时,如果直接调用 setState,整个巨大的 Widget Tree 将被无情地高频摧毁并重建。在此,系统引入了两层防护机制来保驾护航:
5.1 数据结构的 O(1) 优化
我们并没有使用常规的 List 进行数据存储,而是采用了位于 dart:collection 库中的双端队列 Queue。
dart
void _handleNewFrame(EEGFrame frame) {
if (!_isRunning) return;
_dataQueue.addLast(frame);
if (_dataQueue.length > maxVisiblePoints) {
_dataQueue.removeFirst(); // 维持 O(1) 的移出性能
}
_eegBufferNotifier.value = _dataQueue.toList(growable: false);
}
在保持滑动窗口固定长度(如 300 个点位)时,频繁剔除数组头部元素的代价是极度高昂的 O ( N ) O(N) O(N) 数组迁移拷贝。而 Queue 巧妙地规避了内存断层,其 removeFirst 呈现严谨的 O ( 1 ) O(1) O(1) 时间复杂度,保住了主线程在处理海量长片段时的最后一口气。
5.2 RepaintBoundary 边界隔离
在 Widget 树的组装区,我们将脑电波渲染层使用 RepaintBoundary 进行了强行包裹,并结合了极其轻量的局部监听构建器。
dart
ClipRRect(
child: RepaintBoundary(
child: ValueListenableBuilder<List<EEGFrame>>(
valueListenable: _eegBufferNotifier,
builder: (context, frames, child) {
return CustomPaint(
painter: EEGPainter(frames: frames, maxPoints: maxVisiblePoints),
size: Size.infinite,
);
},
),
),
)
RepaintBoundary 的核心机制在于它强行命令 Flutter 的 Render Object 在合成阶段(Compositing phase)为此节点专门开辟一个离屏图层(Offscreen Texture)。由于它的存在,这块如同疯狂心跳般以 60 帧重绘的画布,绝对不会株连外层的标题栏与底部控制按钮触发无意义的重构。
六、 核心代码拆解(三):Canvas 多通道时序对齐与数学映射渲染
步入底层 EEGPainter 的心脏地带,核心挑战在于如何将一条杂糅着毫秒时间戳与毫伏级微电信号的列表,精准投影到二维的像素网格内。
6.1 多轨网格分割与空间剥离
医学界面强调绝对的整齐划一。整个屏幕垂直高度被严密划分为 channelCount 等份,每条轨道的 Y 轴原点被锁定在分轨的中线上:laneCenterY = i * laneHeight + laneHeight / 2。
EEGPainter
-List<EEGFrame> frames
-int maxPoints
-List<Color> channelColors
+void paint(Canvas canvas, Size size)
-void _drawMedicalGrid(Canvas canvas, Size size)
-void _drawChannelLabel(Canvas canvas, String text)
+bool shouldRepaint()
CustomPainter
6.2 像素归一化映射 (Normalization)
电生理学信号的值域可能横跨 − 100 μ V -100 \mu V −100μV 到 + 100 μ V +100 \mu V +100μV。如果不加干预,直接作为像素坐标传入 Path,波形会瞬间冲出自身轨道甚至贯穿出整个屏幕。
dart
final double normalizedValue = frames[j].channels[i] / 150.0;
// 计算最终像素 Y 坐标:中心原点减去实际偏移(Canvas 坐标系 Y 轴向下递增)
final double y = laneCenterY - (normalizedValue * (laneHeight / 2) * 0.8);
通过上述公式,我们强行给信号加盖了一层振幅"限幅器"。除以常数 150.0 后,信号将收敛于 − 1 -1 −1 至 1 1 1 之间,随后乘上对应轨道高度的一半并缩水 20%(预留安全边界)。最终通过 path.lineTo(x, y) 构建出完整矢量的脑电视觉曲线。
此外,在进行 Canvas.drawPath 之前,通过施加了轻微抗锯齿(Anti-Alias)并在底部辅以一抹细微的同色系发光阴影 canvas.drawShadow,为冰冷的折线赋予了深邃且生动的现代医疗仪器赛博质感。
七、 结语:基于多线程与原生图层的未来终端
从单纯的单线程串行计算,跨越至本次采用 Isolate 通讯构建的多通道并行发生器,Flutter 展现了其作为现代跨端基座所具有的强劲可塑性。
在真正的临床重症监护室系统(CIS)或是尖端的非侵入式脑机接口前端架构中,所面临的信号将不仅限于四通道的模拟波形。届时,我们可能会通过底层硬件接口(如 FFI 接入 C/C++ 驱动),直接抽取来自数十个电极帽节点的极高频数据流。然而,本次所建立的"环形队列+隔离并发引擎+离屏边界渲染"铁三角阵型,已经具备了全面承接真实海量生物电位差洪流的架构底蕴。
在探索了复杂渲染与异步并发之后,关于生命信息的流转与呈现体系已经基本落成。接下来的演进,我们将探讨如何令这些宝贵的高频生命体征数据,进行安全且持久的本地固化落地。
完整代码
bash
import 'dart:async';
import 'dart:collection';
import 'dart:isolate';
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() {
runApp(const EEGBrainwaveApp());
}
class EEGBrainwaveApp extends StatelessWidget {
const EEGBrainwaveApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'EEG Multi-Channel Monitor',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: const Color(0xFF0B0F19), // 深邃的暗夜蓝背景
colorScheme: const ColorScheme.dark(
primary: Color(0xFF00E5FF),
surface: Color(0xFF131A2A),
),
),
home: const EEGMonitorWorkspace(),
);
}
}
// -----------------------------------------------------------------------------
// 实体与通讯协议定义
// -----------------------------------------------------------------------------
/// 单帧脑电波数据载体
class EEGFrame {
final double timestamp;
final List<double> channels; // 包含 4 个通道的数据 (Alpha, Beta, Theta, Delta)
EEGFrame(this.timestamp, this.channels);
}
/// 发送给 Isolate 计算引擎的指令枚举
enum EEGCommand { start, stop, stateAwake, stateSleep, stateSeizure }
// -----------------------------------------------------------------------------
// UI 主线程视图控制器
// -----------------------------------------------------------------------------
class EEGMonitorWorkspace extends StatefulWidget {
const EEGMonitorWorkspace({super.key});
@override
State<EEGMonitorWorkspace> createState() => _EEGMonitorWorkspaceState();
}
class _EEGMonitorWorkspaceState extends State<EEGMonitorWorkspace> {
// Isolate 通讯端口
Isolate? _engineIsolate;
ReceivePort? _receivePort;
SendPort? _sendPort;
// 状态流转控制
bool _isRunning = false;
String _currentState = '清醒模式 (Awake)';
// 环形缓冲区配置
final int maxVisiblePoints = 300;
final ValueNotifier<List<EEGFrame>> _eegBufferNotifier = ValueNotifier([]);
final Queue<EEGFrame> _dataQueue = Queue();
@override
void initState() {
super.initState();
_initEngine();
}
/// 初始化独立计算线程 (Isolate)
Future<void> _initEngine() async {
_receivePort = ReceivePort();
// 启动 Isolate,将当前接收端口的 SendPort 传递过去
_engineIsolate = await Isolate.spawn(
_eegCalculationEngine,
_receivePort!.sendPort,
debugName: 'EEGEngine',
);
// 监听来自引擎的数据流
_receivePort!.listen((message) {
if (message is SendPort) {
// 第一次握手:保存引擎的发送端口
_sendPort = message;
} else if (message is EEGFrame) {
// 接收高速计算得出的波形帧
_handleNewFrame(message);
}
});
}
/// 环形缓冲区数据入列与状态推送
void _handleNewFrame(EEGFrame frame) {
if (!_isRunning) return;
_dataQueue.addLast(frame);
if (_dataQueue.length > maxVisiblePoints) {
_dataQueue.removeFirst(); // 剔除超窗旧数据,维持 O(1) 性能
}
// 触发 ValueNotifier 重绘
_eegBufferNotifier.value = _dataQueue.toList(growable: false);
}
void _sendCommand(EEGCommand cmd) {
_sendPort?.send(cmd);
setState(() {
if (cmd == EEGCommand.start) _isRunning = true;
if (cmd == EEGCommand.stop) _isRunning = false;
if (cmd == EEGCommand.stateAwake) _currentState = '清醒模式 (Awake - Beta波活跃)';
if (cmd == EEGCommand.stateSleep) _currentState = '深度睡眠 (Sleep - Delta波主导)';
if (cmd == EEGCommand.stateSeizure) _currentState = '发作期 (Seizure - 全频段暴涨)';
});
}
@override
void dispose() {
_receivePort?.close();
_engineIsolate?.kill(priority: Isolate.immediate);
_eegBufferNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('多通道脑电波 (EEG) 同步监控中枢', style: TextStyle(fontWeight: FontWeight.w600, letterSpacing: 1.2)),
backgroundColor: const Color(0xFF131A2A),
elevation: 0,
actions: [
Center(
child: Padding(
padding: const EdgeInsets.only(right: 24.0),
child: Text('当前状态: $_currentState', style: const TextStyle(color: Color(0xFF00E5FF), fontWeight: FontWeight.bold)),
),
)
],
),
body: Column(
children: [
// 顶级核心视图:脑电波波形图渲染区
Expanded(
child: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF060913),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white10),
boxShadow: const [BoxShadow(color: Colors.black45, blurRadius: 10, spreadRadius: 2)],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
// 使用 RepaintBoundary 彻底隔离高频重绘区域
child: RepaintBoundary(
child: ValueListenableBuilder<List<EEGFrame>>(
valueListenable: _eegBufferNotifier,
builder: (context, frames, child) {
return CustomPaint(
painter: EEGPainter(frames: frames, maxPoints: maxVisiblePoints),
size: Size.infinite,
);
},
),
),
),
),
),
// 底部控制总台
Container(
padding: const EdgeInsets.all(24),
color: const Color(0xFF131A2A),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildControlButton(
icon: _isRunning ? Icons.pause : Icons.play_arrow,
label: _isRunning ? '暂停监测' : '启动监测',
color: _isRunning ? Colors.amber : const Color(0xFF00E5FF),
onPressed: () => _sendCommand(_isRunning ? EEGCommand.stop : EEGCommand.start),
),
Container(width: 1, height: 40, color: Colors.white24),
_buildControlButton(
icon: Icons.remove_red_eye,
label: '注入清醒状态',
color: Colors.greenAccent,
onPressed: () => _sendCommand(EEGCommand.stateAwake),
),
_buildControlButton(
icon: Icons.bedtime,
label: '注入深睡状态',
color: Colors.indigoAccent,
onPressed: () => _sendCommand(EEGCommand.stateSleep),
),
_buildControlButton(
icon: Icons.warning_amber,
label: '触发皮层放电',
color: Colors.redAccent,
onPressed: () => _sendCommand(EEGCommand.stateSeizure),
),
],
),
)
],
),
);
}
Widget _buildControlButton({required IconData icon, required String label, required Color color, required VoidCallback onPressed}) {
return ElevatedButton.icon(
onPressed: onPressed,
icon: Icon(icon, color: Colors.white),
label: Text(label, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: color.withOpacity(0.8),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
}
}
// -----------------------------------------------------------------------------
// 高频数学与渲染底层:CustomPainter
// -----------------------------------------------------------------------------
class EEGPainter extends CustomPainter {
final List<EEGFrame> frames;
final int maxPoints;
EEGPainter({required this.frames, required this.maxPoints});
final List<String> channelNames = ['Beta (14-30 Hz)', 'Alpha (8-13 Hz)', 'Theta (4-7 Hz)', 'Delta (0.5-3 Hz)'];
final List<Color> channelColors = [
const Color(0xFFFF5252), // Beta 激进红
const Color(0xFF69F0AE), // Alpha 冥想绿
const Color(0xFF448AFF), // Theta 潜意识蓝
const Color(0xFFFFD740), // Delta 深睡黄
];
@override
void paint(Canvas canvas, Size size) {
final int channelCount = 4;
final double laneHeight = size.height / channelCount;
// 绘制医学标准的毫秒级背景网格线
_drawMedicalGrid(canvas, size, laneHeight, channelCount);
if (frames.isEmpty) return;
// 为每个波段通道绘制时序信号
for (int i = 0; i < channelCount; i++) {
final double laneCenterY = i * laneHeight + laneHeight / 2;
final Path path = Path();
final double dx = size.width / (maxPoints - 1);
for (int j = 0; j < frames.length; j++) {
final double x = j * dx;
// 反转 Y 轴,映射振幅到像素 (将 -100~100 的幅值压缩至半条轨道高度)
final double normalizedValue = frames[j].channels[i] / 150.0;
final double y = laneCenterY - (normalizedValue * (laneHeight / 2) * 0.8);
if (j == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
// 配置发光抗锯齿画笔
final Paint tracePaint = Paint()
..color = channelColors[i]
..strokeWidth = 1.8
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round
..isAntiAlias = true;
// 为通道线增加柔和的阴影弥散效果,增强医疗大屏质感
canvas.drawShadow(path, channelColors[i], 2.0, false);
canvas.drawPath(path, tracePaint);
// 绘制通道标签刻度文本
_drawChannelLabel(canvas, channelNames[i], channelColors[i], i * laneHeight + 10);
}
}
void _drawMedicalGrid(Canvas canvas, Size size, double laneHeight, int channelCount) {
final Paint gridPaint = Paint()
..color = Colors.white.withOpacity(0.08)
..strokeWidth = 1.0;
final Paint subGridPaint = Paint()
..color = Colors.white.withOpacity(0.03)
..strokeWidth = 0.5;
// 绘制横向基线与辅助线
for (int i = 0; i < channelCount; i++) {
final double y = i * laneHeight;
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
canvas.drawLine(Offset(0, y + laneHeight / 2), Offset(size.width, y + laneHeight / 2), subGridPaint);
}
// 绘制纵向时间刻度线
for (double x = 0; x < size.width; x += 50) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), x % 200 == 0 ? gridPaint : subGridPaint);
}
}
void _drawChannelLabel(Canvas canvas, String text, Color color, double y) {
final TextPainter tp = TextPainter(
text: TextSpan(
text: text,
style: TextStyle(color: color.withOpacity(0.9), fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1),
),
textDirection: TextDirection.ltr,
);
tp.layout();
// 绘制带有暗色底托的文字背景,防干扰
final Rect bgRect = Rect.fromLTWH(10, y - 2, tp.width + 16, tp.height + 4);
canvas.drawRRect(RRect.fromRectAndRadius(bgRect, const Radius.circular(4)), Paint()..color = const Color(0xFF0B0F19).withOpacity(0.8));
tp.paint(canvas, Offset(18, y));
}
@override
bool shouldRepaint(covariant EEGPainter oldDelegate) {
return true; // 实时刷新的底层约束,依靠外部 RepaintBoundary 阻断雪崩
}
}
// -----------------------------------------------------------------------------
// Isolate 后台计算引擎:完全脱离主线程的重度运算区
// -----------------------------------------------------------------------------
enum _SimulationState { awake, sleep, seizure }
/// 驻留于后台线程的脑电生理生成器
void _eegCalculationEngine(SendPort mainSendPort) {
final ReceivePort isolateReceivePort = ReceivePort();
// 建立双向通信通道
mainSendPort.send(isolateReceivePort.sendPort);
Timer? dspTimer;
double t = 0.0;
final math.Random random = math.Random();
_SimulationState simState = _SimulationState.awake;
// 监听来自主线程的干预指令
isolateReceivePort.listen((message) {
if (message is EEGCommand) {
switch (message) {
case EEGCommand.start:
dspTimer ??= Timer.periodic(const Duration(milliseconds: 16), (timer) {
t += 0.016; // 时间以 16ms 步进
// 依据不同的大脑状态,配置频段振幅包络线
double ampBeta = 1.0, ampAlpha = 1.0, ampTheta = 1.0, ampDelta = 1.0;
if (simState == _SimulationState.awake) {
ampBeta = 1.5; ampAlpha = 0.8; ampTheta = 0.3; ampDelta = 0.1;
} else if (simState == _SimulationState.sleep) {
ampBeta = 0.2; ampAlpha = 0.4; ampTheta = 1.2; ampDelta = 2.0;
} else if (simState == _SimulationState.seizure) {
ampBeta = 4.0; ampAlpha = 3.0; ampTheta = 2.5; ampDelta = 2.0;
}
// 傅里叶逆变换原理合成波形:频率、振幅与白噪声的叠加
// 1. Beta 通道 (14-30Hz)
final double betaV = ampBeta * 20 * math.sin(2 * math.pi * 22 * t)
+ ampBeta * 15 * math.cos(2 * math.pi * 16 * t)
+ random.nextDouble() * 10 * ampBeta;
// 2. Alpha 通道 (8-13Hz)
final double alphaV = ampAlpha * 30 * math.sin(2 * math.pi * 10 * t)
+ random.nextDouble() * 8 * ampAlpha;
// 3. Theta 通道 (4-7Hz)
final double thetaV = ampTheta * 40 * math.sin(2 * math.pi * 6 * t)
+ random.nextDouble() * 5 * ampTheta;
// 4. Delta 通道 (0.5-3Hz)
final double deltaV = ampDelta * 60 * math.sin(2 * math.pi * 1.5 * t)
+ random.nextDouble() * 3 * ampDelta;
// 打包数据推入主线程
mainSendPort.send(EEGFrame(t, [betaV, alphaV, thetaV, deltaV]));
});
break;
case EEGCommand.stop:
dspTimer?.cancel();
dspTimer = null;
break;
case EEGCommand.stateAwake:
simState = _SimulationState.awake;
break;
case EEGCommand.stateSleep:
simState = _SimulationState.sleep;
break;
case EEGCommand.stateSeizure:
simState = _SimulationState.seizure;
break;
}
}
});
}