Skip to content

Commit

Permalink
Merge pull request #22 from phip1611/dev
Browse files Browse the repository at this point in the history
merge dev for v0.4.0 release
  • Loading branch information
phip1611 authored Mar 28, 2021
2 parents c30344a + 14de697 commit 39290be
Show file tree
Hide file tree
Showing 19 changed files with 820 additions and 146 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
language: rust
rust:
- stable
- nightly
cache: cargo

script:
- cargo build --all-targets
# - cargo build --all-targets --release
- cargo test --all-targets
# - cargo test --all-targets --release
- rustup target add thumbv7em-none-eabihf
- cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-complex"
- cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-real"

# examples
- cargo run --release --example mp3-samples
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## v0.4.0
- MSRV is now Rust 1.51 (sorry but that's the only way I can make it `no_std`-compatible)
- This crate is now really `no_std`
- you can choose between three FFT implementations at compile time via Cargo features.
The new default feature is `rustfft-complex` (which is still `std`) but there are also
the two new `no_std`-compatible targets `microfft-complex` (more accurate, like rustfft)
and `microfft-real` (faster, less accurate)
- several small improvements and fixes, see: https://github.com/phip1611/spectrum-analyzer/milestone/3?closed=1

## v0.3.0
- `FrequencySpectrum::min()` and `FrequencySpectrum::max()`
now return tuples/pairs (issue #6)
Expand Down
29 changes: 24 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,44 @@ description = """
A simple and fast `no_std` library to get the frequency spectrum of a digital signal (e.g. audio) using FFT.
It follows the KISS principle and consists of simple building blocks/optional features.
"""
version = "0.3.0"
version = "0.4.0"
authors = ["Philipp Schuster <phip1611@gmail.com>"]
edition = "2018"
keywords = ["fft", "spectrum", "frequencies", "audio", "dsp"]
categories = ["multimedia", "no-std"]
readme = "README.md"
license = "MIT"

homepage = "https://github.com/phip1611/spectrum-analyzer"
repository = "https://github.com/phip1611/spectrum-analyzer"
documentation = "https://docs.rs/spectrum-analyzer"

# fixes build problems of wrong feature resolution in microfft crate, see
# https://gitlab.com/ra_kete/microfft-rs/-/merge_requests/11
resolver = "2"

# room for mutliple fft implementations in the future, for example "rustfft" that needs STD
[features]
default = ["rustfft-complex"]
# best option for STD environments, most accurate and performant + more than 4096 samples!
rustfft-complex = ["rustfft"]
# a bit slower but more accurate: uses regular FFT with complex numbers
microfft-complex = ["microfft"]
# faster but less accurate: uses an FFT algorithm that only works on real numbers
microfft-real = ["microfft"]

[dependencies]
rustfft = "5.0.1"
float-cmp = "0.8.0" # approx. compare floats
rustfft = { version = "5.0.1", optional = true }
microfft = { version = "0.3.1", optional = true }
# override num-complex dependency of microfft; otherwise build problems :(
num-complex = { version = "0.3", default-features = false }
float-cmp = "0.8.0" # approx. compare floats; not only in tests but also during runtime
# sin() cos() log10() etc for no_std-environments; as of Rust 1.51 the default
# functions are only part of the standard lib
libm = "0.2.1"

[dev-dependencies]
minimp3 = "0.5.1"
audio-visualizer = "0.2.1"
audio-visualizer = "0.2.2"

# otherwise FFT and other code is too slow
[profile.dev]
Expand Down
23 changes: 23 additions & 0 deletions EDUCATIONAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## How to use FFT to get a frequency spectrum?

This library is full of additional and useful links and comments about how an FFT result
can be used to get a frequency spectrum. In this document I want to give a short introduction
where inside the code you can find specific things.

**TL;DR:** Although this crate has 1400 lines of code, **the part which gets the frequency and
their values from the FFT is small and simple**. Most of the code is related to my convenient
abstraction over the FFT result including several getters, transform/scaling functions, and
tests.

**I don't explain how FFT works but how you use the result!**
If you want to understand that too:

- check out all links provided [at the end of README.md](/README.md)
- look into `lib.rs` (**probalby gives you 90 percent of the things you want to know**)
and the comments over the FFT abstraction in `src/fft/mod.rs` and
`src/fft/rustfft-complex/mod.rs`.


This is everything important you need. Everything inside
`spectrum.rs` and the other files is just convenient stuff + tests for when you
want to use this crate in your program.
73 changes: 65 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
# Rust: library for frequency spectrum analysis using FFT
A simple and fast `no_std` library to get the frequency spectrum of a digital signal (e.g. audio) using FFT.
It follows the KISS principle and consists of simple building blocks/optional features. In short, this is
a convenient wrapper around the great [rustfft](https://crates.io/crates/rustfft)
library.
a convenient wrapper around several FFT implementations which you can choose from during compilation time
via Cargo features.

**I'm not an expert on digital signal processing. Code contributions are highly welcome! :)**
**I'm not an expert on digital signal processing. Code contributions are highly welcome! 🙂**

## How to use
**The MSRV (minimum supported Rust version) is 1.51 Stable because this crate needs the
"resolver" feature of Cargo to cope with build problems occurring in `microfft`-crate.**

## I want to understand how FFT can be used to get a spectrum
Please see file [/EDUCATIONAL.md](/EDUCATIONAL.md).

## How to use (including `no_std`-environments)
Most tips and comments are located inside the code, so please check out the repository on
Github! Anyway, the most basic usage looks like this:

### FFT implementation as compile time configuration via Cargo features
This crate offers multiple FFT implementations using the crates `rustfft` and `microfft` - both are great, shout-out to
the original creators and all contributors! `spectrum-analyzer` offers three features, where exactly one feature
is allowed to be activated, otherwise the build breaks! To see differences between the implementations, plot the results
or look into the screenshots of this README.

- `rustfft-complex` **default**, **std (recommended)**: for regular applications, most accurate and most performance
- `microfft-complex` **no_std (recommended)**: more accurate than `microfft-real`
- `microfft-real` **no_std**, less accurate but faster than `microfft-complex`

### Cargo.toml
```Cargo.toml
# only needed when using "microfft"-feature
# fixes build problems of wrong feature resolution in microfft crate, see
# https://gitlab.com/ra_kete/microfft-rs/-/merge_requests/11
# this requires Rust Stable 1.51
resolver = "2"

# by default feature "rustfft-complex" is used
spectrum-analyzer = "<latest>"
# or for no_std/microcontrollers
spectrum-analyzer = { version = "<latest>", default-features = false, features = "microfft-complex" }
# or
spectrum-analyzer = { version = "<latest>", default-features = false, features = "microfft-real" }
```

### your_binary.rs
```rust
use spectrum_analyzer::{samples_fft_to_spectrum, FrequencyLimit};
use spectrum_analyzer::windows::hann_window;
Expand All @@ -22,7 +56,7 @@ fn main() {
let spectrum_hann_window = samples_fft_to_spectrum(
// (windowed) samples
&hann_window,
// sample rate
// sampling rate
44100,
// optional frequency limit: e.g. only interested in frequencies 50 <= f <= 150?
FrequencyLimit::All,
Expand All @@ -40,11 +74,11 @@ fn main() {

## Scaling the frequency values/amplitudes
As already mentioned, there are lots of comments in the code. Short story is:
Type `ComplexSpectrumScalingFunction` can do anything whereas `BasicSpectrumScalingFunction`
Type `ComplexSpectrumScalingFunction` can do anything like `BasicSpectrumScalingFunction` whereas `BasicSpectrumScalingFunction`
is easier to write, especially for Rust beginners.

## Performance
*Measurements taken on i7-8650U @ 3 Ghz (Single-Core) with optimized build*
*Measurements taken on i7-8650U @ 3 Ghz (Single-Core) with optimized build and using `rustfft` as FFT implementation*


| Operation | Time |
Expand All @@ -58,7 +92,7 @@ is easier to write, especially for Rust beginners.

## Example visualization
In the following example you can see a basic visualization of frequencies `0 to 4000Hz` for
a layered signal of sine waves of `50`, `1000`, and `3777Hz` @ `41000Hz` sample rate. The peaks for the
a layered signal of sine waves of `50`, `1000`, and `3777Hz` @ `41000Hz` sampling rate. The peaks for the
given frequencies are clearly visible. Each calculation was done with `2048` samples, i.e. ≈46ms.

**The noise (wrong peaks) also comes from clipping of the added sine waves!**
Expand All @@ -80,3 +114,26 @@ Peaks (50, 1000, 3777 Hz) are clearly visible and Hamming window reduces noise a
I tested f64 but the additional accuracy doesn't pay out the ~40% calculation overhead (on x86_64).
### What can I do against the noise?
Apply a window function, like Hann window or Hamming window. But I'm not an expert on this.

## Good resources with more information
- Interpreting FFT Results: https://www.gaussianwaves.com/2015/11/interpreting-fft-results-complex-dft-frequency-bins-and-fftshift/
- FFT basic concepts: https://www.youtube.com/watch?v=z7X6jgFnB6Y
- „The Fundamentals of FFT-Based Signal Analysis and Measurement“ https://www.sjsu.edu/people/burford.furman/docs/me120/FFT_tutorial_NI.pdf
- Fast Fourier Transforms (FFTs) and Windowing: https://www.youtube.com/watch?v=dCeHOf4cJE0

Also check out my blog post! https://phip1611.de/2021/03/programmierung-und-skripte/frequency-spectrum-analysis-with-fft-in-rust/

### Real vs Complex FFT: Accuracy
The FFT implementations have different advantages and your decision for one of
them is a tradeoff between accuracy and computation time. The following two
screenshots (60 and 100 Hz sine waves) visualize a spectrum obtained by real FFT
respectively complex FFT. The complex FFT result is much smoother and more accurate
as you can clearly see.

⚠ Because of a frequency resolution of ~10Hz in this example (4096 samples, 44100Hz sampling rate), the peaks are not exactly at 60/100 Hz. ⚠

#### Real FFT (less accuracy)
![Spectrum obtained using real FFT: 60 Hz and 100 Hz sine wave signal](real-fft-60_and_100_hz.png "Spectrum obtained using real FFT: 60 Hz and 100 Hz sine wave signal")
#### Complex FFT (more accuracy)
![Spectrum obtained using complex FFT: 60 Hz and 100 Hz sine wave signal](complex-fft-60_and_100_hz.png "Spectrum obtained complex real FFT: 60 Hz and 100 Hz sine wave signal")

12 changes: 12 additions & 0 deletions check-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
echo "checks that this builds on std+no_std + that all tests run"
cargo build --all-targets
# cargo build --all-targets --release
cargo test --all-targets
# cargo test --all-targets --release
rustup target add thumbv7em-none-eabihf
cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-complex"
cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-real"


# run examples
cargo run --release --example mp3-samples
Binary file added complex-fft-60_and_100_hz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 39290be

Please sign in to comment.