Skip to content

Commit

Permalink
Merge pull request #61 from phip1611/updates
Browse files Browse the repository at this point in the history
misc updates and preparations for v1.6.0
  • Loading branch information
phip1611 authored Dec 17, 2024
2 parents f8e44ac + 7876563 commit 1d18746
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 161 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ jobs:

- run: cargo run --release --example mp3-samples

miri:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
components: miri
- uses: Swatinem/rust-cache@v2
- name: Install required Linux packages for "audio-visualizer"/cpal/minifb
run: sudo apt update && sudo apt -y install libasound2-dev libxkbcommon-dev
- run: cargo miri test --all-features

style_checks:
runs-on: ubuntu-latest
strategy:
Expand Down
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Changelog

## Unreleased
- Added FFT buffer size of 32768
## Unreleased (yet)


## 1.6.0 (2024-12-16)
## 1.6.0 (2024-12-17)
- dependency updates
- MSRV bump but only for the tests and examples, not library users
- Added FFT buffer size of 32768
- Optimized implementation, resulting in less unnecessary copying of data
- Removed excessive stack usage for large input data

## 1.5.0 (2023-09-21)
- fixed the build by updating the dependencies
Expand Down
99 changes: 0 additions & 99 deletions examples/bench.rs

This file was deleted.

27 changes: 21 additions & 6 deletions examples/mp3-samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,24 @@ use spectrum_analyzer::scaling::scale_to_zero_to_one;
use spectrum_analyzer::windows::{blackman_harris_4term, hamming_window, hann_window};
use spectrum_analyzer::{samples_fft_to_spectrum, FrequencyLimit};
use std::fs::File;
use std::path::PathBuf;
use std::time::Instant;

const TEST_OUT_DIR: &str = "test/out";
/// Returns the location where tests should store files they produce.
fn test_out_dir() -> PathBuf {
let path = std::env::var("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let dir = PathBuf::from(dir);
dir.join("target")
});
let path = path.join("test_generated");
if !path.exists() {
std::fs::create_dir(path.clone()).unwrap();
}
path
}

fn main() {
println!("bass drum example:");
Expand Down Expand Up @@ -213,31 +228,31 @@ fn to_spectrum_and_plot(

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

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

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

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

spectrum_static_plotters_png_visualize(
&spectrum_blackman_harris_7term_window.to_map(),
TEST_OUT_DIR,
test_out_dir().to_str().unwrap(),
&format!("{}--blackman-harris-7-term-window.png", filename),
);
}
Expand Down
109 changes: 83 additions & 26 deletions src/fft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,63 +22,120 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

//! 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.
//! 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 fewer results as there are no mirrored
//! frequencies.
/// FFT base result type.
pub use microfft::Complex32;

use alloc::vec::Vec;
use core::convert::TryInto;
use core::mem;
use microfft::real;

/// The result of a FFT is always complex but because different FFT crates might
/// use different versions of "num-complex", each implementation exports
/// it's own version that gets used in lib.rs for binary compatibility.
pub use microfft::Complex32;

/// Calculates the real FFT by invoking the proper function corresponding to the
/// buffer length.
/// Calculates the FFT by invoking the function of [`microfft::real`] that
/// corresponds to the input size.
macro_rules! real_fft_n {
($buffer:expr, $( $i:literal ),*) => {
match $buffer.len() {
$(
$i => {
let mut buffer: [_; $i] = $buffer.try_into().unwrap();
let fixed_size_view = $buffer.as_mut_slice().try_into().unwrap();
paste::paste! (
real::[<rfft_$i>]
)(&mut buffer).to_vec()
)(fixed_size_view)
}
)*
_ => { unimplemented!("unexpected buffer len") }
_ => { unimplemented!("should be one of the supported buffer lengths, but was {}", $buffer.len()) }
}
};
}

/// Real FFT using [`microfft::real`].
/// FFT using [`microfft::real`].
pub struct 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.
/// Calculates the FFT For the given input samples and returns a [`Vec`] of
/// [`Complex32`] with length `samples.len() / 2 + 1`.
///
/// 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]
pub(crate) fn calc(samples: &[f32]) -> Vec<Complex32> {
let mut fft_res: Vec<Complex32> = real_fft_n!(
samples, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
assert_eq!(
samples.len() % 2,
0,
"buffer length must be a multiple of two!"
);
let mut vec_buffer = Vec::with_capacity(samples.len() + 2 /* Nyquist */);
assert_eq!(
vec_buffer.capacity() % 2,
0,
"vector capacity must be a multiple of two for safe casting!"
);

vec_buffer.extend_from_slice(samples);

// The result is a view into the buffer.
// We discard the view and directly operate on the buffer.
let _fft_res: &mut [Complex32] = real_fft_n!(
&mut vec_buffer,
2,
4,
8,
16,
32,
64,
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768
);

// We transform the original vector while preserving its memory, to
// prevent any reallocation or unnecessary copying.
let mut buffer = {
let ptr = vec_buffer.as_mut_ptr().cast::<Complex32>();
let len = vec_buffer.len() / 2;
let capacity = vec_buffer.capacity() / 2;
let new_buffer_view = unsafe { Vec::from_raw_parts(ptr, len, capacity) };
mem::forget(vec_buffer);
new_buffer_view
};

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

#[cfg(test)]
mod tests {
use crate::fft::FftImpl;

/// This test is primarily for miri.
#[test]
fn test_memory_safety() {
let samples = [1.0, 2.0, 3.0, 4.0];
let fft = FftImpl::calc(&samples);

assert_eq!(fft.len(), 2 + 1);
}
}
Loading

0 comments on commit 1d18746

Please sign in to comment.