# 信号实验箱 **Repository Path**: finalize/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**: 95 - **Forks**: 35 - **Created**: 2022-03-28 - **Last Updated**: 2025-11-17 ## Categories & Tags **Categories**: mathlibs **Tags**: Qt, 工具, 傅立叶, 信号, cmake ## README # Signal Test Box [![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) This software is an expression-based signal calculation and plotting tool. Its main function is to sample the input signal, perform various operations on the sampled sequence, and plot the results as waveforms. It supports the use of dynamic libraries to extend the functions callable within the software. This software supports cross-platform deployment, thanks to QT's cross-platform API and CMake's cross-platform build capabilities. You can compile targets for different platforms on a single host machine. It has been tested on Ubuntu 22.04 (based on Win11 WSL2), Win7/Win10/Win11, Android x86/Android armeabi-v7a (based on Win11 WSA and Realme X2 pro). The software supports internationalization using QT's internationalization mechanism. Currently, it has Simplified Chinese translation data (the software uses English as the original language and then adds translation files for other languages to achieve internationalization, which is also the recommended practice by QT). The software will automatically adapt to the language settings of the operating system at startup. If the operating system's language is neither Simplified Chinese nor English, it will default to English. Since the support libraries for this software are introduced as `git submodules`, please use the `git clone --recursive` command to clone the submodules together when cloning the repository. If you forget to do this during cloning, you can remedy it by executing the `git submodule init` and `git submodule update` commands in the source path after cloning. If you use the `Download ZIP` function instead of `git clone`, please also download [z-fft](https://gitee.com/finalize/z-fft) and extract it to the `external_libs` path. The final file directory structure should look like this: ![子模块](readme_rc/submodule.png) This document consists of multiple files. Any other files mentioned in this document are in the form of hyperlinks (blue text). Simply click to jump to the corresponding document. If you have any questions regarding usage, reading the source code, or building, you can submit Issues in the repository. I will address them when I see them. - [1. Simple Example](#1-simple-example) - [2. Working Principle](#2-working-principle) - [3. Signal Definition](#3-signal-definition) - [4. Spectrum Mode](#4-spectrum-mode) - [5. Built-in Compiler](#5-built-in-compiler) - [5.1. Expression Syntax](#51-expression-syntax) - [5.2. Comment Syntax](#52-comment-syntax) - [5.3. Array Syntax](#53-array-syntax) - [5.3.1. Array Data Filling Rules](#531-array-data-filling-rules) - [5.4. if Condition Syntax](#54-if-condition-syntax) - [5.5. Keywords, Special Variables, and Constants](#55-keywords-special-variables-and-constants) - [6. Function Libraries](#6-function-libraries) - [7. Workspace](#7-workspace) - [8. Releases](#8-releases) - [9. Building from Source](#9-building-from-source) - [10. Filter Guide](#10-filter-guide) - [11. Contributing to This Repository](#11-contributing-to-this-repository) ## 1. Simple Example Right-click in the signal list box (long press on Android), select "Add Signal" from the pop-up menu, then enter the signal expression, set the sampling rate and the number of sampling points, and click "Calculate Current Signal" to see the waveform. The chart displaying the waveform can be zoomed in and out (mouse wheel operation) and panned (hold down the left mouse button and drag). ![信号0](./readme_rc/sig1.png) ![信号1](./readme_rc/sig2.png) Signals can be nested, allowing operations on two signals conveniently, with a maximum nesting depth of 32 layers. ![信号2](./readme_rc/sig3.png) In addition to sine and cosine, there are some other built-in functions available, such as software random `rand` and hardware random `hrand`. ![信号3](./readme_rc/sig4.png) ![信号4](./readme_rc/sig5.png) For Fourier transform, the `length` function is called on the transformation result because the `fft` function outputs complex numbers, and the `length` function calculates the modulus of the vector, thus obtaining the amplitude spectrum. ![输入信号](./readme_rc/mix_sig.png) ![幅度谱](./readme_rc/fft.png) The software comes with a `filters` library that includes several filter functions. Among them, `lpf` and `hpf` are IIR filters, which are low-pass and high-pass filters respectively, accepting the sampling sequence and cutoff frequency as parameters. The `fir` is an FIR filter, accepting the sampling sequence, filter order, and filter coefficients (which can be exported using MATLAB) as parameters. Here are examples of the effects of a 1st-order IIR low-pass filter, a 3rd-order IIR low-pass filter, and a 32nd-order FIR low-pass filter. For more information about filters, please refer to [10. Filter Guide](#10-filter-guide). ![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) By examining the spectrum after filtering, it can be observed that higher-order filters achieve better filtering effects. ![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. Working Principle 1. A signal can be described by an expression **f**, and at any given time **t**, the signal's intensity is **f(t)**. 2. If a specific time **t** is given, the result of the expression **f(t)** is the sampled value of the signal **f** at time **t**. 3. **t** can be calculated based on the sampling frequency set in the software, and then sampled **N** times, where **N** is the number of sampling points set in the software. 4. Before calculation, the software compiles the expression of the signal to be calculated into a syntax tree. 5. Each node in the syntax tree calculates the values of its child nodes before calculating its own value, so the value of the root node is the value of the entire expression. 6. If there are mutual references between custom signals, the inner signal will be calculated first to obtain its value, and then the outer signal will be calculated. 7. There is a maximum nesting level limit for signals to prevent infinite recursion caused by a signal referencing itself or multiple signals cross-referencing each other. ## 3. Signal Definition All signal definitions in the software are in the form of expressions. For example, `sin(t)` represents a digitally sampled **sin** signal, and `sin(100*t) + sin(200*t)` represents the superposition of two signals with different frequencies. Signals can reference each other. For example, `sig0 = sin(100*t) sig1 = sin(200*t) sig2 = sig0 + sig1`. Self-referencing or cross-referencing signals will trigger the maximum nesting limit. ## 4. Spectrum Mode After using the `fft` function to calculate the Fourier transform of a signal, if you only use the `length` function to obtain the amplitude spectrum, the X-axis of the chart will be in terms of points, as shown in the figure below: ![iir_freq_index](readme_rc/iir_lv1_freq_index.png) You can see that the X-coordinate of the crosshair is 10, which indicates the index of this data in the sampling sequence. If you want to know the corresponding frequency value of this point, you need to calculate it yourself. In the figure above, X=10, the sampling rate is 5KHz, and the number of sampling points is 256. Therefore, the frequency at this point is `5000/256*10=195.3Hz`, which corresponds to the 200Hz frequency component in the original signal. Although this calculation is simple, it is cumbersome. Since all the parameters required for the calculation are available in the software, you can check the spectrum mode checkbox in the lower right corner to let the software calculate it. After checking the spectrum mode, the meaning of the horizontal axis will change to the actual frequency value. For example, in the figure below, the same signal is used for plotting, but the spectrum mode is turned on. You can see that the X value at the crosshair position is 195.313, which matches our calculation result. ![IIR_freq1](./readme_rc/iir_lv1_freq.png) It should be noted that the spectrum mode actually only displays half of the actual calculated data. That is, if the number of sampling points is 1024, the chart will only display the first 512 data points. This is because the spectrum is symmetrical, so only the first half of the spectrum is sufficient for analysis. ## 5. Built-in Compiler The software includes a simple built-in compiler that supports common mathematical expression syntax, double-slash single-line comment syntax, array syntax, and **if** condition syntax. ### 5.1. Expression Syntax Mathematical expression syntax refers to the most common and simplest mathematical expressions, such as `(sin(t)+cos(t+3))*6`. Most operators are supported, including addition, subtraction, multiplication, division, modulus, bitwise AND, bitwise OR, bitwise XOR, logical AND, and logical OR. Some special mathematical operations are provided in the form of functions, such as convolution and Fourier transform. ### 5.2. Comment Syntax Double-slash single-line comments are the same as single-line comments in C language, for example, `sin(t)//sin of t`. All characters after the double slashes are ignored. ### 5.3. Array Syntax Array syntax is similar to Python's List, using square brackets as boundaries and commas as separators, for example, [1, 2, 3]. However, since the basic working principle of the software is sampling, the actual size of each array is the size of the sampling points, so there is a data filling issue. #### 5.3.1. Array Data Filling Rules If an array contains only one number, this number is used to fill the array to the size of the sampling points. For example, if the sampling points are 128, the array [1] represents an array containing 128 ones. In this case, it is also equivalent to writing only the number 1, and a single number will also be expanded to a length of 128. If an array is larger than 1 but smaller than the sampling points, zeros are added to the end of the array to fill it to the length of the sampling points. If an array is exactly the size of the sampling points, the data in the array is used directly. If an array is larger than the sampling points, the array is truncated, and only the first N data points are used. ### 5.4. if Condition Syntax The execution logic of the **if** condition syntax is the same as the ternary operator in C language, but it does not use the `?:` symbol. Instead, it uses the **if** and **else** keywords, and the syntax order is slightly different. In fact, the syntax here is consistent with Python's design. The syntax rule is `mathematical expression + if keyword + comparison expression [+ else + mathematical expression]`. The part in square brackets is optional. For example: 1. `5 if index > 15 else 3` // equivalent to C language `index > 15 ? 5 : 3` 2. `5 if index > 15` // omitting the else clause, equivalent to `index > 15 ? 5 : 0` The **if** condition syntax combined with the **index** variable can conveniently generate step signals and impulse signals, for example: 1. `1 if index >= 0` // unit step signal 2. `1 if index == 0` // impulse signal with amplitude 1 3. `1 if index >= 5` // unit step signal shifted right by 5 4. `1 if index == 5` // impulse signal shifted right by 5 5. `5 if index >= 0` // step signal with amplitude 5 6. `5 if index == 0` // impulse signal with amplitude 5 ### 5.5. Keywords, Special Variables, and Constants The compiler recognizes the following two keywords: 1. **if**: Marks the beginning of an if statement, followed by a condition. 2. **else**: Marks the beginning of an else clause, followed by an expression. The compiler supports the following special variables: 1. **t**: Represents the current time, which is the **n**-th sampling divided by the sampling frequency **fs**, `t = n/fs`. 2. **index**: Represents the current sampling number, which is the so-called **n**. For example, if the sampling points are 1024, then **index** is the sequence [0-1023]. 3. **N**: Represents the total number of sampling points, which is the same as the sampling points set in the software. Note: This is **uppercase N**, which is different from **lowercase n**. The compiler **does not support** lowercase n but uses **index** as an equivalent variable. 4. **fs**: Represents the current sampling rate, in Hz. The compiler supports the following constants: 1. **pi**: Represents the value of pi. ## 6. Function Libraries All functions are parsed from external function libraries. Each function library needs to provide a `pLibFunction_t lib_init(void)` function, which can be used to perform some initialization actions when the library is loaded. This function needs to return a function registry, which describes which symbols in the dynamic library file need to be loaded and the number of parameters required by these functions (for parameter checking during signal compilation). In addition, the function library can export a `void lib_exit(void)` function for cleanup work when the library is unloaded. This function is optional; if it is not exported, it will not be called, and it does not affect the import of the library. If there is no need for initialization actions, the `lib_init` function can be replaced by the macro `register_function_lib`. This macro definition is equivalent to defining the `lib_init` function and returning the function registry specified by the parameters, saving the user from writing the code to define the function library. Currently, the software includes five built-in libraries: `basic`, `filters`, `io`, `transform`, and `window`. These are the basic function library, digital filter library, IO access library, signal transform library, and window function library, respectively. For specific library function descriptions, please refer to [lib/readme.md](lib/readme.md). Similarly, if you build your own dynamic libraries, you can place them in the `lib` subdirectory of the installation directory and then call them in the signal expressions. ## 7. Workspace All signals, current sampling rate, and sampling point settings within the software together constitute a workspace. The software can save this workspace as a JSON file and restore a workspace from a JSON file. The structure of this JSON file is straightforward and easy to understand, making it convenient for manual editing. Therefore, you can also manually create a workspace file and import it directly into the software. Currently, the `workspace_demo` directory in this repository provides some demo workspaces for verifying software functions or library functions, which you can import and use directly. For detailed information about the workspace and workspace files, please refer to [workspace_demo/readme.md](workspace_demo/readme.md). ## 8. Releases To avoid the hassle of building the software yourself, you can find the `Releases` section on the right side of the repository, below the `About` section. Here, you can download the compiled and packaged software for direct use. However, please note that releases are only made after significant version updates. Therefore, the features of the release you download may not be as up-to-date as the source code. If you want to experience the latest version of the software, please refer to [9. Building from Source](#9-building-from-source) to build the latest version yourself. ## 9. Building from Source This project uses `CMake` as the top-level build system. `CMake` can generate files required by various lower-level build systems, such as `Makefile` or `build.ninja`. Additionally, the built-in compiler of this project is not entirely handwritten. The lexical analyzer and parser are built based on `flex`/`bison`. Therefore, the user's development environment needs to have `flex` and `bison` available during the build process. Since `commit hash: cc33da5de23c4434de462cadfa59c189196cbb15`, the project no longer uses the previous method to specify toolchains and various path-related information. It now uses `CMake`'s `Preset` mechanism for environment configuration. If you have previously compiled software for the `Windows` platform and then need to compile for the `Android` platform, please delete the `build` directory first, and vice versa. For detailed procedures on how to build `Windows exe` and `Android APK`, please refer to [build_project.md](build_project.md). ## 10. Filter Guide The functions in the built-in filter library of the software are mainly divided into two types: IIR filters and FIR filters. Among them, `lpf` and `hpf` belong to IIR filters, while `average` and `fir` belong to FIR filters. The usage methods of the two types of filters are different. For information on how to use various filter functions, how to design FIR filters using MATLAB, and how to use the exported coefficients, please refer to [coefficient_demo/readme.md](coefficient_demo/readme.md). ## 11. Contributing to This Repository If you wish to participate in the development of this project, please follow these steps: 1. Click fork in the cloud repository to copy this repository to your own account. If you encounter a situation where you cannot fork, it means that this repository is undergoing some major modifications. To prevent the creation of incomplete branches, forking will be disabled during this period. Please try again after a few days. 2. Clone the repository under your account to your local machine. 3. After committing code locally, push it to the cloud (at this point, it is pushed to your cloud repository). 4. Submit a pull request to this repository, with the direction being [your repository] -> [this repository]. 5. Members of this repository will review (code style and implementation logic) and test (whether the functionality meets expectations) the pull request code. Once the tests pass, the code will be merged into this repository.