Skip to content

Commit

Permalink
Merge pull request #53 from phip1611/remove-fft-impls
Browse files Browse the repository at this point in the history
Remove fft impls
  • Loading branch information
phip1611 authored Mar 4, 2023
2 parents a900867 + 784adf7 commit 7a6bd70
Show file tree
Hide file tree
Showing 12 changed files with 65 additions and 442 deletions.
36 changes: 8 additions & 28 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,19 @@ jobs:

# build with all features/fft implementations
- run: cargo build --all-targets
- run: cargo build --all-targets --no-default-features --features "rustfft-complex"
- run: cargo build --all-targets --no-default-features --features "microfft-complex"
- run: cargo build --all-targets --no-default-features --features "microfft-real"

# run tests with all features/fft implementations
- run: cargo test --all-targets
- run: cargo test --all-targets --no-default-features --features "rustfft-complex"
- 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"
- run: cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-real"
- run: cargo build --target thumbv7em-none-eabihf

# run examples with all features/fft implementations
- run: cargo run --release --example mp3-samples
- run: cargo run --release --example mp3-samples --no-default-features --features "rustfft-complex"
- run: cargo run --release --example mp3-samples --no-default-features --features "microfft-complex"
- run: cargo run --release --example mp3-samples --no-default-features --features "microfft-real"

style_checks:
runs-on: ubuntu-latest
Expand All @@ -68,21 +58,11 @@ jobs:
profile: default
toolchain: ${{ matrix.rust }}
override: true
- name: Rustfmt (checks all source code/all features)
- name: Install required Linux packages for "audio-visualizer"/cpal/minifb
run: sudo apt update && sudo apt -y install libasound2-dev libxkbcommon-dev
- name: Rustfmt
run: cargo fmt -- --check
- name: Clippy (default feature)
run: cargo clippy
- name: Clippy (rustfft-complex)
run: cargo clippy --no-default-features --features "rustfft-complex"
- name: Clippy (microfft-complex)
run: cargo clippy --no-default-features --features "microfft-complex"
- name: Clippy (microfft-real)
run: cargo clippy --no-default-features --features "microfft-real"
- name: Rustdoc (default feature)
run: cargo doc
- name: Rustdoc (rustfft-complex)
run: cargo doc --no-default-features --features "rustfft-complex"
- name: Rustdoc (microfft-complex)
run: cargo doc --no-default-features --features "microfft-complex"
- name: Rustdoc (microfft-real)
run: cargo doc --no-default-features --features "microfft-real"
- name: Clippy
run: cargo clippy --all-targets
- name: Rustdoc
run: cargo doc --document-private-items
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# Changelog

## 1.3.0 (2022-12-XX)
# 1.4.0 (2023-03-04)
- dropped all optional FFT features (`microfft-complex`, `microfft-real`,
`rustfft-complex`) and made `microfft::real` the default FFT implementation.
This is breaking but only for a small percentage of users.
- dependency updates

