Skip to content

Commit

Permalink
Merge pull request #51 from phip1611/dev
Browse files Browse the repository at this point in the history
Release v1.3
  • Loading branch information
phip1611 authored Mar 4, 2023
2 parents 843c0de + ebc6bf5 commit a900867
Show file tree
Hide file tree
Showing 27 changed files with 680 additions and 402 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
max_line_length = 80

[*.nix]
indent_size = 2

[*.yml]
indent_size = 2
7 changes: 5 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
rust:
- stable
- nightly
- 1.56.1
- 1.61.0 # MSRV
steps:
- uses: actions/checkout@v2
# Important preparation step: override the latest default Rust version in GitHub CI
Expand All @@ -39,6 +39,9 @@ jobs:
- run: cargo test --all-targets --no-default-features --features "microfft-complex"
- run: cargo test --all-targets --no-default-features --features "microfft-real"

# run benchmark: right now, there is no reporting or so from the results
- run: cargo bench

# test `no_std`-build with all features/fft implementations
- run: rustup target add thumbv7em-none-eabihf
- run: cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-complex"
Expand All @@ -55,7 +58,7 @@ jobs:
strategy:
matrix:
rust:
- stable
- 1.61.0 # MSRV
steps:
- uses: actions/checkout@v2
# Important preparation step: override the latest default Rust version in GitHub CI
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 1.3.0 (2022-12-XX)
- dependency updates
- MSRV is now `1.61.0`
- `FrequencySpectrum::apply_scaling_fn` now requires a reference to `&mut self`:
This is breaking but only for a small percentage of users. Performance is
slightly improved as less heap allocations are required.
- `FrequencySpectrum` is now `Send` and interior mutability is dropped:
You can wrap the struct in a `Mutex` or similar types now!
- `FrequencySpectrum::to_map` doesn't has the `scaling_fn` parameter anymore.
This is breaking but only for a small percentage of users.
- `FrequencySpectrum::to_mel_map` added for getting the spectrum in the
[mel](https://en.wikipedia.org/wiki/Mel_scale) scale.
-
- small internal code quality and performance improvements

## 1.2.6 (2022-07-20)
- fixed wrong scaling in `scaling::divide_by_N_sqrt` (<https://github.com/phip1611/spectrum-analyzer/issues/41>)

Expand Down
16 changes: 11 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "spectrum-analyzer"
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.
An easy to use and fast `no_std` library (with `alloc`) to get the frequency
spectrum of a digital signal (e.g. audio) using FFT.
"""
version = "1.2.6"
version = "1.3.0"
authors = ["Philipp Schuster <phip1611@gmail.com>"]
edition = "2021"
keywords = ["fft", "spectrum", "frequencies", "audio", "dsp"]
Expand All @@ -20,6 +20,10 @@ exclude = [
".github"
]

[[bench]]
name = "fft_spectrum_bench"
harness = false

[features]
# by default we use microfft-real: it's the fastest and totally fit's our needs.
# As of version 0.5.0 there is no advantage by using other implementations.
Expand Down Expand Up @@ -47,12 +51,14 @@ minimp3 = "0.5"
# visualize spectrum in tests and examples
audio-visualizer = "0.3"
# get audio input in examples
cpal = "0.13"
cpal = "0.15.0"
# audio data buffering
ringbuffer = "0.8" # REQUIRES Rust Stable 1.55 because it uses "ringbuffer v0.8"
ringbuffer = "0.12.0"
rand = "0.8" # for benchmark
# exit in examples
ctrlc = "3.2"
# for benchmark
criterion = "0.4"


# otherwise FFT and other code is too slow
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Philipp Schuster
Copyright (c) 2023 Philipp Schuster

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
59 changes: 29 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
# 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 several FFT implementations which you can choose from during compilation time
via Cargo features.
An easy to use and fast `no_std` library (with `alloc`) to get the frequency
spectrum of a digital signal (e.g. audio) using FFT.

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

The **MSRV** (minimum supported Rust version) is 1.56.1 stable, because this crate uses
Rust edition 2021.
The **MSRV** (minimum supported Rust version) is `1.61.0`.

## 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:
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
By default this crate uses the `real`-module from the great `microfft`-crate. It's the fastest implementation
and as of version `v0.5.0` there should be no valid reason why you should ever change this. The multiple features
are there mainly for educational reasons and to support me while programming/testing.
By default, this crate uses the `real`-module from the great `microfft`-crate.
It's the fastest implementation and as of version `v0.5.0` there should be no
valid reason why you should ever change this. The multiple features are there
mainly for educational reasons and to support me during programming/testing.

### Cargo.toml
```toml
Expand Down Expand Up @@ -68,7 +64,6 @@ fn main() {
## Performance
*Measurements taken on i7-1165G7 @ 2.80GHz (Single-threaded) with optimized build*


| Operation | Time |
| ------------------------------------------------------ | ------:|
| Hann Window with 4096 samples | ≈68µs |
Expand All @@ -78,8 +73,8 @@ fn main() {
| FFT (`microfft/complex`) to spectrum with 4096 samples | ≈250µs |

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

**The noise (wrong peaks) also comes from clipping of the added sine waves!**
Expand All @@ -89,34 +84,38 @@ Peaks (50, 1000, 3777 Hz) are clearly visible but also some noise.
![Visualization of spectrum 0-4000Hz of layered sine signal (50, 1000, 3777 Hz)) with no window function.](res/spectrum_sine_waves_50_1000_3777hz--no-window.png "Peaks (50, 1000, 3777 Hz) are clearly visible but also some noise.")

### Spectrum with *Hann window function* on samples before FFT
Peaks (50, 1000, 3777 Hz) are clearly visible and Hann window reduces noise a little bit. Because this example has few noise, you don't see much difference.
Peaks (50, 1000, 3777 Hz) are clearly visible and Hann window reduces noise a
little. Because this example has few noise, you don't see much difference.
![Visualization of spectrum 0-4000Hz of layered sine signal (50, 1000, 3777 Hz)) with Hann window function.](res/spectrum_sine_waves_50_1000_3777hz--hann-window.png "Peaks (50, 1000, 3777 Hz) are clearly visible and Hann window reduces noise a little bit. Because this example has few noise, you don't see much difference.")

### Spectrum with *Hamming window function* on samples before FFT
Peaks (50, 1000, 3777 Hz) are clearly visible and Hamming window reduces noise a little bit. Because this example has few noise, you don't see much difference.
Peaks (50, 1000, 3777 Hz) are clearly visible and Hamming window reduces noise a
little. Because this example has few noise, you don't see much difference.
![Visualization of spectrum 0-4000Hz of layered sine signal (50, 1000, 3777 Hz)) with Hamming window function.](res/spectrum_sine_waves_50_1000_3777hz--hamming-window.png "Peaks (50, 1000, 3777 Hz) are clearly visible and Hamming window reduces noise a little bit. Because this example has few noise, you don't see much difference.")

## Live Audio + Spectrum Visualization
Execute example `$ cargo run --release --example live-visualization`. It will show you
how you can visualize audio data in realtime + the current spectrum.
Execute example `$ cargo run --release --example live-visualization`. It will
show you how you can visualize audio data in realtime + the current spectrum.

![Example visualization of real-time audio + spectrum analysis](res/live_demo_spectrum_green_day_holiday.gif "Example visualization of real-time audio + spectrum analysis")

## Building and Executing Tests
To execute tests you need the package `libfreetype6-dev` (on Ubuntu/Debian). This is required because
not all tests are "automatic unit tests" but also tests that you need to check visually, by looking at the
generated diagram of the spectrum.
To execute tests you need the package `libfreetype6-dev` (on Ubuntu/Debian).
This is required because not all tests are "automatic unit tests" but also tests
that you need to check visually, by looking at the generated diagram of the
spectrum.

## Trivia / FAQ
### Why f64 and no f32?
I tested f64 but the additional accuracy doesn't pay out the ~40% calculation overhead (on x86_64).
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.
Apply a window function, like Hann window or Hamming window.

## 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
- 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/
Also check out my [blog post](https://phip1611.de/2021/03/programmierung-und-skripte/frequency-spectrum-analysis-with-fft-in-rust/).
57 changes: 57 additions & 0 deletions benches/fft_spectrum_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use spectrum_analyzer::{
samples_fft_to_spectrum, scaling, windows, FrequencyLimit, FrequencySpectrum,
};

fn spectrum_without_scaling(samples: &[f32]) -> FrequencySpectrum {
samples_fft_to_spectrum(samples, 44100, FrequencyLimit::All, None).unwrap()
}

fn spectrum_with_scaling(samples: &[f32]) -> FrequencySpectrum {
samples_fft_to_spectrum(
samples,
44100,
FrequencyLimit::All,
Some(&scaling::divide_by_N_sqrt),
)
.unwrap()
}

fn spectrum_with_multiple_scaling(samples: &[f32]) -> FrequencySpectrum {
let mut spectrum = spectrum_with_scaling(samples);

let mut working_buffer = vec![(0.0.into(), 0.0.into()); spectrum.data().len()];

spectrum
.apply_scaling_fn(&scaling::divide_by_N_sqrt, &mut working_buffer)
.unwrap();
spectrum
.apply_scaling_fn(&scaling::divide_by_N_sqrt, &mut working_buffer)
.unwrap();
spectrum
.apply_scaling_fn(&scaling::divide_by_N_sqrt, &mut working_buffer)
.unwrap();
spectrum
}

fn criterion_benchmark(c: &mut Criterion) {
// create 2048 random samples
let samples = (0..2048)
.map(|_| rand::random::<i16>())
.map(|x| x as f32)
.collect::<Vec<_>>();
let hann_window = windows::hann_window(&samples);

c.bench_function("spectrum without scaling", |b| {
b.iter(|| spectrum_without_scaling(black_box(&hann_window)))
});
c.bench_function("spectrum with scaling", |b| {
b.iter(|| spectrum_without_scaling(black_box(&hann_window)))
});
c.bench_function("spectrum with multiple scaling steps", |b| {
b.iter(|| spectrum_with_multiple_scaling(black_box(&hann_window)))
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
2 changes: 2 additions & 0 deletions check-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ cargo test --all-targets --no-default-features --features "rustfft-complex"
cargo test --all-targets --no-default-features --features "microfft-complex"
cargo test --all-targets --no-default-features --features "microfft-real"

cargo bench

cargo fmt -- --check # (--check doesn't change the files)

cargo doc
Expand Down
2 changes: 1 addition & 1 deletion examples/bench.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2021 Philipp Schuster
Copyright (c) 2023 Philipp Schuster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion examples/live-visualization.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2021 Philipp Schuster
Copyright (c) 2023 Philipp Schuster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2021 Philipp Schuster
Copyright (c) 2023 Philipp Schuster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
12 changes: 6 additions & 6 deletions examples/mp3-samples.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2021 Philipp Schuster
Copyright (c) 2023 Philipp Schuster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -212,31 +212,31 @@ fn to_spectrum_and_plot(
}*/

spectrum_static_plotters_png_visualize(
&spectrum_no_window.to_map(None),
&spectrum_no_window.to_map(),
TEST_OUT_DIR,
&format!("{}--no-window.png", filename),
);

spectrum_static_plotters_png_visualize(
&spectrum_hamming_window.to_map(None),
&spectrum_hamming_window.to_map(),
TEST_OUT_DIR,
&format!("{}--hamming-window.png", filename),
);

spectrum_static_plotters_png_visualize(
&spectrum_hann_window.to_map(None),
&spectrum_hann_window.to_map(),
TEST_OUT_DIR,
&format!("{}--hann-window.png", filename),
);

spectrum_static_plotters_png_visualize(
&spectrum_blackman_harris_4term_window.to_map(None),
&spectrum_blackman_harris_4term_window.to_map(),
TEST_OUT_DIR,
&format!("{}--blackman-harris-4-term-window.png", filename),
);

spectrum_static_plotters_png_visualize(
&spectrum_blackman_harris_7term_window.to_map(None),
&spectrum_blackman_harris_7term_window.to_map(),
TEST_OUT_DIR,
&format!("{}--blackman-harris-7-term-window.png", filename),
);
Expand Down
9 changes: 0 additions & 9 deletions rust-toolchain.toml

This file was deleted.

16 changes: 16 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell rec {
nativeBuildInputs = with pkgs; [
pkg-config
];

buildInputs = with pkgs; [
alsa-lib
fontconfig
libxkbcommon
xorg.libXcursor
xorg.libX11
];

LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}";
}
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2021 Philipp Schuster
Copyright (c) 2023 Philipp Schuster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading

0 comments on commit a900867

Please sign in to comment.