# 信号实验箱 **Repository Path**: liuke8023cc/signal-test-box ## Basic Information - **Project Name**: 信号实验箱 - **Description**: 基于QT、z-fft实现的微型可视化信号运算/变换/处理工具 - **Primary Language**: C++ - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 35 - **Created**: 2023-03-23 - **Last Updated**: 2023-03-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 信号实验箱 [![star](https://gitee.com/finalize/signal-test-box/badge/star.svg?theme=dark)](https://gitee.com/finalize/signal-test-box/stargazers)[![fork](https://gitee.com/finalize/signal-test-box/badge/fork.svg?theme=dark)](https://gitee.com/finalize/signal-test-box/members) 本软件为一个基于表达式的信号计算、绘图软件, 主要功能为对输入的信号进行采样, 然后对采样序列进行各种运算, 并将运算结果绘制为波形, 支持使用动态库扩充软件内可调用的函数 For native English speaker, please check out [English Version Readme](./readme_en.md) That Document have the same content as this one, but write in English. 本软件支持跨平台部署, 这主要得益于QT的跨平台API以及CMake的跨平台构建能力, 你可以在一个宿主机上编译出不同平台的目标, 目前已测试过Ubuntu 22.04(基于Win11 WSL2)、Win7/Win10/Win11、Android x86/Android armeabi-v7a(基于Win11 WSA和Realme X2 pro) 本软件支持国际化, 使用QT的国际化机制实现, 目前拥有简体中文翻译数据(软件内默认使用英文作为原始语言, 然后再添加其他语言的翻译文件实现国际化, 这也是QT翻译机制的推荐做法), 如果你希望添加新的语言, 可以使用QT语言家工具生成当前工程的.ts文件, 例如本工程现有的中文翻译文件[ui/zh_CN.ts](ui/zh_CN.ts), 然后再为生成的ts文件内的英文原文添加目标语言的译文, 添加完成后保存文件, 将ts文件发布为qm文件即可 目前软件在启动时会获取操作系统使用的语言设置, 因此会自动适配语言, 如果操作系统的语言是中文简体和英文之外的语言, 会默认使用英文(如果你自行添加了其他语言的翻译文件就可以显示翻译后的语言, 总的来说就是翻译不了的一律不翻译) 由于本软件的支持库是以`git submodule`形式引入的, 所以请在克隆仓库时, 使用`git clone --recursive`命令递归将子模块一起克隆下来, 如果克隆时忘记, 也可以在克隆完成之后在源码路径下执行`git submodule init`和`git submodule update`两条指令来补救 如果你使用`Download ZIP`功能而不是`git clone`, 那么请将[z-fft](https://gitee.com/finalize/z-fft)一同下载, 并解压到`external_libs`路径下, 最终应该形成这样的文件目录结构 ![子模块](readme_rc/submodule.png) 如果你在使用、阅读源码、构建等方面有任何疑问, 均可在仓库内提交`Issues`, 我看到会处理 - [1. 简单示例](#1-简单示例) - [2. 工作原理](#2-工作原理) - [3. 信号定义](#3-信号定义) - [4. 频谱模式](#4-频谱模式) - [5. 内置编译器](#5-内置编译器) - [6. 函数库](#6-函数库) - [7. 工作区](#7-工作区) - [8. 发行版](#8-发行版) - [9. 向本仓库提交代码](#9-向本仓库提交代码) - [10. 构建源码](#10-构建源码) - [10.1. 环境配置](#101-环境配置) - [10.2. 首选方式](#102-首选方式) - [10.3. 手动方式](#103-手动方式) - [11. 滤波器指南](#11-滤波器指南) - [12. 安卓平台编译指南](#12-安卓平台编译指南) ## 1. 简单示例 在信号列表框内右键单击(安卓平台为长按), 在弹出的菜单内选择新增信号, 然后输入信号表达式, 设置采样率和采样点数后点击计算当前信号即可看到波形 ![信号0](./readme_rc/sig1.png) ![信号1](./readme_rc/sig2.png) 信号可以嵌套使用, 方便拿两个信号做运算, 最大可嵌套32层 ![信号2](./readme_rc/sig3.png) 除正余弦以外, 内置还有一些其他的函数可供使用, 例如软件随机rand和硬件随机hrand ![信号3](./readme_rc/sig4.png) ![信号4](./readme_rc/sig5.png) 傅立叶变换, 对变换结果调用length是因为fft函数输出为复数, 而length函数为向量求模函数, 因此可求出幅度谱 ![输入信号](./readme_rc/mix_sig.png) ![幅度谱](./readme_rc/fft.png) 软件自带的`filters`库内带有几个滤波函数, 其中`lpf`与`hpf`为IIR滤波器, 分别为低通滤波器与高通滤波器, 接收采样序列和截止频率作为参数, `fir`为FIR滤波器, 接收采样序列、滤波器阶数和滤波器系数(可使用matlab导出)作为参数 这里给出1阶IIR低通滤波器、3阶IIR低通滤波器和32阶FIR低通滤波器的示例效果, 关于滤波器的更多信息, 请阅读[11. 滤波器指南](#11-滤波器指南) ![OrigSig](./readme_rc/orig_sig.png) ![IIR_filter1](./readme_rc/iir_lv1.png) ![IIR_filter2](./readme_rc/iir_lv3.png) ![FIR_filter](./readme_rc/fir_lv32.png) 查看滤波之后的频谱图可以发现, 高阶数的滤波器可达到较好的滤波效果 ![IIR_freq1](./readme_rc/iir_lv1_freq.png) ![IIR_freq2](./readme_rc/iir_lv3_freq.png) ![FIR_freq](./readme_rc/fir_lv32_freq.png) ## 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. 频谱模式 在使用`fft`函数计算信号的傅里叶变换后, 如果只使用`length`函数求出幅度谱的话, 图表的X轴坐标为点数, 例如下图 ![iir_freq_index](readme_rc/iir_lv1_freq_index.png) 可以看到十字光标所在位置X坐标为10, 这表示的是此数据在采样序列里的下标, 如果想要知道此点对应的频率值是多少就需要自己计算, 上图中X=10, 采样率5KHz, 采样点数为256点, 因此此处的频率为`5000/256*10=195.3Hz`, 也就是原信号里的200Hz频率成分. 进行这样的计算虽然简单, 但是却很麻烦, 而且计算所需的所有参数在软件内都有, 所以, 可以通过勾选右下角的频谱模式复选框来让软件去计算 勾选频谱模式之后, 横坐标的含义将变为实际的频率值, 例如下图中, 这里还是用的一样的信号去绘图, 只是打开了频谱模式而已, 可以看到十字光标所在位置的X值为195.313, 与我们的计算结果一致 ![IIR_freq1](./readme_rc/iir_lv1_freq.png) 需要注意的是, 频谱模式实际上只会显示一半的实际计算数据, 即采样点数为1024点时, 图表内实际上只会显示前512个数据点, 这是由于频谱具有对称性, 因此只需要前一半的频谱就足够分析了 ## 5. 内置编译器 软件内置一个简单的编译器, 支持常用的数学表达式文法、双斜杠单行注释文法和 **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 ## 6. 函数库 所有的函数均为外部函数库解析得到, 每个函数库均需要提供`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`、`conv`、`sum`等常用函数, 可用于产生信号或者数值计算, `sin`/`cos`为正弦/余弦函数, `length`为复数求模函数, `min`/`max`为最大/最小值函数, `rand`/`hrand`为软件/硬件随机数函数, `abs`为绝对值函数, `conv`为卷积运算函数, `sum`为序列求和函数 滤波器库包括了常见的数字滤波器, 例如低通滤波器`lpf`、高通滤波器`hpf`、FIR滤波器`fir`和均值滤波器`average`, 其中`lpf`和`hpf`为IIR滤波器 变换库目前有`fft`、`ifft`、`dft`和`idft`函数, 调用`z-fft`完成傅里叶正变换和反变换, 其中`dft`和`idft`是基于傅里叶变换定义式实现的原始算法, `fft`和`ifft`是`dft`和`idf`的高效实现算法(基2的DIT算法) IO访问库目前只实现了`read_file`和`write_file`两个读写文件的接口, 可用于导入信号, 导入滤波器系数或者保存信号 同样的, 如果你自己构建了动态库, 你可以把他们放置在安装目录的lib子目录下, 然后在信号表达式内调用即可 ## 7. 工作区 软件内所有的信号、当前的采样率和采样点数的设置共同构成了一个工作区, 而软件可以将这个工作区保存为一个json文件, 并且可从json文件恢复一个工作区, 此json文件的结构是简洁明了的, 很容易看懂其结构, 也很方便手写, 所以你也可以手写一个工作区文件然后直接导入到软件内, 目前此仓库的`workspace_demo`目录下提供了一些用于验证软件功能或验证库功能的demo工作区, 你可以直接导入使用 关于工作区以及工作区文件的详细说明, 请参阅[workspace_demo/readme.md](workspace_demo/readme.md) ## 8. 发行版 为了避免使用者自行构建的麻烦, 你可以在仓库右侧, `简介`的下方找到`发行版`一栏, 在这里是编译并打包好的软件, 可直接下载使用. 但是请注意, 只有较大版本更新之后才会发布发行版, 因此你所下载到的发行版的功能可能是不如源码强的, 如果要体验最新版软件请参考[10. 构建源码](#10-构建源码)自行构建最新版软件 ## 9. 向本仓库提交代码 如果你希望参与到本项目的开发中, 请参照如下流程 1. 在云端仓库内点击fork, 将本仓库复制到你自己的名下, 如果遇到不能fork的情况, 说明本仓库正在进行一些较大的修改, 为了防止产生半成品分支, 在此期间会禁止fork, 请等待几天之后再次尝试 2. 将你自己名下的仓库克隆至本地 3. 在本地提交代码后, 推送到云端(此时是推送到了你名下的云端仓库) 4. 向本仓库提交pull request, 方向为[你的仓库] -> [本仓库] 5. 本仓库的成员会审核(代码风格和实现逻辑)并测试(功能是否达到预期)pull request的代码, 测试通过后此代码将被合并进入本仓库 ## 10. 构建源码 本工程使用`CMake`作为顶层构建系统, `CMake`可产生多种下层构建系统所需文件, 例如`Makefile`或`ninja.build` 除此之外, 本工程的内置编译器并非纯手写完成, 词法分析器和语法分析器基于`flex`/`bison`构建, 因此在构建时需要用户的开发环境内有可用的`flex`和`bison` 本项目自从`commit hash: cc33da5de23c4434de462cadfa59c189196cbb15`之后, 不再使用原先的方式指定工具链和各种路径相关的信息, 目前使用`CMake`的`Preset`机制进行环境配置 如果你事先编译过`Windows`平台软件, 然后需要编译`Android`平台, 请先删除`build`目录, 反过来也是一样的 ### 10.1. 环境配置 构建之前, 请先安装`CMake`和`Ninja`, 前者用于产生后者所需的文件, 后者则负责执行文件中的规则, 调用编译器编译和链接程序 然后是`QT`, 本软件使用了`QT 5.15`, 安装时请注意不要安装MSVC编译器版本的, 要安装GCC编译器版本的QT, 同时也要选中`Charts`模块和`GCC 8.1.0`编译器本身 为了能让`CMake`找到`QT`, 还需要将`QT`安装目录下的`Qt5Config.cmake`文件所在的文件夹配置到`CMAKE_PREFIX_PATH`环境变量下, 否则`CMake`在配置工程的时候会在`find_package`语句上报错, 此文件位于`QT安装目录\版本号\mingw81_32或者mingw81_64\lib\cmake\Qt5` 除此之外, 还需要安装`flex`和`bison`, 推荐的方式是安装**虚拟的linux环境**, 比如`Cygwin`, 在安装`Cygwin`时, 选择`flex`和`bison`的软件包即可, 在`Cygwin`安装完成后, 将`Cygwin`的`bin`目录的路径加入到`PATH`环境变量中 如果安装`Cygwin`时没有选择`flex`和`bison`, 可以在安装完成后再次运行安装程序重新选择, 实际上这并不会重新安装, 只会把你对软件包的各种修改应用到已装好的`Cygwin`环境里 在顶层`CMakeLists.txt`文件同级目录下, `CMake`可以支持`CMakePresets.json`和`CMakeUserPresets.json`两个文件用于配置各类参数, 包括配置时的预设、构建时的预设和测试时的预设, 其中`CMakePresets.json`是项目级的预设文件, `CMakeUserPresets.json`是用户级的预设文件, 本项目自带的项目级配置文件内的所有配置均为项目默认配置, 因此并不保证在所有人的开发环境下生效, 如果你需要在自己的开发环境内进行开发, 那么有如下两种方式进行适配, 两种方式适合不同的开发场景, 请自行选择 1. 如果你只想要在本地编译, 或者想要基于本项目进行二次开发, 但并不准备提交代码到本仓库, 那么请直接修改[CMakePresets.json](CMakePresets.json)文件内的工具链路径配置即可, 需要注意的是, 本项目基于GPL-2.0开源协议发布, 也就是说二次开发后仍然需要保持开源, 不得闭源发布 2. 如果你需要向本仓库内提交代码, 那么请新建一个名为`CMakeUserPresets.json`的文件, 然后在此文件内编写你自己的各项配置(可复制项目级配置文件然后修改), 此文件不应当被提交到版本控制系统 ### 10.2. 首选方式 本人使用vscode开发本工程, 所使用到的工作区文件也在源码目录下, 完整下载源码后双击[tiny-signal-box.code-workspace](./tiny-signal-box.code-workspace)文件即可自动打开vscode对代码进行编辑和浏览.工作区文件内包括了vscode的设置项, 构建所用的task以及调试所需要的launch配置, 因此一般来说无需使用者再次配置, 只要你是通过工作区文件打开的工程, 那么你所使用的配置就和我的是一样的 这里推荐将vscode的内置终端替换为`Cygwin`所使用的`bash.exe`, 关于如何配置vscode的内置终端, 请查看[Terminal profiles](https://code.visualstudio.com/docs/editor/integrated-terminal#_terminal-profiles) 如果要在vscode内构建, 请按照工作区文件给出的推荐, 安装推荐的扩展程序(打开工作区时, 如果有一些推荐扩展你没有安装, vscode也会弹窗提示), 此处列出的是扩展程序的ID, 在搜索时可以唯一确定到某个具体的扩展程序而不会出现重名的问题 ![推荐的扩展程序的ID](./readme_rc/recommandExt.png) `CMake Tools`扩展程序默认支持预设文件, 当你在源码路径下配置好了`CMakePresets.json`或`CMakeUserPresets.json`, 那么当你打开工作区之后, 就可以看到左下角可以选择使用哪套预设 ![选择预设](readme_rc/cmake_select_preset1.png) 下图为本项目自带的预设, 这些预设只有你修改项目级预设文件后才能保证可用, 否则请选择你自己编写的预设 ![选择预设](readme_rc/cmake_select_preset2.png) 选择`Windows`前缀的就是编译Windows平台的应用程序, 选择`Android`前缀就是编译apk包, 带有`Default`后缀的选项的是本项目的默认配置, 除非你修改了项目级的预设文件, 或者你的环境配置与文件中一致, 否则的话这些配置就不能在你的电脑上正常工作 选择配置预设之后, 会自动选择对应的构建预设, 如果你需要编译发行版, 请手动切换配置预设 选择好预设之后, 在vscode内按下Ctrl+Shift+B快捷键即可开始编译, 若你的生成任务默认快捷键不是这个, 也可以手动执行 编译完成后, 按下Crtl+Shift+T快捷键运行测试任务即可看到程序运行效果 若运行中出现问题, 直接在vscode内调试即可(快捷键F5), 但是请确保工作区文件内的gdb可执行文件的路径配置正确 如果你想编译出apk文件并在安卓平台上运行本软件, 那么只需要选择`Android`前缀的配置预设即可, 其余步骤与`Windows`相同, 但是前提是你的安卓开发环境配置正确, 详细说明请参阅[12. 安卓平台编译指南](#12-安卓平台编译指南) ### 10.3. 手动方式 首选方式的本质实际上只不过是把命令的调用交给了vscode的扩展程序来做而已, 本质上依然是在控制台执行命令, 因此这个过程我们可以手动来做, 这样就不再依赖于vscode及其扩展程序 cmake的编译过程分为配置(configure)和构建(build)两步, 配置的步骤会生成底层构建系统所需的文件, 例如`Makefile`或`ninja.build`, 而构建的步骤实际上就是调用`make`或`ninja`去编译工程 本软件在Windows下编译exe的的配置命令为`cmake --preset "Windows Debug Default"`, 这里的`Windows Debug Default`是配置预设的名称, 如果你创建了用户级预设文件, 并且准备使用自己的配置, 请改为你自己给定的名字 配置完成之后的构建命令为`cmake --build build --target all` 两条命令执行完成之后, 就可以在build目录下看到可执行文件了 `Android`平台与`Windows`类似, 只需要在配置时传入不同的预设名即可, 构建使用相同的命令, 但是前提是安卓的开发环境要正确配置, 详细说明请参阅[12. 安卓平台编译指南](#12-安卓平台编译指南) ## 11. 滤波器指南 软件自带的滤波库内的函数, 主要分为两种, 即IIR滤波器和FIR滤波器, 其中`lpf`和`hpf`属于IIR滤波器, `average`和`fir`属于FIR滤波器, 两种滤波器的使用方法是不一样的 `lpf`和`hpf`本身都是一阶的IIR滤波器, 并且无需使用者提供系数, 只需要提供采样序列和截止频率即可, 由于所有的函数的输出都是采样序列, 因此可以嵌套使用构成任意阶数的IIR滤波器 `average`内部使用滑动均值滤波, 参数为采样序列和窗口大小, 它可看做系数为`1/窗口大小`的FIR滤波器, 也是最简单的一种fir滤波器 `fir`为直接型FIR滤波器, 参数为采样序列、滤波器阶数和滤波器系数, 内部对信号和滤波器系数做卷积运算, 因此改变系数就可以变为任何一种FIR滤波器, 此系数可由matlab计算导出保存为文件, 再使用`read_file`函数将系数读入为采样序列, 通过传参的方式让`fir`函数使用 目前此仓库的`coefficient_demo`目录下提供了一些用matlab设计好的系数, 可以在信号表达式内直接使用, 这些demo参数文件的命名方式为: 类型_截止频率_采样率, 系数的采样率和软件设置的采样率一致才可使用 关于如何使用matlab设计FIR滤波器, 如何使用导出的系数, 请参阅[coefficient_demo/readme.md](coefficient_demo/readme.md) ## 12. 安卓平台编译指南 在进行安卓的编译之前, 首先要配置安卓的开发环境, 包括`JDK`、`安卓SDK`、`安卓NDK`以及`QT For Andriod`, 这些环境的配置方式网络上有许多教程, 此处不再赘述 本项目提供的预设文件已经包括了安卓构建所需要设置的所有CMake变量, 你只需要将这些变量中涉及路径的项目配置正确即可 预设文件配置正确之后, 在控制台执行`cmake --preset "Android Debug Default"`即可生成用于编译apk的构建系统 然后执行`cmake --build build --target all`, 等待命令跑完即可在build目录下看到apk文件 除了在控制台手动编译apk以外, 你也可以借助`CMake Tools`扩展程序更为简易的切换到`Android Debug Default`预设项去构建 需要注意的是`QT 5.15`的安卓提供了四种不同的体系结构的库, 源码路径下的预设文件指定的目标平台是armeabi-v7a, 此设置适用于大部分手机, 但是如果你需要在电脑上跑请把`"ANDROID_ABI": "armeabi-v7a"`的设置修改为x86, `"ANDROID_BUILD_ABI_armeabi-v7a": "ON"`修改为OFF, `"ANDROID_BUILD_ABI_x86": "OFF"`修改为ON, 其它体系结构的修改方法类似