## 1.3.0 (2023-03-04)
- 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
Expand All @@ -12,7 +17,6 @@
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)
Expand Down
16 changes: 1 addition & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,8 @@ exclude = [
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.
# They still exist mainly for educational purposes, testing during development
# and for possible future enhancements
default = ["microfft-real"]
# std
rustfft-complex = ["rustfft"]
# no_std
microfft-complex = ["microfft"]
# no_std, overall fastest
microfft-real = ["microfft"]

[dependencies]
rustfft = { version = "6.0", optional = true }
microfft = { version = "0.5", optional = true, features = ["size-16384"] }
microfft = { version = "0.5", features = ["size-16384"] }
# approx. compare floats; not only in tests but also during runtime
float-cmp = "0.9"
# sin() cos() log10() etc for no_std-environments; these are not part of Core library
Expand Down
18 changes: 9 additions & 9 deletions EDUCATIONAL.md
Original file line number Diff line number Diff line change
@@ -1,23 +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
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
**TL;DR:** Although this crate has over 1000 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
- 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

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.
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,13 @@ mainly for educational reasons and to support me during programming/testing.
# by default feature "microfft-real" is used
[dependencies]
spectrum-analyzer = "<latest version, see crates.io>"

# or if you need another feature (FFT implementation)
[dependencies.spectrum-analyzer]
default-features = false # important! only one feature at a time works!
version = "<latest version, see crates.io>"
features = ["rustfft-complex"] # or on of the other features
```

### your_binary.rs
```rust
use spectrum_analyzer::{samples_fft_to_spectrum, FrequencyLimit};
use spectrum_analyzer::windows::hann_window;
use spectrum_analyzer::scaling::divide_by_N;
use spectrum_analyzer::scaling::divide_by_N_sqrt;

/// Minimal example.
fn main() {
Expand All @@ -52,7 +46,7 @@ fn main() {
// optional frequency limit: e.g. only interested in frequencies 50 <= f <= 150?
FrequencyLimit::All,
// optional scale
Some(&divide_by_N),
Some(&divide_by_N_sqrt),
).unwrap();

for (fr, fr_val) in spectrum_hann_window.data().iter() {
Expand All @@ -64,13 +58,16 @@ fn main() {
## Performance
*Measurements taken on i7-1165G7 @ 2.80GHz (Single-threaded) with optimized build*

| Operation | Time |
| ------------------------------------------------------ | ------:|
| Hann Window with 4096 samples | ≈68µs |
| Hamming Window with 4096 samples | ≈118µs |
| FFT (`rustfft/complex`) to spectrum with 4096 samples | ≈170µs |
| FFT (`microfft/real`) to spectrum with 4096 samples | ≈90µs |
| FFT (`microfft/complex`) to spectrum with 4096 samples | ≈250µs |
I've tested multiple FFT implementations. Below you can find out why I decided
to use `microfft`.

| Operation | Time |
|---------------------------------------------------------| ------:|
| Hann Window with 4096 samples | ≈68µs |
| Hamming Window with 4096 samples | ≈118µs |
| FFT (`rustfft`) to spectrum with 4096 samples | ≈170µs |
| FFT (`microfft::real`) to spectrum with 4096 samples | ≈90µs |
| 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
Expand Down
22 changes: 2 additions & 20 deletions check-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,20 @@ set -x

echo "checks that this builds on std+no_std + that all tests run + that all features compile"
cargo build --all-targets
cargo build --all-targets --no-default-features --features "rustfft-complex"
cargo build --all-targets --no-default-features --features "microfft-complex"
cargo build --all-targets --no-default-features --features "microfft-real"

cargo test --all-targets
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
cargo doc --no-default-features --features "rustfft-complex"
cargo doc --no-default-features --features "microfft-complex"
cargo doc --no-default-features --features "microfft-real"
cargo doc --document-private-items

cargo clippy --all-targets
cargo clippy --all-targets --no-default-features --features "rustfft-complex"
cargo clippy --all-targets --no-default-features --features "microfft-complex"
cargo clippy --all-targets --no-default-features --features "microfft-real"

# test no_std
rustup target add thumbv7em-none-eabihf
# nope, thats BS: this crate needs STD
# cargo check --target thumbv7em-none-eabihf --no-default-features --features "rustfft-complex"
cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-complex"
cargo check --target thumbv7em-none-eabihf --no-default-features --features "microfft-real"
cargo build --target thumbv7em-none-eabihf

# run examples
cargo run --release --example mp3-samples
cargo run --release --example mp3-samples --no-default-features --features "rustfft-complex"
cargo run --release --example mp3-samples --no-default-features --features "microfft-complex"
cargo run --release --example mp3-samples --no-default-features --features "microfft-real"
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pkgs.mkShell rec {
nativeBuildInputs = with pkgs; [
pkg-config
cargo-nextest
];

buildInputs = with pkgs; [
Expand Down
47 changes: 19 additions & 28 deletions src/fft/microfft_real.rs → src/fft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
//! Real FFT using `microfft::real`.
//! Works in `no_std`-environments, maximum sample length is 4096 (with microfft version 0.4.0)
//! and it's faster than a "typical" complex FFT.

use alloc::vec::Vec;
//! Real FFT using [`microfft::real`] that is very fast and also works in `no_std`
//! environments. It is faster than regular fft (with the `rustfft` crate for
//! example). The difference to a complex FFT, as with `rustfft` is, that the
//! result vector contains less results as there are no mirrored frequencies.

use crate::fft::Fft;
use alloc::vec::Vec;
use core::convert::TryInto;
use microfft::real;

Expand All @@ -36,13 +36,21 @@ use microfft::real;
/// it's own version that gets used in lib.rs for binary compatibility.
pub use microfft::Complex32;

/// Dummy struct with no properties but used as a type
/// to implement a concrete FFT strategy using (`microfft::real`).
/// Real FFT using [`microfft::real`].
pub struct FftImpl;

impl Fft<Complex32> for FftImpl {
impl FftImpl {
/// Calculates the FFT For the given input samples and returns a Vector of
/// of [`Complex32`] with length `samples.len() / 2 + 1`, where the first
/// index corresponds to the DC component and the last index to the Nyquist
/// frequency.
///
/// # Parameters
/// - `samples`: Array with samples. Each value must be a regular floating
/// point number (no NaN or infinite) and the length must be
/// a power of two. Otherwise, the function panics.
#[inline]
fn fft_apply(samples: &[f32]) -> Vec<Complex32> {
pub(crate) fn calc(samples: &[f32]) -> Vec<Complex32> {
let buffer = samples;
let mut res = {
if buffer.len() == 2 {
Expand Down Expand Up @@ -94,29 +102,12 @@ impl Fft<Complex32> for FftImpl {
}
};

// `microfft::real` documentation says: the Nyquist frequency real value is
// packed inside the imaginary part of the DC component.
// `microfft::real` documentation says: the Nyquist frequency real value
// is packed inside the imaginary part of the DC component.
let nyquist_fr_pos_val = res[0].im;
res[0].im = 0.0;
// manually add the nyquist frequency
res.push(Complex32::new(nyquist_fr_pos_val, 0.0));
res
}

#[inline]
fn fft_relevant_res_samples_count(samples_len: usize) -> usize {
// `microfft::real` uses a real FFT and the result is exactly
// N/2 elements of type Complex<f32> long. The documentation of
// `microfft::real` says about this:
// The produced output is the first half out the output returned by
// the corresponding `N`-point CFFT, i.e. the real DC value and
// `N/2 - 1` positive-frequency terms. Additionally, the real-valued
// coefficient at the Nyquist frequency is packed into the imaginary part
// of the DC bin.
//
// But as you can see in apply_fft() I manually add the Nyquist frequency
// therefore "+1". For this real-FFT implementation this equals to the total
// length of fft_apply()-result
samples_len / 2 + 1
}
}
Loading

0 comments on commit 7a6bd70

Please sign in to comment.