# 信号实验箱
**Repository Path**: Ms_bubble/signal-test-box
## Basic Information
- **Project Name**: 信号实验箱
- **Description**: 基于QT、fftw3实现的微型可视化信号运算/变换/处理工具
- **Primary Language**: C++
- **License**: GPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 35
- **Created**: 2022-11-08
- **Last Updated**: 2022-11-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 信号实验箱
本软件为一个基于表达式的信号计算、绘图软件, 主要功能为对输入的信号进行采样, 然后对采样序列进行各种运算, 并将运算结果绘制为波形
如果你在使用、阅读源码、构建等方面有任何疑问, 均可在仓库内提交`Issues`, 我看到会处理
- [1. 简单示例](#1-简单示例)
- [2. 工作原理](#2-工作原理)
- [3. 信号定义](#3-信号定义)
- [4. 内置编译器](#4-内置编译器)
- [5. 函数库](#5-函数库)
- [6. 工作区](#6-工作区)
- [7. 发行版](#7-发行版)
- [8. 构建源码](#8-构建源码)
- [8.1. 推荐的方式](#81-推荐的方式)
- [8.2. 手动方式](#82-手动方式)
- [9. 滤波器指南](#9-滤波器指南)
## 1. 简单示例
输入信号表达式, 设置采样率和采样点数即可看到波形


信号可以嵌套使用, 方便拿两个信号做运算, 最大可嵌套15层

除正余弦以外, 内置还有一些其他的函数可供使用, 例如软件随机rand和硬件随机hrand


傅立叶变换, 对变换结果调用length是因为fft函数输出为复数, 而length函数为向量求模函数, 因此可求出幅度谱


软件自带的`filters`库内带有几个滤波函数, 其中`lpf`与`hpf`为IIR滤波器, 分别为低通滤波器与高通滤波器, 接收采样序列和截止频率作为参数, `fir`为FIR滤波器, 接收采样序列、滤波器阶数和滤波器系数(可使用matlab导出)作为参数
这里给出1阶IIR低通滤波器、3阶IIR低通滤波器和32阶FIR低通滤波器的示例效果, 关于滤波器的更多信息, 请阅读[9. 滤波器指南](#9-滤波器指南)




查看滤波之后的频谱图可以发现, 高阶数的滤波器可达到较好的滤波效果



## 2. 工作原理
1. 一个信号可用一个表达式 **f** 进行描述, 在任意的 **t** 时刻, 信号的强度为 **f(t)**
2. 若给定某时刻 **t** , 则表达式 **f(t)** 的运算结果即为信号 **f** 在 **t** 时刻的信号强度采样值
3. **t** 可根据软件内设置的采样频率计算得出, 然后重复采样 **N** 次, **N** 为软件内设置的采样点数
4. 软件在计算前会将要计算的信号的表达式编译为语法树
5. 语法树内每个节点在计算自身的值之前, 会先将子节点的值计算出来, 因此根节点的计算值即为表达式整体的值
6. 若自定义信号之间存在相互引用, 则内层信号将会先运算得到运算值, 然后再进行外层信号的运算
7. 信号存在最大嵌套层数限制, 防止某信号引用自身或者多个信号交叉引用造成无限递归的情况
## 3. 信号定义
在软件内所有的信号定义均为表达式形式, 例如`sin(t)`便是代表了经过数字采样的 **sin** 信号, 例如`sin(100*t) + sin(200*t)`便是将两个频率不同的信号叠加
信号间可以相互引用, 例如`sig0 = sin(100*t) sig1 = sin(200*t) sig2 = sig0+sig1`
信号自身引用自身或交叉引用均会触发最大嵌套限制
## 4. 内置编译器
软件内置一个简单的编译器, 支持常用的数学表达式文法、双斜杠单行注释文法和 **if** 条件文法
数学表达式文法即最常用最简单的数学表达式, 例如`(sin(t)+cos(t+3))*6`
双斜杠单行注释与C语言的单行注释相同, 例如`sin(t)//sin of t`, 双斜杠后的所有字符均被忽略
**if**条件文法的执行逻辑与C语言的三目运算符相同, 但是并不使用`?:`符号而是使用**if**和**else**关键字, 并且语法顺序也略有不同, 实际上这里的语法和python的设计一致, 语法规则为`数学表达式+if关键字+比较表达式[+else+数学表达式]`, 方括号内的部分是可省略的, 例如:
1. 5 if index > 15 else 3//等价于C语言 index > 15 ? 5 : 3
2. 5 if index > 15//省略else子语句的写法, 等价于 index > 15 ? 5 : 0
**if**条件语法搭配 **index** 变量可非常方便的产生阶跃信号和冲击信号, 例如:
1. 1 if index >= 0//单位阶跃信号
2. 1 if index == 0//单位冲击信号
3. 1 if index >= 5//向右移动5的单位阶跃信号
4. 1 if index == 5//向右移动5的单位冲击信号
5. 5 if index >= 0//幅值为5的阶跃信号
6. 5 if index == 0//幅值为5的冲击信号
编译器支持如下特殊变量:
1. **t** , 代表当前的时间, 也就是第 **n** 次采样除以采样频率 **fs**, `t = n/fs`
2. **index** , 代表当前是第几次采样, 也就是所谓的 **n**, 例如采样点数1024, 那么 **index** 就是[0-1023]的序列
编译器支持一个常量 **pi** , 代表圆周率
编译器对于符号的处理和解析是大小写无关的, pi等价PI Pi pI, t也等价于T
## 5. 函数库
所有的函数均为外部函数库解析得到, 每个函数库均需要提供`pLibFunction_t lib_init(void)`函数, 可用于库被载入时执行一些初始化动作, 并且此函数需要返回函数注册表, 用于描述需要加载动态库文件里面的哪些符号, 以及这些函数所需要的参数数量(用于信号编译时的参数检查)
除此之外, 函数库还可以导出`void lib_exit(void)`函数用于在库被卸载时做一些收尾清理工作, 此函数是可选函数, 如果没有导出则不调用, 不影响库的导入
如果没有初始化动作的需求, 则`lib_init`函数可被宏`register_function_lib`替换, 此宏定义相当于定义`lib_init`函数, 并返回参数指定的函数注册表, 节去使用者编写定义函数库的代码
目前软件内置了4个库, 即`basic`、`filters`、`io`和`transform`, 分别是常用的数学函数库, 数字滤波器库, IO访问库和信号变换库
数学库包括了`sin`、`cos`、`length`、`min/max`、`rand/hrand`、`abs`等常用函数, 可用于产生信号或者数值计算
滤波器库包括了常见的数字滤波器, 例如低通滤波器`lpf`、高通滤波器`hpf`和均值滤波器`average`
变换库目前只有`fft`这一个函数, 调用fftw3完成傅里叶变换
IO访问库目前只实现了`read_file`和`write_file`两个读写文件的接口, 可用于导入信号, 导入滤波器系数或者保存信号
同样的, 如果你自己构建了动态库, 你可以把他们放置在安装目录的lib子目录下, 然后在信号表达式内调用即可
## 6. 工作区
软件内所有的信号、当前的采样率和采样点数的设置共同构成了一个工作区, 而软件可以将这个工作区保存为一个json文件, 并且可从json文件恢复一个工作区, 此json文件的结构是简洁明了的, 很容易看懂其结构, 也很方便手写, 所以你也可以手写一个工作区文件然后直接导入到软件内, 目前此仓库的`workspace_demo`目录下提供了一些用于验证软件功能或验证库功能的demo工作区, 你可以直接导入使用
## 7. 发行版
为了避免使用者自行构建的麻烦, 你可以在仓库右侧, `简介`的下方找到`发行版`一栏, 在这里是编译并打包好的软件, 可直接下载使用. 但是请注意, 只有较大版本更新之后才会发布发行版, 因此你所下载到的发行版的功能可能是不如源码强的, 如果要体验最新版软件请参考[8. 构建源码](#8-构建源码)自行构建最新版软件
## 8. 构建源码
本工程使用`CMake`作为顶层构建系统, `CMake`可产生多种下层构建系统所需文件, 例如`Makefile`
除此之外, 本工程的内置编译器并非纯手写完成, 词法分析器和语法分析器基于`flex`/`bison`构建, 因此在构建时需要用户的开发环境内有可用的`flex`和`bison`
### 8.1. 推荐的方式
本人使用vscode开发本工程, 所使用到的工作区文件也在源码目录下, 完整下载源码后双击[tiny-signal-box.code-workspace](./tiny-signal-box.code-workspace)文件即可自动打开vscode对代码进行编辑和浏览.工作区文件内包括了vscode的设置项, 构建所用的task以及调试所需要的launch配置, 因此无需使用者再次配置, 只要你是通过工作区文件打开的工程, 那么你所使用的配置就和我的是一样的
构建之前, 请先安装`CMake`和`Ninja`, 前者用于产生后者所需的文件, 后者则负责执行文件中的规则, 调用编译器编译和链接程序
除此之外, 还需要安装`flex`和`bison`, 推荐的方式是安装**虚拟的linux环境**, 比如`Cygwin`, 在安装`Cygwin`时, 选择`flex`和`bison`的软件包即可, 在`Cygwin`安装完成后, 将vscode的内置终端替换为`Cygwin`所使用的`bash.exe`
如果安装`Cygwin`时没有选择`flex`和`bison`, 可以在安装完成后再次运行安装程序重新选择, 实际上这并不会重新安装, 只会把你对软件包的各种修改应用到已装好的`Cygwin`里
关于如何配置vscode的内置终端, 请查看[Terminal profiles](https://code.visualstudio.com/docs/editor/integrated-terminal#_terminal-profiles)
如果要在vscode内构建, 请安装`CMake Tools`扩展程序, 安装好后再次打开工作区文件, vscode会在上方正中央弹出选择工具链的选择框, 选择QT提供的工具链然后进行CMake配置, 配置完成后源码目录下会出现build文件夹, 然后在vscode内按下Ctrl+Shift+B快捷键即可开始编译, 若你的生成任务默认快捷键不是这个, 也可以手动执行
编译完成后, 按下Crtl+Shift+T快捷键运行测试任务即可看到程序运行效果
若运行中出现问题, 直接在vscode内调试即可(快捷键F5), 但是请确保工作区文件内的gdb可执行文件的路径配置正确
### 8.2. 手动方式
如果你是`make`使用者, 请在`CMake`配置时传入`-G Unix Makefiles`参数, 并正确指定你所使用的工具链, 然后在构建目录下执行`make -j`
如果你是`ninja`使用者, 请在`CMake`配置时传入`-G Ninja`参数, 并正确指定你所使用的工具链, 然后在构建路径下执行`ninja`
## 9. 滤波器指南
软件自带的滤波库内的函数, 主要分为两种, 即IIR滤波器和FIR滤波器, 其中`lpf`和`hpf`属于IIR滤波器, `average`和`fir`属于FIR滤波器, 两种滤波器的使用方法是不一样的
`lpf`和`hpf`本身都是一阶的IIR滤波器, 并且无需使用者提供系数, 只需要提供采样序列和截止频率即可, 由于所有的函数的输出都是采样序列, 因此可以嵌套使用构成任意阶数的IIR滤波器
`average`内部使用滑动均值滤波, 参数为采样序列和窗口大小, 它可看做系数为`1/窗口大小`的FIR滤波器, 也是最简单的一种fir滤波器
`fir`为直接型FIR滤波器, 参数为采样序列、滤波器阶数和滤波器系数, 内部对信号和滤波器系数做卷积运算, 因此改变系数就可以变为任何一种FIR滤波器, 此系数可由matlab计算导出保存为文件, 再使用`read_file`函数将系数读入为采样序列, 通过传参的方式让`fir`函数使用
目前此仓库的`matlab_coefficient_demo`目录下提供了一些用matlab设计好的系数, 可以在信号表达式内直接使用, 这些demo参数文件的命名方式为: fir_(截止频率)_(采样率), 系数的采样率和软件设置的采样率一致才可使用
关于如何使用matlab设计FIR滤波器, 如何使用导出的系数, 请参阅[matlab_coefficient_demo/fir_2k_5k](./matlab_coefficient_demo/fir_2k_5k)